点击上方蓝字 江湖评谈设为关注/星标
前言
从rustc开始,它的单个文件compile一般格式是rustc xx.rs。多个则是cargo。注意,以下是编译+二进制部分,看不懂的可以来知识星球一起学习。
详细
Rustc实际上调用了两次Glibc,以及一次d-linux-x86-64.so.2`_start函数,顺序分别是Glibc->_start->Glibc。我们可以b main下,在第二glibc的时候:
(lldb) di
rustc`main:
0x55555556dd00 <+0>: push rax
0x55555556dd01 <+1>: mov rcx, rsi
0x55555556dd04 <+4>: movsxd rdx, edi
0x55555556dd07 <+7>: lea rax, [rip - 0x10e] ; rustc_main::main
-> 0x55555556dd0e <+14>: mov rdi, rsp
0x55555556dd11 <+17>: mov qword ptr [rdi], rax
0x55555556dd14 <+20>: lea rsi, [rip + 0x54ff5]
0x55555556dd1b <+27>: mov r8d, 0x3
0x55555556dd21 <+33>: call qword ptr [rip + 0x59c99]
0x55555556dd27 <+39>: pop rcx
0x55555556dd28 <+40>: ret
(lldb) register read rax
rax = 0x000055555556dc00 rustc`rustc_main::main
光标在0x55555556dd0e,地址0x55555556dd07处,rax被赋值。后rax被放入了rdi,后者则是x64-linux约定的第一个参数。0x55555556dd21 处的call(lang_start_internal),信息如下:
frame #0: 0x00007ffff7f2a920 libstd-52417a9a08ba8fb9.so`std::rt::lang_start_internal::hcee5ed89fc25829a at rt.rs:118
(lldb) source info
Lines found in module `libstd-52417a9a08ba8fb9.so
[0x00007ffff7f2a920-0x00007ffff7f2a94f): /rustc/3f5fd8dd41153bc5fdca9427e9e05be2c767ba23/library/std/src/rt.rs:118
lang_start_internal之前说过了,它的第一个参数是一个函数指针。它通过 panic::catch_unwind最终调用了传递到函数指针,中间都是安全性质的调用,此处不予关注。。对于llvm compile之后的可执行rust独立文件,这个函数指针是rust-main入口。但这里是rustc-driver.main()。因第一参数,可反推以上rax为函数指针地址:
(lldb) di -s $rax
rustc`rustc_main::main:
0x55555556dc00 <+0>: push rax
0x55555556dc01 <+1>: call qword ptr [rip + 0x59db1]
rustc`_start:
0x55555556dc07 <+0>: xor ebp, ebp
0x55555556dc09 <+2>: mov r9, rdx
0x55555556dc0c <+5>: pop rsi
0x55555556dc0d <+6>: mov rdx, rsp
0x55555556dc10 <+9>: and rsp, -0x10
0x55555556dc14 <+13>: push rax
0x55555556dc15 <+14>: push rsp
0x55555556dc16 <+15>: mov r8, qword ptr [rip + 0x59d5b]
下断于0x55555556dc00。就可以来到了call rustc-driver.main()处。
(lldb) c
(lldb) s
Process 7126 stopped
* thread #1, name = 'rustc', stop reason = instruction step into
frame #0: 0x000055555556dc01 rustc`rustc_main::main + 1
rustc`rustc_main::main:
-> 0x55555556dc01 <+1>: call qword ptr [rip + 0x59db1]
rustc`_start:
0x55555556dc07 <+0>: xor ebp, ebp
0x55555556dc09 <+2>: mov r9, rdx
0x55555556dc0c <+5>: pop rsi
call qword ptr [rip + 0x59db1]即是。因./x.py build国内网问题,此处无debug symbols。直接看其source code。
先来看下rustc入口
//rust/compiler/rustc/src/main.rs
fn main() {
#[cfg(feature = "jemalloc-sys")]
{
use std::os::raw::{c_int, c_void};
#[used]
static _F1: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::calloc;
#[used]
static _F2: unsafe extern "C" fn(*mut *mut c_void, usize, usize) -> c_int =
jemalloc_sys::posix_memalign;
#[used]
static _F3: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::aligned_alloc;
#[used]
static _F4: unsafe extern "C" fn(usize) -> *mut c_void = jemalloc_sys::malloc;
#[used]
static _F5: unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void = jemalloc_sys::realloc;
#[used]
static _F6: unsafe extern "C" fn(*mut c_void) = jemalloc_sys::free;
#[cfg(target_os = "macos")]
{
extern "C" {
fn _rjem_je_zone_register();
}
#[used]
static _F7: unsafe extern "C" fn() = _rjem_je_zone_register;
}
}
rustc_driver::main()
}
它这里的rustc_driver::main()是compiler/rustc_driver/src/lib.rs。注意rustc_driver把rustc_driver_impl里的符号导到了rustc_driver。所以rustc_driver才得以调用main。
#![allow(internal_features)]
#![feature(rustdoc_internals)]
#![doc(rust_logo)]
pub use rustc_driver_impl::*; //pub use 用于将另一个模块的所有公共项重新导出。rustc_driver_impl 是另一个模块或 crate 的名称,
// 这一行将 rustc_driver_impl 模块中的所有公共项重新导出到当前模块。
compiler/rustc_driver_impl/src/lib.rs:main
pub fn main() -> ! {
let start_time = Instant::now();
let start_rss = get_resident_set_size();
let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());
init_rustc_env_logger(&early_dcx);
signal_handler::install();
let mut callbacks = TimePassesCallbacks::default();
let using_internal_features = install_ice_hook(DEFAULT_BUG_REPORT_URL, |_| ());
install_ctrlc_handler();
let exit_code = catch_with_exit_code(|| {
RunCompiler::new(&args::raw_args(&early_dcx)?, &mut callbacks)
.set_using_internal_features(using_internal_features)
.run()
});
if let Some(format) = callbacks.time_passes {
let end_rss = get_resident_set_size();
print_time_passes_entry("total", start_time.elapsed(), start_rss, end_rss, format);
}
process::exit(exit_code)
}
但实际上我们只需关注:
let exit_code = catch_with_exit_code(|| {
RunCompiler::new(&args::raw_args(&early_dcx)?, &mut callbacks)
.set_using_internal_features(using_internal_features)
.run()
});
catch_with_exit_code的参数是闭包,闭包里面RunCompiler::new两个参数:
impl<'a, 'b> RunCompiler<'a, 'b> {
pub fn new(at_args: &'a [String], callbacks: &'b mut (dyn Callbacks + Send)) -> Self {
Self {
at_args,
callbacks,
file_loader: None,
make_codegen_backend: None,
using_internal_features: Arc::default(),
}
}
'a表示此参数在传递参数的生命周期内存活,impl定义RunCompiler结构的参数传递。at_args: &'a [String](变量:类型),[String]可变形字符串,&表示引用的意思。&'b mut (dyn Callbacks + Send)) -> Self,dyn 关键字表示这是一个动态类型,它指向实现了 Callbacks trait 的类型。这意味着你可以在运行时使用这个引用调用 Callbacks trait 中定义的方法。+send添加了Send方法,Self表示返回自己,这里是返回RunCompiler。
RunCompiler里面有个using_internal_features: Arc::default(),后面可以通过set_using_internal_features来调用。
下面重点看run:
pub fn run(self) -> interface::Result<()> {
run_compiler(
self.at_args,
self.callbacks,
self.file_loader,
self.make_codegen_backend,
self.using_internal_features,
)
}
run_compiler
fn run_compiler(
at_args: &[String],
callbacks: &mut (dyn Callbacks + Send),
file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
make_codegen_backend: Option<
Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>,
>,
using_internal_features: Arc<std::sync::atomic::AtomicBool>,
) -> interface::Result<()> {
let mut default_early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());
// Throw away the first argument, the name of the binary.
// In case of at_args being empty, as might be the case by
// passing empty argument array to execve under some platforms,
// just use an empty slice.
//
// This situation was possible before due to arg_expand_all being
// called before removing the argument, enabling a crash by calling
// the compiler with @empty_file as argv[0] and no more arguments.
let at_args = at_args.get(1..).unwrap_or_default();
let args = args::arg_expand_all(&default_early_dcx, at_args)?;
let Some(matches) = handle_options(&default_early_dcx, &args) else { return Ok(()) };
let sopts = config::build_session_options(&mut default_early_dcx, &matches);
if let Some(ref code) = matches.opt_str("explain") {
handle_explain(&default_early_dcx, diagnostics_registry(), code, sopts.color);
return Ok(());
}
file_loader: Option<Box<dyn FileLoader + Send + Sync>>,接收一个可选的 Box 类型的 file_loader,该类型实现了 FileLoader trait 并且也实现了 Send 和 Sync traits。
make_codegen_backend: Option<Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>>,接收一个可选的 Box 类型的 make_codegen_backend,该类型是一个闭包,它接受 config::Options 的引用并返回一个实现了 CodegenBackend trait 的 Box 类型。
using_internal_features: Arc<std::sync::atomic::AtomicBool>,接收一个 Arc<AtomicBool> 类型的 using_internal_features,用于在线程间安全地共享是否使用内部特性信息。
let mut default_early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());创建一个 EarlyDiagCtxt 实例 default_early_dcx,用于早期诊断上下文。
主要是看
let at_args = at_args.get(1..).unwrap_or_default();
//函数处理命令行参数,返回处理后的参数列表
let args = args::arg_expand_all(&default_early_dcx, at_args)?;
let Some(matches) = handle_options(&default_early_dcx, &args) else { return Ok(()) };
arg_expand_all处理命令行的参数,处理完了之后,返回结果。比如:
#rustc --version
rustc 1.80.1 (3f5fd8dd4 2024-08-06)
这里面细节很多,后面有时间再看
二进制
经过rustc/cargo之后的rust二进制独立文件,起手就是传入rust-main。所以如果在逆向层面来看,rust依然需要混淆/虚拟代码等保护。参考:Rust编译器研究+.NET9 PreView7。
结尾
rust语法总体来说,比较简洁,编译层级同C++差不多或者稍微难点。
往期精彩回顾