20.1 MSVC

MSVC2010 /Ox选项编译:

Listing 20.1: Optimizing MSVC 2010: /Ox /GS- /MD

  1. __a$ = 8 ; size = 4
  2. __b$ = 12 ; size = 4
  3. _comp PROC
  4. mov eax, DWORD PTR __a$[esp-4]
  5. mov ecx, DWORD PTR __b$[esp-4]
  6. mov eax, DWORD PTR [eax]
  7. mov ecx, DWORD PTR [ecx]
  8. cmp eax, ecx
  9. jne SHORT $LN4@comp
  10. xor eax, eax
  11. ret 0
  12. $LN4@comp:
  13. xor edx, edx
  14. cmp eax, ecx
  15. setge dl
  16. lea eax, DWORD PTR [edx+edx-1]
  17. ret 0
  18. _comp ENDP
  19. _numbers$ = -40 ; size = 40
  20. _argc$ = 8 ; size = 4
  21. _argv$ = 12 ; size = 4
  22. _main PROC
  23. sub esp, 40 ; 00000028H
  24. push esi
  25. push OFFSET _comp
  26. push 4
  27. lea eax, DWORD PTR _numbers$[esp+52]
  28. push 10 ; 0000000aH
  29. push eax
  30. mov DWORD PTR _numbers$[esp+60], 1892 ; 00000764H
  31. mov DWORD PTR _numbers$[esp+64], 45 ; 0000002dH
  32. mov DWORD PTR _numbers$[esp+68], 200 ; 000000c8H
  33. mov DWORD PTR _numbers$[esp+72], -98 ; ffffff9eH
  34. mov DWORD PTR _numbers$[esp+76], 4087 ; 00000ff7H
  35. mov DWORD PTR _numbers$[esp+80], 5
  36. mov DWORD PTR _numbers$[esp+84], -12345 ; ffffcfc7H
  37. mov DWORD PTR _numbers$[esp+88], 1087 ; 0000043fH
  38. mov DWORD PTR _numbers$[esp+92], 88 ; 00000058H
  39. mov DWORD PTR _numbers$[esp+96], -100000 ; fffe7960H
  40. call _qsort
  41. add esp, 16 ; 00000010H
  42. ...

第四个参数传递了一个地址标签_comp,指向了comp()函数。

我们来看MSVCR80.DLL(包含C标准库函数的MSVC DLL模块)里该函数的内部调用:

Listing 20.2: MSVCR80.DLL

  1. .text:7816CBF0 ; void __cdecl qsort(void *, unsigned int, unsigned int, int (__cdecl *)(const void *, const void *))
  2. .text:7816CBF0 public _qsort
  3. .text:7816CBF0 _qsort proc near
  4. .text:7816CBF0
  5. .text:7816CBF0 lo = dword ptr -104h
  6. .text:7816CBF0 hi = dword ptr -100h
  7. .text:7816CBF0 var_FC = dword ptr -0FCh
  8. .text:7816CBF0 stkptr = dword ptr -0F8h
  9. .text:7816CBF0 lostk = dword ptr -0F4h
  10. .text:7816CBF0 histk = dword ptr -7Ch
  11. .text:7816CBF0 base = dword ptr 4
  12. .text:7816CBF0 num = dword ptr 8
  13. .text:7816CBF0 width = dword ptr 0Ch
  14. .text:7816CBF0 comp = dword ptr 10h
  15. .text:7816CBF0
  16. .text:7816CBF0 sub esp, 100h
  17. ....
  18. .text:7816CCE0 loc_7816CCE0: ; CODE XREF: _qsort+B1
  19. .text:7816CCE0 shr eax, 1
  20. .text:7816CCE2 imul eax, ebp
  21. .text:7816CCE5 add eax, ebx
  22. .text:7816CCE7 mov edi, eax
  23. .text:7816CCE9 push edi
  24. .text:7816CCEA push ebx
  25. .text:7816CCEB call [esp+118h+comp]
  26. .text:7816CCF2 add esp, 8
  27. .text:7816CCF5 test eax, eax
  28. .text:7816CCF7 jle short loc_7816CD04

第四个参数comp传递函数指针,comp()有两个参数,参数被检测后才执行。

这种使用函数指针的方式有一定的风险。第一种原因是如果你用qsort()调用了错误的函数指针,可能造成程序崩溃,并且这个错误很难被发现。

第二个原因是即使回调函数类型完全正确,使用错误的参数调用函数可能会导致更严重的问题。进程崩溃不是最大的问题,最大的问题是崩溃的原因—编译器很难发现这种潜在的问题。

20.1.1 MSVC + OllyDbg

我们在OD中加载我们的例子,并在comp()函数下断点。

我们可以看到第一次comp()调用时是如何比较的:fig.20.1.OD代码窗口显示了比较的值。我们还可以看到SP指向的RA地址在qsort()函数空间里(实际上位于MSVCR100.DLL)。

