web-dev-qa-db-ja.com

mainが初期化された後、スタックに__libc_csu_initへの戻りアドレスが含まれるのはなぜですか?

簡単なプログラムをCで記述してコンパイルし、gdbで開いて、11行目にブレークポイントを設定し、スタックを検査しました。

  1 #include<stdio.h>
  2
  3 int main(int argc, char *argv[]){
  4     char arr[4] = "AABB";
  5     int square = foo(2);
  6     exit(0);
  7 }
  8
  9 int foo(int x){
 10     char buff[4] = "CCDD";
 11     printf("Enter a value:"); // set breakpoint for this line
 12     gets(buff);
 13     return x*x;
 14 }
 15
 16 void bar(){
 17     printf("This program was hacked");
 18 }
 19

スタックを見ると、メイン関数が初期化された後、foo(x)が呼び出される前に、スタックに__libc_csu_initの2つのアドレスが配置されていることがわかりました。すべてのプログラムがinitとfiniを呼び出すことを理解していますが、メイン関数が呼び出される前にそれらの参照をスタックに配置する必要はありませんか?

(gdb) x/16a $esp
0xbffff110:    0x8000    0x44444343    0xbffff148    0x8048442 <main+48>
0xbffff120:    0x2    0xb7fbe000    0x804a000    0x80484c1 <__libc_csu_init+33>
0xbffff130:    0xb7fbe3bc    0x8048218    0x80484a9 <__libc_csu_init+9>    0x0
0xbffff140:    0x1    0x42424141    0x0    0xb7e0c5a6

0x42424141が__libc_csu_initよりも高いアドレスにあるのはなぜですか?

2
Hugh Pearse

メイン関数には、戻りポインターの直前に__libc_csu_initへのポインターが含まれています。これは、スタックフレームが機能するためです。 関数に入る前に、呼び出し元はスタックフレームポインターと戻りポインターをプッシュします。

リターンポインタには、関数の終了時に関数が戻るアドレスが含まれています。

フレームポインターには、スタックのの先頭のアドレスが含まれているため、スタックをどこに巻き戻すかがわかります。

リターンポインターとは異なり、スタックフレームポインターは必ずしもスタックにプッシュする必要はありませんが(そのアドレスはレジスターに格納できます)、現在のスタックから一部のアドレスにアクセスすることはその値から差し引くことになるため、最適化に役立ちます。 。

下の画像、つまり関数呼び出しスタックの例では、スタックフレームポインターは、「フレームポインター」の矢印が指す場所にプッシュされます-作成者は、レジスターにのみ格納されると想定しました。プログラムの場合は、__ libc_csu_initへのアドレスが含まれます。

enter image description here

フレームポインターの詳細については、Wikipediaの記事を参照してください。 https://en.wikipedia.org/wiki/Call_stack

そして今、証明のために、-fomit-frame-pointerフラグを使用して(上の画像のように動作を強制する)単純なプログラムをコンパイルしてみましょう(プログラムのようにフレームポインターをスタックにプッシュします)。

int main(int argc, char *argv[])
{
// placing nop instruction just for readibility of disassembled code
asm("nop");
}

パイなしでコンパイルされているので、PICはブレークポイントを妨げません。

g++ answer.cpp -o answer -fno-pie -fomit-frame-pointer

Gdbで逆アセンブルされ、「nop」命令で行にブレークポイントが設定され、スタックポインタの後に最初の12ワードが出力されます。

