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 関数がいくつかなくても問題ありませんが、どの関数がカーネル呼び出しとして「純粋に」存在するかどうかはわかりません。私は組み立てが大好きですが、ここでの私の目標ではありません。ある時点で libc を使用しない静的システムを可能にするために、可能な限り厳密に C にとどまることを望んでいます (必要に応じて、いくつかの外部アセンブリ ファイルで問題ありません)。

読んでくれてありがとう!

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

メインと一致するように、どのレジスタに何を含むかを変更したことに注意してください。前のコードでは、それらの順序が少し間違っていました。

于 2013-01-19T00:01:35.817 に答える