Rust编译器+语法探究

文摘   2024-08-18 12:03   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

从rustc开始,它的单个文件compile一般格式是rustc xx.rs。多个则是cargo。注意,以下是编译+二进制部分,看不懂的可以来知识星球一起学习。

详细

Rustc实际上调用了两次Glibc,以及一次d-linux-x86-64.so.2`_start函数,顺序分别是Glibc->_start->Glibc。我们可以b main下,在第二glibc的时候:

(lldb) dirustc`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(lldbsource infoLines 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 $raxrustc`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) sProcess 7126 stopped* thread #1, name = 'rustc', stop reason = instruction step into    frame #0: 0x000055555556dc01 rustc`rustc_main::main + 1rustc`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.rsfn 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 --versionrustc 1.80.1 (3f5fd8dd4 2024-08-06)

这里面细节很多,后面有时间再看


二进制

经过rustc/cargo之后的rust二进制独立文件,起手就是传入rust-main。所以如果在逆向层面来看,rust依然需要混淆/虚拟代码等保护。参考:Rust编译器研究+.NET9 PreView7


结尾

rust语法总体来说,比较简洁,编译层级同C++差不多或者稍微难点。

往期精彩回顾

Rust语法简析

Rust编译器研究+.NET9 PreView7

Rust原生级跨平台GUI开发Tauri


江湖评谈
记录,分享,自由。
 最新文章