{"id":51514761,"url":"https://github.com/jamesgober/linker-lang","last_synced_at":"2026-07-08T10:01:47.475Z","repository":{"id":368497005,"uuid":"1274273555","full_name":"jamesgober/linker-lang","owner":"jamesgober","description":"ELF/PE object linking for compiled-language output.","archived":false,"fork":false,"pushed_at":"2026-06-30T20:15:39.000Z","size":69,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T22:07:20.264Z","etag":null,"topics":["compiler","elf","linker","pe","rust"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["jamesgober"]}},"created_at":"2026-06-19T10:50:59.000Z","updated_at":"2026-06-30T20:13:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/linker-lang","commit_stats":null,"previous_names":["jamesgober/linker-lang"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/linker-lang","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Flinker-lang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Flinker-lang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Flinker-lang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Flinker-lang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/linker-lang/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Flinker-lang/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35260671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","elf","linker","pe","rust"],"created_at":"2026-07-08T10:01:45.269Z","updated_at":"2026-07-08T10:01:47.464Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":["https://github.com/sponsors/jamesgober"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n    \u003cbr\u003e\n    \u003cb\u003elinker-lang\u003c/b\u003e\n    \u003cbr\u003e\n    \u003csub\u003e\u003csup\u003eOBJECT LINKER\u003c/sup\u003e\u003c/sub\u003e\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/linker-lang\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/linker-lang\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/linker-lang\"\u003e\u003cimg alt=\"Downloads\" src=\"https://img.shields.io/crates/d/linker-lang?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/linker-lang\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/linker-lang\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/linker-lang/actions\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/jamesgober/linker-lang/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md\"\u003e\u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.85%2B-blue\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"left\"\u003e\n    \u003cp\u003e\n        linker-lang is the CODE-tier crate: it combines independently compiled objects into a single laid-out image — symbol resolution, section merging, and relocation patching. Part of the -lang language-construction family; see _strategy/LANG_COLLECTION.md for the master plan.\n    \u003c/p\u003e\n    \u003cbr\u003e\n    \u003chr\u003e\n    \u003cp\u003e\n        \u003cstrong\u003eMSRV is 1.85+\u003c/strong\u003e (Rust 2024 edition).\n    \u003c/p\u003e\n    \u003cblockquote\u003e\n        \u003cstrong\u003eStatus: stable.\u003c/strong\u003e The public API is frozen as of \u003ccode\u003e1.0.0\u003c/code\u003e and follows Semantic Versioning, with no breaking changes before \u003ccode\u003e2.0\u003c/code\u003e. See \u003ca href=\"./docs/API.md#semver-promise\"\u003e\u003ccode\u003edocs/API.md\u003c/code\u003e\u003c/a\u003e for the SemVer promise and the \u003ca href=\"./CHANGELOG.md\"\u003e\u003ccode\u003eCHANGELOG.md\u003c/code\u003e\u003c/a\u003e.\n    \u003c/blockquote\u003e\n\u003c/div\u003e\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Overview\n\nA compiler's back end emits each translation unit on its own: a few named byte sections, some symbols that mark addresses inside them, and relocations that still need an address filled in. Linking is the step that joins those islands into something you can load.\n\nlinker-lang does exactly that, and nothing more. Hand it a list of [`Object`](./docs/API.md#object)s and it lays their sections out end to end, resolves every [symbol](./docs/API.md#object) to its final address in that layout, and patches each [relocation](./docs/API.md#object) with the address it was waiting for. The result is an [`Image`](./docs/API.md#image) whose bytes are ready to load and whose symbol table says where everything ended up.\n\nThe model is format-agnostic. Nothing in it names ELF or PE: an `Object` is what a reader for either format would populate, and an `Image` is what a writer would serialize. The crate carries the linking logic without committing to a container format, so a reader and a writer slot in on either side of it later.\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Installation\n\n```toml\n[dependencies]\nlinker-lang = \"1\"\n```\n\nOr from the terminal:\n\n```bash\ncargo add linker-lang\n```\n\n\u003cbr\u003e\n\n## Quick Start\n\n```rust\nuse linker_lang::{link, Object, Width};\n\n// The code: a symbol `start` at the top of a `.text` section.\nlet mut code = Object::new(\"code\");\ncode.section(\".text\", [0x90, 0x90, 0x90, 0x90]);\ncode.define(\"start\", \".text\", 0);\n\n// The data: an 8-byte slot that should hold the address of `start`.\nlet mut data = Object::new(\"data\");\ndata.section(\".data\", [0u8; 8]);\ndata.relocate(\".data\", 0, \"start\", Width::U64, 0);\n\nlet image = link(\u0026[code, data]).expect(\"symbols resolve\");\n\n// `.text` is laid out first at address 0, so `start` resolves there, and the slot in\n// `.data` was patched with that address.\nassert_eq!(image.symbol(\"start\"), Some(0));\nlet slot = image.section(\".data\").unwrap().data();\nassert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0);\n\n// The Display impl is a readable link map:\n//   entry = none\n//   .text @ 0x0000000000000000 (4 bytes)\n//   .data @ 0x0000000000000004 (8 bytes)\n//   symbols:\n//       0x0000000000000000 start\nprintln!(\"{image}\");\n```\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Linking model\n\nA link takes a list of objects and produces one image. Three passes do the work:\n\n| Pass | What it does |\n|---|---|\n| **Merge sections** | Sections that share a name across objects are concatenated, in object order, into one output [section](./docs/API.md#outputsection). The output sections are placed at addresses, end to end from the base address. |\n| **Resolve symbols** | Each [symbol](./docs/API.md#object) resolves to an absolute address — its section's address, plus where its object landed in that section, plus the offset. A name defined twice is a [`DuplicateSymbol`](./docs/API.md#linkerror). |\n| **Patch relocations** | Each [relocation](./docs/API.md#object) is a hole that must hold the address of a symbol, plus an addend. The linker resolves the target and writes the address into the section bytes, little-endian, in the requested [`Width`](./docs/API.md#width). |\n\nSections are addressed by name, so building an object reads the way the layout does:\n\n```rust\nuse linker_lang::{Linker, Object, Width};\n\n// Two compiled functions go into `.text`; a dispatch table in `.data` points at them.\nlet mut code = Object::new(\"code\");\ncode.section(\".text\", vec![0u8; 16]);\ncode.define(\"on_start\", \".text\", 0);\ncode.define(\"on_tick\", \".text\", 8);\n\nlet mut table = Object::new(\"table\");\ntable.section(\".data\", vec![0u8; 16]);\ntable.relocate(\".data\", 0, \"on_start\", Width::U64, 0);\ntable.relocate(\".data\", 8, \"on_tick\", Width::U64, 0);\n\nlet image = Linker::new().base_address(0x40_0000).entry(\"on_start\").link(\u0026[code, table]).unwrap();\n\nassert_eq!(image.entry(), image.symbol(\"on_start\"));\nlet slots = image.section(\".data\").unwrap().data();\nassert_eq!(u64::from_le_bytes(slots[0..8].try_into().unwrap()), image.symbol(\"on_start\").unwrap());\nassert_eq!(u64::from_le_bytes(slots[8..16].try_into().unwrap()), image.symbol(\"on_tick\").unwrap());\n```\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## API Overview\n\nFor a complete reference with examples, see [`docs/API.md`](./docs/API.md).\n\n- [`link`](./docs/API.md#link) — the one-call shortcut: link with the default configuration.\n- [`Linker`](./docs/API.md#linker) — the configurable form: a base address and an optional entry point.\n- [`Object`](./docs/API.md#object) — a compilation unit: named byte sections, symbols, and relocations.\n- [`Width`](./docs/API.md#width) — the size of a relocated address (`U32` / `U64`).\n- [`Image`](./docs/API.md#image) — the linked result: laid-out sections, the symbol table, the entry point, and a link-map `Display`.\n- [`OutputSection`](./docs/API.md#outputsection) — one laid-out section: name, address, and final bytes.\n- [`LinkError`](./docs/API.md#linkerror) — the reason a link could not be completed.\n\n### Error handling\n\nEvery way a link can fail is a distinct [`LinkError`](./docs/API.md#linkerror) variant that names the object, symbol, or section involved — a symbol defined twice, a relocation or entry point with no definition, a slot that runs past its section or an address that does not fit it. The linker reports the reason rather than producing a partial or wrong image.\n\n```rust\nuse linker_lang::{link, LinkError, Object, Width};\n\nlet mut obj = Object::new(\"uses.o\");\nobj.section(\".data\", [0u8; 8]);\nobj.relocate(\".data\", 0, \"external\", Width::U64, 0); // never defined\n\nmatch link(\u0026[obj]) {\n    Err(LinkError::UndefinedSymbol { name, object }) =\u003e {\n        assert_eq!((name.as_str(), object.as_str()), (\"external\", \"uses.o\"));\n    }\n    other =\u003e panic!(\"expected UndefinedSymbol, got {other:?}\"),\n}\n```\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Performance\n\nPerformance is a hard constraint, not an afterthought (see [`REPS.md`](./REPS.md)). A link is a few linear passes: sections are concatenated into preallocated buffers, symbol lookups go through an ordered map, and each relocation is one resolve and one little-endian write. The cost is linear in the total input size, with no copies beyond the section bytes themselves.\n\nRun the benchmarks on your hardware for trends:\n\n```bash\ncargo bench --bench bench\n```\n\nThe suite covers the two cost drivers — many small objects wired together by a relocation table (symbol resolution and patching dominate), and a few large sections (the byte merge dominates).\n\n\u003cbr\u003e\n\u003chr\u003e\n\u003cbr\u003e\n\n## Configuration\n\n### Feature Flags\n\n| Feature | Default | Description |\n|---|---|---|\n| `std` | on | Links the standard library. Without it the crate is `#![no_std]` and needs only `alloc`. |\n| `serde` | off | Derives `Serialize` / `Deserialize` for `Image`, `OutputSection`, `Width`, and `LinkError`, so a linked image or a failure can be cached, logged, or moved between tools. |\n\n```toml\n# no_std build:\nlinker-lang = { version = \"1\", default-features = false }\n\n# with serialization:\nlinker-lang = { version = \"1\", features = [\"serde\"] }\n```\n\n\u003cbr\u003e\n\n## Examples\n\nTwo runnable examples live in [`examples/`](./examples):\n\n```bash\n# Link hand-built objects with a cross-object relocation and print the link map.\ncargo run --example link_map\n\n# Compile functions with `codegen-lang`, then link them into one image.\ncargo run --example from_codegen\n```\n\n\u003cbr\u003e\n\n## Testing\n\n```bash\n# Unit, integration (workflow, invalid input, properties), and doc tests\ncargo test --all-features\n\n# Property tests only\ncargo test --all-features --test properties\n\n# Lints and formatting\ncargo fmt --all -- --check\ncargo clippy --all-targets --all-features -- -D warnings\n\n# Benchmarks (Criterion)\ncargo bench --bench bench\n```\n\nThe integration suite in [`tests/workflow.rs`](./tests/workflow.rs) compiles functions with [`codegen-lang`](https://crates.io/crates/codegen-lang), wraps each as a linkable object, and links them — exercising symbol resolution, section merging, and relocation patching on real backend output. [`tests/invalid.rs`](./tests/invalid.rs) confirms every failure is reported as the precise error, and [`tests/properties.rs`](./tests/properties.rs) generates random links and checks symbol addresses and patched slots against an independent computation.\n\n\u003cbr\u003e\n\n## Cross-Platform Support\n\nThe crate is pure, dependency-light Rust with no platform-specific code, and is tested on Linux, macOS, and Windows (x86_64) through the CI matrix on stable and the 1.85 MSRV.\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Contributing\n\nEngineering standards for this crate are the [Rust Efficiency \u0026amp; Performance Standards](./REPS.md); the current scope and plan are in [`dev/ROADMAP.md`](./dev/ROADMAP.md). Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.\n\n\u003cbr\u003e\n\n\u003cdiv id=\"license\"\u003e\n    \u003ch2\u003eLicense\u003c/h2\u003e\n    \u003cp\u003eLicensed under either of\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003cb\u003eApache License, Version 2.0\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-APACHE\"\u003eLICENSE-APACHE\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003cb\u003eMIT License\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-MIT\"\u003eLICENSE-MIT\u003c/a\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003eat your option.\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  \u003csup\u003eCOPYRIGHT \u003csmall\u003e\u0026copy;\u003c/small\u003e 2026 \u003cstrong\u003eJames Gober \u003cme@jamesgober.com\u003e.\u003c/strong\u003e\u003c/sup\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Flinker-lang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Flinker-lang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Flinker-lang/lists"}