<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 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同時也在改變,因為它是指向棧的 棧中的數據又在哪?那么看一下調試器右下方的窗口: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2db79fc.png) 圖 5.1 然后我們可以看到有三列,棧的地址,元組數據,以及一些OllyDbg的注釋,OllyDbg可以識別像printf()這樣的字符串,以及后面的三個值。 右擊選中字符串,然后點擊”follow in dump”,然后字符串就會出現在左側顯示內存數據的地方,這些內存的數據可以被編輯。我們可以修改這些字符串,之后這個例子的結果就會變的不同,現在可能并不是很實用。但是作為練習卻非常好,可以體會每部分是如何工作的。 再按一次F8(step over) 然后我們就可以看到輸出 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2dcca9c.png) 圖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或者別的數據填充這些值。 因為在棧指針寄存器之上的數據都是無用的。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2ddf3d4.png) 圖5.3 OllyDbg:main()初始處 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2e060c7.png) 圖5.4 OllyDbg:printf()執行時 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2e25781.png) 圖5.5 Ollydbg:printf()執行后 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec2e45f18.png) 圖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并不在意函數參數是怎樣傳遞的,同樣也假想編譯器可能用特殊的結構傳送參數而一點也不是通過棧。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看