Skip to main content

linker_layout/
lib.rs

1//! This crate defines a format for providing information about where a linker put stuff.
2
3use anyhow::Context;
4use anyhow::Result;
5use serde::Deserialize;
6use serde::Serialize;
7use std::io::Write;
8use std::ops::Range;
9use std::path::Path;
10use std::path::PathBuf;
11
12#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default)]
13pub struct Metrics {
14    /// Total number of range-extension thunks allocated across all input objects.
15    pub thunk_count: u64,
16}
17
18#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
19pub struct Layout {
20    /// The input files to the linker.
21    pub files: Vec<InputFile>,
22
23    /// Metrics collected during linking.
24    #[serde(default)]
25    pub metrics: Metrics,
26}
27
28#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
29pub struct InputFile {
30    /// Path to the input file on disk. In case of archives, multiple inputs may have the same
31    /// path.
32    pub path: PathBuf,
33
34    /// If the input is an archive, then contains information about where in the archive the file
35    /// came from.
36    pub archive_entry: Option<ArchiveEntryInfo>,
37
38    /// Sections that were written to the output. Indexes correspond to the sections in the input
39    /// file. Contains None for sections that were discarded or weren't fully copied.
40    pub sections: Vec<Option<Section>>,
41
42    /// The file was a temporary file, most likely generated by a linker plugin. Absence of the
43    /// file should not be treated as an error.
44    pub temporary: bool,
45}
46
47#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
48pub struct ArchiveEntryInfo {
49    /// The range within the file that contains the archive entry (not including the entry header).
50    pub range: Range<usize>,
51
52    pub identifier: Vec<u8>,
53}
54
55#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
56pub struct Section {
57    pub mem_range: Range<u64>,
58}
59
60impl Layout {
61    pub fn write(&self, writer: &mut impl Write) -> Result<()> {
62        postcard::to_io(self, writer)?;
63        Ok(())
64    }
65
66    pub fn to_bytes(&self) -> Result<Vec<u8>> {
67        Ok(postcard::to_stdvec(self)?)
68    }
69
70    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
71        postcard::from_bytes(bytes).context("Invalid linker layout")
72    }
73}
74
75#[must_use]
76pub fn layout_path(base_path: &Path) -> PathBuf {
77    // We always want to append, not use with_extension, since we don't want to remove any existing
78    // extension, otherwise we'd likely get collisions.
79    let mut s = base_path.as_os_str().to_owned();
80    s.push(".layout");
81    PathBuf::from(s)
82}
83
84impl std::fmt::Display for InputFile {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        self.path.display().fmt(f)?;
87        if let Some(e) = self.archive_entry.as_ref() {
88            write!(f, " @ {}", String::from_utf8_lossy(&e.identifier))?;
89        }
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_round_trip() {
100        let layout = Layout {
101            files: vec![InputFile {
102                path: PathBuf::new(),
103                archive_entry: None,
104                sections: vec![Some(Section { mem_range: 42..48 })],
105                temporary: true,
106            }],
107            metrics: Metrics { thunk_count: 3 },
108        };
109        let bytes = layout.to_bytes().unwrap();
110        let layout2 = Layout::from_bytes(&bytes).unwrap();
111        assert_eq!(layout, layout2);
112    }
113}