]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_codegen_ssa/src/back/metadata.rs
Rollup merge of #92747 - swenson:bignum-bit-length-optimization, r=scottmcm
[rust.git] / compiler / rustc_codegen_ssa / src / back / metadata.rs
1 //! Reading of the rustc metadata for rlibs and dylibs
2
3 use std::fs::File;
4 use std::io::Write;
5 use std::path::Path;
6
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
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;
19 use rustc_metadata::EncodedMetadata;
20 use rustc_session::cstore::MetadataLoader;
21 use rustc_session::Session;
22 use rustc_target::abi::Endian;
23 use rustc_target::spec::Target;
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> {
82     let file = match object::File::parse(bytes) {
83         Ok(f) => f,
84         // The parse above could fail for odd reasons like corruption, but for
85         // now we just interpret it as this target doesn't support metadata
86         // emission in object files so the entire byte slice itself is probably
87         // a metadata file. Ideally though if necessary we could at least check
88         // the prefix of bytes to see if it's an actual metadata object and if
89         // not forward the error along here.
90         Err(_) => return Ok(bytes),
91     };
92     file.section_by_name(section)
93         .ok_or_else(|| format!("no `{}` section in '{}'", section, path.display()))?
94         .data()
95         .map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e))
96 }
97
98 fn create_object_file(sess: &Session) -> Option<write::Object<'static>> {
99     let endianness = match sess.target.options.endian {
100         Endian::Little => Endianness::Little,
101         Endian::Big => Endianness::Big,
102     };
103     let architecture = match &sess.target.arch[..] {
104         "arm" => Architecture::Arm,
105         "aarch64" => Architecture::Aarch64,
106         "x86" => Architecture::I386,
107         "s390x" => Architecture::S390x,
108         "mips" => Architecture::Mips,
109         "mips64" => Architecture::Mips64,
110         "x86_64" => {
111             if sess.target.pointer_width == 32 {
112                 Architecture::X86_64_X32
113             } else {
114                 Architecture::X86_64
115             }
116         }
117         "powerpc" => Architecture::PowerPc,
118         "powerpc64" => Architecture::PowerPc64,
119         "riscv32" => Architecture::Riscv32,
120         "riscv64" => Architecture::Riscv64,
121         "sparc64" => Architecture::Sparc64,
122         // Unsupported architecture.
123         _ => return None,
124     };
125     let binary_format = if sess.target.is_like_osx {
126         BinaryFormat::MachO
127     } else if sess.target.is_like_windows {
128         BinaryFormat::Coff
129     } else {
130         BinaryFormat::Elf
131     };
132
133     let mut file = write::Object::new(binary_format, architecture, endianness);
134     match architecture {
135         Architecture::Mips => {
136             // copied from `mipsel-linux-gnu-gcc foo.c -c` and
137             // inspecting the resulting `e_flags` field.
138             let e_flags = elf::EF_MIPS_CPIC
139                 | elf::EF_MIPS_PIC
140                 | if sess.target.options.cpu.contains("r6") {
141                     elf::EF_MIPS_ARCH_32R6 | elf::EF_MIPS_NAN2008
142                 } else {
143                     elf::EF_MIPS_ARCH_32R2
144                 };
145             file.flags = FileFlags::Elf { e_flags };
146         }
147         Architecture::Mips64 => {
148             // copied from `mips64el-linux-gnuabi64-gcc foo.c -c`
149             let e_flags = elf::EF_MIPS_CPIC
150                 | elf::EF_MIPS_PIC
151                 | if sess.target.options.cpu.contains("r6") {
152                     elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008
153                 } else {
154                     elf::EF_MIPS_ARCH_64R2
155                 };
156             file.flags = FileFlags::Elf { e_flags };
157         }
158         Architecture::Riscv64 if sess.target.options.features.contains("+d") => {
159             // copied from `riscv64-linux-gnu-gcc foo.c -c`, note though
160             // that the `+d` target feature represents whether the double
161             // float abi is enabled.
162             let e_flags = elf::EF_RISCV_RVC | elf::EF_RISCV_FLOAT_ABI_DOUBLE;
163             file.flags = FileFlags::Elf { e_flags };
164         }
165         _ => {}
166     };
167     Some(file)
168 }
169
170 // For rlibs we "pack" rustc metadata into a dummy object file. When rustc
171 // creates a dylib crate type it will pass `--whole-archive` (or the
172 // platform equivalent) to include all object files from an rlib into the
173 // final dylib itself. This causes linkers to iterate and try to include all
174 // files located in an archive, so if metadata is stored in an archive then
175 // it needs to be of a form that the linker will be able to process.
176 //
177 // Note, though, that we don't actually want this metadata to show up in any
178 // final output of the compiler. Instead this is purely for rustc's own
179 // metadata tracking purposes.
180 //
181 // With the above in mind, each "flavor" of object format gets special
182 // handling here depending on the target:
183 //
184 // * MachO - macos-like targets will insert the metadata into a section that
185 //   is sort of fake dwarf debug info. Inspecting the source of the macos
186 //   linker this causes these sections to be skipped automatically because
187 //   it's not in an allowlist of otherwise well known dwarf section names to
188 //   go into the final artifact.
189 //
190 // * WebAssembly - we actually don't have any container format for this
191 //   target. WebAssembly doesn't support the `dylib` crate type anyway so
192 //   there's no need for us to support this at this time. Consequently the
193 //   metadata bytes are simply stored as-is into an rlib.
194 //
195 // * COFF - Windows-like targets create an object with a section that has
196 //   the `IMAGE_SCN_LNK_REMOVE` flag set which ensures that if the linker
197 //   ever sees the section it doesn't process it and it's removed.
198 //
199 // * ELF - All other targets are similar to Windows in that there's a
200 //   `SHF_EXCLUDE` flag we can set on sections in an object file to get
201 //   automatically removed from the final output.
202 pub fn create_rmeta_file(sess: &Session, metadata: &[u8]) -> Vec<u8> {
203     let mut file = if let Some(file) = create_object_file(sess) {
204         file
205     } else {
206         // This is used to handle all "other" targets. This includes targets
207         // in two categories:
208         //
209         // * Some targets don't have support in the `object` crate just yet
210         //   to write an object file. These targets are likely to get filled
211         //   out over time.
212         //
213         // * Targets like WebAssembly don't support dylibs, so the purpose
214         //   of putting metadata in object files, to support linking rlibs
215         //   into dylibs, is moot.
216         //
217         // In both of these cases it means that linking into dylibs will
218         // not be supported by rustc. This doesn't matter for targets like
219         // WebAssembly and for targets not supported by the `object` crate
220         // yet it means that work will need to be done in the `object` crate
221         // to add a case above.
222         return metadata.to_vec();
223     };
224     let section = file.add_section(
225         file.segment_name(StandardSegment::Debug).to_vec(),
226         b".rmeta".to_vec(),
227         SectionKind::Debug,
228     );
229     match file.format() {
230         BinaryFormat::Coff => {
231             file.section_mut(section).flags =
232                 SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE };
233         }
234         BinaryFormat::Elf => {
235             file.section_mut(section).flags =
236                 SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 };
237         }
238         _ => {}
239     };
240     file.append_section_data(section, metadata, 1);
241     file.write().unwrap()
242 }
243
244 // Historical note:
245 //
246 // When using link.exe it was seen that the section name `.note.rustc`
247 // was getting shortened to `.note.ru`, and according to the PE and COFF
248 // specification:
249 //
250 // > Executable images do not use a string table and do not support
251 // > section names longer than 8 characters
252 //
253 // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
254 //
255 // As a result, we choose a slightly shorter name! As to why
256 // `.note.rustc` works on MinGW, see
257 // https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/lld/COFF/Writer.cpp#L1190-L1197
258 pub fn create_compressed_metadata_file(
259     sess: &Session,
260     metadata: &EncodedMetadata,
261     symbol_name: &str,
262 ) -> Vec<u8> {
263     let mut compressed = rustc_metadata::METADATA_HEADER.to_vec();
264     FrameEncoder::new(&mut compressed).write_all(metadata.raw_data()).unwrap();
265     let mut file = if let Some(file) = create_object_file(sess) {
266         file
267     } else {
268         return compressed.to_vec();
269     };
270     let section = file.add_section(
271         file.segment_name(StandardSegment::Data).to_vec(),
272         b".rustc".to_vec(),
273         SectionKind::ReadOnlyData,
274     );
275     match file.format() {
276         BinaryFormat::Elf => {
277             // Explicitly set no flags to avoid SHF_ALLOC default for data section.
278             file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 };
279         }
280         _ => {}
281     };
282     let offset = file.append_section_data(section, &compressed, 1);
283
284     // For MachO and probably PE this is necessary to prevent the linker from throwing away the
285     // .rustc section. For ELF this isn't necessary, but it also doesn't harm.
286     file.add_symbol(Symbol {
287         name: symbol_name.as_bytes().to_vec(),
288         value: offset,
289         size: compressed.len() as u64,
290         kind: SymbolKind::Data,
291         scope: SymbolScope::Dynamic,
292         weak: false,
293         section: SymbolSection::Section(section),
294         flags: SymbolFlags::None,
295     });
296
297     file.write().unwrap()
298 }