按F8直到函数返回到qsort()函数:fig20.2.这里比较函数被调用。

第二次调用comp()—当前比较的值不相同:fig203。

20.1 MSVC - 图1

Figure 20.1: OllyDbg: first call of comp()

20.1 MSVC - 图2

Figure 20.2: OllyDbg: the code in qsort() right a_er comp() call

20.1 MSVC - 图3

Figure 20.3: OllyDbg: second call of comp()

20.1.2 MSVC + tracer

我们来看成对比较,来对10个数字进行排序:1892, 45, 200, -98, 4087, 5, -12345, 1087, 88,-100000.

我们找到comp()函数中的CMP指令地址,并在其地址0x0040100C上设置断点。

tracer.exe -l:17_1.exe bpx=17_1.exe!0x0040100C

断点中断是的寄存器地址:

  1. PID=4336|New process 17_1.exe
  2. (0) 17_1.exe!0x40100c
  3. EAX=0x00000764 EBX=0x0051f7c8 ECX=0x00000005 EDX=0x00000000
  4. ESI=0x0051f7d8 EDI=0x0051f7b4 EBP=0x0051f794 ESP=0x0051f67c
  5. EIP=0x0028100c
  6. FLAGS=IF
  7. (0) 17_1.exe!0x40100c
  8. EAX=0x00000005 EBX=0x0051f7c8 ECX=0xfffe7960 EDX=0x00000000
  9. ESI=0x0051f7d8 EDI=0x0051f7b4 EBP=0x0051f794 ESP=0x0051f67c
  10. EIP=0x0028100c
  11. FLAGS=PF ZF IF
  12. (0) 17_1.exe!0x40100c
  13. EAX=0x00000764 EBX=0x0051f7c8 ECX=0x00000005 EDX=0x00000000
  14. ESI=0x0051f7d8 EDI=0x0051f7b4 EBP=0x0051f794 ESP=0x0051f67c
  15. EIP=0x0028100c
  16. FLAGS=CF PF ZF IF
  17. ...

过滤EAX和ECX得到:

  1. EAX=0x00000764 ECX=0x00000005
  2. EAX=0x00000005 ECX=0xfffe7960
  3. EAX=0x00000764 ECX=0x00000005
  4. EAX=0x0000002d ECX=0x00000005
  5. EAX=0x00000058 ECX=0x00000005
  6. EAX=0x0000043f ECX=0x00000005
  7. EAX=0xffffcfc7 ECX=0x00000005
  8. EAX=0x000000c8 ECX=0x00000005
  9. EAX=0xffffff9e ECX=0x00000005
  10. EAX=0x00000ff7 ECX=0x00000005
  11. EAX=0x00000ff7 ECX=0x00000005
  12. EAX=0xffffff9e ECX=0x00000005
  13. EAX=0xffffff9e ECX=0x00000005
  14. EAX=0xffffcfc7 ECX=0xfffe7960
  15. EAX=0x00000005 ECX=0xffffcfc7
  16. EAX=0xffffff9e ECX=0x00000005
  17. EAX=0xffffcfc7 ECX=0xfffe7960
  18. EAX=0xffffff9e ECX=0xffffcfc7
  19. EAX=0xffffcfc7 ECX=0xfffe7960
  20. EAX=0x000000c8 ECX=0x00000ff7
  21. EAX=0x0000002d ECX=0x00000ff7
  22. EAX=0x0000043f ECX=0x00000ff7
  23. EAX=0x00000058 ECX=0x00000ff7
  24. EAX=0x00000764 ECX=0x00000ff7
  25. EAX=0x000000c8 ECX=0x00000764
  26. EAX=0x0000002d ECX=0x00000764
  27. EAX=0x0000043f ECX=0x00000764
  28. EAX=0x00000058 ECX=0x00000764
  29. EAX=0x000000c8 ECX=0x00000058
  30. EAX=0x0000002d ECX=0x000000c8
  31. EAX=0x0000043f ECX=0x000000c8
  32. EAX=0x000000c8 ECX=0x00000058
  33. EAX=0x0000002d ECX=0x000000c8
  34. EAX=0x0000002d ECX=0x00000058

有34对。因此快速排序算法对10个数字排序需要34此对比操作。

20.1.3 MSVC + tracer (code coverage)

我们使用跟踪特性收集寄存器的值并在IDA中查看。

跟踪comp()函数所有指令:

tracer.exe -l:17_1.exe bpf=17_1.exe!0x00401000,trace:cc

IDA加载.idc脚本:fig20.4。

IDA给出了函数名字(PtFuncCompare)—IDA认为该函数指针被传递给qsort()。

可以看到a和b指向数组不同的位置,并且相差4-32bit的字节数。

0x401010 和 0x401012之间的指令从没有被执行:事实上comp()从来不返回0,因为没有相等的元素。

20.1 MSVC - 图4

Figure 20.4: tracer and IDA. N.B.: some values are cutted at right