簡単なプログラムを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よりも高いアドレスにあるのはなぜですか?
メイン関数には、戻りポインターの直前に__libc_csu_initへのポインターが含まれています。これは、スタックフレームが機能するためです。 関数に入る前に、呼び出し元はスタックフレームポインターと戻りポインターをプッシュします。
リターンポインタには、関数の終了時に関数が戻るアドレスが含まれています。
フレームポインターには、スタックの前の先頭のアドレスが含まれているため、スタックをどこに巻き戻すかがわかります。
リターンポインターとは異なり、スタックフレームポインターは必ずしもスタックにプッシュする必要はありませんが(そのアドレスはレジスターに格納できます)、現在のスタックから一部のアドレスにアクセスすることはその値から差し引くことになるため、最適化に役立ちます。 。
下の画像、つまり関数呼び出しスタックの例では、スタックフレームポインターは、「フレームポインター」の矢印が指す場所にプッシュされます-作成者は、レジスターにのみ格納されると想定しました。プログラムの場合は、__ libc_csu_initへのアドレスが含まれます。
フレームポインターの詳細については、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の呼び出し)について説明しているいくつかのページから、その非常に具体的なトピックに関する本があります。
問題が解決したことを願っています!
よろしく、ダニエル