使用Linux头文件中的unistd.h构建不带libc的静态ELF

时间:2013-01-18 21:20:13

标签: linux gcc posix elf libc

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

我已经阅读了这些文章/问题,这些文章/问题粗略地概括了我正在尝试做什么,但并不完全: http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html

Compiling without 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以提供要使用的符号。然而,我读完了这些符号,虽然有很多llseeks,但它们并不是llseek逐字逐句。

所以我的问题可以在几个方向进行:

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

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

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

感谢您阅读!

2 个答案:

答案 0 :(得分:5)

如果您希望用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;
}

使用低于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

答案 1 :(得分:2)

这远非理想,但是一点点(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匹配 - 我在前面的代码中使用了稍微错误的顺序。