12

我有兴趣使用 Linux 头文件提供的 unistd.h 构建一个没有 (g)libc 的静态 ELF 程序。

我已经阅读了这些文章/问题,这些文章/问题大致了解了我正在尝试做的事情,但并不完全是:http: //www.muppetlabs.com/~breadbox/software/tiny/teensy.html

不使用 libc 编译

https://blogs.oracle.com/ksplice/entry/hello_from_a_libc_free

我有仅依赖于 unistd.h 的基本代码,其中,我的理解是这些函数中的每一个都是由内核提供的,并且不需要 libc。这是我采取的似乎最有希望的道路:

    $ gcc -I /usr/include/asm/ -nostdlib grabbytes.c -o grabbytesstatic
    /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400144
    /tmp/ccn1mSkn.o: In function `main':
    grabbytes.c:(.text+0x38): undefined reference to `open'
    grabbytes.c:(.text+0x64): undefined reference to `lseek'
    grabbytes.c:(.text+0x8f): undefined reference to `lseek'
    grabbytes.c:(.text+0xaa): undefined reference to `read'
    grabbytes.c:(.text+0xc5): undefined reference to `write'
    grabbytes.c:(.text+0xe0): undefined reference to `read'
    collect2: error: ld returned 1 exit status

在此之前,我必须根据内核头文件中的值手动定义 SEEK_END 和 SEEK_SET。否则它会错误地说那些没有定义,这是有道理的。

我想我需要链接到一个未剥离的 vmlinux 以提供要使用的符号。但是,我通读了这些符号,虽然有很多 llseek,但它们并不是 llseek 逐字记录的。

所以我的问题可以有几个方向:

如何指定一个 ELF 文件来使用其中的符号?而且我猜测是否/如何可能,符号将不匹配。如果这是正确的,是否有一个现有的头文件将重新定义 llseek 和 default_llseek 或内核中的任何内容?

有没有更好的方法在没有 libc 的情况下用 C 编写 Posix 代码?

我的目标是使用(可能仅)unistd.h 编写或移植相当标准的 C 代码,并在没有 libc 的情况下调用它。如果没有一些 unistd 函数,我可能没问题,并且不确定哪些函数“纯粹”作为内核调用存在。我喜欢组装,但这不是我的目标。希望尽可能严格地保持 C(如果必须的话,我可以使用一些外部程序集文件),以便在某些时候允许使用 libc-less 静态系统。

感谢您的阅读!

4

2 回答 2

6

如果您想用 C 编写 POSIX 代码,那么放弃 libc 不会有帮助。尽管您可以syscall在汇编程序中实现一个函数,并从内核头文件中复制结构和定义,但您实际上是在编写自己的 libc,这几乎肯定不符合 POSIX。有了所有伟大的 libc 实现,几乎没有理由开始实现自己的。

Dietlibcmusl libc都是节俭的 libc 实现,它们产生了令人印象深刻的小二进制文件。链接器通常很聪明;只要编写了一个库以避免意外引入大量依赖项,那么只有您使用的函数实际上会链接到您的程序中。

这是一个简单的 hello world 程序:

#include<unistd.h>

int main(){
    char str[] = "Hello, World!\n";
    write(1, str, sizeof str - 1);
    return 0;
}

用下面的 musl 编译它会产生一个小于 3K 的二进制文件

$ musl-gcc -Os -static hello.c
$ strip a.out 
$ wc -c a.out
2800 a.out

Dietlibc 生成一个更小的二进制文件,小于 1.5K:

$ diet -Os gcc hello.c
$ strip a.out 
$ wc -c a.out
1360 a.out
于 2013-04-29T17:16:52.597 回答
4

这远非理想,但一点点 (x86_64) 汇编程序让我降至 5KB 以下(但其中大部分是“代码以外的东西” - 实际代码低于 1KB [准确地说是 771 字节],但是文件大小要大得多,我认为是因为代码大小四舍五入到 4KB,然后添加了一些页眉/页脚/额外的东西]

这是我所做的: gcc -g -static -nostdlib -o glibc start.s glibc.c -Os -lc

glibc.c 包含:

#include <unistd.h>

int main()
{
    const char str[] = "Hello, World!\n";
    write(1, str, sizeof(str));

    _exit(0);
}

start.s 包含:

    .globl _start
_start: 
    xor %ebp, %ebp
    mov %rdx, %r9
    mov %rsp, %rdx
    and $~16, %rsp
    push    $0
    push    %rsp

    call    main

    hlt


    .globl _exit
_exit:
    //  We known %RDI already has the exit code... 
    mov $0x3c, %eax
    syscall
    hlt

这样做的重点并不是要表明占用大量空间的不是 glibc 的系统调用部分,而是“准备工作” - 并且要注意,如果您要调用例如 printf,甚至可能是 (v) sprintf 或 exit() 或任何其他“标准库”函数,您处于“没人知道会发生什么”的领域。

编辑:更新“start.s”以将 argc/argv 放在正确的位置:

_start: 
    xor %ebp, %ebp
    mov %rdx, %r9
    pop     %rdi
    mov %rsp, %rsi
    and $~16, %rsp
    push    %rax
    push    %rsp

    // %rdi = argc, %rsi=argv
    call    main

请注意,我已经更改了哪个寄存器包含什么东西,以便它与 main 匹配——我在前面的代码中让它们的顺序略有错误。

于 2013-01-19T00:01:35.817 回答