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が必要なのでしょう。 便宜上、 _startmain呼び出しの間で何が起こっているのかをまとめ_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 0SYS_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ファイル構造についてもう少し見ていきます。

Source: https://habr.com/ru/post/J88101/


All Articles