]>
Commit | Line | Data |
---|---|---|
17df50a5 XL |
1 | //! Reading of the rustc metadata for rlibs and dylibs |
2 | ||
3 | use std::fs::File; | |
a2a8927a | 4 | use std::io::Write; |
17df50a5 XL |
5 | use std::path::Path; |
6 | ||
a2a8927a XL |
7 | use object::write::{self, StandardSegment, Symbol, SymbolSection}; |
8 | use object::{ | |
9 | elf, pe, Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, | |
10 | SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, | |
11 | }; | |
12 | ||
13 | use snap::write::FrameEncoder; | |
14 | ||
17df50a5 | 15 | use rustc_data_structures::memmap::Mmap; |
353b0b11 | 16 | use rustc_data_structures::owned_slice::try_slice_owned; |
17df50a5 | 17 | use rustc_data_structures::sync::MetadataRef; |
064997fb | 18 | use rustc_metadata::fs::METADATA_FILENAME; |
a2a8927a | 19 | use rustc_metadata::EncodedMetadata; |
c295e0f8 | 20 | use rustc_session::cstore::MetadataLoader; |
a2a8927a XL |
21 | use rustc_session::Session; |
22 | use rustc_target::abi::Endian; | |
04454e1e | 23 | use rustc_target::spec::{RelocModel, Target}; |
17df50a5 | 24 | |
17df50a5 XL |
25 | /// The default metadata loader. This is used by cg_llvm and cg_clif. |
26 | /// | |
27 | /// # Metadata location | |
28 | /// | |
29 | /// <dl> | |
30 | /// <dt>rlib</dt> | |
31 | /// <dd>The metadata can be found in the `lib.rmeta` file inside of the ar archive.</dd> | |
32 | /// <dt>dylib</dt> | |
33 | /// <dd>The metadata can be found in the `.rustc` section of the shared library.</dd> | |
34 | /// </dl> | |
9ffffee4 | 35 | #[derive(Debug)] |
17df50a5 XL |
36 | pub struct DefaultMetadataLoader; |
37 | ||
38 | fn load_metadata_with( | |
39 | path: &Path, | |
40 | f: impl for<'a> FnOnce(&'a [u8]) -> Result<&'a [u8], String>, | |
41 | ) -> Result<MetadataRef, String> { | |
42 | let file = | |
43 | File::open(path).map_err(|e| format!("failed to open file '{}': {}", path.display(), e))?; | |
353b0b11 FG |
44 | |
45 | unsafe { Mmap::map(file) } | |
46 | .map_err(|e| format!("failed to mmap file '{}': {}", path.display(), e)) | |
47 | .and_then(|mmap| try_slice_owned(mmap, |mmap| f(mmap))) | |
17df50a5 XL |
48 | } |
49 | ||
50 | impl MetadataLoader for DefaultMetadataLoader { | |
51 | fn get_rlib_metadata(&self, _target: &Target, path: &Path) -> Result<MetadataRef, String> { | |
52 | load_metadata_with(path, |data| { | |
53 | let archive = object::read::archive::ArchiveFile::parse(&*data) | |
54 | .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; | |
55 | ||
56 | for entry_result in archive.members() { | |
57 | let entry = entry_result | |
58 | .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; | |
59 | if entry.name() == METADATA_FILENAME.as_bytes() { | |
60 | let data = entry | |
61 | .data(data) | |
62 | .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; | |
487cf647 | 63 | return search_for_section(path, data, ".rmeta"); |
17df50a5 XL |
64 | } |
65 | } | |
66 | ||
67 | Err(format!("metadata not found in rlib '{}'", path.display())) | |
68 | }) | |
69 | } | |
70 | ||
71 | fn get_dylib_metadata(&self, _target: &Target, path: &Path) -> Result<MetadataRef, String> { | |
487cf647 | 72 | load_metadata_with(path, |data| search_for_section(path, data, ".rustc")) |
17df50a5 XL |
73 | } |
74 | } | |
75 | ||
487cf647 | 76 | pub(super) fn search_for_section<'a>( |
17df50a5 XL |
77 | path: &Path, |
78 | bytes: &'a [u8], | |
79 | section: &str, | |
80 | ) -> Result<&'a [u8], String> { | |
5e7ed085 | 81 | let Ok(file) = object::File::parse(bytes) else { |
17df50a5 XL |
82 | // The parse above could fail for odd reasons like corruption, but for |
83 | // now we just interpret it as this target doesn't support metadata | |
84 | // emission in object files so the entire byte slice itself is probably | |
85 | // a metadata file. Ideally though if necessary we could at least check | |
86 | // the prefix of bytes to see if it's an actual metadata object and if | |
87 | // not forward the error along here. | |
5e7ed085 | 88 | return Ok(bytes); |
17df50a5 XL |
89 | }; |
90 | file.section_by_name(section) | |
91 | .ok_or_else(|| format!("no `{}` section in '{}'", section, path.display()))? | |
92 | .data() | |
93 | .map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e)) | |
94 | } | |
a2a8927a | 95 | |
04454e1e | 96 | pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> { |
a2a8927a XL |
97 | let endianness = match sess.target.options.endian { |
98 | Endian::Little => Endianness::Little, | |
99 | Endian::Big => Endianness::Big, | |
100 | }; | |
101 | let architecture = match &sess.target.arch[..] { | |
102 | "arm" => Architecture::Arm, | |
9c376795 FG |
103 | "aarch64" => { |
104 | if sess.target.pointer_width == 32 { | |
105 | Architecture::Aarch64_Ilp32 | |
106 | } else { | |
107 | Architecture::Aarch64 | |
108 | } | |
109 | } | |
a2a8927a XL |
110 | "x86" => Architecture::I386, |
111 | "s390x" => Architecture::S390x, | |
112 | "mips" => Architecture::Mips, | |
113 | "mips64" => Architecture::Mips64, | |
114 | "x86_64" => { | |
115 | if sess.target.pointer_width == 32 { | |
116 | Architecture::X86_64_X32 | |
117 | } else { | |
118 | Architecture::X86_64 | |
119 | } | |
120 | } | |
121 | "powerpc" => Architecture::PowerPc, | |
122 | "powerpc64" => Architecture::PowerPc64, | |
123 | "riscv32" => Architecture::Riscv32, | |
124 | "riscv64" => Architecture::Riscv64, | |
125 | "sparc64" => Architecture::Sparc64, | |
f2b60f7d FG |
126 | "avr" => Architecture::Avr, |
127 | "msp430" => Architecture::Msp430, | |
128 | "hexagon" => Architecture::Hexagon, | |
129 | "bpf" => Architecture::Bpf, | |
353b0b11 | 130 | "loongarch64" => Architecture::LoongArch64, |
a2a8927a XL |
131 | // Unsupported architecture. |
132 | _ => return None, | |
133 | }; | |
134 | let binary_format = if sess.target.is_like_osx { | |
135 | BinaryFormat::MachO | |
136 | } else if sess.target.is_like_windows { | |
137 | BinaryFormat::Coff | |
138 | } else { | |
139 | BinaryFormat::Elf | |
140 | }; | |
141 | ||
142 | let mut file = write::Object::new(binary_format, architecture, endianness); | |
923072b8 | 143 | let e_flags = match architecture { |
a2a8927a | 144 | Architecture::Mips => { |
04454e1e FG |
145 | let arch = match sess.target.options.cpu.as_ref() { |
146 | "mips1" => elf::EF_MIPS_ARCH_1, | |
147 | "mips2" => elf::EF_MIPS_ARCH_2, | |
148 | "mips3" => elf::EF_MIPS_ARCH_3, | |
149 | "mips4" => elf::EF_MIPS_ARCH_4, | |
150 | "mips5" => elf::EF_MIPS_ARCH_5, | |
151 | s if s.contains("r6") => elf::EF_MIPS_ARCH_32R6, | |
152 | _ => elf::EF_MIPS_ARCH_32R2, | |
153 | }; | |
154 | // The only ABI LLVM supports for 32-bit MIPS CPUs is o32. | |
155 | let mut e_flags = elf::EF_MIPS_CPIC | elf::EF_MIPS_ABI_O32 | arch; | |
156 | if sess.target.options.relocation_model != RelocModel::Static { | |
157 | e_flags |= elf::EF_MIPS_PIC; | |
158 | } | |
159 | if sess.target.options.cpu.contains("r6") { | |
160 | e_flags |= elf::EF_MIPS_NAN2008; | |
161 | } | |
923072b8 | 162 | e_flags |
a2a8927a XL |
163 | } |
164 | Architecture::Mips64 => { | |
165 | // copied from `mips64el-linux-gnuabi64-gcc foo.c -c` | |
5099ac24 FG |
166 | let e_flags = elf::EF_MIPS_CPIC |
167 | | elf::EF_MIPS_PIC | |
168 | | if sess.target.options.cpu.contains("r6") { | |
169 | elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008 | |
170 | } else { | |
171 | elf::EF_MIPS_ARCH_64R2 | |
172 | }; | |
923072b8 | 173 | e_flags |
a2a8927a | 174 | } |
9c376795 FG |
175 | Architecture::Riscv32 | Architecture::Riscv64 => { |
176 | // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc | |
177 | let mut e_flags: u32 = 0x0; | |
178 | let features = &sess.target.options.features; | |
179 | // Check if compressed is enabled | |
180 | if features.contains("+c") { | |
181 | e_flags |= elf::EF_RISCV_RVC; | |
182 | } | |
183 | ||
184 | // Select the appropriate floating-point ABI | |
185 | if features.contains("+d") { | |
186 | e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE; | |
187 | } else if features.contains("+f") { | |
188 | e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE; | |
189 | } else { | |
190 | e_flags |= elf::EF_RISCV_FLOAT_ABI_SOFT; | |
191 | } | |
923072b8 | 192 | e_flags |
a2a8927a | 193 | } |
353b0b11 FG |
194 | Architecture::LoongArch64 => { |
195 | // Source: https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html#_e_flags_identifies_abi_type_and_version | |
196 | elf::EF_LARCH_OBJABI_V1 | elf::EF_LARCH_ABI_DOUBLE_FLOAT | |
197 | } | |
923072b8 | 198 | _ => 0, |
a2a8927a | 199 | }; |
923072b8 FG |
200 | // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI` |
201 | let os_abi = match sess.target.options.os.as_ref() { | |
202 | "hermit" => elf::ELFOSABI_STANDALONE, | |
203 | "freebsd" => elf::ELFOSABI_FREEBSD, | |
204 | "solaris" => elf::ELFOSABI_SOLARIS, | |
205 | _ => elf::ELFOSABI_NONE, | |
206 | }; | |
207 | let abi_version = 0; | |
208 | file.flags = FileFlags::Elf { os_abi, abi_version, e_flags }; | |
a2a8927a XL |
209 | Some(file) |
210 | } | |
211 | ||
5e7ed085 FG |
212 | pub enum MetadataPosition { |
213 | First, | |
214 | Last, | |
215 | } | |
216 | ||
487cf647 FG |
217 | /// For rlibs we "pack" rustc metadata into a dummy object file. |
218 | /// | |
219 | /// Historically it was needed because rustc linked rlibs as whole-archive in some cases. | |
220 | /// In that case linkers try to include all files located in an archive, so if metadata is stored | |
221 | /// in an archive then it needs to be of a form that the linker is able to process. | |
222 | /// Now it's not clear whether metadata still needs to be wrapped into an object file or not. | |
223 | /// | |
224 | /// Note, though, that we don't actually want this metadata to show up in any | |
225 | /// final output of the compiler. Instead this is purely for rustc's own | |
226 | /// metadata tracking purposes. | |
227 | /// | |
228 | /// With the above in mind, each "flavor" of object format gets special | |
229 | /// handling here depending on the target: | |
230 | /// | |
231 | /// * MachO - macos-like targets will insert the metadata into a section that | |
232 | /// is sort of fake dwarf debug info. Inspecting the source of the macos | |
233 | /// linker this causes these sections to be skipped automatically because | |
234 | /// it's not in an allowlist of otherwise well known dwarf section names to | |
235 | /// go into the final artifact. | |
236 | /// | |
237 | /// * WebAssembly - we actually don't have any container format for this | |
238 | /// target. WebAssembly doesn't support the `dylib` crate type anyway so | |
239 | /// there's no need for us to support this at this time. Consequently the | |
240 | /// metadata bytes are simply stored as-is into an rlib. | |
241 | /// | |
242 | /// * COFF - Windows-like targets create an object with a section that has | |
243 | /// the `IMAGE_SCN_LNK_REMOVE` flag set which ensures that if the linker | |
244 | /// ever sees the section it doesn't process it and it's removed. | |
245 | /// | |
246 | /// * ELF - All other targets are similar to Windows in that there's a | |
247 | /// `SHF_EXCLUDE` flag we can set on sections in an object file to get | |
248 | /// automatically removed from the final output. | |
249 | pub fn create_wrapper_file( | |
250 | sess: &Session, | |
251 | section_name: Vec<u8>, | |
252 | data: &[u8], | |
253 | ) -> (Vec<u8>, MetadataPosition) { | |
5099ac24 | 254 | let Some(mut file) = create_object_file(sess) else { |
a2a8927a XL |
255 | // This is used to handle all "other" targets. This includes targets |
256 | // in two categories: | |
257 | // | |
258 | // * Some targets don't have support in the `object` crate just yet | |
259 | // to write an object file. These targets are likely to get filled | |
260 | // out over time. | |
261 | // | |
262 | // * Targets like WebAssembly don't support dylibs, so the purpose | |
263 | // of putting metadata in object files, to support linking rlibs | |
264 | // into dylibs, is moot. | |
265 | // | |
266 | // In both of these cases it means that linking into dylibs will | |
267 | // not be supported by rustc. This doesn't matter for targets like | |
268 | // WebAssembly and for targets not supported by the `object` crate | |
269 | // yet it means that work will need to be done in the `object` crate | |
270 | // to add a case above. | |
487cf647 | 271 | return (data.to_vec(), MetadataPosition::Last); |
a2a8927a XL |
272 | }; |
273 | let section = file.add_section( | |
274 | file.segment_name(StandardSegment::Debug).to_vec(), | |
487cf647 | 275 | section_name, |
a2a8927a XL |
276 | SectionKind::Debug, |
277 | ); | |
278 | match file.format() { | |
279 | BinaryFormat::Coff => { | |
280 | file.section_mut(section).flags = | |
281 | SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE }; | |
282 | } | |
283 | BinaryFormat::Elf => { | |
284 | file.section_mut(section).flags = | |
285 | SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 }; | |
286 | } | |
287 | _ => {} | |
288 | }; | |
487cf647 | 289 | file.append_section_data(section, data, 1); |
5e7ed085 | 290 | (file.write().unwrap(), MetadataPosition::First) |
a2a8927a XL |
291 | } |
292 | ||
293 | // Historical note: | |
294 | // | |
295 | // When using link.exe it was seen that the section name `.note.rustc` | |
296 | // was getting shortened to `.note.ru`, and according to the PE and COFF | |
297 | // specification: | |
298 | // | |
299 | // > Executable images do not use a string table and do not support | |
300 | // > section names longer than 8 characters | |
301 | // | |
302 | // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format | |
303 | // | |
304 | // As a result, we choose a slightly shorter name! As to why | |
305 | // `.note.rustc` works on MinGW, see | |
306 | // https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/lld/COFF/Writer.cpp#L1190-L1197 | |
307 | pub fn create_compressed_metadata_file( | |
308 | sess: &Session, | |
309 | metadata: &EncodedMetadata, | |
310 | symbol_name: &str, | |
311 | ) -> Vec<u8> { | |
312 | let mut compressed = rustc_metadata::METADATA_HEADER.to_vec(); | |
353b0b11 FG |
313 | // Our length will be backfilled once we're done writing |
314 | compressed.write_all(&[0; 4]).unwrap(); | |
a2a8927a | 315 | FrameEncoder::new(&mut compressed).write_all(metadata.raw_data()).unwrap(); |
353b0b11 FG |
316 | let meta_len = rustc_metadata::METADATA_HEADER.len(); |
317 | let data_len = (compressed.len() - meta_len - 4) as u32; | |
318 | compressed[meta_len..meta_len + 4].copy_from_slice(&data_len.to_be_bytes()); | |
319 | ||
5099ac24 | 320 | let Some(mut file) = create_object_file(sess) else { |
a2a8927a XL |
321 | return compressed.to_vec(); |
322 | }; | |
323 | let section = file.add_section( | |
324 | file.segment_name(StandardSegment::Data).to_vec(), | |
325 | b".rustc".to_vec(), | |
326 | SectionKind::ReadOnlyData, | |
327 | ); | |
328 | match file.format() { | |
329 | BinaryFormat::Elf => { | |
330 | // Explicitly set no flags to avoid SHF_ALLOC default for data section. | |
331 | file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 }; | |
332 | } | |
333 | _ => {} | |
334 | }; | |
335 | let offset = file.append_section_data(section, &compressed, 1); | |
336 | ||
337 | // For MachO and probably PE this is necessary to prevent the linker from throwing away the | |
338 | // .rustc section. For ELF this isn't necessary, but it also doesn't harm. | |
339 | file.add_symbol(Symbol { | |
340 | name: symbol_name.as_bytes().to_vec(), | |
341 | value: offset, | |
342 | size: compressed.len() as u64, | |
343 | kind: SymbolKind::Data, | |
344 | scope: SymbolScope::Dynamic, | |
345 | weak: false, | |
346 | section: SymbolSection::Section(section), | |
347 | flags: SymbolFlags::None, | |
348 | }); | |
349 | ||
350 | file.write().unwrap() | |
351 | } |