Skip to main content

linker_lang/
link.rs

1//! Laying objects out into an image: the [`Linker`] and the [`link`] shortcut.
2//!
3//! The link is a few linear passes over the input. First the sections of every object are
4//! concatenated by name into output sections, recording where each object's contribution
5//! lands. Then the output sections are placed at addresses, end to end from the base
6//! address. With addresses known, every symbol resolves to one and goes into the table —
7//! a second definition of a name is a [`DuplicateSymbol`]. Finally each relocation is
8//! resolved against that table and its address written into the section bytes. The entry
9//! point, if configured, is the last symbol looked up.
10
11use alloc::collections::BTreeMap;
12use alloc::string::String;
13use alloc::vec::Vec;
14
15use crate::error::LinkError;
16use crate::image::{Image, OutputSection};
17use crate::object::{Object, Width};
18
19/// A configured linker: a base address and an optional entry point.
20///
21/// `Linker` is the configurable form of [`link`]. Set the address the image is laid out
22/// from with [`base_address`](Linker::base_address) and the symbol execution starts at
23/// with [`entry`](Linker::entry), then call [`link`](Linker::link). The defaults — base
24/// address `0`, no entry point — are what the free [`link`] function uses, so reach for
25/// `Linker` only when you need to change one of them.
26///
27/// A linker holds no per-link state, so one instance links many sets of objects.
28///
29/// # Examples
30///
31/// ```
32/// use linker_lang::{Linker, Object};
33///
34/// let mut obj = Object::new("o");
35/// obj.section(".text", [0u8; 16]);
36/// obj.define("_start", ".text", 0);
37///
38/// let image = Linker::new()
39///     .base_address(0x40_0000)
40///     .entry("_start")
41///     .link(&[obj])
42///     .expect("the entry point is defined");
43///
44/// assert_eq!(image.symbol("_start"), Some(0x40_0000));
45/// assert_eq!(image.entry(), Some(0x40_0000));
46/// ```
47#[derive(Clone, PartialEq, Eq, Debug, Default)]
48pub struct Linker {
49    base_address: u64,
50    entry: Option<String>,
51}
52
53impl Linker {
54    /// Creates a linker with the default configuration: base address `0` and no entry
55    /// point. Equivalent to `Linker::default()`.
56    #[must_use]
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Sets the address the first output section is placed at; later sections follow it.
62    ///
63    /// Every resolved symbol address is relative to this base, so a non-zero base shifts
64    /// the whole image — useful when the image will be loaded at a known address.
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use linker_lang::{Linker, Object};
70    ///
71    /// let mut obj = Object::new("o");
72    /// obj.section(".text", [0u8; 4]);
73    /// obj.define("f", ".text", 0);
74    ///
75    /// let image = Linker::new().base_address(0x1000).link(&[obj]).unwrap();
76    /// assert_eq!(image.symbol("f"), Some(0x1000));
77    /// ```
78    #[must_use]
79    pub const fn base_address(mut self, address: u64) -> Self {
80        self.base_address = address;
81        self
82    }
83
84    /// Sets the symbol whose address becomes the image's [`entry`](Image::entry).
85    ///
86    /// The symbol must be defined by one of the linked objects; if it is not, the link
87    /// fails with [`LinkError::UndefinedEntry`]. Without this, the image has no entry point.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use linker_lang::{Linker, Object};
93    ///
94    /// let mut obj = Object::new("o");
95    /// obj.section(".text", [0u8; 4]);
96    /// obj.define("main", ".text", 0);
97    ///
98    /// let image = Linker::new().entry("main").link(&[obj]).unwrap();
99    /// assert_eq!(image.entry(), Some(0));
100    /// ```
101    #[must_use]
102    pub fn entry(mut self, symbol: impl Into<String>) -> Self {
103        self.entry = Some(symbol.into());
104        self
105    }
106
107    /// Links `objects` into an [`Image`].
108    ///
109    /// The objects' sections are merged by name, every symbol is resolved to an address,
110    /// and every relocation is patched. The objects are consumed by reference and not
111    /// modified.
112    ///
113    /// # Errors
114    ///
115    /// Returns a [`LinkError`] if a symbol is defined twice, a relocation or the entry
116    /// point names a symbol no object defines, a symbol or relocation names a section its
117    /// object never created, a relocation's slot runs past its section or its address does
118    /// not fit the relocation width, or the laid-out image would exceed the address space.
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// use linker_lang::{Linker, Object, Width};
124    ///
125    /// let mut code = Object::new("code");
126    /// code.section(".text", [0u8; 8]);
127    /// code.define("handler", ".text", 0);
128    ///
129    /// let mut table = Object::new("table");
130    /// table.section(".data", [0u8; 8]);
131    /// table.relocate(".data", 0, "handler", Width::U64, 0);
132    ///
133    /// let image = Linker::new().link(&[code, table]).unwrap();
134    /// let slot = image.section(".data").unwrap().data();
135    /// assert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0); // patched with handler's address
136    /// ```
137    pub fn link(&self, objects: &[Object]) -> Result<Image, LinkError> {
138        let (mut sections, contrib) = merge_sections(objects)?;
139        place_sections(&mut sections, self.base_address)?;
140        let symbols = resolve_symbols(objects, &sections, &contrib)?;
141        patch_relocations(objects, &mut sections, &symbols, &contrib)?;
142        let entry = resolve_entry(self.entry.as_deref(), &symbols)?;
143
144        Ok(Image {
145            sections,
146            symbols,
147            entry,
148        })
149    }
150}
151
152/// Maps each output section name to its index in the output list. Built during merging and
153/// consulted while resolving and patching. Borrows the section names from the objects.
154type SectionIndex<'a> = BTreeMap<&'a str, usize>;
155
156/// Per-object record of where that object's contribution to each section name begins within
157/// the merged output section.
158type Contributions<'a> = Vec<BTreeMap<&'a str, u64>>;
159
160/// Concatenates the sections of every object by name, in object order, into a list of
161/// output sections (addresses not yet assigned). Returns the merged sections and, for each
162/// object, the offset its contribution to each section begins at.
163fn merge_sections(
164    objects: &[Object],
165) -> Result<(Vec<OutputSection>, Contributions<'_>), LinkError> {
166    let mut index: SectionIndex<'_> = BTreeMap::new();
167    let mut sections: Vec<OutputSection> = Vec::new();
168    let mut contrib: Contributions<'_> = Vec::with_capacity(objects.len());
169
170    for object in objects {
171        let mut object_contrib: BTreeMap<&str, u64> = BTreeMap::new();
172        for section in &object.sections {
173            let name = section.name.as_str();
174            let slot = match index.get(name) {
175                Some(&i) => i,
176                None => {
177                    let i = sections.len();
178                    index.insert(name, i);
179                    sections.push(OutputSection {
180                        name: section.name.clone(),
181                        address: 0,
182                        data: Vec::new(),
183                    });
184                    i
185                }
186            };
187            let base = sections[slot].data.len() as u64;
188            sections[slot].data.extend_from_slice(&section.data);
189            object_contrib.insert(name, base);
190        }
191        contrib.push(object_contrib);
192    }
193
194    Ok((sections, contrib))
195}
196
197/// Assigns each output section an address, placing them end to end from `base_address`.
198fn place_sections(sections: &mut [OutputSection], base_address: u64) -> Result<(), LinkError> {
199    let mut cursor = base_address;
200    for section in sections.iter_mut() {
201        section.address = cursor;
202        cursor = cursor
203            .checked_add(section.data.len() as u64)
204            .ok_or(LinkError::LayoutOverflow)?;
205    }
206    Ok(())
207}
208
209/// Resolves every defined symbol to an absolute address, rejecting a name defined twice or
210/// a symbol placed in a section its object never created.
211fn resolve_symbols(
212    objects: &[Object],
213    sections: &[OutputSection],
214    contrib: &Contributions<'_>,
215) -> Result<BTreeMap<String, u64>, LinkError> {
216    let index = section_index(sections);
217    let mut symbols: BTreeMap<String, u64> = BTreeMap::new();
218
219    for (object, object_contrib) in objects.iter().zip(contrib) {
220        for symbol in &object.symbols {
221            let address = symbol_address(
222                object,
223                object_contrib,
224                &index,
225                sections,
226                &symbol.section,
227                symbol.offset,
228            )?;
229            if symbols.insert(symbol.name.clone(), address).is_some() {
230                return Err(LinkError::DuplicateSymbol {
231                    name: symbol.name.clone(),
232                });
233            }
234        }
235    }
236
237    Ok(symbols)
238}
239
240/// Patches every relocation: resolves its target, checks the slot fits and the address fits
241/// the width, and writes the address into the section bytes little-endian.
242fn patch_relocations(
243    objects: &[Object],
244    sections: &mut [OutputSection],
245    symbols: &BTreeMap<String, u64>,
246    contrib: &Contributions<'_>,
247) -> Result<(), LinkError> {
248    // Owned keys so this map does not borrow `sections`, which is mutated below.
249    let index: BTreeMap<String, usize> = sections
250        .iter()
251        .enumerate()
252        .map(|(i, section)| (section.name.clone(), i))
253        .collect();
254
255    for (object, object_contrib) in objects.iter().zip(contrib) {
256        for relocation in &object.relocations {
257            let base = section_base(object, object_contrib, &relocation.section)?;
258            let slot = index
259                .get(relocation.section.as_str())
260                .copied()
261                .ok_or_else(|| LinkError::InvalidSection {
262                    object: object.name.clone(),
263                    section: relocation.section.clone(),
264                })?;
265
266            // The slot must fit within this object's own contribution to the section.
267            let section_len = object
268                .section_data(&relocation.section)
269                .map_or(0, <[u8]>::len) as u64;
270            let end = relocation
271                .offset
272                .checked_add(relocation.width.bytes() as u64)
273                .filter(|&end| end <= section_len)
274                .ok_or_else(|| LinkError::RelocationOutOfRange {
275                    object: object.name.clone(),
276                    section: relocation.section.clone(),
277                    offset: relocation.offset,
278                })?;
279
280            let target = symbols
281                .get(relocation.target.as_str())
282                .copied()
283                .ok_or_else(|| LinkError::UndefinedSymbol {
284                    name: relocation.target.clone(),
285                    object: object.name.clone(),
286                })?;
287
288            let value = i128::from(target) + i128::from(relocation.addend);
289            if value < 0 || value > relocation.width.max_value() {
290                return Err(LinkError::RelocationOverflow {
291                    target: relocation.target.clone(),
292                    width: relocation.width,
293                });
294            }
295
296            // `base + offset + width <= base + section_len <= output length`, so the write
297            // is in bounds by construction.
298            let start = (base + relocation.offset) as usize;
299            let bytes = &mut sections[slot].data[start..(base + end) as usize];
300            match relocation.width {
301                Width::U32 => bytes.copy_from_slice(&(value as u32).to_le_bytes()),
302                Width::U64 => bytes.copy_from_slice(&(value as u64).to_le_bytes()),
303            }
304        }
305    }
306
307    Ok(())
308}
309
310/// Looks up the entry-point symbol, if one was configured.
311fn resolve_entry(
312    entry: Option<&str>,
313    symbols: &BTreeMap<String, u64>,
314) -> Result<Option<u64>, LinkError> {
315    match entry {
316        Some(name) => symbols
317            .get(name)
318            .copied()
319            .map(Some)
320            .ok_or_else(|| LinkError::UndefinedEntry { name: name.into() }),
321        None => Ok(None),
322    }
323}
324
325/// Rebuilds the name-to-index map over already-merged sections.
326fn section_index(sections: &[OutputSection]) -> SectionIndex<'_> {
327    sections
328        .iter()
329        .enumerate()
330        .map(|(i, section)| (section.name.as_str(), i))
331        .collect()
332}
333
334/// The offset within its merged output section where `object`'s contribution to `section`
335/// begins, or [`InvalidSection`](LinkError::InvalidSection) if it never created that section.
336fn section_base(
337    object: &Object,
338    object_contrib: &BTreeMap<&str, u64>,
339    section: &str,
340) -> Result<u64, LinkError> {
341    object_contrib
342        .get(section)
343        .copied()
344        .ok_or_else(|| LinkError::InvalidSection {
345            object: object.name.clone(),
346            section: section.into(),
347        })
348}
349
350/// Resolves a symbol or its anchor to an absolute address: section address + contribution
351/// base + offset.
352fn symbol_address(
353    object: &Object,
354    object_contrib: &BTreeMap<&str, u64>,
355    index: &SectionIndex<'_>,
356    sections: &[OutputSection],
357    section: &str,
358    offset: u64,
359) -> Result<u64, LinkError> {
360    let base = section_base(object, object_contrib, section)?;
361    let slot = index
362        .get(section)
363        .copied()
364        .ok_or_else(|| LinkError::InvalidSection {
365            object: object.name.clone(),
366            section: section.into(),
367        })?;
368    sections[slot]
369        .address
370        .checked_add(base)
371        .and_then(|a| a.checked_add(offset))
372        .ok_or(LinkError::LayoutOverflow)
373}
374
375/// Links `objects` into an [`Image`] with the default configuration.
376///
377/// A shortcut for `Linker::new().link(objects)`: base address `0` and no entry point. Use
378/// [`Linker`] when you need to set either.
379///
380/// # Errors
381///
382/// Returns a [`LinkError`] for any of the conditions documented on [`Linker::link`].
383///
384/// # Examples
385///
386/// ```
387/// use linker_lang::{link, Object};
388///
389/// let mut a = Object::new("a");
390/// a.section(".text", [1, 2, 3, 4]);
391/// a.define("a_start", ".text", 0);
392///
393/// let mut b = Object::new("b");
394/// b.section(".text", [5, 6]);
395/// b.define("b_start", ".text", 0);
396///
397/// let image = link(&[a, b]).unwrap();
398///
399/// // `.text` is the two objects' contributions, in order; `b_start` follows a's four bytes.
400/// assert_eq!(image.section(".text").unwrap().data(), &[1, 2, 3, 4, 5, 6]);
401/// assert_eq!(image.symbol("a_start"), Some(0));
402/// assert_eq!(image.symbol("b_start"), Some(4));
403/// ```
404pub fn link(objects: &[Object]) -> Result<Image, LinkError> {
405    Linker::new().link(objects)
406}
407
408#[cfg(test)]
409#[allow(
410    clippy::unwrap_used,
411    reason = "tests build known-valid links, so they cannot fail"
412)]
413mod tests {
414    use super::{Linker, link};
415    use crate::error::LinkError;
416    use crate::object::{Object, Width};
417
418    #[test]
419    fn test_same_named_sections_merge_in_object_order() {
420        let mut a = Object::new("a");
421        a.section(".text", [1, 2]);
422        let mut b = Object::new("b");
423        b.section(".text", [3, 4, 5]);
424
425        let image = link(&[a, b]).unwrap();
426        assert_eq!(image.section(".text").unwrap().data(), &[1, 2, 3, 4, 5]);
427    }
428
429    #[test]
430    fn test_sections_are_placed_end_to_end_from_the_base() {
431        let mut obj = Object::new("o");
432        obj.section(".text", [0u8; 4]);
433        obj.section(".data", [0u8; 8]);
434
435        let image = Linker::new().base_address(0x1000).link(&[obj]).unwrap();
436        assert_eq!(image.section(".text").unwrap().address(), 0x1000);
437        assert_eq!(image.section(".data").unwrap().address(), 0x1004);
438    }
439
440    #[test]
441    fn test_symbol_resolves_to_section_plus_contribution_plus_offset() {
442        let mut a = Object::new("a");
443        a.section(".text", [0u8; 4]);
444        a.define("a_fn", ".text", 2);
445        let mut b = Object::new("b");
446        b.section(".text", [0u8; 4]);
447        b.define("b_fn", ".text", 1);
448
449        let image = Linker::new().base_address(0x10).link(&[a, b]).unwrap();
450        assert_eq!(image.symbol("a_fn"), Some(0x12));
451        assert_eq!(image.symbol("b_fn"), Some(0x10 + 4 + 1));
452    }
453
454    #[test]
455    fn test_relocation_is_patched_with_the_target_address() {
456        let mut code = Object::new("code");
457        code.section(".text", [0u8; 8]);
458        code.define("target", ".text", 4);
459
460        let mut data = Object::new("data");
461        data.section(".data", [0u8; 4]);
462        data.relocate(".data", 0, "target", Width::U32, 0);
463
464        let image = Linker::new()
465            .base_address(0x100)
466            .link(&[code, data])
467            .unwrap();
468        let slot = image.section(".data").unwrap().data();
469        assert_eq!(u32::from_le_bytes(slot.try_into().unwrap()), 0x104);
470    }
471
472    #[test]
473    fn test_relocation_addend_is_added_to_the_address() {
474        let mut obj = Object::new("o");
475        obj.section(".text", [0u8; 8]);
476        obj.define("base", ".text", 0);
477        obj.section(".data", [0u8; 8]);
478        obj.relocate(".data", 0, "base", Width::U64, 16);
479
480        let image = Linker::new().base_address(0x1000).link(&[obj]).unwrap();
481        let slot = image.section(".data").unwrap().data();
482        // `.text` is at 0x1000, so base = 0x1000; plus addend 16 = 0x1010.
483        assert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0x1010);
484    }
485
486    #[test]
487    fn test_duplicate_symbol_is_rejected() {
488        let mut a = Object::new("a");
489        a.section(".text", [0u8; 1]);
490        a.define("dup", ".text", 0);
491        let mut b = Object::new("b");
492        b.section(".text", [0u8; 1]);
493        b.define("dup", ".text", 0);
494
495        assert_eq!(
496            link(&[a, b]),
497            Err(LinkError::DuplicateSymbol { name: "dup".into() })
498        );
499    }
500
501    #[test]
502    fn test_undefined_relocation_target_is_rejected() {
503        let mut obj = Object::new("o");
504        obj.section(".data", [0u8; 8]);
505        obj.relocate(".data", 0, "nowhere", Width::U64, 0);
506
507        assert_eq!(
508            link(&[obj]),
509            Err(LinkError::UndefinedSymbol {
510                name: "nowhere".into(),
511                object: "o".into(),
512            })
513        );
514    }
515
516    #[test]
517    fn test_undefined_entry_is_rejected() {
518        let mut obj = Object::new("o");
519        obj.section(".text", [0u8; 1]);
520
521        assert_eq!(
522            Linker::new().entry("_start").link(&[obj]),
523            Err(LinkError::UndefinedEntry {
524                name: "_start".into()
525            })
526        );
527    }
528
529    #[test]
530    fn test_symbol_in_a_missing_section_is_rejected() {
531        let mut obj = Object::new("o");
532        obj.section(".text", [0u8; 4]);
533        obj.define("ghost", ".rodata", 0); // never created `.rodata`
534
535        assert_eq!(
536            link(&[obj]),
537            Err(LinkError::InvalidSection {
538                object: "o".into(),
539                section: ".rodata".into(),
540            })
541        );
542    }
543
544    #[test]
545    fn test_relocation_past_the_section_end_is_rejected() {
546        let mut obj = Object::new("o");
547        obj.section(".text", [0u8; 4]);
548        obj.define("t", ".text", 0);
549        obj.section(".data", [0u8; 4]);
550        obj.relocate(".data", 1, "t", Width::U64, 0); // 1 + 8 > 4
551
552        assert_eq!(
553            link(&[obj]),
554            Err(LinkError::RelocationOutOfRange {
555                object: "o".into(),
556                section: ".data".into(),
557                offset: 1,
558            })
559        );
560    }
561
562    #[test]
563    fn test_address_too_large_for_width_is_rejected() {
564        let mut obj = Object::new("o");
565        obj.section(".text", [0u8; 4]);
566        obj.define("t", ".text", 0);
567        obj.section(".data", [0u8; 4]);
568        obj.relocate(".data", 0, "t", Width::U32, 0);
569
570        // Base above u32::MAX makes the resolved address overflow a 32-bit slot.
571        assert_eq!(
572            Linker::new().base_address(0x1_0000_0000).link(&[obj]),
573            Err(LinkError::RelocationOverflow {
574                target: "t".into(),
575                width: Width::U32,
576            })
577        );
578    }
579
580    #[test]
581    fn test_negative_addend_below_zero_is_rejected() {
582        let mut obj = Object::new("o");
583        obj.section(".text", [0u8; 4]);
584        obj.define("t", ".text", 0); // address 0
585        obj.section(".data", [0u8; 8]);
586        obj.relocate(".data", 0, "t", Width::U64, -1); // 0 + (-1) < 0
587
588        assert_eq!(
589            link(&[obj]),
590            Err(LinkError::RelocationOverflow {
591                target: "t".into(),
592                width: Width::U64,
593            })
594        );
595    }
596
597    #[test]
598    fn test_negative_addend_within_range_resolves() {
599        let mut obj = Object::new("o");
600        obj.section(".text", [0u8; 8]);
601        obj.define("t", ".text", 4); // address 0x104 with base 0x100
602        obj.section(".data", [0u8; 8]);
603        obj.relocate(".data", 0, "t", Width::U64, -4);
604
605        let image = Linker::new().base_address(0x100).link(&[obj]).unwrap();
606        let slot = image.section(".data").unwrap().data();
607        assert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0x100);
608    }
609
610    #[test]
611    fn test_link_is_deterministic() {
612        let build = || {
613            let mut a = Object::new("a");
614            a.section(".text", [1, 2, 3]);
615            a.define("a", ".text", 0);
616            let mut b = Object::new("b");
617            b.section(".data", [4, 5]);
618            b.define("b", ".data", 1);
619            [a, b]
620        };
621        assert_eq!(link(&build()).unwrap(), link(&build()).unwrap());
622    }
623}