请写一个最小化的程序

108 阅读2分钟

在 Linux 系统中,程序的入口点通常不是 main 函数,而是 _start 函数。_start 是程序的真正入口点,它负责初始化环境(如设置堆栈、解析命令行参数等),然后调用 main 函数。通常情况下,_start 是由 C 运行时库(如 glibc)提供的,但我们可以编写一个最小化的程序,直接使用 _start 作为入口点,而不依赖 glibc

以下是一个最小化的程序,直接从 _start 进入,并使用 Linux 系统调用退出程序:


最小化程序代码

// 最小化程序:直接从 _start 进入,使用系统调用退出

// 定义系统调用号
#define SYS_EXIT 60

// 定义 _start 函数
void _start() {
    // 使用系统调用退出程序
    // 参数:SYS_EXIT 是退出系统调用号,0 是退出状态码
    asm volatile (
        "syscall"  // 执行系统调用
        :
        : "a"(SYS_EXIT), "D"(0)  // 将 SYS_EXIT 放入 eax,0 放入 edi
    );
}

编译和运行

  1. 将上述代码保存为 minimal.c
  2. 使用 gcc 编译,并禁用标准库链接:
    gcc -nostdlib -o minimal minimal.c
    
    • -nostdlib:不链接标准库,避免引入 glibc 的初始化代码。
  3. 运行程序:
    ./minimal
    
  4. 检查退出状态码:
    echo $?
    
    输出应为 0,因为我们在程序中指定了退出状态码为 0

代码说明

  1. _start 函数

    • 这是程序的入口点,由链接器默认指定。
    • 它不依赖 glibc,因此需要手动处理系统调用。
  2. 系统调用

    • 使用 syscall 指令直接调用 Linux 内核。
    • SYS_EXIT 是退出系统调用的编号(在 x86-64 系统中为 60)。
    • 0 是退出状态码,表示程序正常退出。
  3. 内联汇编

    • asm volatile 用于嵌入汇编代码。
    • "a"(SYS_EXIT)SYS_EXIT 放入 eax 寄存器。
    • "D"(0)0 放入 edi 寄存器(系统调用的第一个参数)。

扩展:打印字符串

如果希望程序在退出前打印一条消息,可以使用 SYS_WRITE 系统调用:

// 最小化程序:打印消息并退出

#define SYS_WRITE 1
#define SYS_EXIT  60

void _start() {
    // 打印消息
    const char *msg = "Hello, World!\n";
    asm volatile (
        "syscall"
        :
        : "a"(SYS_WRITE), "D"(1), "S"(msg), "d"(13)
    );

    // 退出程序
    asm volatile (
        "syscall"
        :
        : "a"(SYS_EXIT), "D"(0)
    );
}
  • SYS_WRITE 是写系统调用的编号(在 x86-64 系统中为 1)。
  • 1 是文件描述符(标准输出)。
  • msg 是要打印的字符串地址。
  • 13 是字符串的长度。

编译和运行:

gcc -nostdlib -o minimal_with_print minimal_with_print.c
./minimal_with_print

输出:

Hello, World!

总结

通过直接使用 _start 作为入口点,我们可以编写不依赖 glibc 的最小化程序。这种方法适用于嵌入式系统、操作系统开发等场景,但需要手动处理系统调用和初始化逻辑。