Linking against a Win32-DLL

Jascha Wetzel firstname at mainia.de
Mon Jul 9 13:05:30 PDT 2007


BLS wrote:
> Hallo Jascha,
> I wonder how to setup Ddbg to figure out this information. Not nessesary 
> to say that a step by step (4 dummies) guideline is more than welcome !
> MANY Thanks in advance.
> Bjoern

here's log from the ddbg session.
i've added comments starting with a #

C:\>ddbg wintabtest.exe
Ddbg 0.10 beta - D Debugger
Copyright (c) 2007 Jascha Wetzel
see http://ddbg.mainia.de/doc.html for documentation

Loading symbols from wintabtest.exe
# use line 1 as a shortcut to "the first source line in the file"
->bp win:1
Breakpoint set: wintabtest.d:8 0x402010 all threads
->r
ntdll.dll loaded at 0x7c900000
KERNEL32.dll loaded at 0x7c800000
Wintab32.dll loaded at 0x10000000
USER32.dll loaded at 0x7e410000
GDI32.dll loaded at 0x77f10000
ADVAPI32.dll loaded at 0x77dd0000
RPCRT4.dll loaded at 0x77e70000
Breakpoint 0 hit at wintabtest.d:8 0x402010 thread(5112)
void main(char[][] args)
->da
wintabtest.d:8 void main(char[][] args)
00402010: 55                      push ebp
00402011: 8bec                    mov ebp, esp
wintabtest.d:12         writefln("Getting WTInfo...");
00402013: ff359c504100            push dword [0x41509c]
00402019: ff3598504100            push dword [0x415098]
0040201f: b800514100              mov eax, 0x415100
00402024: 50                      push eax
00402025: e8fe030000              call 0x402428 std.stdio.writefln
wintabtest.d:13         WTInfoA(0, 0, null);
0040202a: 6a00                    push 0x0
0040202c: 6a00                    push 0x0
0040202e: 6a00                    push 0x0
00402030: e8d3250100              call 0x414608 _WTInfoA
wintabtest.d:14         writefln("Received WTInfo");
00402035: ff35b4504100            push dword [0x4150b4]
0040203b: ff35b0504100            push dword [0x4150b0]
00402041: b900514100              mov ecx, 0x415100
00402046: 51                      push ecx
00402047: e8dc030000              call 0x402428 std.stdio.writefln
0040204c: 31c0                    xor eax, eax
0040204e: 83c424                  add esp, 0x24
# here, DMD cleans up the stack for all 3 calls at once
# note that 0x24 = 9*4 accounts for all the 9 PUSH instructions above
# this means also cleaning up for the WTInfoA call
wintabtest.obj
00402051: 5d                      pop ebp
00402052: c3                      ret
->ov
wintabtest.d:12 0x402013 thread(5112)
         writefln("Getting WTInfo...");
->
Getting WTInfo...
wintabtest.d:13 0x40202a thread(5112)
         WTInfoA(0, 0, null);
# now we're just before the 3 PUSHes for the WTInfo parameters
# let's check out the ESP value
->dr
EAX = 00000000  EBX = 008903c4  ECX = 0012ff74  EDX = 0000000b
EDI = 00000001  ESI = 00000001  EBP = 0012ff30  ESP = 0012ff24
EIP = 0040202a  EFL = 00000202
  CS = 0000001b   DS = 00000023   ES = 00000023   FS = 0000003b
  GS = 00000000   SS = 00000023
# and step over the PUSHes and the call
->ov
wintabtest.d:14 0x402035 thread(5112)
         writefln("Received WTInfo");
# check ESP again
->dr
EAX = 00001157  EBX = 008903c4  ECX = 7ec7fb9c  EDX = 10019e38
EDI = 00000001  ESI = 00000001  EBP = 0012ff30  ESP = 0012ff24
EIP = 00402035  EFL = 00000296
  CS = 0000001b   DS = 00000023   ES = 00000023   FS = 0000003b
  GS = 00000000   SS = 00000023
# ESP is still 0x12ff24, therefore WTInfo cleaned up the stack by itself

We have two calls to variadic D functions (writefln), which don't clean 
up the stack (see D's ABI docs). Each call has 3 dword PUSHes, which 
gives us 24 = 0x18 bytes. So the "add esp, 0x24" moves the stack pointer 
12 bytes to much.

Now consider the other version of Marc's snippet, that saves the return 
value in a local variable:

wintabtest.d:8 void main(char[][] args)
00402010: c8040000                enter 0x4, 0x0
wintabtest.d:20         writefln("Getting WTInfo...");
00402014: ff359c504100            push dword [0x41509c]
0040201a: ff3598504100            push dword [0x415098]
00402020: b800514100              mov eax, 0x415100
00402025: 50                      push eax
00402026: e801040000              call 0x40242c std.stdio.writefln
wintabtest.d:21         int value = WTInfoA(0, 0, null);
0040202b: 6a00                    push 0x0
0040202d: 6a00                    push 0x0
0040202f: 6a00                    push 0x0
00402031: e8d2250100              call 0x414608 _WTInfoA
00402036: 8945fc                  mov [ebp-0x4], eax
wintabtest.d:22         writefln("Received WTInfo");
00402039: ff35b4504100            push dword [0x4150b4]
0040203f: ff35b0504100            push dword [0x4150b0]
00402045: b900514100              mov ecx, 0x415100
0040204a: 51                      push ecx
0040204b: e8dc030000              call 0x40242c std.stdio.writefln
00402050: 31c0                    xor eax, eax
00402052: 83c424                  add esp, 0x24
wintabtest.obj
00402055: c9                      leave
00402056: c3                      ret

The ENTER at the beginning of the function saves the current ESP to EBP 
(among other things). The "add esp, 0x24" still corrupts the stack 
pointer, but the LEAVE instruction restores the saved value from EBP, so 
it doesn't hurt.

Now that we know that WTInfo cleans up the stack, we can narrow down the 
possible calling conventions to __stdcall, __fastcall and PASCAL.
Since we were able to link against the library using C convention (after 
adding leading underscores to the function names using the /s switch on 
implib), that eliminates __stdcall and __fastcall, because they require 
more decoration than the leading underscore.
So we change the declaration to extern(Pascal) and observe OPTLINK 
complaining about a missing function "WTINFOA". Apparently DMD's idea of 
function names in Pascal convention is all uppercase, while wintab32.dll 
has C-style function names (which you can also see by looking at the DLL 
file in a text editor).


All this is definitely under-the-hood-type debugging.
Just because it came up in other threads a couple of times: this is not 
the only kind of stuff you can do with a debugger :)
You don't need to know assembly language to make use of a debugger. You 
can simply run it to have line numbers for access violations, extensive 
stack traces or variable contents without having to add printf's and 
recompile.
Debuggers are friendly little creatures, they don't bite :)



More information about the Digitalmars-d mailing list