#linux #data-structures #libc #crt0 #parser #auxv #auxiliary-vector

no-std linux-libc-auxv

Linux将初始堆栈布局传递给应用程序,其中包含argcargvenvp以及位于堆栈指针正上方的auxiliary vector。Linux程序的libc在它的_start符号(即“crt0”)中解析此结构,并在之后将正确的指针作为参数传递给main。此crate帮助在no_std环境和不同的地址空间中构建和解析此数据结构。

3个不稳定版本

0.2.1 2022年3月7日
0.2.0 2021年12月7日
0.1.0 2021年12月2日

#372 in Unix APIs

Download history 45/week @ 2024-03-31 1/week @ 2024-04-07 22/week @ 2024-06-02 39/week @ 2024-06-09 2/week @ 2024-06-16

63 monthly downloads

MIT license

105KB
1.5K SLoC

linux-libc-auxv: 为不同地址空间构建和解析Linux初始堆栈布局

Linux将初始堆栈布局传递给应用程序,其中包含argcargvenvp以及位于堆栈指针正上方的auxiliary vector。Linux程序的libc在它的_start符号(即“crt0”)中解析此结构,并在之后将正确的指针作为参数传递给main。此crate帮助在no_std环境和不同的地址空间中构建和解析此数据结构。

关键词:crt0,堆栈布局,AT值,AT对,auxvec,辅助向量

我已经在一个用于微内核的自定义运行时系统中成功测试了此crate,该系统能够加载和启动未经修改的Linux二进制文件。Linux二进制文件(即libc)可以找到所有参数、环境变量以及辅助向量的数据,并将其打印到stdout。

这与https://crates.io/crates/crt0stackhttps://crates.io/crates/auxv有什么不同?

此crate支持no_std上下文,并允许构建不同地址空间的数据结构,即用户应用程序的地址空间。

当我开始创建这个库时,我只知道后者。它不支持 no_std。因为前者支持 no_std 但不支持不同的地址空间,所以我还是不得不创建这个。对我来说,典型的用例是为不同的地址空间创建数据结构,就像Linux所做的那样。

最后但同样重要的是,我的库支持Linux的更多/所有AT变量。

功能

✅ 构建当前地址空间的数据结构
✅ 构建不同地址空间的数据结构
✅ 解析当前地址空间的数据结构 + 输出引用的数据/指针
✅ 解析不同地址空间的数据结构 + 防止内存错误 / 不解引用指针

限制

32位与64位

辅助向量包含类型为 (usize, usize) 的成对数据。因此,每个条目在32位系统上占用8字节,在64位系统上占用16字节。目前,这个库为编译的架构生成辅助向量。如果需要,可以创建一个问题或PR,这将是一个运行时设置。我从未在32位系统上测试过它,但我相信它将正常工作。

辅助向量与堆栈布局

目前,这个库只能构建和序列化整个初始堆栈布局,但不能单独构建辅助向量。

代码示例

在存储库中有多个代码示例! cargo run --example linux_parse_print_layout 展示了一个真实世界的示例。

最小:构建 + 解析

use linux_libc_auxv::{AuxVar, InitialLinuxLibcStackLayout, InitialLinuxLibcStackLayoutBuilder};

/// Minimal example that builds the initial linux libc stack layout and parses it again.
fn main() {
    let builder = InitialLinuxLibcStackLayoutBuilder::new()
        // can contain terminating zero; not mandatory in the builder
        .add_arg_v("./first_arg\0")
        .add_arg_v("./second_arg")
        .add_env_v("FOO=BAR\0")
        .add_env_v("PATH=/bin")
        .add_aux_v(AuxVar::Clktck(100))
        .add_aux_v(AuxVar::Random([
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        ]))
        .add_aux_v(AuxVar::ExecFn("/usr/bin/foo"))
        .add_aux_v(AuxVar::Platform("x86_64"));

    // memory where we serialize the data structure into
    let mut buf = vec![0; builder.total_size()];

    // assume user stack is at 0x7fff0000
    let user_base_addr = 0x7fff0000;
    unsafe {
        builder.serialize_into_buf(buf.as_mut_slice(), user_base_addr);
    }

    // So far, this is memory safe, as long as the slice is valid memory. No pointers are
    // dereferenced yet.
    let parsed = InitialLinuxLibcStackLayout::from(buf.as_slice());

    println!("There are {} arguments.", parsed.argc());
    println!(
        "There are {} environment variables.",
        parsed.envv_ptr_iter().count()
    );
    println!(
        "There are {} auxiliary vector entries/AT variables.",
        parsed.aux_serialized_iter().count()
    );

    println!("  argv");
    // ptr iter is safe for other address spaces; the other only because here user_addr == write_addr
    for (i, arg) in parsed.argv_ptr_iter().enumerate() {
        println!("    [{}] @ {:?}", i, arg);
    }

    println!("  envp");
    // ptr iter is safe for other address spaces; the other only because here user_addr == write_addr
    for (i, env) in parsed.envv_ptr_iter().enumerate() {
        println!("    [{}] @ {:?}", i, env);
    }

    println!("  aux");
    // ptr iter is safe for other address spaces; the other only because here user_addr == write_addr
    for aux in parsed.aux_serialized_iter() {
        if aux.key().value_in_data_area() {
            println!("    {:?} => @ {:?}", aux.key(), aux.val() as *const u8);
        } else {
            println!("    {:?} => {:?}", aux.key(), aux.val() as *const u8);
        }
    }
}

代码示例输出

There are 2 arguments.
There are 2 environment variables.
There are 5 auxiliary vector entries/AT variables.
  argv
    [0] @ 0x7fff00b0
    [1] @ 0x7fff00bc
  envp
    [0] @ 0x7fff00c9
    [1] @ 0x7fff00d1
  aux
    Platform => @ 0x7fff0090
    Clktck => 0x64
    Random => @ 0x7fff0097
    ExecFn => @ 0x7fff00db
    Null => 0x0

术语(在代码中)

整个数据结构被我称为 InitialLinuxLibcStackLayout。没有官方名称。它包含参数(argcargv)、环境变量(envpenvv)和辅助向量(AT-variablesauxvaux-pairsaux entries)。

argv 数组将引用 argv data area 中的数据,envv 数组将引用 envv data area 中的数据,并且一些 auxv 值可能引用 auxv data area 中的数据。

有时(在一些文章中),辅助向量甚至描述整个数据结构。

数据结构的布局

null                                   [HIGH ADDRESS]
filename (c string)
<env data area>
<args data area>
// round up to 16 byte
<aux vec data area>
// round up to 16 byte alignment
AT_VAR_3 = <points to aux vec data area>
AT_VAR_2 = integer
AT_VAR_1 = integer
// round up to 16 byte alignment
envv[2] = null
envv[1] = <points to env data area>
envv[0] = <points to env data area>
argv[2] = null
argv[1] = <points to args data area>
argv[0] = <points to args data area>
argc = integer <libc entry stack top>  [LOW ADDRESS]

MSRV

1.56.1 稳定版 / Rust 版本 2021

依赖项

~1.5MB
~39K SLoC