linker-lang 1.0.0

Static linking: symbol resolution, section merging, and relocation patching.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
<h1 align="center">
    <img width="99" alt="Rust logo" src="https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg">
    <br><b>linker-lang</b><br>
    <sub><sup>API REFERENCE</sup></sub>
</h1>
<div align="center">
    <sup>
        <a href="../README.md" title="Project Home"><b>HOME</b></a>
        <span>&nbsp;&nbsp;</span>
        <span>API</span>
        <span>&nbsp;&nbsp;</span>
        <a href="../dev/ROADMAP.md" title="Roadmap"><b>ROADMAP</b></a>
    </sup>
</div>
<br>

> **Status: stable (1.0).** The surface below is the `1.0` contract: it follows [Semantic Versioning]#semver-promise and will not change in a breaking way before `2.0`. [`LinkError`]#linkerror is `#[non_exhaustive]`, so a new failure reason stays an additive change. See [`dev/ROADMAP.md`]../dev/ROADMAP.md.

A small static linker: it combines independently compiled [`Object`](#object)s into a single laid-out [`Image`](#image) — merging sections, resolving symbols, and patching relocations. The model is format-agnostic: an ELF or PE reader feeds the same `Object`, and a writer turns an `Image` back into a file.

<br>

## Table of Contents

- [Installation]#installation
- [Linking model]#linking-model
- [Public API]#public-api
  - [`link`]#link
  - [`Linker`]#linker
  - [`Object`]#object
  - [`Width`]#width
  - [`Image`]#image
  - [`OutputSection`]#outputsection
  - [`LinkError`]#linkerror
- [Feature flags]#feature-flags
- [SemVer promise]#semver-promise

<br>
<hr>
<br>

## Installation

```toml
[dependencies]
linker-lang = "1"
```

```bash
cargo add linker-lang
```

The crate is `#![no_std]`-capable: build with `default-features = false` to drop the standard library and depend only on `alloc`.

<br>
<hr>
<br>

## Linking model

A link takes a list of [`Object`](#object)s and produces one [`Image`](#image). Three things happen, in order:

- **Sections merge.** A [`section`]#object is a named run of bytes (`.text`, `.data`, …). Sections that share a name across objects are concatenated into one output section, in the order the objects are given. The output sections are then placed at addresses, end to end from the linker's base address.
- **Symbols resolve.** A [`symbol`]#object names an offset inside a section. Once the layout is fixed, each symbol resolves to an absolute address — its section's address, plus where its object's contribution landed in that section, plus the offset. A name defined by two objects is a [`DuplicateSymbol`]#linkerror.
- **Relocations patch.** A [`relocation`]#object is a hole at some offset in a section that must hold the address of a named symbol, plus an addend. The linker looks the target up, adds the addend, and writes the result into the section bytes little-endian, in the requested [`Width`]#width.

The result is an [`Image`](#image) whose section bytes are final and whose symbol table records where everything ended up. Nothing in the model names a container format: the same `Object` is what an ELF or PE reader would populate, and an `Image` is what a writer would serialize.

<br>
<hr>
<br>

## Public API

### `link`

```rust
pub fn link(objects: &[Object]) -> Result<Image, LinkError>
```

Links `objects` into an [`Image`](#image) with the default configuration — base address `0`, no entry point. This is the shortcut for the common case; reach for [`Linker`](#linker) when you need to set the base address or an entry point.

**Parameters**

- `objects` — the compilation units to link. They are read, not modified. Their sections merge by name in this order, so the order is the layout order.

**Returns**

- `Ok(Image)` — the linked image.
- `Err(LinkError)` — see [`LinkError`]#linkerror for the reasons a link can fail.

**Examples**

Merge two objects and read back the resolved symbol table:

```rust
use linker_lang::{link, Object};

let mut a = Object::new("a");
a.section(".text", [1, 2, 3, 4]);
a.define("a_start", ".text", 0);

let mut b = Object::new("b");
b.section(".text", [5, 6]);
b.define("b_start", ".text", 0);

let image = link(&[a, b]).unwrap();

// `.text` is the two contributions in object order; `b_start` follows a's four bytes.
assert_eq!(image.section(".text").unwrap().data(), &[1, 2, 3, 4, 5, 6]);
assert_eq!(image.symbol("a_start"), Some(0));
assert_eq!(image.symbol("b_start"), Some(4));
```

Patch a cross-object relocation:

```rust
use linker_lang::{link, Object, Width};

let mut code = Object::new("code");
code.section(".text", [0u8; 8]);
code.define("handler", ".text", 0);

let mut table = Object::new("table");
table.section(".data", [0u8; 8]);
table.relocate(".data", 0, "handler", Width::U64, 0);

let image = link(&[code, table]).unwrap();
let slot = image.section(".data").unwrap().data();
assert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0); // handler's address
```

<br>

### `Linker`

```rust
pub struct Linker { /* private fields */ }
```

The configurable form of [`link`](#link): a base address and an optional entry point. Set them with the builder methods, then call [`Linker::link`](#linker). A linker holds no per-link state, so one instance links many sets of objects. Implements `Default` (base address `0`, no entry point).

**Methods**

| Method | Returns | Description |
|---|---|---|
| `new()` | `Linker` | A linker with the default configuration. Same as `Linker::default()`. |
| `base_address(addr)` | `Linker` | Sets the address the first output section is placed at; later sections follow. Every resolved address is relative to this base. |
| `entry(symbol)` | `Linker` | Sets the symbol whose address becomes the image's [`entry`]#image. The symbol must be defined, or the link fails with [`UndefinedEntry`]#linkerror. |
| `link(objects)` | `Result<Image, LinkError>` | Links `objects` with this configuration. See [`link`]#link for the failure modes. |

**Examples**

Lay an image out at a load address with an entry point:

```rust
use linker_lang::{Linker, Object};

let mut obj = Object::new("o");
obj.section(".text", [0u8; 16]);
obj.define("_start", ".text", 0);

let image = Linker::new()
    .base_address(0x40_0000)
    .entry("_start")
    .link(&[obj])
    .expect("the entry point is defined");

assert_eq!(image.symbol("_start"), Some(0x40_0000));
assert_eq!(image.entry(), Some(0x40_0000));
```

Reuse one linker for several links:

```rust
use linker_lang::{Linker, Object};

let linker = Linker::new().base_address(0x1000);
for name in ["x", "y"] {
    let mut obj = Object::new(name);
    obj.section(".text", [0u8; 4]);
    obj.define(name, ".text", 0);
    assert_eq!(linker.link(&[obj]).unwrap().symbol(name), Some(0x1000));
}
```

<br>

### `Object`

```rust
pub struct Object { /* private fields */ }
```

One compilation unit handed to the linker: named byte sections, the symbols defined in them, and the relocations still to be patched. This is the linker's input island — the form a backend or an object-file reader produces. An object carries no addresses of its own; [`link`](#link) assigns them.

Sections are addressed by name. Appending to a name that already exists extends that section, so an object holds at most one section per name.

**Methods**

| Method | Returns | Description |
|---|---|---|
| `new(name)` | `Object` | An empty object. `name` identifies it in diagnostics. |
| `name()` | `&str` | The object's name. |
| `section(name, data)` | `u64` | Appends `data` to the section `name`, creating it if new. Returns the offset within the section where `data` begins — `0` for a fresh section, the previous length when extending. |
| `define(name, section, offset)` | `()` | Defines a symbol `name` at `offset` bytes into `section`. |
| `relocate(section, offset, target, width, addend)` | `()` | Records that the `width` bytes at `offset` in `section` hold the address of `target`, plus `addend`. |

**Parameters — `relocate`**

- `section` — the section the slot lives in.
- `offset` — the slot's byte offset within this object's contribution to that section.
- `target` — the symbol whose address fills the slot.
- `width` — the slot size: [`Width::U32`]#width or [`Width::U64`]#width. The address is written little-endian.
- `addend` — a signed value added to the target's address before it is written.

**Examples**

Build an object with code and a symbol at its start:

```rust
use linker_lang::Object;

let mut obj = Object::new("greeter.o");
obj.section(".text", b"\xc3".to_vec()); // one byte standing in for `ret`
obj.define("greet", ".text", 0);
assert_eq!(obj.name(), "greeter.o");
```

Place several symbols in one section, using the returned offsets as anchors:

```rust
use linker_lang::Object;

let mut obj = Object::new("multi.o");
let first = obj.section(".text", [0u8; 8]);   // 0
obj.define("first", ".text", first);
let second = obj.section(".text", [0u8; 8]);  // 8 — appended after the first chunk
obj.define("second", ".text", second);
```

Build a function-pointer table that needs two relocations:

```rust
use linker_lang::{Object, Width};

let mut obj = Object::new("vectors.o");
obj.section(".data", [0u8; 16]);
obj.relocate(".data", 0, "entry", Width::U64, 0); // slot 0 -> address of `entry`
obj.relocate(".data", 8, "entry", Width::U64, 8); // slot 1 -> eight bytes past it
```

<br>

### `Width`

```rust
pub enum Width {
    U32,
    U64,
}
```

The size of the address a [relocation](#object) patches in. A relocation writes its resolved address as a little-endian integer of this width; the link fails with [`RelocationOverflow`](#linkerror) if the address does not fit.

**Variants**

| Variant | Bytes | Use for |
|---|---|---|
| `U32` | 4 | A 32-bit pointer slot. |
| `U64` | 8 | A 64-bit pointer slot. |

**Methods**

- `bytes() -> usize` — the number of bytes the width occupies (`4` or `8`).

**Examples**

```rust
use linker_lang::Width;

assert_eq!(Width::U32.bytes(), 4);
assert_eq!(Width::U64.bytes(), 8);
```

<br>

### `Image`

```rust
pub struct Image { /* private fields */ }
```

A linked image: the laid-out sections, the resolved symbol table, and the entry point. This is what [`link`](#link) produces on success. Its sections are placed at fixed addresses with their relocations patched, its symbol table maps every defined name to its address, and its entry is the address of the configured entry point, if any.

The `Display` implementation renders a readable link map: the entry point, each section with its address and size, and the symbol table sorted by name.

**Methods**

| Method | Returns | Description |
|---|---|---|
| `sections()` | `&[OutputSection]` | The laid-out sections, in address order. |
| `section(name)` | `Option<&OutputSection>` | The section named `name`, or `None`. |
| `symbol(name)` | `Option<u64>` | The resolved address of `name`, or `None` if no object defined it. |
| `symbols()` | `impl Iterator<Item = (&str, u64)>` | The symbol table as `(name, address)` pairs, sorted by name. |
| `entry()` | `Option<u64>` | The entry-point address, or `None` if none was configured. |
| `len()` | `usize` | The number of sections. |
| `is_empty()` | `bool` | Whether the image has no sections. |

**Examples**

Look up addresses and the entry point:

```rust
use linker_lang::{Linker, Object};

let mut obj = Object::new("o");
obj.section(".text", [0u8; 8]);
obj.define("main", ".text", 0);
obj.define("loop", ".text", 4);

let image = Linker::new().entry("main").link(&[obj]).unwrap();

assert_eq!(image.symbol("main"), Some(0));
assert_eq!(image.symbol("loop"), Some(4));
assert_eq!(image.entry(), Some(0));
assert_eq!(image.symbols().count(), 2);
```

Render the link map:

```rust
use linker_lang::{Linker, Object};

let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.define("main", ".text", 0);
let image = Linker::new().entry("main").link(&[obj]).unwrap();

let map = image.to_string();
assert!(map.contains("entry = 0x0000000000000000"));
assert!(map.contains(".text @ 0x0000000000000000 (4 bytes)"));
assert!(map.contains("0x0000000000000000 main"));
```

<br>

### `OutputSection`

```rust
pub struct OutputSection { /* private fields */ }
```

One laid-out section in an [`Image`](#image): the concatenation of every input section that shared its name, placed at a fixed address. Its bytes are final — relocations into it have been patched — so it is ready to be written to a file or loaded at its address.

**Methods**

| Method | Returns | Description |
|---|---|---|
| `name()` | `&str` | The section's name. |
| `address()` | `u64` | The address the section begins at. |
| `data()` | `&[u8]` | The section's final, relocated bytes. |
| `len()` | `usize` | The number of bytes. |
| `is_empty()` | `bool` | Whether the section has no bytes. |

**Examples**

```rust
use linker_lang::{link, Object};

let mut obj = Object::new("o");
obj.section(".text", [1, 2, 3, 4]);
let image = link(&[obj]).unwrap();

let text = image.section(".text").unwrap();
assert_eq!(text.name(), ".text");
assert_eq!(text.address(), 0);
assert_eq!(text.data(), &[1, 2, 3, 4]);
assert_eq!(text.len(), 4);
```

<br>

### `LinkError`

```rust
#[non_exhaustive]
pub enum LinkError {
    DuplicateSymbol { name: String },
    UndefinedSymbol { name: String, object: String },
    UndefinedEntry { name: String },
    InvalidSection { object: String, section: String },
    RelocationOutOfRange { object: String, section: String, offset: u64 },
    RelocationOverflow { target: String, width: Width },
    LayoutOverflow,
}
```

The reason a link could not be completed. Each variant points at the object, symbol, or section involved so the producer can be fixed. `LinkError` implements `Display` and `std::error::Error`. The enum is `#[non_exhaustive]`, so a `match` on it must include a wildcard arm.

**Variants**

| Variant | Meaning | What to do |
|---|---|---|
| `DuplicateSymbol { name }` | Two objects define `name`. | Rename one, or drop the duplicate definition. |
| `UndefinedSymbol { name, object }` | A relocation in `object` targets `name`, which nobody defines. | Define the symbol, or remove the reference. |
| `UndefinedEntry { name }` | The configured [entry point]#linker is not defined. | Define it, or link without an entry point. |
| `InvalidSection { object, section }` | A symbol or relocation names a `section` the object never created. | Fix the section name — it is almost certainly a typo. |
| `RelocationOutOfRange { object, section, offset }` | A relocation's slot runs past the end of its section. | Correct the offset, or size the section to cover the slot. |
| `RelocationOverflow { target, width }` | The resolved address is negative after the addend, or larger than `width` holds. | Widen the slot, or adjust the layout so the address fits. |
| `LayoutOverflow` | The laid-out image would exceed the 64-bit address space. | Lower the base address or the total section size. |

**Examples**

Match on the reason:

```rust
use linker_lang::{link, LinkError, Object};

let mut a = Object::new("a");
a.section(".text", [0u8; 1]);
a.define("main", ".text", 0);

let mut b = Object::new("b");
b.section(".text", [0u8; 1]);
b.define("main", ".text", 0);

match link(&[a, b]) {
    Err(LinkError::DuplicateSymbol { name }) => assert_eq!(name, "main"),
    other => panic!("expected DuplicateSymbol, got {other:?}"),
}
```

Use it as a `std::error::Error`:

```rust
use linker_lang::{link, Object, Width};
use std::error::Error;

let mut obj = Object::new("uses.o");
obj.section(".data", [0u8; 8]);
obj.relocate(".data", 0, "external", Width::U64, 0);

let err = link(&[obj]).unwrap_err();
let _as_error: &dyn Error = &err;
assert!(err.to_string().contains("external"));
```

<br>
<hr>
<br>

## Feature flags

| Feature | Default | Description |
|---|---|---|
| `std` | on | Links the standard library. Without it the crate is `#![no_std]` and needs only `alloc`. |
| `serde` | off | Derives `Serialize` / `Deserialize` for [`Image`]#image, [`OutputSection`]#outputsection, [`Width`]#width, and [`LinkError`]#linkerror, so a linked image or a failure can be cached, logged, or moved between tools. |

```toml
# no_std build:
linker-lang = { version = "1", default-features = false }

# with serialization:
linker-lang = { version = "1", features = ["serde"] }
```

<br>

## SemVer promise

As of `1.0.0` the public surface above is frozen. The crate follows [Semantic Versioning](https://semver.org):

- No documented item is removed or changed in a breaking way within `1.x`; breaking changes wait for `2.0`.
- New functionality is additive and arrives in minor releases. [`LinkError`]#linkerror is `#[non_exhaustive]`, so a new failure reason — a new relocation kind, a new layout constraint — is a minor change, not a breaking one; a `match` on it must keep a wildcard arm.
- The MSRV is Rust `1.85`; raising it is a minor change, never a patch.
- Behaviour is part of the contract: a set of objects that links today keeps linking, symbol addresses are laid out the same way, and the link-map `Display` form is stable for a given image.

This file is updated in lockstep with every release so it always matches the code.

<br>
<hr>

<sub>Copyright &copy; 2026 <strong>James Gober</strong>.</sub>