不使用libc进行编译

83

我想在没有(g)libc的情况下编译我的C代码。 我怎样才能停用它,哪些函数依赖于它?

我尝试了-nostdlib,但是它没有帮助:代码可以编译和运行,但我仍然可以在可执行文件的十六进制转储中找到libc的名称。


2
“-nostdlib”应该可以解决问题,您使用的是哪个平台/编译器版本? - Carl Norum
“doesn't help” 的意思是那个操作没有禁用库,还是你无法使用该标志编译任何内容? - Josh Lee
4
你可能也想加上“-nostartupfiles”。意为在启动时不加载配置文件。 - user1831086
4
http://blog.ksplice.com/2010/03/libc-free-world/ 提供了非常好的描述,可以精确地控制gcc的程序输出。编辑:他们(ksplice)刚刚发布了上述教程/指南的第2部分。在这里查看它:http://blog.ksplice.com/2010/04/libc-free-world-2/ 这主要涉及链接器设置,以从文件中删除不必要的东西。 - Adam Shiemke
10
没有-nostartupfiles选项。你可能是想用-nostartfiles,这个选项已经被-nostdlib包含了。 - ataylor
1
这是一篇关于优化 ELF 二进制文件的绝佳文章,涵盖了从可执行文件中剥离 stdlib 的内容:Linux 上创建非常小的 ELF 可执行文件的旋风教程 - Michał Trybus
2个回答

104
如果您使用-nostdlib编译您的代码,则无法调用任何C库函数(当然),但您也不会得到常规的C引导代码。 特别地,在Linux上,程序的真正入口点不是 main(),而是一个名为_start()的函数。 标准库通常提供这个函数的版本,其中运行一些初始化代码,然后调用main()
尝试使用 gcc -nostdlib -m32 编译此代码:
// Tell the compiler incoming stack alignment is not RSP%16==8 or ESP%16==12
__attribute__((force_align_arg_pointer))
void _start() {

    /* main body of program: call main(), etc */

    /* exit system call */
    asm("movl $1,%eax;"
        "xorl %ebx,%ebx;"
        "int  $0x80"
    );
    __builtin_unreachable();  // tell the compiler to make sure side effects are done before the asm statement
}

_start()函数应该总是以调用exit(或其他不返回的系统调用,如exec)结束。上面的示例直接使用内联汇编调用系统调用,因为通常的exit()不可用。


14
对于64位架构,汇编代码应该长成这样:asm("mov rax,60; mov rdi,0; syscall") - sigalor
10
除了 @sigalor 的评论之外,要使用 gcc 进行编译,您需要使用 AT&T 语法,因此应该如下所示:asm(mov $60,%rax; mov $0,%rdi; syscall) - lanoxx
3
为什么_start()函数总是要以调用exit()结束?如果我在start()函数中不写exit()会怎样? - Destructor
4
在我的系统上,@Destructor卡住了几秒钟,然后出现段错误。 - Moonchild
2
注意,堆栈在进入 _start 时的对齐方式不同:堆栈上没有返回地址,因此它仍然对齐为 16 字节。但是 GCC 将假定它是一个正常的函数,所以当它调用其他函数时,将违反 ABI。使用 -mincoming-stack-boundary=2 选项(文档)来告诉 GCC,在函数进入时堆栈可能只对齐为 4 字节,或者在 _start 上使用 __attribte__((target("something"))) - Peter Cordes
显示剩余6条评论

13
最简单的方法是将C代码编译为目标文件(使用gcc -c生成一些*.o文件),然后直接使用链接器(ld)将其链接起来。您将不得不使用一些额外的对象文件,例如/usr/lib/crt1.o等,以获得工作可执行文件(在内核看到的入口点和main()函数之间还需要一些工作)。要知道该链接什么,请尝试链接glibc,使用gcc -v:这应该会向您展示通常包含在可执行文件中的内容。
您会发现gcc生成的代码可能有一些依赖于一些隐藏函数。其中大多数在libgcc.a中。还可能存在对memcpy()memmove()memset()memcmp()的隐藏调用,它们在libc中,因此您可能需要提供自己的版本(只要您对性能没有太高要求,这并不难)。
如果您查看生成的汇编代码(使用-S标志),有时事情可能会变得更加清晰。

我必须使用_start而不是main,但当我尝试调用libc函数时,gcc并不会抱怨。如果我删除所有的libc调用,那么libc链接是否会消失? - u149796
3
不是直接的。如果您尝试使用 gcc -v 命令,您会看到 gcc 给链接器一些对象文件(即 *.o 文件)。链接器将包含所有被给予的对象文件。"消失"仅出现在库文件(*.a文件)中,因为它们是对象文件的存储库,链接器可自由使用或不使用它们。 - Thomas Pornin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接