在 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
);
}
编译和运行
- 将上述代码保存为
minimal.c。 - 使用
gcc编译,并禁用标准库链接:gcc -nostdlib -o minimal minimal.c-nostdlib:不链接标准库,避免引入glibc的初始化代码。
- 运行程序:
./minimal - 检查退出状态码:
输出应为echo $?0,因为我们在程序中指定了退出状态码为0。
代码说明
-
_start函数:- 这是程序的入口点,由链接器默认指定。
- 它不依赖
glibc,因此需要手动处理系统调用。
-
系统调用:
- 使用
syscall指令直接调用 Linux 内核。 SYS_EXIT是退出系统调用的编号(在 x86-64 系统中为60)。0是退出状态码,表示程序正常退出。
- 使用
-
内联汇编:
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 的最小化程序。这种方法适用于嵌入式系统、操作系统开发等场景,但需要手动处理系统调用和初始化逻辑。