]>
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 | ||
49aad941 | 15 | use object::elf::NT_GNU_PROPERTY_TYPE_0; |
17df50a5 | 16 | use rustc_data_structures::memmap::Mmap; |
49aad941 | 17 | use rustc_data_structures::owned_slice::{try_slice_owned, OwnedSlice}; |
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>, | |
49aad941 | 41 | ) -> Result<OwnedSlice, String> { |
17df50a5 XL |
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 { | |
49aad941 | 51 | fn get_rlib_metadata(&self, _target: &Target, path: &Path) -> Result<OwnedSlice, String> { |
17df50a5 XL |
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 | ||
49aad941 | 71 | fn get_dylib_metadata(&self, _target: &Target, path: &Path) -> Result<OwnedSlice, 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 | |
49aad941 FG |
96 | fn add_gnu_property_note( |
97 | file: &mut write::Object<'static>, | |
98 | architecture: Architecture, | |
99 | binary_format: BinaryFormat, | |
100 | endianness: Endianness, | |
101 | ) { | |
102 | // check bti protection | |
103 | if binary_format != BinaryFormat::Elf | |
104 | || !matches!(architecture, Architecture::X86_64 | Architecture::Aarch64) | |
105 | { | |
106 | return; | |
107 | } | |
108 | ||
109 | let section = file.add_section( | |
110 | file.segment_name(StandardSegment::Data).to_vec(), | |
111 | b".note.gnu.property".to_vec(), | |
112 | SectionKind::Note, | |
113 | ); | |
114 | let mut data: Vec<u8> = Vec::new(); | |
115 | let n_namsz: u32 = 4; // Size of the n_name field | |
116 | let n_descsz: u32 = 16; // Size of the n_desc field | |
117 | let n_type: u32 = NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor | |
118 | let header_values = [n_namsz, n_descsz, n_type]; | |
119 | header_values.iter().for_each(|v| { | |
120 | data.extend_from_slice(&match endianness { | |
121 | Endianness::Little => v.to_le_bytes(), | |
122 | Endianness::Big => v.to_be_bytes(), | |
123 | }) | |
124 | }); | |
125 | data.extend_from_slice(b"GNU\0"); // Owner of the program property note | |
126 | let pr_type: u32 = match architecture { | |
127 | Architecture::X86_64 => 0xc0000002, | |
128 | Architecture::Aarch64 => 0xc0000000, | |
129 | _ => unreachable!(), | |
130 | }; | |
131 | let pr_datasz: u32 = 4; //size of the pr_data field | |
132 | let pr_data: u32 = 3; //program property descriptor | |
133 | let pr_padding: u32 = 0; | |
134 | let property_values = [pr_type, pr_datasz, pr_data, pr_padding]; | |
135 | property_values.iter().for_each(|v| { | |
136 | data.extend_from_slice(&match endianness { | |
137 | Endianness::Little => v.to_le_bytes(), | |
138 | Endianness::Big => v.to_be_bytes(), | |
139 | }) | |
140 | }); | |
141 | file.append_section_data(section, &data, 8); | |
142 | } | |
143 | ||
04454e1e | 144 | pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> { |
a2a8927a XL |
145 | let endianness = match sess.target.options.endian { |
146 | Endian::Little => Endianness::Little, | |
147 | Endian::Big => Endianness::Big, | |
148 | }; | |
149 | let architecture = match &sess.target.arch[..] { | |
150 | "arm" => Architecture::Arm, | |
9c376795 FG |
151 | "aarch64" => { |
152 | if sess.target.pointer_width == 32 { | |
153 | Architecture::Aarch64_Ilp32 | |
154 | } else { | |
155 | Architecture::Aarch64 | |
156 | } | |
157 | } | |
a2a8927a XL |
158 | "x86" => Architecture::I386, |
159 | "s390x" => Architecture::S390x, | |
160 | "mips" => Architecture::Mips, | |
161 | "mips64" => Architecture::Mips64, | |
162 | "x86_64" => { | |
163 | if sess.target.pointer_width == 32 { | |
164 | Architecture::X86_64_X32 | |
165 | } else { | |
166 | Architecture::X86_64 | |
167 | } | |
168 | } | |
169 | "powerpc" => Architecture::PowerPc, | |
170 | "powerpc64" => Architecture::PowerPc64, | |
171 | "riscv32" => Architecture::Riscv32, | |
172 | "riscv64" => Architecture::Riscv64, | |
173 | "sparc64" => Architecture::Sparc64, | |
f2b60f7d FG |
174 | "avr" => Architecture::Avr, |
175 | "msp430" => Architecture::Msp430, | |
176 | "hexagon" => Architecture::Hexagon, | |
177 | "bpf" => Architecture::Bpf, | |
353b0b11 | 178 | "loongarch64" => Architecture::LoongArch64, |
a2a8927a XL |
179 | // Unsupported architecture. |
180 | _ => return None, | |
181 | }; | |
182 | let binary_format = if sess.target.is_like_osx { | |
183 | BinaryFormat::MachO | |
184 | } else if sess.target.is_like_windows { | |
185 | BinaryFormat::Coff | |
186 | } else { | |
187 | BinaryFormat::Elf | |
188 | }; | |
189 | ||
190 | let mut file = write::Object::new(binary_format, architecture, endianness); | |
49aad941 FG |
191 | if sess.target.is_like_osx { |
192 | if let Some(build_version) = macho_object_build_version_for_target(&sess.target) { | |
193 | file.set_macho_build_version(build_version) | |
194 | } | |
195 | } | |
923072b8 | 196 | let e_flags = match architecture { |
a2a8927a | 197 | Architecture::Mips => { |
04454e1e FG |
198 | let arch = match sess.target.options.cpu.as_ref() { |
199 | "mips1" => elf::EF_MIPS_ARCH_1, | |
200 | "mips2" => elf::EF_MIPS_ARCH_2, | |
201 | "mips3" => elf::EF_MIPS_ARCH_3, | |
202 | "mips4" => elf::EF_MIPS_ARCH_4, | |
203 | "mips5" => elf::EF_MIPS_ARCH_5, | |
204 | s if s.contains("r6") => elf::EF_MIPS_ARCH_32R6, | |
205 | _ => elf::EF_MIPS_ARCH_32R2, | |
206 | }; | |
207 | // The only ABI LLVM supports for 32-bit MIPS CPUs is o32. | |
208 | let mut e_flags = elf::EF_MIPS_CPIC | elf::EF_MIPS_ABI_O32 | arch; | |
209 | if sess.target.options.relocation_model != RelocModel::Static { | |
210 | e_flags |= elf::EF_MIPS_PIC; | |
211 | } | |
212 | if sess.target.options.cpu.contains("r6") { | |
213 | e_flags |= elf::EF_MIPS_NAN2008; | |
214 | } | |
923072b8 | 215 | e_flags |
a2a8927a XL |
216 | } |
217 | Architecture::Mips64 => { | |
218 | // copied from `mips64el-linux-gnuabi64-gcc foo.c -c` | |
5099ac24 FG |
219 | let e_flags = elf::EF_MIPS_CPIC |
220 | | elf::EF_MIPS_PIC | |
221 | | if sess.target.options.cpu.contains("r6") { | |
222 | elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008 | |
223 | } else { | |
224 | elf::EF_MIPS_ARCH_64R2 | |
225 | }; | |
923072b8 | 226 | e_flags |
a2a8927a | 227 | } |
9c376795 FG |
228 | Architecture::Riscv32 | Architecture::Riscv64 => { |
229 | // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc | |
230 | let mut e_flags: u32 = 0x0; | |
231 | let features = &sess.target.options.features; | |
232 | // Check if compressed is enabled | |
233 | if features.contains("+c") { | |
234 | e_flags |= elf::EF_RISCV_RVC; | |
235 | } | |
236 | ||
237 | // Select the appropriate floating-point ABI | |
238 | if features.contains("+d") { | |
239 | e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE; | |
240 | } else if features.contains("+f") { | |
241 | e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE; | |
242 | } else { | |
243 | e_flags |= elf::EF_RISCV_FLOAT_ABI_SOFT; | |
244 | } | |
923072b8 | 245 | e_flags |
a2a8927a | 246 | } |
353b0b11 FG |
247 | Architecture::LoongArch64 => { |
248 | // Source: https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html#_e_flags_identifies_abi_type_and_version | |
249 | elf::EF_LARCH_OBJABI_V1 | elf::EF_LARCH_ABI_DOUBLE_FLOAT | |
250 | } | |
923072b8 | 251 | _ => 0, |
a2a8927a | 252 | }; |
923072b8 FG |
253 | // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI` |
254 | let os_abi = match sess.target.options.os.as_ref() { | |
255 | "hermit" => elf::ELFOSABI_STANDALONE, | |
256 | "freebsd" => elf::ELFOSABI_FREEBSD, | |
257 | "solaris" => elf::ELFOSABI_SOLARIS, | |
258 | _ => elf::ELFOSABI_NONE, | |
259 | }; | |
260 | let abi_version = 0; | |
49aad941 | 261 | add_gnu_property_note(&mut file, architecture, binary_format, endianness); |
923072b8 | 262 | file.flags = FileFlags::Elf { os_abi, abi_version, e_flags }; |
a2a8927a XL |
263 | Some(file) |
264 | } | |
265 | ||
49aad941 FG |
266 | /// Apple's LD, when linking for Mac Catalyst, requires object files to |
267 | /// contain information about what they were built for (LC_BUILD_VERSION): | |
268 | /// the platform (macOS/watchOS etc), minimum OS version, and SDK version. | |
269 | /// This returns a `MachOBuildVersion` if necessary for the target. | |
270 | fn macho_object_build_version_for_target( | |
271 | target: &Target, | |
272 | ) -> Option<object::write::MachOBuildVersion> { | |
273 | if !target.llvm_target.ends_with("-macabi") { | |
274 | return None; | |
275 | } | |
276 | /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz" | |
277 | /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200 | |
278 | fn pack_version((major, minor): (u32, u32)) -> u32 { | |
279 | (major << 16) | (minor << 8) | |
280 | } | |
281 | ||
282 | let platform = object::macho::PLATFORM_MACCATALYST; | |
283 | let min_os = (14, 0); | |
284 | let sdk = (16, 2); | |
285 | ||
286 | let mut build_version = object::write::MachOBuildVersion::default(); | |
287 | build_version.platform = platform; | |
288 | build_version.minos = pack_version(min_os); | |
289 | build_version.sdk = pack_version(sdk); | |
290 | Some(build_version) | |
291 | } | |
292 | ||
5e7ed085 FG |
293 | pub enum MetadataPosition { |
294 | First, | |
295 | Last, | |
296 | } | |
297 | ||
487cf647 FG |
298 | /// For rlibs we "pack" rustc metadata into a dummy object file. |
299 | /// | |
300 | /// Historically it was needed because rustc linked rlibs as whole-archive in some cases. | |
301 | /// In that case linkers try to include all files located in an archive, so if metadata is stored | |
302 | /// in an archive then it needs to be of a form that the linker is able to process. | |
303 | /// Now it's not clear whether metadata still needs to be wrapped into an object file or not. | |
304 | /// | |
305 | /// Note, though, that we don't actually want this metadata to show up in any | |
306 | /// final output of the compiler. Instead this is purely for rustc's own | |
307 | /// metadata tracking purposes. | |
308 | /// | |
309 | /// With the above in mind, each "flavor" of object format gets special | |
310 | /// handling here depending on the target: | |
311 | /// | |
312 | /// * MachO - macos-like targets will insert the metadata into a section that | |
313 | /// is sort of fake dwarf debug info. Inspecting the source of the macos | |
314 | /// linker this causes these sections to be skipped automatically because | |
315 | /// it's not in an allowlist of otherwise well known dwarf section names to | |
316 | /// go into the final artifact. | |
317 | /// | |
318 | /// * WebAssembly - we actually don't have any container format for this | |
319 | /// target. WebAssembly doesn't support the `dylib` crate type anyway so | |
320 | /// there's no need for us to support this at this time. Consequently the | |
321 | /// metadata bytes are simply stored as-is into an rlib. | |
322 | /// | |
323 | /// * COFF - Windows-like targets create an object with a section that has | |
324 | /// the `IMAGE_SCN_LNK_REMOVE` flag set which ensures that if the linker | |
325 | /// ever sees the section it doesn't process it and it's removed. | |
326 | /// | |
327 | /// * ELF - All other targets are similar to Windows in that there's a | |
328 | /// `SHF_EXCLUDE` flag we can set on sections in an object file to get | |
329 | /// automatically removed from the final output. | |
330 | pub fn create_wrapper_file( | |
331 | sess: &Session, | |
332 | section_name: Vec<u8>, | |
333 | data: &[u8], | |
334 | ) -> (Vec<u8>, MetadataPosition) { | |
5099ac24 | 335 | let Some(mut file) = create_object_file(sess) else { |
a2a8927a XL |
336 | // This is used to handle all "other" targets. This includes targets |
337 | // in two categories: | |
338 | // | |
339 | // * Some targets don't have support in the `object` crate just yet | |
340 | // to write an object file. These targets are likely to get filled | |
341 | // out over time. | |
342 | // | |
343 | // * Targets like WebAssembly don't support dylibs, so the purpose | |
344 | // of putting metadata in object files, to support linking rlibs | |
345 | // into dylibs, is moot. | |
346 | // | |
347 | // In both of these cases it means that linking into dylibs will | |
348 | // not be supported by rustc. This doesn't matter for targets like | |
349 | // WebAssembly and for targets not supported by the `object` crate | |
350 | // yet it means that work will need to be done in the `object` crate | |
351 | // to add a case above. | |
487cf647 | 352 | return (data.to_vec(), MetadataPosition::Last); |
a2a8927a XL |
353 | }; |
354 | let section = file.add_section( | |
355 | file.segment_name(StandardSegment::Debug).to_vec(), | |
487cf647 | 356 | section_name, |
a2a8927a XL |
357 | SectionKind::Debug, |
358 | ); | |
359 | match file.format() { | |
360 | BinaryFormat::Coff => { | |
361 | file.section_mut(section).flags = | |
362 | SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE }; | |
363 | } | |
364 | BinaryFormat::Elf => { | |
365 | file.section_mut(section).flags = | |
366 | SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 }; | |
367 | } | |
368 | _ => {} | |
369 | }; | |
487cf647 | 370 | file.append_section_data(section, data, 1); |
5e7ed085 | 371 | (file.write().unwrap(), MetadataPosition::First) |
a2a8927a XL |
372 | } |
373 | ||
374 | // Historical note: | |
375 | // | |
376 | // When using link.exe it was seen that the section name `.note.rustc` | |
377 | // was getting shortened to `.note.ru`, and according to the PE and COFF | |
378 | // specification: | |
379 | // | |
380 | // > Executable images do not use a string table and do not support | |
381 | // > section names longer than 8 characters | |
382 | // | |
383 | // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format | |
384 | // | |
385 | // As a result, we choose a slightly shorter name! As to why | |
386 | // `.note.rustc` works on MinGW, see | |
387 | // https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/lld/COFF/Writer.cpp#L1190-L1197 | |
388 | pub fn create_compressed_metadata_file( | |
389 | sess: &Session, | |
390 | metadata: &EncodedMetadata, | |
391 | symbol_name: &str, | |
392 | ) -> Vec<u8> { | |
393 | let mut compressed = rustc_metadata::METADATA_HEADER.to_vec(); | |
353b0b11 FG |
394 | // Our length will be backfilled once we're done writing |
395 | compressed.write_all(&[0; 4]).unwrap(); | |
a2a8927a | 396 | FrameEncoder::new(&mut compressed).write_all(metadata.raw_data()).unwrap(); |
353b0b11 FG |
397 | let meta_len = rustc_metadata::METADATA_HEADER.len(); |
398 | let data_len = (compressed.len() - meta_len - 4) as u32; | |
399 | compressed[meta_len..meta_len + 4].copy_from_slice(&data_len.to_be_bytes()); | |
400 | ||
5099ac24 | 401 | let Some(mut file) = create_object_file(sess) else { |
a2a8927a XL |
402 | return compressed.to_vec(); |
403 | }; | |
404 | let section = file.add_section( | |
405 | file.segment_name(StandardSegment::Data).to_vec(), | |
406 | b".rustc".to_vec(), | |
407 | SectionKind::ReadOnlyData, | |
408 | ); | |
409 | match file.format() { | |
410 | BinaryFormat::Elf => { | |
411 | // Explicitly set no flags to avoid SHF_ALLOC default for data section. | |
412 | file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 }; | |
413 | } | |
414 | _ => {} | |
415 | }; | |
416 | let offset = file.append_section_data(section, &compressed, 1); | |
417 | ||
418 | // For MachO and probably PE this is necessary to prevent the linker from throwing away the | |
419 | // .rustc section. For ELF this isn't necessary, but it also doesn't harm. | |
420 | file.add_symbol(Symbol { | |
421 | name: symbol_name.as_bytes().to_vec(), | |
422 | value: offset, | |
423 | size: compressed.len() as u64, | |
424 | kind: SymbolKind::Data, | |
425 | scope: SymbolScope::Dynamic, | |
426 | weak: false, | |
427 | section: SymbolSection::Section(section), | |
428 | flags: SymbolFlags::None, | |
429 | }); | |
430 | ||
431 | file.write().unwrap() | |
432 | } |