<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>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # CHAPER7 訪問傳遞參數 現在我們來看函數調用者通過棧把參數傳遞到被調用函數。被調用函數是如何訪問這些參數呢? ``` #!cpp #include <stdio.h> int f (int a, int b, int c) { return a*b+c; }; int main() { printf ("%d ", f(1, 2, 3)); return 0; }; ``` ## 7.1 X86 ### 7.1.1 MSVC 如下為相應的反匯編代碼(MSVC 2010 Express) Listing 7.2 MSVC 2010 Express ``` #!bash _TEXT SEGMENT _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 _c$ = 16 ; size = 4 _f PROC push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] imul eax, DWORD PTR _b$[ebp] add eax, DWORD PTR _c$[ebp] pop ebp ret 0 _f ENDP _main PROC push ebp mov ebp, esp push 3 ; 3rd argument push 2 ; 2nd argument push 1 ; 1st argument call _f add esp, 12 push eax push OFFSET $SG2463 ; ’%d’, 0aH, 00H call _printf add esp, 8 ; return 0 xor eax, eax pop ebp ret 0 _main ENDP ``` 我們可以看到函數main()中3個數字被圧棧,然后函數f(int, int, int)被調用。函數f()內部訪問參數時使用了像_ a$=8 的宏,同樣,在函數內部訪問局部變量也使用了類似的形式,不同的是訪問參數時偏移值(為正值)。因此EBP寄存器的值加上宏_a$的值指向壓棧參數。 `_a$[ebp]`的值被存儲在寄存器eax中,IMUL指令執行后,eax的值為eax與`_b$[ebp]`的乘積,然后eax與`_c$[ebp]`的值相加并將和放入eax寄存器中,之后返回eax的值。返回值作為printf()的參數。 ### 7.1.2 MSVC+OllyDbg 我們在OllyDbg中觀察,跟蹤到函數f()使用第一個參數的位置,可以看到寄存器EBP指向棧底,圖中使用紅色箭頭標識。棧幀中第一個被保存的是EBP的值,第二個是返回地址(RA),第三個是參數1,接下來是參數2,以此類推。因此,當我們訪問第一個參數時EBP應該加8(2個32-bit字節寬度)。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec3302c56.png) Figure 7.1: OllyDbg: 函數f()內部 ### 7.1.3 GCC 使用GCC4.4.1編譯后在IDA中查看 Listing 7.3: GCC 4.4.1 ``` #!bash public f f proc near arg_0 = dword ptr 8 arg_4 = dword ptr 0Ch arg_8 = dword ptr 10h push ebp mov ebp, esp mov eax, [ebp+arg_0] ; 1st argument imul eax, [ebp+arg_4] ; 2nd argument add eax, [ebp+arg_8] ; 3rd argument pop ebp retn f endp public main main proc near var_10 = dword ptr -10h var_C = dword ptr -0Ch var_8 = dword ptr -8 push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 10h mov [esp+10h+var_8], 3 ; 3rd argument mov [esp+10h+var_C], 2 ; 2nd argument mov [esp+10h+var_10], 1 ; 1st argument call f mov edx, offset aD ; "%d " mov [esp+10h+var_C], eax mov [esp+10h+var_10], edx call _printf mov eax, 0 leave retn main endp ``` 幾乎相同的結果。 執行兩個函數后棧指針ESP并沒有顯示恢復,因為倒數第二個指令LEAVE(B.6.2)會自動恢復棧指針。 ## 7.2 X64 x86-64架構下有點不同,函數參數(4或6)使用寄存器傳遞,被調用函數通過訪問寄存器來訪問傳遞進來的參數。 ### 7.2.1 MSVC MSVC優化后: Listing 7.4: MSVC 2012 /Ox x64 ``` #!bash $SG2997 DB ’%d’, 0aH, 00H main PROC sub rsp, 40 mov edx, 2 lea r8d, QWORD PTR [rdx+1] ; R8D=3 lea ecx, QWORD PTR [rdx-1] ; ECX=1 call f lea rcx, OFFSET FLAT:$SG2997 ; ’%d’ mov edx, eax call printf xor eax, eax add rsp, 40 ret 0 main ENDP f PROC ; ECX - 1st argument ; EDX - 2nd argument ; R8D - 3rd argument imul ecx, edx lea eax, DWORD PTR [r8+rcx] ret 0 f ENDP ``` 我們可以看到函數f()直接使用寄存器來操作參數,LEA指令用來做加法,編譯器認為使用LEA比使用ADD指令要更快。在mian()中也使用了LEA指令,編譯器認為使用LEA比使用MOV指令效率更高。 我們來看看MSVC沒有優化的情況: Listing 7.5: MSVC 2012 x64 ``` #!bash f proc near ; shadow space: arg_0 = dword ptr 8 arg_8 = dword ptr 10h arg_10 = dword ptr 18h ; ECX - 1st argument ; EDX - 2nd argument ; R8D - 3rd argument mov [rsp+arg_10], r8d mov [rsp+arg_8], edx mov [rsp+arg_0], ecx mov eax, [rsp+arg_0] imul eax, [rsp+arg_8] add eax, [rsp+arg_10] retn f endp main proc near sub rsp, 28h mov r8d, 3 ; 3rd argument mov edx, 2 ; 2nd argument mov ecx, 1 ; 1st argument call f mov edx, eax lea rcx, $SG2931 ; "%d " call printf ; return 0 xor eax, eax add rsp, 28h retn main endp ``` 這里從寄存器傳遞進來的3個參數因為某種情況又被保存到棧里。這就是所謂的“shadow space”2:每個Win64通常(不是必需)會保存所有4個寄存器的值。這樣做由兩個原因:1)為輸入參數分配所有寄存器(即使是4個)太浪費,所以要通過堆棧來訪問;2)每次中斷下來調試器總是能夠定位函數參數3。 調用者負責在棧中分配“shadow space”。 ### 7.2.2 GCC GCC優化后的代碼: Listing 7.6: GCC 4.4.6 -O3 x64 ``` #!bash f: ; EDI - 1st argument ; ESI - 2nd argument ; EDX - 3rd argument imul esi, edi lea eax, [rdx+rsi] ret main: sub rsp, 8 mov edx, 3 mov esi, 2 mov edi, 1 call f mov edi, OFFSET FLAT:.LC0 ; "%d " mov esi, eax xor eax, eax ; number of vector registers passed call printf xor eax, eax add rsp, 8 ret ``` GCC無優化代碼: Listing 7.7: GCC 4.4.6 x64 ``` #!bash f: ; EDI - 1st argument ; ESI - 2nd argument ; EDX - 3rd argument push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov DWORD PTR [rbp-8], esi mov DWORD PTR [rbp-12], edx mov eax, DWORD PTR [rbp-4] imul eax, DWORD PTR [rbp-8] add eax, DWORD PTR [rbp-12] leave ret main: push rbp mov rbp, rsp mov edx, 3 mov esi, 2 mov edi, 1 call f mov edx, eax mov eax, OFFSET FLAT:.LC0 ; "%d " mov esi, edx mov rdi, rax mov eax, 0 ; number of vector registers passed call printf mov eax, 0 leave ret ``` System V *NIX [21]沒有“shadow space”,但被調用者可能會保存參數,這也是造成寄存器短缺的原因。 ### 7.2.3 GCC: uint64_t instead int 我們例子使用的是32位int,寄存器也為32位寄存器(前綴為E-)。 為處理64位數值內部會自動調整為64位寄存器: ``` #!cpp #include <stdio.h> #include <stdint.h> uint64_t f (uint64_t a, uint64_t b, uint64_t c) { return a*b+c; }; int main() { printf ("%lld ", f(0x1122334455667788,0x1111111122222222,0x3333333344444444)); return 0; }; ``` Listing 7.8: GCC 4.4.6 -O3 x64 ``` #!cpp f proc near imul rsi, rdi lea rax, [rdx+rsi] retn f endp main proc near sub rsp, 8 mov rdx, 3333333344444444h ; 3rd argument mov rsi, 1111111122222222h ; 2nd argument mov rdi, 1122334455667788h ; 1st argument call f mov edi, offset format ; "%lld " mov rsi, rax xor eax, eax ; number of vector registers passed call _printf xor eax, eax add rsp, 8 retn main endp ``` 代碼非常相似,只是使用了64位寄存器(前綴為R)。 ## 7.3 ARM ### 7.3.1 未優化的Keil + ARM mode ``` #!bash .text:000000A4 00 30 A0 E1 MOV R3, R0 .text:000000A8 93 21 20 E0 MLA R0, R3, R1, R2 .text:000000AC 1E FF 2F E1 BX LR ... .text:000000B0 main .text:000000B0 10 40 2D E9 STMFD SP!, {R4,LR} .text:000000B4 03 20 A0 E3 MOV R2, #3 .text:000000B8 02 10 A0 E3 MOV R1, #2 .text:000000BC 01 00 A0 E3 MOV R0, #1 .text:000000C0 F7 FF FF EB BL f .text:000000C4 00 40 A0 E1 MOV R4, R0 .text:000000C8 04 10 A0 E1 MOV R1, R4 .text:000000CC 5A 0F 8F E2 ADR R0, aD_0 ; "%d " .text:000000D0 E3 18 00 EB BL __2printf .text:000000D4 00 00 A0 E3 MOV R0, #0 .text:000000D8 10 80 BD E8 LDMFD SP!, {R4,PC} ``` main()函數里調用了另外兩個函數,3個值被傳遞到f(); 正如前面提到的,ARM通常使用前四個寄存器(R0-R4)傳遞前四個值。 f()函數使用了前三個寄存器(R0-R2)作為參數。 MLA (Multiply Accumulate)指令將R3寄存器和R1寄存器的值相乘,然后再將乘積與R2寄存器的值相加將結果存入R0,函數返回R0。 一條指令完成乘法和加法4,如果不包括SIMD新的FMA指令5,通常x86下沒有這樣的指令。 第一條指令MOV R3,R0,看起來冗余是因為該代碼是非優化的。 BX指令返回到LR寄存器存儲的地址,處理器根據狀態模式從Thumb狀態轉換到ARM狀態,或者反之。函數f()可以被ARM代碼或者Thumb代碼調用,如果是Thumb代碼調用BX將返回到調用函數并切換到Thumb模式,或者反之。 ### 7.3.2 Optimizing Keil + ARM mode ``` #!bash .text:00000098 f .text:00000098 91 20 20 E0 MLA R0, R1, R0, R2 .text:0000009C 1E FF 2F E1 BX LR ``` 這里f()編譯時使用完全優化模式(-O3),MOV指令被優化,現在MLA使用所有輸入寄存器并將結果置入R0寄存器。 ### 7.3.3 Optimizing Keil + thumb mode ``` #!bash .text:0000005E 48 43 MULS R0, R1 .text:00000060 80 18 ADDS R0, R0, R2 .text:00000062 70 47 BX LR ``` Thumb模式下沒有MLA指令,編譯器做了兩次間接處理,MULS指令使R0寄存器的值與R1寄存器的值相乘并將結果存入R0。ADDS指令將R0與R2的值相加并將結果存入R0。
                  <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>

                              哎呀哎呀视频在线观看