Rust 如何从正在运行的程序中检索输入的 argc 和 argv 值?

YHS*_*SPY 6 rust

我知道 Rust 应用程序初始化条目是由rustc. 我检查了compiler/rustc_codegen_ssa/src/base.rs 中的代码,它的一部分如下所示。

fn create_entry_fn<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
    cx: &'a Bx::CodegenCx,
    rust_main: Bx::Value,
    rust_main_def_id: DefId,
    use_start_lang_item: bool,
) -> Bx::Function {
    // The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
    // depending on whether the target needs `argc` and `argv` to be passed in.
    let llfty = if cx.sess().target.main_needs_argc_argv {
        cx.type_func(&[cx.type_int(), cx.type_ptr_to(cx.type_i8p())], cx.type_int())
    } else {
        cx.type_func(&[], cx.type_int())
    };
Run Code Online (Sandbox Code Playgroud)

而且我在同一个文件中发现的东西真的很有趣,就像我下面展示的那样,从评论中,我们可以了解到 Rust 正在这个地方收集输入的 argc 和 argv,这两个参数都将在lang_start稍后传递到函数中如果我理解正确。

/// Obtain the `argc` and `argv` values to pass to the rust start function.
fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
    cx: &'a Bx::CodegenCx,
    bx: &mut Bx,
) -> (Bx::Value, Bx::Value) {
    if cx.sess().target.main_needs_argc_argv {
        // Params from native `main()` used as args for rust start function
        let param_argc = bx.get_param(0);
        let param_argv = bx.get_param(1);
        let arg_argc = bx.intcast(param_argc, cx.type_isize(), true);
        let arg_argv = param_argv;
        (arg_argc, arg_argv)
    } else {
        // The Rust start function doesn't need `argc` and `argv`, so just pass zeros.
        let arg_argc = bx.const_int(cx.type_int(), 0);
        let arg_argv = bx.const_null(cx.type_ptr_to(cx.type_i8p()));
        (arg_argc, arg_argv)
    }
}
Run Code Online (Sandbox Code Playgroud)

但我也发现了另一个地方,它似乎与我上面在library/std/src/sys/unix/args.rs 中展示的一样。例如,如果您在 Macos 上运行 Rust 应用程序,Rust 似乎会使用两个 FFI 函数(_NSGetArgc / _NSGetArgv)来检索 argc 和 argv:

#[cfg(any(target_os = "macos", target_os = "ios"))]
mod imp {
    use super::Args;
    use crate::ffi::CStr;

    pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}

    pub fn cleanup() {}

    #[cfg(target_os = "macos")]
    pub fn args() -> Args {
        use crate::os::unix::prelude::*;
        extern "C" {
            // These functions are in crt_externs.h.
            fn _NSGetArgc() -> *mut libc::c_int;
            fn _NSGetArgv() -> *mut *mut *mut libc::c_char;
        }

        let vec = unsafe {
            let (argc, argv) =
                (*_NSGetArgc() as isize, *_NSGetArgv() as *const *const libc::c_char);
            (0..argc as isize)
                .map(|i| {
                    let bytes = CStr::from_ptr(*argv.offset(i)).to_bytes().to_vec();
                    OsStringExt::from_vec(bytes)
                })
                .collect::<Vec<_>>()
        };
        Args { iter: vec.into_iter() }
    }
Run Code Online (Sandbox Code Playgroud)

那么,这两个地方有什么区别呢?真正检索的东西实际上是哪个地方?

Kok*_*iwi 5

要直接回答“哪个地方实际上进行真正的检索工作?”这个问题,这取决于:

  • 目标操作系统:Linux、MacOS、Windows、WebAssembly
  • 目标“环境”(例如 libc):glibc、musl、wasi,甚至 Rust 中的 miri

它们基本上要么作为参数传递给程序入口点,要么通过使用函数/系统调用“全局”提供:

  • 在第一种情况下(作为参数传递),Rust 编译器生成用于初始化两个静态值ARGC和的代码ARGV(位于std/src/sys/unix/args.rs#L87),然后供std::env::args()开发人员使用。

    请注意,根据所使用的 libc,此阶段是由_start某些 ld+libc 特定例程完成的(考虑到动态链接时会变得混乱)。对于 glibc,它是由 GNU 非标准完成的“init_array”扩展(主要用于“cdylib”板条箱/.so可执行文件):std/src/sys/unix/args.rs#L108-L128

    另外,如果您使用属性直接指定入口点,#[start]您可以直接访问argc/argv值(compiler/rustc_codegen_ssa/src/base.rs#L447)

  • 在第二种情况下,不需要初始化代码,并且std::env::args()在需要时调用 args-getter 函数,正如您在 MacOS 上注意到的那样

例如,MacOS(显然还有 Windows)使用这两种方法,提供argc/argv既作为参数,_start又作为可从任何地方调用的 getter 函数,Rust 使用的就是这样。

Linux 实际上只使用第一种情况,尽管如果 glibc 提供一些函数来获取这些值(通过一些 wibbly wobbly 魔术方法)也就不足为奇了,但标准方法是第一种。

要进一步阅读,您可以查看一些有关 Linux 上“程序加载器”的链接和文章(遗憾的是,一般来说,关于该主题的内容并不多,尤其是对于其他操作系统):