演習として、 Cでプログラムを作成します。それを逆アセンブルし、すべてのコードを自分で説明できるほど単純です。
簡単そうですね。
読者は、プログラムをコンパイルしてLinuxで作業した経験があることが期待されています。 アセンブリコードを読み取るための少しの機能も役立ちます。
したがって、ここに最も単純なhellworldがあります。
jesstess @ kid-charlemagne:〜/ c $ cat hello.c
#include <stdio.h>
int main()
{
printf( "Hello World \ n");
0を返します。
}
コンパイルして、文字数を計算します。
jesstess @ kid-charlemagne:〜/ c $ gcc -o hello hello.c
jesstess @ kid-charlemagne:〜/ c $ wc -cこんにちは
10931こんにちは
フィガセ! これらの11キロバイトはどこから来たのですか?
objdump -t hello
は、識別子テーブルに79個のエントリを表示します。それらのほとんどは標準ライブラリを担当します。
したがって、使用しません。 そして、包含を取り除くために
printf
も使用しません。
jesstess @ kid-charlemagne:〜/ c $ cat hello.c
int main()
{
char * str = "Hello World";
0を返します。
}
文字数を再コンパイルして再カウントします。
jesstess @ kid-charlemagne:〜/ c $ gcc -o hello hello.c
jesstess @ kid-charlemagne:〜/ c $ wc -cこんにちは
10892こんにちは
ほとんど何も変わっていませんか? ハ!
問題は、リンク中にgccがまだスタートアップファイル(?)を使用していることです。 証拠?
-nostdlib
し、その後(ドキュメントによると)gccはリンク時にシステムライブラリとスタートアップファイルを使用しません。 明示的にリンカに転送されたファイルのみが使用されます。
jesstess @ kid-charlemagne:〜/ c $ gcc -nostdlib -o hello hello.c
/ usr / bin / ld:警告:エントリシンボル_startが見つかりません。 デフォルトは00000000004000e8
警告だけで、まだ試してください:
jesstess @ kid-charlemagne:〜/ c $ wc -cこんにちは
1329こんにちは
よさそう! 私たちはサイズをずっと正気に減らしました(注文全体と同じくらい!)...
jesstess @ kid-charlemagne:〜/ c $ ./hello
セグメンテーション障害
...デフォルトで支払われました。 くそー
楽しみのために、アセンブラを理解する前にプログラムを実行します。
プログラムの実行に必要と思われる
_start
文字
_start
何をしますか? libcを使用する場合、通常どこで定義されますか?
デフォルトでは、
リンカーの観点から見ると、プログラムへの実際のエントリポイントである
main
ではなく
_start
です。 通常、
_start
再配置可能
ELF crt1.o
定義されています。 これを確認するには、worldwordを
crt1.o
リンクし、
_start
検出されたことに注目します(ただし、他のスタートアップシンボルlibcが定義されていないため、他の問題がありました)。
#リンクせずにソースをコンパイル
jesstess @ kid-charlemagne:〜/ c $ gcc -Os -c hello.c
#今リンクしよう
jesstess @ kid-charlemagne:〜/ c $ ld /usr/lib/crt1.o -o hello hello.o
/usr/lib/crt1.o:関数「_start」:
/build/buildd/glibc-2.9/csu/../sysdeps/x86_64/elf/start.S:106: `__libc_csu_finiへの未定義の参照 '
/build/buildd/glibc-2.9/csu/../sysdeps/x86_64/elf/start.S:107: `__libc_csu_init 'への未定義の参照
/build/buildd/glibc-2.9/csu/../sysdeps/x86_64/elf/start.S:113: `__libc_start_main 'への未定義の参照
チェックは、このコンピューターの
_start
がlibcソースにあることを報告しました。
sysdeps/x86_64/elf/start.S
sysdeps/x86_64/elf/start.S
。 この愉快にコメントされたファイルは、
_start
文字をエクスポートし、スタックといくつかのレジスタを初期化し、
__libc_start_main
を呼び出し
__libc_start_main
。 一番下を見ると
csu/libc-start.c
csu/libc-start.c
、プログラムの
_main
呼び出しを確認できます。
/ *特別なことはなく、関数を呼び出すだけです* /
result = main(argc、argv、__ environ MAIN_AUXVEC_PARAM);
...そして行きましょう。
それで、なぜ
_start
が必要なのでしょう。 便宜上、
_start
と
main
呼び出しの間で何が起こっているのかをまとめ
_start
のものを初期化し、
main
呼び出します。 また、libcは必要ないので、
main
を呼び出すもののみを知っている独自の
_start
シンボルをエクスポートし、それにリンクします。
jesstess @ kid-charlemagne:〜/ c $ cat stubstart.S
.globl _start
_start:
メインに電話
_startアセンブリスタブを使用して、hello worldをコンパイルおよび実行します。
jesstess @ kid-charlemagne:〜/ c $ gcc -nostdlib stubstart.S -o hello hello.c
jesstess @ kid-charlemagne:〜/ c $ ./hello
セグメンテーション障害
ほら、コンパイルの問題はもうありません。 しかし、セグメンテーション違反はなくなりませんでした。 なんで? デバッグ情報をコンパイルして、gdbを見てください。
main
ブレークポイントを設定し、segfaultの前にプログラムをステップごとに実行します。
jesstess @ kid-charlemagne:〜/ c $ gcc -g -nostdlib stubstart.S -o hello hello.c
jesstess @ kid-charlemagne:〜/ c $ gdbこんにちは
GNU gdb 6.8-debian
Copyright(C)2008 Free Software Foundation、Inc.
ライセンスGPLv3 +:GNU GPLバージョン3以降
これはフリーソフトウェアです。自由に変更して再配布できます。
法律で許可されている範囲での保証はありません。 「show copy」と入力します
詳細については「保証を表示」。
このGDBは、「x86_64-linux-gnu」として構成されました...
(gdb)メインを中断
0x4000f4のブレークポイント1:ファイルhello.c、3行目
(gdb)実行
開始プログラム:/ home / jesstess / c / hello
ブレークポイント1、メイン()でhello.c:5
5 char * str = "Hello World";
(gdb)ステップ
6は0を返します。
(gdb)ステップ
7}
(gdb)ステップ
_start()に0x00000000004000ed
(gdb)ステップ
関数_startを終了するまでシングルステップ
行番号情報はありません。
main()at helloint.c:4
4 {
(gdb)ステップ
ブレークポイント1、メイン()helloint.c:5
5 char * str = "Hello World";
(gdb)ステップ
6は0を返します。
(gdb)ステップ
7}
(gdb)ステップ
プログラムは信号SIGSEGV、セグメンテーション障害を受信しました。
0x0000000000000001 in ?? ()
(gdb)
なに?
main
は2回実行されますか? ...それでは、アセンブラを取り上げます。
jesstess @ kid-charlemagne:〜/ c $ objdump -d hello
こんにちは:ファイル形式elf64-x86-64
セクション.textの分解:
00000000004000e8 <_start>:
4000e8:e8 03 00 00 00 callq 4000f0
4000ed:90 nop
4000ee:90 nop
4000ef:90 nop
00000000004000f0:
4000f0:55 push%rbp
4000f1:48 89 e5 mov%rsp、%rbp
4000f4:48 c7 45 f8 03 01 40 movq $ 0x400103、-0x8(%rbp)
4000fb:00
4000fc:b8 00 00 00 00 mov $ 0x0、%eax
400101:c9 leaveq
400102:c3 retq
へえ! アセンブラの詳細な分析については、後ほど説明します
callq
から
main
戻った後、いくつかの
nop
を実行し
main
直接
main
戻ります。 (関数を呼び出すための標準的な準備の一環として)スタックにリターン命令ポインターを設定せずに
main
再入力が行われたため、2番目の
retq
呼び出しはスタックからダミーのリターン命令ポインターを取得しようとし、プログラムがクラッシュします。 完了する方法が必要です。
文字通り。
callq
から
%eax
戻った後、プッシュ
1
が行われ、
システムコールコードは sys_exit
となります。
%ebx 0
に
SYS_exit
れた正しい完了を報告する必要があります。唯一の引数は
SYS_exit
です。 ここで、
int $0x80
割り込みでカーネルに入ります。
jesstess @ kid-charlemagne:〜/ c $ cat stubstart.S
.globl _start
_start:
メインに電話
movl $ 1、%eax
xorl%ebx、%ebx
int $ 0x80
jesstess @ kid-charlemagne:〜/ c $ gcc -nostdlib stubstart.S -o hello hello.c
jesstess @ kid-charlemagne:〜/ c $ ./hello
jesstess @ kid-charlemagne:〜/ c $
やった! プログラムは、gdbを介して実行されると、コンパイル、起動、さらには正常に実行されます。
libcの無料の世界からこんにちは!
2番目のパートでは、アセンブラコードを詳細に分析し、プログラムをより複雑にした場合の動作を確認し、x86アーキテクチャでのリンク、呼び出し規約、バイナリELFファイル構造についてもう少し見ていきます。