本帖最后由 天行健 于 2024-12-12 22:25 编辑
现在让我们扩展"hello, world"(2)中的示例,将其中main()函数中printf的部分替换成这样 - #include <stdio.h>
- int main()
- {
- printf("a=%d; b=%d; c=%d", 1, 2, 3);
- return 0;
- };
复制代码
5.1 x86: 3个参数5.1.1 MSVC在我们用MSVC 2010 Express编译后可以看到: - $SG3830 DB ’a=%d; b=%d; c=%d’, 00H
- ...
- push 3
- push 2
- push 1
- push OFFSET $SG3830
- call _printf
- add esp, 16 ; 00000010H
复制代码
这和之前的代码几乎一样,但是我们现在可以看到printf()的参数被反序压入了栈中。第一个参数被最后压入。 另外,在32bit的环境下int类型变量占4 bytes。 那么,这里有4个参数 4*4=16 —— 恰好在栈中占据了16bytes:一个32bit字符串指针,和3个int类型变量。 当函数执行完后,执行"ADD ESP, X"指令恢复栈指针寄存器(ESP 寄存器)。通常可以在这里推断函数参数的个数:用 X除以4。 当然,这只涉及__cdecl函数调用方式。 也可以在最后一个函数调用后,把几个ADD ESP, X指令合并成一个。 - push a1
- push a2
- call ...
- ...
- push a1
- call ...
- ...
- push a1
- push a2
- push a3
- call ...
- add esp, 24
复制代码
5.1.2 MSVC 与 ollyDbg现在我们来在OllyDbg中加载这个范例。我们可以尝试在MSVC 2012 加 /MD 参数编译这个示例,也就是链接MSVCR*.dll,那么我们就可以在debugger中清楚的看到调用的函数。 在OllyDbg中载入程序,最开始的断点在ntdll.dll中,接着按F9(run),然后第二个断点在CRT-code中。现在我们来找main()函数。 往下滚动屏幕,找到下图这段代码(MSVC把main()函数分配在代码段开始处) 见图5.3 点击 PUSH EBP指令,按下F2(设置断点)然后按下F9(run),通过这些操作来跳过CRT-code,因为我们现在还不必关注这部分。 按6次F8(step over)。见图5.4 现在EIP 指向了CALL printf的指令。和其他调试器一样,OllyDbg高亮了有值改变的寄存器。所以每次你按下F8,EIP都在改变然后它看起来便是红色的。ESP同时也在改变,因为它是指向栈的 栈中的数据又在哪?那么看一下调试器右下方的窗口: 图 5.1 然后我们可以看到有三列,栈的地址,元组数据,以及一些OllyDbg的注释,OllyDbg可以识别像printf()这样的字符串,以及后面的三个值。 右击选中字符串,然后点击”follow in dump”,然后字符串就会出现在左侧显示内存数据的地方,这些内存的数据可以被编辑。我们可以修改这些字符串,之后这个例子的结果就会变的不同,现在可能并不是很实用。但是作为练习却非常好,可以体会每部分是如何工作的。 再按一次F8(step over) 然后我们就可以看到输出 图5.2 执行printf()函数 让我们看看寄存器和栈是怎样变化的 见图5.5 EAX寄存器现在是0xD(13).这是正确的,printf()返回打印的字符,EIP也变了—— 事实上现在指向CALL printf之后下一条指令的地址.ECX和EDX的值也改变了。显然,printf()函数的内部机制对它们进行了使用。 很重要的一点ESP的值并没有发生变化,栈的状态也是!我们可以清楚地看到字符串和相应的3个值还是在那里,实际上这就是cdecl调用方式。被调用的函数并不清楚栈中参数,因为这是调用体的任务。 再按一下F8执行ADD ESP, 0见图5.6 ESP改变了,但是值还是在栈中!当然 没有必要用0或者别的数据填充这些值。 因为在栈指针寄存器之上的数据都是无用的。 图5.3 OllyDbg:main()初始处 图5.4 OllyDbg:printf()执行时
图5.5 Ollydbg:printf()执行后 图5.6 OllyDbg ADD ESP, 10执行完后 5.1.3 GCC 现在我们将同样的程序在linux下用GCC4.4.1编译后放入IDA看一下: - main proc near
-
- var_10 = dword ptr -10h
- var_C = dword ptr -0Ch
- var_8 = dword ptr -8
- var_4 = dword ptr -4
-
- push ebp
- mov ebp, esp
- and esp, 0FFFFFFF0h
- sub esp, 10h
- mov eax, offset aADBDCD ; "a=%d; b=%d; c=%d"
- mov [esp+10h+var_4], 3
- mov [esp+10h+var_8], 2
- mov [esp+10h+var_C], 1
- mov [esp+10h+var_10], eax
- call _printf
- mov eax, 0
- leave
- retn
- main endp
复制代码
MSVC与GCC编译后代码的不同点只是参数入栈的方法不同,这里GCC不用PUSH/POP而是直接对栈操作。 5.1.4 GCC与GDB接着我们尝试在linux中用GDB运行下这个示例程序。 -g 表示将debug信息插入可执行文件中 $ gcc 1.c -g -o 1 反编译: - $ gdb 1
- GNU gdb (GDB) 7.6.1-ubuntu
- Copyright (C) 2013 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law. Type "show copying"
- and "show warranty" for details.
- This GDB was configured as "i686-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from /home/dennis/polygon/1...done.
复制代码
表5.1 在printf()处设置断点 - (gdb) b printf
- Breakpoint 1 at 0x80482f0
复制代码
Run 这里没有printf()函数的源码,所以GDB没法显示出源码,但是却可以这样做 - (gdb) run
- Starting program: /home/dennis/polygon/1
-
- Breakpoint 1, __printf (format=0x80484f0 "a=%d; b=%d; c=%d") at printf.c:29
- 29 printf.c: No such file or directory.
复制代码
打印10组栈中的元组数据,左边是栈中的地址 - (gdb) x/10w $esp
- 0xbffff11c: 0x0804844a 0x080484f0 0x00000001 0x00000002
- 0xbffff12c: 0x00000003 0x08048460 0x00000000 0x00000000
- 0xbffff13c: 0xb7e29905 0x00000001
复制代码
最开始的是返回地址(0x0804844a),我们可以确定在这里,于是可以反汇编这里的代码 - (gdb) x/5i 0x0804844a
- 0x804844a <main+45>: mov $0x0,%eax
- 0x804844f <main+50>: leave
- 0x8048450 <main+51>: ret
- 0x8048451: xchg %ax,%ax
- 0x8048453: xchg %ax,%ax
复制代码
两个XCHG指令,明显是一些垃圾数据,可以忽略 第二个(0x080484f0)是一处格式化字符串 - (gdb) x/s 0x080484f0
- 0x80484f0: "a=%d; b=%d; c=%d"
复制代码
而其他三个则是printf()函数的参数,另外的可能只是栈中的垃圾数据,但是也可能是其他函数的数据,例如它们的本地变量。这里可以忽略。 执行 finish ,表示执行到函数结束。在这里是执行到printf()完。 - (gdb) finish
- Run till exit from #0 __printf (format=0x80484f0 "a=%d; b=%d; c=%d") at printf.c:29
- main () at 1.c:6
- 6 return 0;
- Value returned is $2 = 13
复制代码
GDB显示了printf()函数在eax中的返回值,这是打印字符的数量,就像在OllyDbg中一样。 我们同样看到了return 0;及这在1.c文件中第6行所代表的含义。1.c文件就在当前目录下,GDB就在那找到了字符串。但是GDB又是怎么知道当前执行到了哪一行? 事实上这和编译器有关,当生成调试信息时,同样也保存了一张代码行号与指令地址的关系表。 查看EAX中储存的13: - (gdb) info registers
- eax 0xd 13
- ecx 0x0 0
- edx 0x0 0
- ebx 0xb7fc0000 -1208221696
- esp 0xbffff120 0xbffff120
- ebp 0xbffff138 0xbffff138
- esi 0x0 0
- edi 0x0 0
- eip 0x804844a 0x804844a <main+45>
- ...
复制代码
反汇编当前的指令 - (gdb) disas
- Dump of assembler code for function main:
- 0x0804841d <+0>: push %ebp
- 0x0804841e <+1>: mov %esp,%ebp
- 0x08048420 <+3>: and $0xfffffff0,%esp
- 0x08048423 <+6>: sub $0x10,%esp
- 0x08048426 <+9>: movl $0x3,0xc(%esp)
- 0x0804842e <+17>: movl $0x2,0x8(%esp)
- 0x08048436 <+25>: movl $0x1,0x4(%esp)
- 0x0804843e <+33>: movl $0x80484f0,(%esp)
- 0x08048445 <+40>: call 0x80482f0 <printf@plt>
- => 0x0804844a <+45>: mov $0x0,%eax
- 0x0804844f <+50>: leave
- 0x08048450 <+51>: ret
- End of assembler dump.
复制代码
GDB默认使用AT&T语法显示,当然也可以转换至intel: - (gdb) set disassembly-flavor intel
- (gdb) disas
- Dump of assembler code for function main:
- 0x0804841d <+0>: push ebp
- 0x0804841e <+1>: mov ebp,esp
- 0x08048420 <+3>: and esp,0xfffffff0
- 0x08048423 <+6>: sub esp,0x10
- 0x08048426 <+9>: mov DWORD PTR [esp+0xc],0x3
- 0x0804842e <+17>: mov DWORD PTR [esp+0x8],0x2
- 0x08048436 <+25>: mov DWORD PTR [esp+0x4],0x1
- 0x0804843e <+33>: mov DWORD PTR [esp],0x80484f0
- 0x08048445 <+40>: call 0x80482f0 <printf@plt>
- => 0x0804844a <+45>: mov eax,0x0
- 0x0804844f <+50>: leave
- 0x08048450 <+51>: ret
- End of assembler dump.
复制代码
执行下一条指令,GDB显示了结束大括号,代表着这里是函数结束部分。
在执行完MOV EAX, 0后我们可以看到EAX就已经变为0了。 - (gdb) info registers
- eax 0x0 0
- ecx 0x0 0
- edx 0x0 0
- ebx 0xb7fc0000 -1208221696
- esp 0xbffff120 0xbffff120
- ebp 0xbffff138 0xbffff138
- esi 0x0 0
- edi 0x0 0
- eip 0x804844f 0x804844f <main+50>
- ...
复制代码
5.2 x64: 8个参数为了看其他参数如何通过栈传递的,我们再次修改代码将参数个数增加到9个(printf()格式化字符串和8个int 变量) - #include <stdio.h>
- int main() {
- printf("a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
- ", 1, 2, 3, 4, 5, 6, 7, 8);
- return 0;
- };
复制代码
5.2.1 MSVC正如我们之前所见,在win64下开始的4个参数传递至RCX,RDX,R8,R9寄存器, 然而 MOV指令,替代PUSH指令。用来准备栈数据,所以值都是直接写入栈中 - $SG2923 DB ’a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d’, 0aH, 00H
-
- main PROC
- sub rsp, 88
-
- mov DWORD PTR [rsp+64], 8
- mov DWORD PTR [rsp+56], 7
- mov DWORD PTR [rsp+48], 6
- mov DWORD PTR [rsp+40], 5
- mov DWORD PTR [rsp+32], 4
- mov r9d, 3
- mov r8d, 2
- mov edx, 1
- lea rcx, OFFSET FLAT:$SG2923
- call printf
-
- ; return 0
- xor eax, eax
-
- add rsp, 88
- ret 0
- main ENDP
- _TEXT ENDS
- END
复制代码
表5.2:msvc 2010 x64 5.2.2 GCC在*NIX系统,对于x86-64这也是同样的原理,除了前6个参数传递给了RDI,RSI,RDX,RCX,R8,R9寄存器。GCC将生成的代码字符指针写入了EDI而不是RDI(如果有的话)——我们在2.2.2节看到过这部分 同样我们也看到在寄存器EAX被清零前有个printf() call: - .LC0:
- .string "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
- "
- main:
- sub rsp, 40
- mov r9d, 5
- mov r8d, 4
- mov ecx, 3
- mov edx, 2
- mov esi, 1
- mov edi, OFFSET FLAT:.LC0
- xor eax, eax ; number of vector registers passed
- mov DWORD PTR [rsp+16], 8
- mov DWORD PTR [rsp+8], 7
- mov DWORD PTR [rsp], 6
- call printf
- ; return 0
- xor eax, eax
- add rsp, 40
- ret
复制代码
表5.3:GCC 4.4.6 –o 3 x64 5.2.3 GCC + GDB 让我们在GDB中尝试这个例子。 $ gcc -g 2.c -o 2 反编译: - $ gdb 2
- GNU gdb (GDB) 7.6.1-ubuntu
- Copyright (C) 2013 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law. Type "show copying"
- and "show warranty" for details.
- This GDB was configured as "x86_64-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from /home/dennis/polygon/2...done.
复制代码
表5.4:在printf()处下断点,然后run - (gdb) b printf
- Breakpoint 1 at 0x400410
- (gdb) run
- Starting program: /home/dennis/polygon/2
- Breakpoint 1, __printf (format=0x400628 "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
- ") at
- printf.c:29
- 29 printf.c: No such file or directory.
复制代码
寄存器RSI/RDX/RCX/R8/R9都有应有的值,RIP则是printf()函数地址 - (gdb) info registers
- rax 0x0 0
- rbx 0x0 0
- rcx 0x3 3
- rdx 0x2 2
- rsi 0x1 1
- rdi 0x400628 4195880
- rbp 0x7fffffffdf60 0x7fffffffdf60
- rsp 0x7fffffffdf38 0x7fffffffdf38
- r8 0x4 4
- r9 0x5 5
- r10 0x7fffffffdce0 140737488346336
- r11 0x7ffff7a65f60 140737348263776
- r12 0x400440 4195392
- r13 0x7fffffffe040 140737488347200
- r14 0x0 0
- r15 0x0 0
- rip 0x7ffff7a65f60 0x7ffff7a65f60 <__printf>
- ...
复制代码
表5.5 检查格式化字符串 - (gdb) x/s $rdi
- 0x400628: "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
- "
复制代码
用x/g命令显示栈内容 - (gdb) x/10g $rsp
- 0x7fffffffdf38: 0x0000000000400576 0x0000000000000006
- 0x7fffffffdf48: 0x0000000000000007 0x00007fff00000008
- 0x7fffffffdf58: 0x0000000000000000 0x0000000000000000
- 0x7fffffffdf68: 0x00007ffff7a33de5 0x0000000000000000
- 0x7fffffffdf78: 0x00007fffffffe048 0x0000000100000000
复制代码
与之前一样,第一个栈元素是返回地址,我们也同时也看到在高32位的8也没有被清除。 0x00007fff00000008,这是因为是32位int类型的,因此,高寄存器或堆栈部分可能包含一些随机垃圾数值。 printf()函数执行之后将返回控制,GDB会显示整个main()函数。
执行完printf()后,就会清零EAX,然后发现EAX早已为0,RIP现在则指向LEAVE指令。 - (gdb) finish
- Run till exit from #0 __printf (format=0x400628 "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
- n") at printf.c:29
- a=1; b=2; c=3; d=4; e=5; f=6; g=7; h=8
- main () at 2.c:6
- 6 return 0;
- Value returned is $1 = 39
- (gdb) next
- 7 };
- (gdb) info registers
- rax 0x0 0
- rbx 0x0 0
- rcx 0x26 38
- rdx 0x7ffff7dd59f0 140737351866864
- rsi 0x7fffffd9 2147483609
- rdi 0x0 0
- rbp 0x7fffffffdf60 0x7fffffffdf60
- rsp 0x7fffffffdf40 0x7fffffffdf40
- r8 0x7ffff7dd26a0 140737351853728
- r9 0x7ffff7a60134 140737348239668
- r10 0x7fffffffd5b0 140737488344496
- r11 0x7ffff7a95900 140737348458752
- r12 0x400440 4195392
- r13 0x7fffffffe040 140737488347200
- r14 0x0 0
- r15 0x0 0
- rip 0x40057b 0x40057b <main+78>
- ...
复制代码
5.3 ARM:3个参数习惯上,ARM传递参数的规则(参数调用)如下:前4个参数传递给了R0-R3寄存器,其余的参数则在栈中。这和fastcall或者win64传递参数很相似 5.3.1 Non-optimizing Keil + ARM mode(非优化keil编译模式 + ARM环境)- .text:00000014 printf_main1
- .text:00000014 10 40 2D E9 STMFD SP!, {R4,LR}
- .text:00000018 03 30 A0 E3 MOV R3, #3
- .text:0000001C 02 20 A0 E3 MOV R2, #2
- .text:00000020 01 10 A0 E3 MOV R1, #1
- .text:00000024 1D 0E 8F E2 ADR R0, aADBDCD ; "a=%d; b=%d; c=%d
- "
- .text:00000028 0D 19 00 EB BL __2printf
- .text:0000002C 10 80 BD E8 LDMFD SP!, {R4,PC}
复制代码
所以 前四个参数按照它们的顺序传递给了R0-R3, printf()中的格式化字符串指针在R0中,然后1在R1,2在R2,3在R3. 到目前为止没有什么不寻常的。 5.3.2 Optimizing Keil + ARM mode(优化的keil编译模式 + ARM环境)- .text:00000014 EXPORT printf_main1
- .text:00000014 printf_main1
- .text:00000014 03 30 A0 E3 MOV R3, #3
- .text:00000018 02 20 A0 E3 MOV R2, #2
- .text:0000001C 01 10 A0 E3 MOV R1, #1
- .text:00000020 1E 0E 8F E2 ADR R0, aADBDCD ; "a=%d; b=%d; c=%d
- "
- .text:00000024 CB 18 00 EA B __2printf
复制代码
表5.7: Optimizing Keil + ARM mode 这是在针对ARM optimized (-O3)版本下的,我们可以B作为最后一个指令而不是熟悉的BL。另外一个不同之处在optimized与之前的(compiled without optimization)对比发现函数prologue 和 epilogue(储存R0和LR值的寄存器),B指令仅仅跳向另一处地址,没有任何关于LR寄存器的操作,也就是说它和x86中的jmp相似,为什么会这样?因为代码就是这样,事实上,这和前面相似,主要有两点原因 1)不管是栈还是SP(栈指针),都有被修改。2)printf()的调用是最后的指令,所以之后便没有了。完成之后,printf()函数就返回到LR储存的地址处。但是指针地址从函数调用的地方转移到了LR中!接着就会从printf()到那里。结果,我们不需要保存LR,因为我们没有必要修改LR。因为除了printf()函数外没有其他函数了。另外,除了这个调用外,我们不需要再做别的。这就是为什么这样编译是可行的。 5.3.3 Optimizing Keil + thumb mode- .text:0000000C printf_main1
- .text:0000000C 10 B5 PUSH {R4,LR}
- .text:0000000E 03 23 MOVS R3, #3
- .text:00000010 02 22 MOVS R2, #2
- .text:00000012 01 21 MOVS R1, #1
- .text:00000014 A4 A0 ADR R0, aADBDCD ; "a=%d; b=%d; c=%d
- "
- .text:00000016 06 F0 EB F8 BL __2printf
- .text:0000001A 10 BD POP {R4,PC}
复制代码
表5.8:Optimizing Keil + thumb mode 和non-optimized for ARM mode代码没什么明显的区别 5.4 ARM: 8 arguments我们再用之前9个参数的那个例子 - void printf_main2()
- {
- printf("a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
- ", 1, 2, 3, 4, 5, 6, 7, 8);
- };
复制代码
这些代码可以分成几个部分: Function prologue: 最开始的”STR LR, [SP,#var_4]!”指令将LR储存在栈中,因为我们将用这个寄存器调用printf()。 第二个” SUB SP, SP, #0x14”指令减了SP(栈指针),为了在栈上分配0x14(20)bytes的内存,实际上我们需要传递5个 32-bit的数据通过栈传递给printf()函数,而且每个占4bytes,也就是5*4=20。另外4个32-bit的数据将会传递给寄存器。 通过栈传递5,6,7和8: 然后,5,6,7,8分别被写入了R0,R1,R2及R3寄存器。然后ADD R12, SP,#0x18+var_14指令将栈中指针的地址写入,并且在这里会向R12写入4个值,var_14是一个汇编宏,相当于0x14,这些都由IDA简明的创建表示访问栈的变量,var_?在IDA中表示栈中的本地变量,所以SP+4将被写入R12寄存器。下一步的”STMIA R12, R0-R3”指令将R0-R3寄存器的内容写在了R2指向的指针处。STMIA指令指Store Multiple Increment After, Increment After指R12寄存器在有值写入后自增4。 通过栈传递4: 4存在R0中,然后这个值在” STR R0, [SP,#0x18+var_18]”指令帮助下,存在了栈上,var_18是0x18,偏移量为0.所以R0寄存器中的值将会写在SP指针指向的指针处。 通过寄存器传递1,2,3: 开始3个数(a,b,c)(分别是1,2,3)正好在printf()函数调用前被传递到了R1,R2,R3寄存器中。 然后另外5个值通过栈传递。 printf() 调用: “ADD SP, SP, #0x14”指令将SP指针返回到之前的指针处,因此清除了栈,当然,栈中之前写入的数据还在那,但是当后来的函数被调用时那里则会被重写。 “LDR PC, [SP+4+var_4],#4"指令将LR中储存的值载入到PC指针,因此函数结束。 5.4.2 Optimizing Keil: thumb mode- .text:0000001C printf_main2
- .text:0000001C
- .text:0000001C var_18 = -0x18
- .text:0000001C var_14 = -0x14
- .text:0000001C var_8 = -8
- .text:0000001C
- .text:0000001C 00 B5 PUSH {LR}
- .text:0000001E 08 23 MOVS R3, #8
- .text:00000020 85 B0 SUB SP, SP, #0x14
- .text:00000022 04 93 STR R3, [SP,#0x18+var_8]
- .text:00000024 07 22 MOVS R2, #7
- .text:00000026 06 21 MOVS R1, #6
- .text:00000028 05 20 MOVS R0, #5
- .text:0000002A 01 AB ADD R3, SP, #0x18+var_14
- .text:0000002C 07 C3 STMIA R3!, {R0-R2}
- .text:0000002E 04 20 MOVS R0, #4
- .text:00000030 00 90 STR R0, [SP,#0x18+var_18]
- .text:00000032 03 23 MOVS R3, #3
- .text:00000034 02 22 MOVS R2, #2
- .text:00000036 01 21 MOVS R1, #1
- .text:00000038 A0 A0 ADR R0, aADBDCDDDEDFDGD ; "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%"...
- .text:0000003A 06 F0 D9 F8 BL __2printf
- .text:0000003E
- .text:0000003E loc_3E ; CODE XREF: example13_f+16
- .text:0000003E 05 B0 ADD SP, SP, #0x14
- .text:00000040 00 BD POP {PC}
复制代码
几乎和之前的例子是一样的,然后这是thumb 代码,值入栈的确不同:先是8,然后5,6,7,第三个是4。 5.4.3 Optimizing Xcode (LLVM): ARM mode- __text:0000290C _printf_main2
- __text:0000290C
- __text:0000290C var_1C = -0x1C
- __text:0000290C var_C = -0xC
- __text:0000290C
- __text:0000290C 80 40 2D E9 STMFD SP!, {R7,LR}
- __text:00002910 0D 70 A0 E1 MOV R7, SP
- __text:00002914 14 D0 4D E2 SUB SP, SP, #0x14
- __text:00002918 70 05 01 E3 MOV R0, #0x1570
- __text:0000291C 07 C0 A0 E3 MOV R12, #7
- __text:00002920 00 00 40 E3 MOVT R0, #0
- __text:00002924 04 20 A0 E3 MOV R2, #4
- __text:00002928 00 00 8F E0 ADD R0, PC, R0
- __text:0000292C 06 30 A0 E3 MOV R3, #6
- __text:00002930 05 10 A0 E3 MOV R1, #5
- __text:00002934 00 20 8D E5 STR R2, [SP,#0x1C+var_1C]
- __text:00002938 0A 10 8D E9 STMFA SP, {R1,R3,R12}
- __text:0000293C 08 90 A0 E3 MOV R9, #8
- __text:00002940 01 10 A0 E3 MOV R1, #1
- __text:00002944 02 20 A0 E3 MOV R2, #2
- __text:00002948 03 30 A0 E3 MOV R3, #3
- __text:0000294C 10 90 8D E5 STR R9, [SP,#0x1C+var_C]
- __text:00002950 A4 05 00 EB BL _printf
- __text:00002954 07 D0 A0 E1 MOV SP, R7
- __text:00002958 80 80 BD E8 LDMFD SP!, {R7,PC}
复制代码
几乎和我们之前遇到的一样,除了STMFA(Store Multiple Full Ascending)指令,它和STMIB(Store Multiple Increment Before)指令一样,这个指令直到下个寄存器的值写入内存时会增加SP寄存器中的值,但是反过来却不同。 另外一个地方我们可以轻松的发现指令是随机分布的,例如,R0寄存器中的值在三个地方初始,在0x2918,0x2920,0x2928。而这一个指令就可以搞定。然而,optimizing compiler有它自己的原因,对于如何更好的放置指令,通常,处理器尝试同时执行并行的指令,例如像” MOVT R0, #0”和” ADD R0, PC,R0”就不能同时执行了,因为它们同时都在修改R0寄存器,另一方面”MOVT R0, #0”和”MOV R2, #4”指令却可以同时执行,因为执行效果并没有任何冲突。 大概,编译器就是这样尝试编译的,可能。 5.4.4 Optimizing Xcode (LLVM): thumb-2 mode- __text:00002BA0 _printf_main2
- __text:00002BA0
- __text:00002BA0 var_1C = -0x1C
- __text:00002BA0 var_18 = -0x18
- __text:00002BA0 var_C = -0xC
- __text:00002BA0
- __text:00002BA0 80 B5 PUSH {R7,LR}
- __text:00002BA2 6F 46 MOV R7, SP
- __text:00002BA4 85 B0 SUB SP, SP, #0x14
- __text:00002BA6 41 F2 D8 20 MOVW R0, #0x12D8
- __text:00002BAA 4F F0 07 0C MOV.W R12, #7
- __text:00002BAE C0 F2 00 00 MOVT.W R0, #0
- __text:00002BB2 04 22 MOVS R2, #4
- __text:00002BB4 78 44 ADD R0, PC ; char *
- __text:00002BB6 06 23 MOVS R3, #6
- __text:00002BB8 05 21 MOVS R1, #5
- __text:00002BBA 0D F1 04 0E ADD.W LR, SP, #0x1C+var_18
- __text:00002BBE 00 92 STR R2, [SP,#0x1C+var_1C]
- __text:00002BC0 4F F0 08 09 MOV.W R9, #8
- __text:00002BC4 8E E8 0A 10 STMIA.W LR, {R1,R3,R12}
- __text:00002BC8 01 21 MOVS R1, #1
- __text:00002BCA 02 22 MOVS R2, #2
- __text:00002BCC 03 23 MOVS R3, #3
- __text:00002BCE CD F8 10 90 STR.W R9, [SP,#0x1C+var_C]
- __text:00002BD2 01 F0 0A EA BLX _printf
- __text:00002BD6 05 B0 ADD SP, SP, #0x14
- __text:00002BD8 80 BD POP {R7,PC}
复制代码
几乎和前面的例子相同,除了thumb-instructions在这里被替代使用了 5.5 by the way值得一提的是,这些x86,x64,fastcall和ARM传递参数的不同表现了CPU并不在意函数参数是怎样传递的,同样也假想编译器可能用特殊的结构传送参数而一点也不是通过栈。
|