Crash Dump Analysis Patterns (Part 50)
Beginner users of WinDbg sometimes confuse the first 3 parameters (or 4 for x64) displayed by kb or kv commands with real function parameters:
0:000> kbnL
# ChildEBP RetAddr Args to Child
00 002df5f4 0041167b 002df97c 00000000 7efdf000 ntdll!DbgBreakPoint
01 002df6d4 004115c9 00000000 40000000 00000001 CallingConventions!A::thiscallFunction+0×2b
02 002df97c 004114f9 00000001 40001000 00000002 CallingConventions!fastcallFunction+0×69
03 002dfbf8 0041142b 00000000 40000000 00000001 CallingConventions!cdeclFunction+0×59
04 002dfe7c 004116e8 00000000 40000000 00000001 CallingConventions!stdcallFunction+0×5b
05 002dff68 00411c76 00000001 005a2820 005a28c8 CallingConventions!wmain+0×38
06 002dffb8 00411abd 002dfff0 7d4e7d2a 00000000 CallingConventions!__tmainCRTStartup+0×1a6
07 002dffc0 7d4e7d2a 00000000 00000000 7efdf000 CallingConventions!wmainCRTStartup+0xd
08 002dfff0 00000000 00411082 00000000 000000c8 kernel32!BaseProcessStart+0×28
The calling sequence for it is:
stdcallFunction(0, 0x40000000, 1, 0x40001000, 2, 0x40002000) ->
cdeclFunction(0, 0x40000000, 1, 0x40001000, 2, 0x40002000) ->
fastcallFunction(0, 0x40000000, 1, 0x40001000, 2, 0x40002000) ->
A::thiscallFunction(0, 0x40000000, 1, 0x40001000, 2, 0x40002000)
and we see that only in the case of fastcall calling convention we have discrepancy due to the fact that the first 2 parameters are passed not via stack but through ECX and EDX:
0:000> ub 004114f9
CallingConventions!cdeclFunction+0x45
004114e5 push ecx
004114e6 mov edx,dword ptr [ebp+14h]
004114e9 push edx
004114ea mov eax,dword ptr [ebp+10h]
004114ed push eax
004114ee mov edx,dword ptr [ebp+0Ch]
004114f1 mov ecx,dword ptr [ebp+8]
004114f4 call CallingConventions!ILT+475(?fastcallFunctionYIXHHHHHHZ) (004111e0)
However if we have full symbols we can see all parameters:
0:000> .frame 2
02 002df97c 004114f9 CallingConventions!fastcallFunction+0x69
0:000> dv /i /V
prv param 002df974 @ebp-0x08 a = 0
prv param 002df968 @ebp-0x14 b = 1073741824
prv param 002df984 @ebp+0x08 c = 1
prv param 002df988 @ebp+0x0c d = 1073745920
prv param 002df98c @ebp+0x10 e = 2
prv param 002df990 @ebp+0x14 f = 1073750016
prv local 002df7c7 @ebp-0x1b5 obj = class A
prv local 002df7d0 @ebp-0x1ac dummy = int [100]
How does dv command know about values in ECX and EDX which were definitely overwritten by later code? This is because the called function prolog saved them as local variables which you can notice as negative offsets for EBP register in dv output above:
0:000> uf CallingConventions!fastcallFunction
CallingConventions!fastcallFunction
32 00411560 push ebp
32 00411561 mov ebp,esp
32 00411563 sub esp,27Ch
32 00411569 push ebx
32 0041156a push esi
32 0041156b push edi
32 0041156c push ecx
32 0041156d lea edi,[ebp-27Ch]
32 00411573 mov ecx,9Fh
32 00411578 mov eax,0CCCCCCCCh
32 0041157d rep stos dword ptr es:[edi]
32 0041157f pop ecx
32 00411580 mov dword ptr [ebp-14h],edx
32 00411583 mov dword ptr [ebp-8],ecx
…
…
…
I call this pattern as False Function Parameters where double checks and knowledge of calling conventions are required. Sometimes this pattern is a consequence of another pattern that I previously called Optimized Code.
x64 stack traces don’t show any discrepancies except the fact that thiscall function parameters are shifted to the right:
0:000> kbL
RetAddr : Args to Child : Call Site
00000001`40001397 : cccccccc`cccccccc cccccccc`cccccccc cccccccc`cccccccc cccccccc`cccccccc : ntdll!DbgBreakPoint
00000001`40001233 : 00000000`0012fa94 cccccccc`00000000 cccccccc`40000000 cccccccc`00000001 : CallingConventions!A::thiscallFunction+0×37
00000001`40001177 : cccccccc`00000000 cccccccc`40000000 cccccccc`00000001 cccccccc`40001000 : CallingConventions!fastcallFunction+0×93
00000001`400010c7 : cccccccc`00000000 cccccccc`40000000 cccccccc`00000001 cccccccc`40001000 : CallingConventions!cdeclFunction+0×87
00000001`400012ae : cccccccc`00000000 cccccccc`40000000 cccccccc`00000001 cccccccc`40001000 : CallingConventions!stdcallFunction+0×87
00000001`400018ec : 00000001`00000001 00000000`00481a80 00000000`00000000 00000001`400026ee : CallingConventions!wmain+0×4e
00000001`4000173e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : CallingConventions!__tmainCRTStartup+0×19c
00000000`77d5964c : 00000000`77d59620 00000000`00000000 00000000`00000000 00000000`0012ffa8 : CallingConventions!wmainCRTStartup+0xe
00000000`00000000 : 00000001`40001730 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseProcessStart+0×29
How this can happen if the standard x64 calling convention passes the first 4 parameters via ECX, EDX, R8 and R9? This is because the called function prolog saved them on stack (this might not be true in the case of optimized code):
0:000> uf CallingConventions!fastcallFunction
CallingConventions!fastcallFunction
32 00000001`400011a0 44894c2420 mov dword ptr [rsp+20h],r9d
32 00000001`400011a5 4489442418 mov dword ptr [rsp+18h],r8d
32 00000001`400011aa 89542410 mov dword ptr [rsp+10h],edx
32 00000001`400011ae 894c2408 mov dword ptr [rsp+8],ecx
...
...
...
A::thiscallFunction function passes this pointer via ECX too and this explains the right shift of parameters.
Here is the C++ code I used for experimentation:
#include "stdafx.h"
#include <windows.h>
void __stdcall stdcallFunction (int, int, int, int, int, int);
void __cdecl cdeclFunction (int, int, int, int, int, int);
void __fastcall fastcallFunction (int, int, int, int, int, int);
class A
{
public:
void thiscallFunction (int, int, int, int, int, int) { DebugBreak(); };
};
void __stdcall stdcallFunction (int a, int b, int c, int d, int e, int f)
{
int dummy[100] = {0};
cdeclFunction (a, b, c, d, e, f);
}
void __cdecl cdeclFunction (int a, int b, int c, int d, int e, int f)
{
int dummy[100] = {0};
fastcallFunction (a, b, c, d, e, f);
}
void __fastcall fastcallFunction (int a, int b, int c, int d, int e, int f)
{
int dummy[100] = {0};
A obj;
obj.thiscallFunction (a, b, c, d, e, f);
}
int _tmain(int argc, _TCHAR* argv[])
{
stdcallFunction (0, 0x40000000, 1, 0x40001000, 2, 0x40002000);
return 0;
}
- Dmitry Vostokov @ DumpAnalysis.org -
October 21st, 2010 at 4:55 pm
[…] Debugging Experts Magazine Online Today we introduce an icon for False Function Parameters pattern: […]