3

brk (int 0x80 with 45 in %rax) を使用して、単純なメモリ マネージャー プログラムをアセンブリに実装し、ブロックを順番に出力しようとしているときに、segfault が発生し続けました。しばらくすると、エラーを再現することしかできませんでしたが、なぜこれが起こっているのかわかりません:

    .section    .data
    helloworld:
    .ascii  "hello world"
    .section    .text
    .globl      _start
_start:

    push    %rbp
    mov     %rsp, %rbp

    movq    $45, %rax  
    movq    $0, %rbx   #brk(0) should just return the current break of the programm
    int     $0x80

    #incq    %rax #segfault
    #addq    $1, %rax #segfault
    movq    $0, %rax #works fine?
    #addq    $1, %rax #segfault again?

    movq    $helloworld, %rdi
    call    printf

    movq    $1, %rax #exit
    int     $0x80

ここの例では、コメント行がコメント解除されている場合、segfault がありますが、一部のコマンド (de movq $0、%rax など) は問題なく動作します。私の他のプログラムでは、最初のいくつかの printf は機能しますが、3 つ目はクラッシュします... 他の質問を探していると、printf がメモリを割り当てることがあり、brk を使用すべきではないと聞きました。この場合、ヒープが破損するためです。または何か...私は非常に混乱しています、誰かそれについて何か知っていますか?

編集: printf を機能させるには %rax=0 が必要であることがわかりました。

4

4 に答える 4

3

当面の問題は、間違ったシステム コール番号を使用していることです。45 はSYS_brkoni*86ですが、on x86_64 45 はSYS_recvfromです。同様に、SYS_exitは 60x86_64です。次のように正しい番号を見つけることができます。

echo "#include <syscall.h>" | gcc -xc - -E -dD | egrep '__NR_(brk|exit) '
#define __NR_brk 12
#define __NR_exit 60

echo "#include <syscall.h>" | gcc -xc - -E -dD -m32 | egrep '__NR_(brk|exit) '
#define __NR_exit 1
#define __NR_brk 45

2 番目の問題はint $0x80、システム コールを呼び出す標準的な方法ではないことx86_64です。syscall代わりに使用する必要があります。

nneonneo が正しく指摘したように、3 番目の問題は、システム コール on への引数がではなく 、 などでx86_64渡されることです。%rdi%rsi%rbx

上記の変更により、(printf をコメントアウトして)次のようになります。

strace ./a.out
execve("./a.out", ["./a.out"], [/* 68 vars */]) = 0
brk(0)                                  = 0x15b9000
_exit(0)                                = ?

それを元のプログラム (printf なし) と比較します。

execve("./a.out", ["./a.out"], [/* 68 vars */]) = 0
recvfrom(0, NULL, 0, 0, NULL, NULL)     = 29184000
write(6291768, NULL, 0 <unfinished ... exit status 0>
于 2012-10-07T21:43:13.980 に答える
2

他の誰もこれを指摘していないので: いいえ、呼び出し元の関数が現在の実行可能イメージの唯一の実装 (の一部) でない限り、sbrkゼロ以外の引数で呼び出すことは安全ではありません。より具体的には、実装の外部から領域のサイズを変更すると、の内部データ構造が破損する可能性が非常に高くなり、次にorを使用するときにプログラムがクラッシュします。さらに、async-signal-safe 関数の短いリスト(リストはそのドキュメントの最後近くにあります)にないC ライブラリ関数は内部で呼び出すことが許可されており、特に Linux では確実に呼び出します。brkmallocbrkmallocmallocmallocfreemallocprintf

PS アセンブリ言語でコーディングしている場合でも、C ライブラリのシムを使用してシステム コールを作成する必要があります。これにより、x86-32 と x86-64 の間のシステム コール番号の違いから解放errnoされ、プロセッサで利用可能な最も効率的なトラップ シーケンスを設定して使用するようにするなど、他の方法でも役立ちます。

于 2012-11-28T22:53:33.223 に答える
2

次の問題は、x86_64 システム コール呼び出し規則が %rdi、%rsi、%rdx、%r10、%r8、および %r9 を使用して、最大 6 つの引数をこの順序で渡すことです (%ebx、%ecx、%edx ではなく)。 、%esi、%edi、%ebp x86_32 で使用)。

brkしたがって、の最初の引数を %rdiに入れる必要があります。

movq    $12, %rax  
movq    $0, %rdi
syscall

それをサポートするカーネルでは、 を使用int 0x80すると、実際には 32 ビットのシステム コールが呼び出され、32 ビットの呼び出し番号とレジスタが割り当てられます (互換性のため)。そのようなカーネルがあれば、コード スニペットは機能するはずです。そうしないと、プログラムは実行されるとすぐに死んでしまいますint 0x80

于 2012-10-07T21:53:08.343 に答える