8

x86/x86_64アセンブリについてもっと知りたいです。悲しいかな、私はMacを使っています。問題ありませんよね?

$ gcc --version
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 
5658) (LLVM build 2336.11.00)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

作成する必要のあるコードの種類のベースラインを取得するために、Cで簡単な「HelloWorld」を作成しました。私は大学に戻って少しx86を実行し、多数のチュートリアルを調べましたが、ここで見ている奇妙な出力のように見えるものはありません。

.section    __TEXT,__text,regular,pure_instructions
.globl  _main
.align  4, 0x90
_main:
Leh_func_begin1:
pushq   %rbp
Ltmp0:
movq    %rsp, %rbp
Ltmp1:
subq    $32, %rsp
Ltmp2:
movl    %edi, %eax
movl    %eax, -4(%rbp)
movq    %rsi, -16(%rbp)
leaq    L_.str(%rip), %rax
movq    %rax, %rdi
callq   _puts
movl    $0, -24(%rbp)
movl    -24(%rbp), %eax
movl    %eax, -20(%rbp)
movl    -20(%rbp), %eax
addq    $32, %rsp
popq    %rbp
ret
Leh_func_end1:

.section    __TEXT,__cstring,cstring_literals
L_.str:
.asciz   "Hello, World!"

.section    __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame0:
Lsection_eh_frame:
Leh_frame_common:
Lset0 = Leh_frame_common_end-Leh_frame_common_begin
.long   Lset0
Leh_frame_common_begin:
.long   0
.byte   1
.asciz   "zR"
.byte   1
.byte   120
.byte   16
.byte   1
.byte   16
.byte   12
.byte   7
.byte   8
.byte   144
.byte   1
.align  3
Leh_frame_common_end:
.globl  _main.eh
_main.eh:
Lset1 = Leh_frame_end1-Leh_frame_begin1
.long   Lset1
Leh_frame_begin1:
Lset2 = Leh_frame_begin1-Leh_frame_common
.long   Lset2
Ltmp3:
.quad   Leh_func_begin1-Ltmp3
Lset3 = Leh_func_end1-Leh_func_begin1
.quad   Lset3
.byte   0
.byte   4
Lset4 = Ltmp0-Leh_func_begin1
.long   Lset4
.byte   14
.byte   16
.byte   134
.byte   2
.byte   4
Lset5 = Ltmp1-Ltmp0
.long   Lset5
.byte   13
.byte   6
.align  3
Leh_frame_end1:


.subsections_via_symbols

さて...状況は少し変わったかもしれませんが、これはアセンブリコードであっても必ずしも友好的ではありません。私はこれに頭を包むのに苦労しています...誰かがこのコードで何が起こっているのか、そしてなぜそれがすべて必要なのかを分析するのを手伝ってくれるでしょうか?

よろしくお願いします。

4

3 に答える 3

11

質問は実際にはそれらの奇妙なラベルとデータに関するものであり、実際にはコード自体に関するものではないので、私はそれらにいくつかの光を当てるだけです。

プログラムの命令が実行エラー(0による除算、アクセスできないメモリ領域へのアクセス、特権命令の実行の試みなど)を引き起こす場合、例外(C ++の種類の例外ではなく、割り込みの種類)が発生します。の)そしてCPUにOSカーネルで適切な例外ハンドラを実行させる。これらの例外を完全に禁止すると、話は非常に短くなり、OSは単にプログラムを終了します。

ただし、プログラムに独自の例外を処理させることには利点があるため、OSハンドラーのプライマリ例外ハンドラーは、処理のためにいくつかの例外をプログラムに反映します。たとえば、プログラムが例外からの回復を試みたり、終了する前に意味のあるクラッシュレポートを保存したりできます。

いずれの場合も、次のことを知っておくと便利です。

  • 例外が発生した関数、その中の問題のある命令だけではありません
  • その関数を呼び出した関数、その関数を呼び出した関数など

そしておそらく(主にデバッグ用):

  • この命令が生成されたソースコードファイルの行
  • これらの関数呼び出しが行われた行
  • 関数パラメータ

なぜコールツリーを知る必要があるのですか?

プログラムが独自の例外ハンドラーを登録する場合、通常はC++trycatchブロックのようなものを実行します。

fxn()
{
  try
  {
    // do something potentially harmful
  }
  catch()
  {
    // catch and handle attempts to do something harmful
  }
  catch()
  {
    // catch and handle attempts to do something harmful
  }
}

これらのいずれもcatchesキャッチされない場合、例外は、の呼び出し元に伝播し、場合によってはの呼び出し元の呼び出し元に伝播しfxnます。例外をキャッチfxnするが存在するcatchか、プログラムを単に終了するデフォルトの例外ハンドラーが存在するまでです。

したがって、それぞれがカバーするコード領域を知る必要があり、即時/が例外をキャッチせず、バブルアップする必要がある場合に、次に近い(たとえば、の呼び出し元で)try到達する方法を知る必要があります。tryfxntrycatch

ブロックの範囲tryと場所はcatch、実行可能ファイルの特別なセクションで簡単にエンコードでき、操作も簡単です(これらの範囲で問題のある命令アドレスをバイナリ検索するだけです)。tryただし、例外が発生した関数からリターンアドレスを見つける必要がある場合があるため、次の外部ブロックを把握するのは困難です。

