libcの無料の世界からこんにちは! (パート1)

演習として、 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ファイル構造についてもう少し見ていきます。



All Articles