簡単そうですね。
読者は、プログラムをコンパイルして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ファイル構造についてもう少し見ていきます。