Rust作为一门新的系统语言,具有高性能、内存安全可靠、高效特性,越来越受到业界的关注及学习热潮。

LRFRC系列文章尝试从另外一个角度来学习Rust语言,通过了解编译器rustc基本原理来学习Rust语言,以便更好的理解和分析编译错误提示,更快的写出更好的Rust代码。
在了解Rust语言基础以及相关语法、生成AST语法树、简易宏和过程宏之后,rustc会分析其内容并生成符合ELF标准的动态库和可执行文件,但其如何实现Rust语言本身定制的一些特性比如crate包元素的导出和加载引用、宏的导入导出等?这往往需要自定义格式的中间文件;
不知是否留意过,使用cargo build编译时,会在对应target目录下产生一些以rlib、rmeta为后缀的文件,其编码格式以及其作用究竟是怎样的?非常值得了解了解;
现介绍rustc编译过程中生成的中间文件rlib和rmeta相关内容;
一、rlib静态库
rlib是rustc编译过程生成的以crate包为单元的静态库,类似于编译C/C++时生成的以.a为后缀的静态库;
1.自定义资源包
简单的来看,rlib静态库就是一个自定义的资源包,它除了类似.a中包含的目标文件.o之外,还包含一个lib.rmeta文件,使用binutils工具ar可查看其内容;
下面以Rust标准库中包含的liblibc-c07c01153ab72617.rlib来分析其格式及内容;
其中名称中c07c01153ab72617为128位的hash值,称为Crate Disambiguator;
libc-c07c01153ab72617.libc.b90w410b-cgu.0.rcgu.o文件为object file;
文件名中的b90w410b为64位的hash值,称为Strict Version Hash;
cgu指code generate unit简写,数值0-9,表示将整个libc crate包看成一个编码生成单元,按照一定规则分成10个不同的object file;
object file中包含不同函数编码实现逻辑;
为啥需要分割包含出不同的object file? 其一、为了复用标准ELF的可重定义文件; 其二、crate包内部rs文件之间存在不同mod逻辑与C/C++文件不同;
// 使用ar工具显示rlib文件中包含的文件
$ ar t liblibc-c07c01153ab72617.rlib
libc-c07c01153ab72617.libc.b90w410b-cgu.0.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.1.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.2.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.3.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.4.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.5.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.6.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.7.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.8.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.9.rcgu.o
lib.rmeta
// 使用ar工具分解出rlib文件包含的文件
$ ar x liblibc-c07c01153ab72617.rlib
// **.rcgu.o文件为ELF可重定向目标文件
$ file libc-c07c01153ab72617.libc.b90w410b-cgu.0.rcgu.o
libc-c07c01153ab72617.libc.b90w410b-cgu.0.rcgu.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
// lib.rmeta是一个自定义二进制格式的文件,其中包含生成它的rustc版本号等;
// 具体格式后续介绍
$ vim -b lib.rmeta
00000000: 7275 7374 0000 0005 0031 25de 2a72 7573 rust.....1%.*rus
00000010: 7463 2031 2e35 332e 302d 6265 7461 2e33 tc 1.53.0-beta.3
00000020: 2028 3832 6238 3632 3136 3420 3230 3231 (82b862164 2021
00000030: 2d30 352d 3232 2918 7275 7374 635f 7374 -05-22).rustc_st
00000040: 645f 776f 726b 7370 6163 655f 636f 7265 d_workspace_core
00000050: f395 f2c7 a299 b182 6e00 0211 2d36 3262 ........n...-62b
00000060: 3964 3836 6436 3232 3932 6165 3304 636f 9d86d62292ae3.co
00000070: 7265 d3b8 878e fc8c c4da ea01 0002 112d re.............-
00000080: 3066 6465 3461 6562 3038 6266 3837 3636 0fde4aeb08bf8766
00000090: 6300 0104 7574 696c 0100 ffc3 010e 0001 c...util........
000000a0: 0e74 6172 6765 745f 6665 6174 7572 6500 .target_feature.
...................................................................
2.Crate Disambiguator
A.hash计算
Crate Disambiguator为128位的hash值,它由-C metadata参数进行hash计算而来;
pub(crate) fn compute_crate_disambiguator(session: &Session)
-> CrateDisambiguator {
use std::hash::Hasher;
// The crate_disambiguator is a 128 bit hash.
let mut hasher = StableHasher::new();
let mut metadata = session.opts.cg.metadata.clone();
// We don't want the crate_disambiguator to dependent on
// the order -C metadata arguments, so sort them:
metadata.sort();
// Every distinct -C metadata value is only incorporated once:
metadata.dedup();
hasher.write(b"metadata");
for s in &metadata {
// Also incorporate the length of a metadata string,
// so that we generate different values
// for `-Cmetadata=ab -Cmetadata=c` and
// `-Cmetadata=a -Cmetadata=bc`
hasher.write_usize(s.len());
hasher.write(s.as_bytes());
}
// Also incorporate crate type, so that we don't get symbol conflicts
// when linking against a library of the same name,
// if this is an executable.
let is_exe = session.crate_types().contains(&CrateType::Executable);
hasher.write(if is_exe { b"exe" } else { b"lib" });
CrateDisambiguator::from(hasher.finish::<Fingerprint>())
}
B.为啥需要Crate Disambiguator?
它存在的目的在于:方便开发者在Rust代码中通过指定crate名称即可引用其他crate;
被引用的crate版本及类型由包编译工具cargo根据Cargo.toml来确定,然后通过-C metadata参数传递给rustc;
使用hash可以区分同一个名称的crate的不同版本;
另外基于这个hash的唯一性,可以用实现标识符名称的重编,并保证其唯一性;
C.Crate Disambiguator独特性
特别是同一crate可以使用具有相同名称的不同版本crate中具有相同名称的函数或结构定义,但函数的实现逻辑可以不同;
通过Crate Disambiguator可较好解决Node.js、Go、C++中对外部包或模块的不同版本函数依赖的复杂性,
从而轻松实现对相同包的不同版本依赖及其标识符名称重编,并对开发者透明;
3.Strict Version Hash
Strict Version Hash为64位的hash值,从其名称可知,它用来严格的细粒度的区分crate版本;
用来hash的元素包括:
- 它包含的HIR Nodes hash;// HIR为AST之后生成的高级中间描述
- 所有它依赖的上游crate hash;
- 所有它包含的源代码文件名称;
- 其他命令行编译选项比如-C metadata等;
二、crate meta元数据
metadata用来描述crate的元数据,以自定义的二进制格式来表示,可用来快速检查crate依赖和从代码中生成文档等,由于rmeta不涉及object文件,它不可用来链接;
使用cargo meta、depgraph工具,利用meta元数据,可以更全面的查看分析crate的详细信息及依赖;
1.元数据内容
其包含的主要内容如下:
- 生成元数据时使用的rustc版本;
- Strict Version Hash值;
- Crate Disambiguator值;
- 源代码信息比如文件名;
- 导出或公开的宏、traits、types、items、mods等;
- 可选的MIR编码;
2.元数据存在形式
元数据可以有三种形式存在,第一种,是以lib.meta形式包含在rlib文件;第二种,以独立的.rmeta文件形式出现; 第三种是以.rustc段的形式出现在ELF动态库中;
其中.rmeta文件的生成往往应用在管道化的编译过程中,以加速并行编译和增量编译;
以Rust标准库中包含的liblibc-c07c01153ab72617.rlib来查看其元数据;
// rlib中包含的lib.rmeta与独立的liblibc-c07c01153ab72617.rmeta内容一致;
$ md5sum liblibc-c07c01153ab72617.rmeta
36dc315da796f2a605f627263f2c9047 liblibc-c07c01153ab72617.rmeta
$ md5sum lib.rmeta
36dc315da796f2a605f627263f2c9047 lib.rmeta
$ readelf -t libstd-cfb98f81760ed48d.so
// 省略其他部分Section
.................................................................................
[20] .dynamic
DYNAMIC DYNAMIC 00000000003c8e90 00000000001c8e90 3
0000000000000210 0000000000000010 0 8
[0000000000000003]: WRITE, ALLOC
[21] .got
PROGBITS PROGBITS 00000000003c90a0 00000000001c90a0 0
0000000000000f60 0000000000000008 0 8
[0000000000000003]: WRITE, ALLOC
[22] .data
PROGBITS PROGBITS 00000000003ca000 00000000001ca000 0
00000000000000e2 0000000000000000 0 8
[0000000000000003]: WRITE, ALLOC
[23] .bss
NOBITS NOBITS 00000000003ca0e8 00000000001ca0e2 0
00000000000002d0 0000000000000000 0 8
[0000000000000003]: WRITE, ALLOC
[24] .comment
PROGBITS PROGBITS 0000000000000000 00000000001ca0e2 0
0000000000000011 0000000000000001 0 1
[0000000000000030]: MERGE, STRINGS
[25] .rustc
PROGBITS PROGBITS 0000000000000000 00000000001ca100 0
000000000030c9ad 0000000000000000 0 16
[0000000000000000]:
.................................................................................
3.元数据的生成
A.start_codegen触发元数据生成
根据初识rustc编译主流程中的介绍,rustc完成parse、expansion、analysis之后会使用linker进行link; 在link过程中会调用start_codegen,在其中会先进行encode_and_write_metadata即编码生成metadata,然后进行codegen_crate生成二进制编码;
// src/librustc_interface/passes.rs
/// Runs the codegen backend, after which the AST and analysis can
/// be discarded.
pub fn start_codegen<'tcx>(
codegen_backend: &dyn CodegenBackend,
tcx: TyCtxt<'tcx>,
outputs: &OutputFilenames,
) -> Box<dyn Any> {
info!("Pre-codegen\n{:?}", tcx.debug_stats());
let (metadata, need_metadata_module) = encode_and_write_metadata(tcx, outputs);
let codegen = tcx.sess.time("codegen_crate", move || {
codegen_backend.codegen_crate(tcx, metadata, need_metadata_module)
});
info!("Post-codegen\n{:?}", tcx.debug_stats());
// 省略其他部分代码
}
B.编码及生成元数据
使用encode_and_write_metadata调用enocode_meta_impl及encode_crate_root生成元数据;
// src/librustc_interface/passes.rs
fn encode_and_write_metadata(
tcx: TyCtxt<'_>,
outputs: &OutputFilenames,
) -> (middle::cstore::EncodedMetadata, bool) {
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum MetadataKind {
None,
Uncompressed,
Compressed,
}
// 根据CrateType类型来决定是否需要生成metadata及是否需要压缩
let metadata_kind = tcx
.sess
.crate_types()
.iter()
.map(|ty| match *ty {
CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib
=> MetadataKind::None,
CrateType::Rlib => MetadataKind::Uncompressed,
CrateType::Dylib | CrateType::ProcMacro => MetadataKind::Compressed,
})
.max()
.unwrap_or(MetadataKind::None);
let metadata = match metadata_kind {
MetadataKind::None => middle::cstore::EncodedMetadata::new(),
// 编码生成metadata
MetadataKind::Uncompressed | MetadataKind::Compressed
=> tcx.encode_metadata(), // 调用encode_metadata
// 省略其他部分代码
// 调用encode_metadata_impl
// src/librustc_metadata/rmeta/encoder.rs
fn encode_metadata_impl(tcx: TyCtxt<'_>) -> EncodedMetadata {
// 创建编码器
let mut encoder = opaque::Encoder::new(vec![]);
encoder.emit_raw_bytes(METADATA_HEADER).unwrap();
// Will be filled with the root position after encoding everything.
encoder.emit_raw_bytes(&[0, 0, 0, 0]).unwrap();
let source_map_files = tcx.sess.source_map().files();
let source_file_cache = (source_map_files[0].clone(), 0);
let hygiene_ctxt = HygieneEncodeContext::default();
// 在当前TyCtx创建编码Context
let mut ecx = EncodeContext {
opaque: encoder,
tcx,
feat: tcx.features(),
tables: Default::default(),
lazy_state: LazyState::NoNode,
type_shorthands: Default::default(),
predicate_shorthands: Default::default(),
source_file_cache,
interpret_allocs: Default::default(),
required_source_files,
is_proc_macro: tcx.sess.crate_types().contains(&CrateType::ProcMacro),
hygiene_ctxt: &hygiene_ctxt,
};
// 编码写入rustc版本
rustc_version().encode(&mut ecx).unwrap();
// 编码写入CrateRoot
let root = ecx.encode_crate_root();
let mut result = ecx.opaque.into_inner();
// Encode the root position.
let header = METADATA_HEADER.len();
let pos = root.position.get();
result[header + 0] = (pos >> 24) as u8;
result[header + 1] = (pos >> 16) as u8;
result[header + 2] = (pos >> 8) as u8;
result[header + 3] = (pos >> 0) as u8;
EncodedMetadata { raw_data: result }
}
fn encode_crate_root(&mut self) -> Lazy<CrateRoot<'tcx>> {
let mut i = self.position();
// Encode the crate deps
let crate_deps = self.encode_crate_deps();
let dylib_dependency_formats = self.encode_dylib_dependency_formats();
let dep_bytes = self.position() - i;
// Encode the lib features.
i = self.position();
let lib_features = self.encode_lib_features();
let lib_feature_bytes = self.position() - i;
// Encode the language items.
i = self.position();
let lang_items = self.encode_lang_items();
let lang_items_missing = self.encode_lang_items_missing();
let lang_item_bytes = self.position() - i;
// Encode the diagnostic items.
i = self.position();
let diagnostic_items = self.encode_diagnostic_items();
let diagnostic_item_bytes = self.position() - i;
i = self.position();
let native_libraries = self.encode_native_libraries();
let native_lib_bytes = self.position() - i;
let foreign_modules = self.encode_foreign_modules();
// Encode DefPathTable
i = self.position();
self.encode_def_path_table();
let def_path_table_bytes = self.position() - i;
// Encode the def IDs of impls, for coherence checking.
i = self.position();
let impls = self.encode_impls();
let impl_bytes = self.position() - i;
let tcx = self.tcx;
// Encode MIR.
i = self.position();
self.();
let mir_bytes = self.position() - i;
// Encode the items.
i = self.position();
self.encode_def_ids();
self.encode_info_for_items();
let item_bytes = self.position() - i;
// 省略其他部分代码
}
C.CrateRoot描述元数据
struct CrateRoot中描述metadata中包含的内容;
// src/librustc_metadata/rmeta/mod.rs
crate struct CrateRoot<'tcx> {
name: Symbol,
triple: TargetTriple,
extra_filename: String,
hash: Svh, // Strict Version hash值
// 前面提到的CrateDisambiguator hash值
disambiguator: CrateDisambiguator,
panic_strategy: PanicStrategy,
edition: Edition,
has_global_allocator: bool,
has_panic_handler: bool,
has_default_lib_allocator: bool,
plugin_registrar_fn: Option<DefIndex>,
proc_macro_decls_static: Option<DefIndex>,
proc_macro_stability: Option<attr::Stability>,
// crate的依赖
crate_deps: Lazy<[CrateDep]>,
dylib_dependency_formats: Lazy<[Option<LinkagePreference>]>,
// features配置
lib_features: Lazy<[(Symbol, Option<Symbol>)]>,
// items描述
lang_items: Lazy<[(DefIndex, usize)]>,
lang_items_missing: Lazy<[lang_items::LangItem]>,
diagnostic_items: Lazy<[(Symbol, DefIndex)]>,
native_libraries: Lazy<[NativeLib]>,
foreign_modules: Lazy<[ForeignModule]>,
impls: Lazy<[TraitImpls]>,
interpret_alloc_index: Lazy<[u32]>,
tables: LazyTables<'tcx>,
/// The DefIndex's of any proc macros declared by this crate.
proc_macro_data: Option<Lazy<[DefIndex]>>,
exported_symbols: Lazy!([(ExportedSymbol<'tcx>, SymbolExportLevel)]),
syntax_contexts: SyntaxContextTable,
expn_data: ExpnDataTable,
/// The DefIndex's of any proc macros declared by this crate.
proc_macro_data: Option<Lazy<[DefIndex]>>,
exported_symbols: Lazy!([(ExportedSymbol<'tcx>, SymbolExportLevel)]),
syntax_contexts: SyntaxContextTable,
expn_data: ExpnDataTable,
source_map: Lazy<[rustc_span::SourceFile]>,
compiler_builtins: bool,
needs_allocator: bool,
needs_panic_runtime: bool,
no_builtins: bool,
panic_runtime: bool,
profiler_runtime: bool,
symbol_mangling_version: SymbolManglingVersion,
}
4.元数据读取及crate加载
在编译解析crate标识符名称时会加载读取其依赖的crate元数据;
A.元数据加载
pub const METADATA_FILENAME: &str = "lib.rmeta";
// src/librustc_codegen_llvm/metadata.rs
impl MetadataLoader for LlvmMetadataLoader {
fn get_rlib_metadata(&self, _: &Target, filename: &Path)
-> Result<MetadataRef, String> {
// 从rlib静态资源包中读取lib.rmeta元数据
let archive =
ArchiveRO::open(filename).map(|ar| OwningRef::new(Box::new(ar))).map_err(|e| {
debug!("llvm didn't like `{}`: {}", filename.display(), e);
format!("failed to read rlib metadata in '{}': {}", filename.display(), e)
})?;
let buf: OwningRef<_, [u8]> = archive.try_map(|ar| {
ar.iter()
.filter_map(|s| s.ok())
.find(|sect| sect.name() == Some(METADATA_FILENAME))
.map(|s| s.data())
.ok_or_else(|| {
debug!("didn't find '{}' in the archive", METADATA_FILENAME);
format!("failed to read rlib metadata: '{}'", filename.display())
})
})?;
Ok(rustc_erase_owner!(buf))
}
fn get_dylib_metadata(&self, target: &Target, filename: &Path)
-> Result<MetadataRef, String> {
unsafe {
// 从dylib动态库section中读取metadata元数据
let buf = path_to_c_string(filename);
let mb = llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf.as_ptr())
.ok_or_else(|| format!("error reading library: '{}'", filename.display()))?;
// 读取ObjectFile
let of =
ObjectFile::new(mb).map(|of| OwningRef::new(Box::new(of))).ok_or_else(|| {
format!("provided path not an object file: '{}'", filename.display())
})?;
// 从中搜索meta section
let buf = of.try_map(|of| search_meta_section(of, target, filename))?;
Ok(rustc_erase_owner!(buf))
}
}
}
fn search_meta_section<'a>(
of: &'a ObjectFile,
target: &Target,
filename: &Path,
) -> Result<&'a [u8], String> {
unsafe {
let si = mk_section_iter(of.llof);
while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False {
let mut name_buf = None;
let name_len = llvm::LLVMRustGetSectionName(si.llsi, &mut name_buf);
// 获取section name, 省略其他部分代码
debug!("get_metadata_section: name {}", name);
// 如果section名称为.rustc则当成meta section
if read_metadata_section_name(target) == name {
let cbuf = llvm::LLVMGetSectionContents(si.llsi);
let csz = llvm::LLVMGetSectionSize(si.llsi) as usize;
// The buffer is valid while the object file is around
let buf: &'a [u8] = slice::from_raw_parts(cbuf as *const u8, csz);
return Ok(buf);
}
llvm::LLVMMoveToNextSection(si.llsi);
}
}
Err(format!("metadata not found: '{}'", filename.display()))
}
fn read_metadata_section_name(_target: &Target) -> &'static str {
".rustc"
}
B.CrateLocator
CrateLocator提供接口根据路径及meta加载指定crate;
CrateLocator加载搜索路径由–extern指定或者为rustc提供的sysroot;
-
list_file_metadata接口
调用get_metadata_section,其调用get_rlib_metadata或get_dylib_metadata;
-
find_commandline_library接口
调用extract_lib、extract_one,然后调用get_metadata_section;
-
find_library_crate接口
调用extract_lib、extract_one,然后调用get_metadata_section;
-
maybe_load_library_crate接口
调用find_library_crate或find_commandline_library加载crate;
C.CrateLoader
CrateLoader提供接口解析标识符名称并加载对应crate;
-
resolve_crate接口
调用maybe_resolve_crate,然后调用CrateLocator的maybe_load_library_crate接口加载crate;
-
process_path_extern接口
调用resolve_crate接口解析外部路径及标识符;
-
process_extern_crate接口
调用resolve_crate接口加载外部crate;
其中process_path_extern、process_extern_crate作为对外公共接口,可提供给其他rustc_resolve模块使用;
三、总结
通过前面的分析与解读,学习了rustc编译过程中产生的rlib和rmeta后缀文件的格式、元数据包含的内容及生成流程、
利用元数据加载其他crate的主要接口,为理解rustc中如何解析外部crate及其标识符打下基础;
初步理解Crate Disambiguator和Strict Version hash值,特别是Rust语言标识符重编、可混合使用相同名称crate的不同版本函数对其依赖;
参考
更多文章可使用微信扫公众号二维码查看
