|
|
※阅读文章※ |
gcc常用的编译选项对代码的影响作者:alert7 [文章出自: www.fanqiang.com] 测试环境 redhat 6.2 ★ 前言 本文讨论gcc的一些常用编译选项对代码的影响。当然代码变了, 它的内存布局也就会变了,随之exploit也就要做相应的变动。 gcc的编译选项实在太多,本文检了几个最常用的选项。 ★ 演示程序 [alert7@redhat62 alert7]$ cat > test.c #include <stdio.h> void hi(void) { printf("hi"); } int main(int argc, char *argv[]) { hi(); return 0; } ★ 一般情况 [alert7@redhat62 alert7]$ gcc -o test test.c [alert7@redhat62 alert7]$ wc -c test 11773 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80483e4 <main>: push %ebp 0x80483e5 <main+1>: mov %esp,%ebp 0x80483e7 <main+3>: call 0x80483d0 <hi> 0x80483ec <main+8>: xor %eax,%eax 0x80483ee <main+10>: jmp 0x80483f0 <main+12> 0x80483f0 <main+12>: leave 0x80483f1 <main+13>: ret .... End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80483d0 <hi>: push %ebp 0x80483d1 <hi+1>: mov %esp,%ebp 0x80483d3 <hi+3>: push $0x8048450 0x80483d8 <hi+8>: call 0x8048308 <printf> 0x80483dd <hi+13>: add $0x4,%esp 0x80483e0 <hi+16>: leave 0x80483e1 <hi+17>: ret 0x80483e2 <hi+18>: mov %esi,%esi End of assembler dump. 来看看部分的内存映象 (内存高址) +--------+ |bffffbc4| argv的地址(即argv[0]的地址) 0xbffffb84 +--------+ |00000001| argc的值 0xbffffb80 +--------+ |400309cb|main的返回地址 0xbffffb7c +--------+ <-- 调用main函数前的esp |bffffb98| 调用main函数前的ebp 0xbffffb78 +--------+ <-- main函数的ebp |080483ec| hi()的返回地址 0xbffffb74 +--------+ |bffffb78| 调用hi()前的esp 0xbffffb70 +--------+ |08048450| "hi"的地址 0xbffffb6c +--------+ | ...... | (内存低址) leave 指令所做的操作相当于MOV ESP,EBP 然后 POP EBP ret 指令所做的操作相当于POP EIP ★ -O 编译选项 With `-O', the compiler tries to reduce code size and execution time. When you specify `-O', the two options `-fthread-jumps' and `-fdefer-pop' are turned on 优化,减少代码大小和执行的时间 [alert7@redhat62 alert7]$ gcc -O -o test test.c [alert7@redhat62 alert7]$ wc -c test 11757 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80483d8 <main>: push %ebp 0x80483d9 <main+1>: mov %esp,%ebp 0x80483db <main+3>: call 0x80483c8 <hi> 0x80483e0 <main+8>: xor %eax,%eax 0x80483e2 <main+10>: leave 0x80483e3 <main+11>: ret 0x80483e4 <main+12>: nop ... End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80483c8 <hi>: push %ebp 0x80483c9 <hi+1>: mov %esp,%ebp 0x80483cb <hi+3>: push $0x8048440 0x80483d0 <hi+8>: call 0x8048308 <printf> 0x80483d5 <hi+13>: leave 0x80483d6 <hi+14>: ret 0x80483d7 <hi+15>: nop End of assembler dump. 在main()中,把一条jmp指令优化掉了,很显然,这条指令是可以不需要的。 在hi()中,把add $0x4,%esp优化掉了,这会不会使stack不平衡呢? 来看看部分的内存映象 (内存高址) +--------+ |bffffbc4| argv的地址(即argv[0]的地址) 0xbffffb84 +--------+ |00000001| argc的值 0xbffffb80 +--------+ |400309cb|main的返回地址 0xbffffb7c +--------+ <-- 调用main函数前的esp |bffffb98| 调用main函数前的ebp 0xbffffb78 +--------+ <-- main函数的ebp |080483e0| hi()的返回地址 0xbffffb74 +--------+ |bffffb78| 调用hi()前的esp 0xbffffb70 +--------+ |08048440| "hi"的地址 0xbffffb6c +--------+ | ...... | (内存低址) leave 指令所做的操作相当于把MOV ESP,EBP 然后 POP EBP 看到leave指令操作了没有,先把ebp-->esp,再pop ebp,这样即使 在过程内堆栈的esp,ebp是不平衡的,但只要返回时候碰到leave指令 就会平衡了,所以把add $0x4,%esp优化掉也是没有问题的。 ★ -O2 编译选项 -O2 Optimize even more. Nearly all supported optimizations that do not involve a space-speed tradeoff are performed. Loop unrolling and function inlining are not done, for example. As compared to -O, this option increases both compilation time and the performance of the generated code. [alert7@redhat62 alert7]$ gcc -O2 -o test test.c [alert7@redhat62 alert7]$ wc -c test 11757 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80483d8 <main>: push %ebp 0x80483d9 <main+1>: mov %esp,%ebp 0x80483db <main+3>: call 0x80483c8 <hi> 0x80483e0 <main+8>: xor %eax,%eax 0x80483e2 <main+10>: leave 0x80483e3 <main+11>: ret ... 0x80483ef <main+23>: nop End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80483c8 <hi>: push %ebp 0x80483c9 <hi+1>: mov %esp,%ebp 0x80483cb <hi+3>: push $0x8048440 0x80483d0 <hi+8>: call 0x8048308 <printf> 0x80483d5 <hi+13>: leave 0x80483d6 <hi+14>: ret 0x80483d7 <hi+15>: nop End of assembler dump. 由于程序比较简单,再优化也没有好优化的了,所以跟-O出来的一样。 ★ -fomit-frame-pointer 编译选项 -fomit-frame-pointer Don't keep the frame pointer in a register for functions that don't need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on most machines. 忽略帧指针。这样在程序就不需要保存,安装,和恢复ebp了。这样ebp也就是一个 free的register了,在函数中就可以随便使用了。 [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -o test test.c [alert7@redhat62 alert7]$ wc -c test 11773 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80483e0 <main>: call 0x80483d0 <hi> 0x80483e5 <main+5>: xor %eax,%eax 0x80483e7 <main+7>: jmp 0x80483f0 <main+16> 0x80483e9 <main+9>: lea 0x0(%esi,1),%esi 0x80483f0 <main+16>: ret .... End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80483d0 <hi>: push $0x8048450 0x80483d5 <hi+5>: call 0x8048308 <printf> 0x80483da <hi+10>: add $0x4,%esp 0x80483dd <hi+13>: ret 0x80483de <hi+14>: mov %esi,%esi End of assembler dump. 在main()和hi()中都去掉了以下指令 push %ebp mov %esp,%ebp//这两条指令安装 leave//这条指令恢复 来看看部分的内存映象 (内存高址) +--------+ |bffffbc4| argv的地址(即argv[0]的地址) 0xbffffb84 +--------+ |00000001| argc的值 0xbffffb80 +--------+ |400309cb|main的返回地址 0xbffffb7c +--------+ |080483e5| hi()的返回地址 0xbffffb78 +--------+ |08048450| "hi"字符串的地址 0xbffffb74 +--------+ | ...... | (内存低址) 没有保存上层执行环境的ebp. ★ -fomit-frame-pointer && -O2 -fomit-frame-pointer编译选项去掉了 push %ebp mov %esp,%ebp//这两条指令安装 leave//这条指令恢复 -O2编译选项去掉了 add $0x4,%esp 两个加起来会不会这四条指令一起去掉,从而使stack不平衡呢? [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -O2 -o test test.c [alert7@redhat62 alert7]$ wc -c test 11741 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80483d8 <main>: call 0x80483c8 <hi> 0x80483dd <main+5>: xor %eax,%eax 0x80483df <main+7>: ret End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80483c8 <hi>: push $0x8048430 0x80483cd <hi+5>: call 0x8048308 <printf> 0x80483d2 <hi+10>: add $0x4,%esp 0x80483d5 <hi+13>: ret 0x80483d6 <hi+14>: mov %esi,%esi End of assembler dump. 来看看部分的内存映象 (内存高址) +--------+ |bffffbc4| argv的地址(即argv[0]的地址) 0xbffffb84 +--------+ |00000001| argc的值 0xbffffb80 +--------+ |400309cb|main的返回地址 0xbffffb7c +--------+ |080483dd| hi()的返回地址 0xbffffb78 +--------+ |08048430| "hi"字符串的地址 0xbffffb74 +--------+ | ...... | (内存低址) 此时就没有把add $0x4,%esp优化掉,如果优化掉的话,整个stack就 会变的不平衡,从而会导致程序出错。 ★ -fPIC 编译选项 -fPIC If supported for the target machine, emit position-independent code, suitable for dynamic linking,even if branches need large displacements. 产生位置无关代码(PIC),一般创建共享库时用到。 在x86上,PIC的代码的符号引用都是通过ebx进行操作的。 [alert7@redhat62 alert7]$ gcc -fPIC -o test test.c [alert7@redhat62 alert7]$ wc -c test 11805 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80483f8 <main>: push %ebp 0x80483f9 <main+1>: mov %esp,%ebp 0x80483fb <main+3>: push %ebx 0x80483fc <main+4>: call 0x8048401 <main+9> 0x8048401 <main+9>: pop %ebx//取得该指令的地址 0x8048402 <main+10>: add $0x1093,%ebx//此时ebx里面存放着是GOT表的地址 0x8048408 <main+16>: call 0x80483d0 <hi> 0x804840d <main+21>: xor %eax,%eax 0x804840f <main+23>: jmp 0x8048411 <main+25> 0x8048411 <main+25>: mov 0xfffffffc(%ebp),%ebx 0x8048414 <main+28>: leave 0x8048415 <main+29>: ret ... End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80483d0 <hi>: push %ebp 0x80483d1 <hi+1>: mov %esp,%ebp 0x80483d3 <hi+3>: push %ebx 0x80483d4 <hi+4>: call 0x80483d9 <hi+9> 0x80483d9 <hi+9>: pop %ebx 0x80483da <hi+10>: add $0x10bb,%ebx 0x80483e0 <hi+16>: lea 0xffffefdc(%ebx),%edx 0x80483e6 <hi+22>: mov %edx,%eax 0x80483e8 <hi+24>: push %eax 0x80483e9 <hi+25>: call 0x8048308 <printf> 0x80483ee <hi+30>: add $0x4,%esp 0x80483f1 <hi+33>: mov 0xfffffffc(%ebp),%ebx 0x80483f4 <hi+36>: leave 0x80483f5 <hi+37>: ret 0x80483f6 <hi+38>: mov %esi,%esi End of assembler dump. 来看看部分的内存映象 (内存高址) +--------+ |bffffbc4| argv的地址(即argv[0]的地址) 0xbffffb84 +--------+ |00000001| argc的值 0xbffffb80 +--------+ |400309cb|main的返回地址 0xbffffb7c +--------+ <-- 调用main函数前的esp |bffffb98| 调用main函数前的ebp 0xbffffb78 +--------+ <-- main函数的ebp |401081ec| 保存的ebx 0xbffffb74 +--------+ |0804840d| (存放过call 0x8048401的下一条指令地址) 0xbffffb70 +--------+ |bffffb78| 调用hi()前的esp 0xbffffb6c +--------+ |08049494| GOT表地址 0xbffffb68 +--------+ |08048470|(存放过call 0x80483d9的下一条指令地址) 0xbffffb64 +--------+ | ...... | (内存低址) ★ -static 编译选项 -static On systems that support dynamic linking, this prevents linking with the shared libraries. On other systems, this option has no effect. 把一些函数都静态的编译到程序中,而无需动态链接了。 [alert7@redhat62 alert7]$ gcc -o test -static test.c [alert7@redhat62 alert7]$ wc -c test 962808 test [alert7@redhat62 alert7]$ gdb -q test (gdb) disass main Dump of assembler code for function main: 0x80481b4 <main>: push %ebp 0x80481b5 <main+1>: mov %esp,%ebp 0x80481b7 <main+3>: call 0x80481a0 <hi> 0x80481bc <main+8>: xor %eax,%eax 0x80481be <main+10>: jmp 0x80481c0 <main+12> 0x80481c0 <main+12>: leave 0x80481c1 <main+13>: ret ... End of assembler dump. (gdb) disass hi Dump of assembler code for function hi: 0x80481a0 <hi>: push %ebp 0x80481a1 <hi+1>: mov %esp,%ebp 0x80481a3 <hi+3>: push $0x8071528 0x80481a8 <hi+8>: call 0x804865c <printf> 0x80481ad <hi+13>: add $0x4,%esp 0x80481b0 <hi+16>: leave 0x80481b1 <hi+17>: ret 0x80481b2 <hi+18>: mov %esi,%esi End of assembler dump. [alert7@redhat62 alert7]$ ldd test not a dynamic executable -static出来的代码已经没有PLT了,GOT虽然有,已经全部为0了。 ★ 小节 抛砖引玉般简单的实例描述了下gcc常用的编译选项对代码的影响。 不正之处,还请斧正。谢谢。 &nbs 文章加入时间: 2004-11-17 14:53:09 责任编辑: w9 (2423 人次查阅) |