7.1 X86

7.1.1 MSVC

如下为相应的反汇编代码(MSVC 2010 Express)

Listing 7.2 MSVC 2010 Express

  1. _TEXT SEGMENT
  2. _a$ = 8 ; size = 4
  3. _b$ = 12 ; size = 4
  4. _c$ = 16 ; size = 4
  5. _f PROC
  6. push ebp
  7. mov ebp, esp
  8. mov eax, DWORD PTR _a$[ebp]
  9. imul eax, DWORD PTR _b$[ebp]
  10. add eax, DWORD PTR _c$[ebp]
  11. pop ebp
  12. ret 0
  13. _f ENDP
  14. _main PROC
  15. push ebp
  16. mov ebp, esp
  17. push 3 ; 3rd argument
  18. push 2 ; 2nd argument
  19. push 1 ; 1st argument
  20. call _f
  21. add esp, 12
  22. push eax
  23. push OFFSET $SG2463 ; ’%d’, 0aH, 00H
  24. call _printf
  25. add esp, 8
  26. ; return 0
  27. xor eax, eax
  28. pop ebp
  29. ret 0
  30. _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字节宽度)。

7.1 X86 - 图1

Figure 7.1: OllyDbg: 函数f()内部

7.1.3 GCC

使用GCC4.4.1编译后在IDA中查看

Listing 7.3: GCC 4.4.1

  1. public f
  2. f proc near
  3. arg_0 = dword ptr 8
  4. arg_4 = dword ptr 0Ch
  5. arg_8 = dword ptr 10h
  6. push ebp
  7. mov ebp, esp
  8. mov eax, [ebp+arg_0] ; 1st argument
  9. imul eax, [ebp+arg_4] ; 2nd argument
  10. add eax, [ebp+arg_8] ; 3rd argument
  11. pop ebp
  12. retn
  13. f endp
  14. public main
  15. main proc near
  16. var_10 = dword ptr -10h
  17. var_C = dword ptr -0Ch
  18. var_8 = dword ptr -8
  19. push ebp
  20. mov ebp, esp
  21. and esp, 0FFFFFFF0h
  22. sub esp, 10h
  23. mov [esp+10h+var_8], 3 ; 3rd argument
  24. mov [esp+10h+var_C], 2 ; 2nd argument
  25. mov [esp+10h+var_10], 1 ; 1st argument
  26. call f
  27. mov edx, offset aD ; "%d
  28. "
  29. mov [esp+10h+var_C], eax
  30. mov [esp+10h+var_10], edx
  31. call _printf
  32. mov eax, 0
  33. leave
  34. retn
  35. main endp

几乎相同的结果。

执行两个函数后栈指针ESP并没有显示恢复,因为倒数第二个指令LEAVE(B.6.2)会自动恢复栈指针。