また、コンパイラは関数パラメータやローカル変数へのアクセスに関与しなくなるrbp+8ような方法でコードを最適化する可能性があるため、スタック上のリターンアドレスを指すことに常に依存しているとは限りません。rbpそれらにアクセスしてrsp+somethingレジスタといくつかの命令を保存することもできますが、関数が異なれば、スタックにローカルに異なるバイト数が割り当てられ、他の関数に渡されるパラメータが異なり、調整も異なるため、 isnrspの値だけが異なります。rspリターンアドレスと呼び出し元の関数を見つけるのに十分ではありません。rspリターンアドレスがスタック上にある場所から任意のバイト数離れた場所にすることができます。

このようなシナリオの場合、コンパイラーは、実行可能ファイルの専用セクションに関数とそのスタックの使用法に関する追加情報を含めます。例外処理コードは、この情報を調べて、例外が呼び出し元の関数とそのtry/catchブロックに伝播する必要がある場合に、スタックを適切に巻き戻します。

したがって、以下のデータに_main.ehはその追加情報が含まれています。とmain()を参照して、の先頭とサイズを明示的にエンコードすることに注意してください。この情報により、例外処理コードは命令をとして識別できます。Leh_func_begin1Leh_func_end1-Leh_func_begin1main()'smain()'s

また、main()あまりユニークではなく、そのスタック/例外情報の一部は他の関数と同じであり、それらの間で共有することは理にかなっているようです。したがって、への参照がありLeh_frame_commonます。

このデータの形式がわからないため、_main.ehこれらの定数の構造と正確な意味についてこれ144以上コメントすることはできません。13しかし、一般的に、コンパイラーまたはデバッガーの開発者でない限り、これらの詳細を知る必要はありません。

これにより、これらのラベルと定数が何のためにあるのかがわかると思います。

于 2013-03-09T06:09:19.017 に答える
4

OK、試してみましょう

//コードの最初のセクション。32ビット境界で整列する必要があるメイン関数を宣言します。

更新:.alignディレクティブの説明が間違っている可能性があります。以下のガスのドキュメントを参照してください。

.section    __TEXT,__text,regular,pure_instructions
.globl  _main
.align  4, 0x90
_main:

前のベースポインタを格納し、ローカル変数にスタックスペースを割り当てます。

Leh_func_begin1:
pushq   %rbp
Ltmp0:
movq    %rsp, %rbp
Ltmp1:
subq    $32, %rsp
Ltmp2:

スタックに引数をプッシュし、puts()を呼び出します

movl    %edi, %eax
movl    %eax, -4(%rbp)
movq    %rsi, -16(%rbp)
leaq    L_.str(%rip), %rax
movq    %rax, %rdi
callq   _puts

戻り値をスタックに置き、ローカルメモリを解放し、ベースポインタを復元して戻ります。

movl    $0, -24(%rbp)
movl    -24(%rbp), %eax
movl    %eax, -20(%rbp)
movl    -20(%rbp), %eax
addq    $32, %rsp
popq    %rbp
ret
Leh_func_end1:

次のセクション、これもコードセクションで、印刷する文字列が含まれています。

.section    __TEXT,__cstring,cstring_literals
L_.str:
.asciz   "Hello, World!"

残りは私にはわかりません。cスタートアップコードやデバッグ情報として使用されるデータである可能性があります。

.section    __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
...

更新:.alignディレクティブに関するドキュメント: http ://sourceware.org/binutils/docs-2.23.1/as/Align.html#Align

「必要なアライメントの指定方法はシステムによって異なります。アーク、hppa、ELFを使用するi386、i860、iq2000、m68k、or32、s390、sparc、tic4x、tic80、xtensaの場合、最初の式はたとえば、 `.align 8'は、8の倍数になるまでロケーションカウンタを進めます。ロケーションカウンタがすでに8の倍数である場合、変更は必要ありません。tic54xの場合、最初の式は単語でのアライメント要求です。 。

ppc、a.out形式を使用するi386、arm、strongarmなどの他のシステムの場合、これは、前進後にロケーションカウンタが持つ必要のある下位0ビットの数です。たとえば、 `.align 3'は、ロケーションカウンタを8の倍数になるまで進めます。ロケーションカウンタがすでに8の倍数である場合、変更は必要ありません。

この不整合は、GASがエミュレートする必要のあるこれらのシステムのさまざまなネイティブアセンブラの動作が異なるためです。GASは、後で説明する.balignおよび.p2alignディレクティブも提供します。これらは、すべてのアーキテクチャで一貫した動作をします(ただし、GASに固有です)。」

// jk

于 2013-03-08T12:16:57.073 に答える
2

ここここで、ディレクティブに関連するほとんどすべての質問に対する回答を見つけることができます。

例えば:

.section    __TEXT,__text,regular,pure_instructions

デフォルトのセクションタイプで名前が付けられたセクションを宣言し、__TEXT,__textこのセクションにマシンコードのみが含まれる(つまりデータが含まれない)ことを指定します。


.globl _main
ラベル_main(シンボル)をグローバルにして、リンカーに表示されるようにします。


.align 4, 0x90
ロケーションカウンタを次の2^4(== 16)バイト境界に揃えます。間のスペースは値0x90(== NOP)で埋められます。

コード自体に関しては、明らかに多くの冗長な中間ロードとストアを実行しています。コメンテーターの1人が提案したように、最適化を有効にしてコンパイルしてみてください。結果のコードの方が理にかなっていることがわかります。

于 2013-03-08T12:12:40.113 に答える