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