(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004004b2 <+0>: mov    %edi,-0x4(%rsp)
   0x00000000004004b6 <+4>: mov    %rsi,-0x10(%rsp)
   0x00000000004004bb <+9>: nop
   0x00000000004004bc <+10>:    mov    $0x0,%eax
   0x00000000004004c1 <+15>:    retq   
End of assembler dump.
(gdb) break *0x00000000004004bb
Breakpoint 1 at 0x4004bb
(gdb) r
Starting program: /home/xdbeef/answer 

Breakpoint 1, 0x00000000004004bb in main ()
(gdb) info registers ebp esp
ebp            0x4004d0            4195536
esp            0xffffdae8          -9496
(gdb) x/12xw 0x7fffffffdae8
0x7fffffffdae8: 0xf7a2d830  0x00007fff  0x00000000  0x00000000
0x7fffffffdaf8: 0xffffdbc8  0x00007fff  0xf7ffcca0  0x00000001
0x7fffffffdb08: 0x004004b2  0x00000000  0x00000000  0x00000000
(gdb) x/12xa 0x7fffffffdae8
0x7fffffffdae8: 0x7ffff7a2d830 <__libc_start_main+240>  0x0
0x7fffffffdaf8: 0x7fffffffdbc8  0x1f7ffcca0
0x7fffffffdb08: 0x4004b2 <main> 0x0
0x7fffffffdb18: 0x161e93edeaec9201  0x4003e0 <_start>
0x7fffffffdb28: 0x7fffffffdbc0  0x0
0x7fffffffdb38: 0x0 0xe9e16c9256ac9201
(gdb) 

ご覧のとおり、メインのスタックフレームの最初で唯一のアドレスはリターンポインタでした。

そして今withフレームポインタ:

g++ answer.cpp -o answer -fno-pie
(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004004b2 <+0>: Push   %rbp
   0x00000000004004b3 <+1>: mov    %rsp,%rbp
   0x00000000004004b6 <+4>: mov    %edi,-0x4(%rbp)
   0x00000000004004b9 <+7>: mov    %rsi,-0x10(%rbp)
   0x00000000004004bd <+11>:    nop
   0x00000000004004be <+12>:    mov    $0x0,%eax
   0x00000000004004c3 <+17>:    pop    %rbp
   0x00000000004004c4 <+18>:    retq   
End of assembler dump.
(gdb) break *0x00000000004004bd
Breakpoint 1 at 0x4004bd
(gdb) r
Starting program: /home/xdbeef/answer 

Breakpoint 1, 0x00000000004004bd in main ()
(gdb) info registers ebp esp
ebp            0xffffdae0          -9504
esp            0xffffdae0          -9504
(gdb) x/12xw 0x7fffffffdae0 
0x7fffffffdae0: 0x004004d0  0x00000000  0xf7a2d830  0x00007fff
0x7fffffffdaf0: 0x00000000  0x00000000  0xffffdbc8  0x00007fff
0x7fffffffdb00: 0xf7ffcca0  0x00000001  0x004004b2  0x00000000
(gdb) x/12xa 0x7fffffffdae0 
0x7fffffffdae0: 0x4004d0 <__libc_csu_init>  0x7ffff7a2d830 <__libc_start_main+240>
0x7fffffffdaf0: 0x0 0x7fffffffdbc8
0x7fffffffdb00: 0x1f7ffcca0 0x4004b2 <main>
0x7fffffffdb10: 0x0 0x716825b9b97d27b5
0x7fffffffdb20: 0x4003e0 <_start>   0x7fffffffdbc0
0x7fffffffdb30: 0x0 0x0

現在、メインのスタックの最初のアドレス(フレームポインター)は__libc_csu_initであり、後者(リターンポインター)は__libc_start_mainのどこかにあり、ここから 'main'関数が呼び出されました。

ところで、他の回答にコメントするのに十分な評判はありませんが、user189437はPatrick Horganのブログに非常に優れた投稿からのスニペットを投稿しました(クレジットはありませんでした)。

http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html

私の別の発言は:info proc map gdbでは、nop命令でブレークポイントを設定すると、__ libc_csu_initがスタック上ではなく、プログラム命令が存在するセクションにあることがわかります。スタックを巻き戻すことができないので、それは意味がありません。まあ、それはおそらく初期フレームポインタを設定するプログラム起動の魔法の一部です。

私は読んでいませんが、プログラムの起動(_startおよびlibc_start_mainの呼び出し)について説明しているいくつかのページから、その非常に具体的なトピックに関する本があります。

https://books.google.pl/books?redir_esc=y&hl=pl&id=YZIoDQAAQBAJ&q=libc+hlt#v=snippet&q=libc%20hlt&f=false

問題が解決したことを願っています!

よろしく、ダニエル

私の知る限り、__libc_csu_initmainはどちらも、__libc_start_mainを呼び出す前に、逆の順序でスタックにプッシュされる引数です。

この例の図は、メインの下部アドレスも示しています: enter image description here

2
user189437