# Chapter 5 printf() 與參數處理
現在讓我們擴展"hello, world"(2)中的示例,將其中main()函數中printf的部分替換成這樣
```
#!cpp
#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編譯后可以看到:
```
#!bash
$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`"指令合并成一個。
```
#!bash
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, 10 見圖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看一下:
```
#!bash
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信息插入可執行文件中
```
#!bash
$ gcc 1.c -g -o 1
```
反編譯:
```
#!bash
$ 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()處設置斷點
```
#!bash
(gdb) b printf
Breakpoint 1 at 0x80482f0
```
Run 這里沒有printf()函數的源碼,所以GDB沒法顯示出源碼,但是卻可以這樣做
```
#!bash
(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組棧中的元組數據,左邊是棧中的地址
```
#!bash
(gdb) x/10w $esp
0xbffff11c: 0x0804844a 0x080484f0 0x00000001 0x00000002
0xbffff12c: 0x00000003 0x08048460 0x00000000 0x00000000
0xbffff13c: 0xb7e29905 0x00000001
```
最開始的是返回地址(0x0804844a),我們可以確定在這里,于是可以反匯編這里的代碼
```
#!bash
(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)是一處格式化字符串
```
#!bash
(gdb) x/s 0x080484f0
0x80484f0: "a=%d; b=%d; c=%d"
```
而其他三個則是printf()函數的參數,另外的可能只是棧中的垃圾數據,但是也可能是其他函數的數據,例如它們的本地變量。這里可以忽略。 執行 finish ,表示執行到函數結束。在這里是執行到printf()完。
```
#!bash
(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:
```
#!bash
(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>
...
```
反匯編當前的指令
```
#!bash
(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:
```
#!bash
(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顯示了結束大括號,代表著這里是函數結束部分。
```
#!bash
(gdb) step
7 };
```
在執行完MOV EAX, 0后我們可以看到EAX就已經變為0了。
```
#!bash
(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 變量)
```
#!cpp
#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指令。用來準備棧數據,所以值都是直接寫入棧中
```
#!bash
$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中嘗試這個例子。
```
#!bash
$ gcc -g 2.c -o 2
```
反編譯:
```
#!bash
$ 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()函數地址
```
#!bash
(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 檢查格式化字符串
```
#!bash
(gdb) x/s $rdi
0x400628: "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
"
```
用 x/g命令顯示棧內容
```
#!bash
(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()函數。
```
#!bash
(gdb) set disassembly-flavor intel
(gdb) disas 0x0000000000400576
Dump of assembler code for function main:
0x000000000040052d <+0>: push rbp
0x000000000040052e <+1>: mov rbp,rsp
0x0000000000400531 <+4>: sub rsp,0x20
0x0000000000400535 <+8>: mov DWORD PTR [rsp+0x10],0x8
0x000000000040053d <+16>: mov DWORD PTR [rsp+0x8],0x7
0x0000000000400545 <+24>: mov DWORD PTR [rsp],0x6
0x000000000040054c <+31>: mov r9d,0x5
0x0000000000400552 <+37>: mov r8d,0x4
0x0000000000400558 <+43>: mov ecx,0x3
0x000000000040055d <+48>: mov edx,0x2
0x0000000000400562 <+53>: mov esi,0x1
0x0000000000400567 <+58>: mov edi,0x400628
0x000000000040056c <+63>: mov eax,0x0
0x0000000000400571 <+68>: call 0x400410 <printf@plt>
0x0000000000400576 <+73>: mov eax,0x0
0x000000000040057b <+78>: leave
0x000000000040057c <+79>: ret
End of assembler dump.
```
執行完printf()后,就會清零EAX,然后發現EAX早已為0,RIP現在則指向LEAVE指令。
```
#!bash
(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環境)
```
#!bash
.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環境)
```
#!bash
.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
```
#!bash
.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個參數的那個例子
```
#!bash
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);
};
```
### 5.4.1 Optimizing Keil: ARM mode
```
#!bash
.text:00000028 printf_main2
.text:00000028
.text:00000028 var_18 = -0x18
.text:00000028 var_14 = -0x14
.text:00000028 var_4 = -4
.text:00000028
.text:00000028 04 E0 2D E5 STR LR, [SP,#var_4]!
.text:0000002C 14 D0 4D E2 SUB SP, SP, #0x14
.text:00000030 08 30 A0 E3 MOV R3, #8
.text:00000034 07 20 A0 E3 MOV R2, #7
.text:00000038 06 10 A0 E3 MOV R1, #6
.text:0000003C 05 00 A0 E3 MOV R0, #5
.text:00000040 04 C0 8D E2 ADD R12, SP, #0x18+var_14
.text:00000044 0F 00 8C E8 STMIA R12, {R0-R3}
.text:00000048 04 00 A0 E3 MOV R0, #4
.text:0000004C 00 00 8D E5 STR R0, [SP,#0x18+var_18]
.text:00000050 03 30 A0 E3 MOV R3, #3
.text:00000054 02 20 A0 E3 MOV R2, #2
.text:00000058 01 10 A0 E3 MOV R1, #1
.text:0000005C 6E 0F 8F E2 ADR R0, aADBDCDDDEDFDGD ; "a=%d; b=%d; c=%d; d=%d;
e=%d; f=%d; g=%"...
.text:00000060 BC 18 00 EB BL __2printf
.text:00000064 14 D0 8D E2 ADD SP, SP, #0x14
.text:00000068 04 F0 9D E4 LDR PC, [SP+4+var_4],#4
```
這些代碼可以分成幾個部分:
#### 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() 調用
#### Function epilogue:
“`ADD SP, SP, #0x14`”指令將SP指針返回到之前的指針處,因此清除了棧,當然,棧中之前寫入的數據還在那,但是當后來的函數被調用時那里則會被重寫。 “`LDR PC, [SP+4+var_4],#4`"指令將LR中儲存的值載入到PC指針,因此函數結束。
### 5.4.2 Optimizing Keil: thumb mode
```
#!bash
.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
```
#!bash
__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
```
#!bash
__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并不在意函數參數是怎樣傳遞的,同樣也假想編譯器可能用特殊的結構傳送參數而一點也不是通過棧。
- 第一章 CPU簡介
- 第二章 Hello,world!
- 第三章? 函數開始和結束
- 第四章 棧
- Chapter 5 printf() 與參數處理
- Chapter 6 scanf()
- CHAPER7 訪問傳遞參數
- Chapter 8 一個或者多個字的返回值
- Chapter 9 指針
- Chapter 10 條件跳轉
- 第11章 選擇結構switch()/case/default
- 第12章 循環結構
- 第13章 strlen()
- Chapter 14 Division by 9
- chapter 15 用FPU工作
- Chapter 16 數組
- Chapter 17 位域
- 第18章 結構體
- 19章 聯合體
- 第二十章 函數指針
- 第21章 在32位環境中的64位值
- 第二十二章 SIMD
- 23章 64位化
- 24章 使用x64下的SIMD來處理浮點數
- 25章 溫度轉換
- 26章 C99的限制
- 27章 內聯函數
- 第28章 得到不正確反匯編結果
- 第29章 花指令
- 第30章 16位Windows
- 第31章 類
- 三十二 ostream