| This is the core of the Wine debugger. The reverse assember | |
| was stolen from Mach more or less intact. It turns out that there are | |
| two variables that are set differently if you are reverse assembling | |
| 16 bit code, and on the whole it seems to work. | |
| NEWS: | |
| The internal debugger has *tons* more capability than it did before. | |
| I have enclosed some examples that show usage at the end of this file. | |
| New features include: | |
| 1) Ability of debugger to read debug information from wine executable | |
| *and* from Win32 executables. Local variable and line number information is | |
| also read and processed. | |
| 2) The internal debugger is capable of 'stepping' to the next | |
| line number, just like gdb. Examples of the commands are: | |
| step | |
| stepi | |
| si | |
| step 3 | |
| si 5 | |
| next | |
| nexti | |
| cont 4 | |
| finish | |
| All of these should be exactly like how gdb does things. | |
| 3) The internal debugger now has a sense of what source file and line | |
| number a given PC is at. New commands to support this are just like gdb, | |
| and include: | |
| list | |
| dir | |
| show dir | |
| there are a variety of formats of arguments for the list command. All | |
| permutations supported by gdb should also be supported. | |
| 4) The internal debugger knows about datatypes of various objects, | |
| for both Win32 *and* the debugging information in the wine executable itself. | |
| I have enclosed an example of how this works at the end. | |
| 5) There are more ways the 'b' command can be used to set breakpoints. | |
| Examples are: | |
| b *0x8190000 | |
| b 1100 | |
| b Usage | |
| b | |
| I don't think this covers all of the permutations that gdb accepts (this should | |
| be cleaned up someday so that all possibilities are acceptable). | |
| 6) The 'print' and 'x' commands should behave more or less exactly | |
| as they do under gdb. The difference is that the way the data is presented | |
| will be slightly different, but the content should be fundamentally the same. | |
| 7) The internal debugger now supports conditional breakpoints, and | |
| automatic display expressions. An example is at the end of this file. The | |
| syntax and usage should be identical to that of gdb. | |
| 8) Type casts can be made from within the debugger, but they currently | |
| don't work with typedef'ed types. They only work with builtin types and | |
| named structures unions, etc. The problem is that internally we don't always | |
| record the typedefed names of structures, so we have no guarantee that we | |
| would know what each type is. This can be fixed, of course - it just takes | |
| more memory. Note that in some cases, typedefed structures could be cast | |
| using '(struct typedfname)' instead of '(typedfname)'. Technically this | |
| isn't quite correct, but if and when the rest of this stuff gets fixed, | |
| this would need to get corrected too. | |
| NOTES: | |
| If it weren't for the fact that gdb doesn't grok the Win32 debug | |
| information, you could just use gdb. The internal debugger should be able | |
| to read and use debugging information for both Win32 and also for the | |
| Wine executable, making it possible to debug the combination of the two | |
| together as if it were one large (very large) entity. | |
| LIMITATIONS AND DIFFERENCES FROM GDB: | |
| You cannot set a breakpoint by file and line number as you can | |
| with gdb. Adding support for this wouldn't be all that tough, I guess, but | |
| it would be a nuisance. You can set a breakpoint given a function and | |
| line number, however. An example would be 'b main:2993'. It turns out | |
| that the way the internal data structures are arranged it is a whole lot | |
| easier to do things in this way than it would be to try and get the | |
| source:line type of breakpoint working, but it would probably be worth it | |
| to try. | |
| Getting stack traces through Wine itself can be a bit tricky. | |
| This is because by default the thing is built with optimization | |
| enabled, and as a result sometimes functions don't get frames, and | |
| lots of variables are optimized into registers. You can turn off | |
| optimization for a few key source files if it will help you. | |
| Memory consumption is getting to be a real problem. I think 32Mb is | |
| no longer sufficient to debug wine - 48 or 64 is probably a whole lot better. | |
| Unfortunately I cannot shut down X to save memory :-). | |
| ************************************************************************* | |
| EXAMPLES: | |
| Here is an example of how I tracked down a bug in Wine. The program | |
| is something that just maps and dumps the contents of a Win32 executable. | |
| It was dying for some reason. | |
| Note that this example is rather old and does not necessarily use current | |
| syntax ! | |
| Start the first time through. | |
| bash$ ls -l dumpexe.exe | |
| -rw-rw-r-- 1 eric devel 168448 Jan 4 13:51 dumpexe.exe | |
| bash$ ./wine -debug './dumpexe.exe -symbol ./dumpexe.exe' | |
| Warning: invalid dir 'e:\test' in path, deleting it. | |
| Win32 task 'W32SXXXX': Breakpoint 1 at 0x081a3450 | |
| Loading symbols from ELF file ./wine... | |
| Loading symbols from ELF file /usr/X11R6/lib/libXpm.so.4.6... | |
| Loading symbols from ELF file /usr/X11R6/lib/libSM.so.6.0... | |
| Loading symbols from ELF file /usr/X11R6/lib/libICE.so.6.0... | |
| Loading symbols from ELF file /usr/X11R6/lib/libXext.so.6.0... | |
| Loading symbols from ELF file /usr/X11R6/lib/libX11.so.6.0... | |
| Loading symbols from ELF file /lib/libm.so.5.0.5... | |
| Loading symbols from ELF file /lib/libc.so.5.2.18... | |
| Loading symbols from ELF file /lib/ld-linux.so.1... | |
| Loading symbols from Win32 file ./dumpexe.exe... | |
| Stopped on breakpoint 1 at 0x081a3450 (_mainCRTStartup) | |
| In 32 bit mode. | |
| *** Invalid address 0x414c5ff8 (KERNEL32_NULL_THUNK_DATA+0x3930ee6c) | |
| 0x081a3450 (_mainCRTStartup): movl %fs:0,%eax | |
| Wine-dbg>b DumpFile | |
| Breakpoint 2 at 0x081a0078 (DumpFile+0x9 [dumpexe.c:2723]) | |
| Wine-dbg>c | |
| Dump File: ./dumpexe.exe | |
| Stopped on breakpoint 2 at 0x081a0078 (DumpFile+0x9 [dumpexe.c:2723]) | |
| Enter path to file dumpexe.c: ../de | |
| 2723 HANDLE hFile = NULL; | |
| 0x081a0078 (DumpFile+0x9 [dumpexe.c:2723]): movl $0x0,0xfffffff4(%ebp) | |
| Wine-dbg>list | |
| 2723 HANDLE hFile = NULL; | |
| 2724 HANDLE hMap = NULL; | |
| 2725 PSTR lpMap = NULL; | |
| 2726 DWORD dwFileSize = 0; | |
| 2727 DWORD dwFileSizeHigh = 0; | |
| 2728 | |
| 2729 PIMAGE_DOS_HEADER lpImageDOS = NULL; | |
| 2730 PIMAGE_FILE_HEADER lpImageFile = NULL; | |
| 2731 PIMAGE_NT_HEADERS lpImageNT = NULL; | |
| 2732 | |
| 2733 /* | |
| Wine-dbg>n 10 | |
| 2747 dwFileSize = GetFileSize(hFile, &dwFileSizeHigh); | |
| 0x081a00ea (DumpFile+0x7b [dumpexe.c:2747]): leal 0xfffffff0(%ebp),%eax | |
| Wine-dbg>n | |
| 2749 && (GetLastError() != NO_ERROR) ) | |
| 0x081a00fb (DumpFile+0x8c [dumpexe.c:2749]): cmpl $-1,0xffffffe8(%ebp) | |
| Wine-dbg>x/d dwFileSize | |
| x/d dwFileSize | |
| 168448 | |
| Wine-dbg>n | |
| 2758 PAGE_READONLY, 0, 0, (LPSTR) NULL); | |
| 0x081a0124 (DumpFile+0xb5 [dumpexe.c:2758]): pushl $0x0 | |
| Wine-dbg>list 2750 | |
| list 2750 | |
| 2750 { | |
| 2751 Fatal("Cannot get size of file %s", lpFileName); | |
| 2752 } | |
| 2753 | |
| 2754 /* | |
| 2755 * map the file | |
| 2756 */ | |
| 2757 hMap = CreateFileMapping(hFile, (LPSECURITY_ATTRIBUTES) NULL, | |
| 2758 PAGE_READONLY, 0, 0, (LPSTR) NULL); | |
| 2759 if( hMap == NULL ) | |
| 2760 { | |
| Wine-dbg>n | |
| 2759 if( hMap == NULL ) | |
| 0x081a013b (DumpFile+0xcc [dumpexe.c:2759]): cmpl $0,0xfffffffc(%ebp) | |
| Wine-dbg>x hMap | |
| 08e48c30 | |
| Wine-dbg>n | |
| 2767 lpMap = (LPSTR) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); | |
| 0x081a0156 (DumpFile+0xe7 [dumpexe.c:2767]): pushl $0x0 | |
| Wine-dbg>n | |
| 2768 if( lpMap == NULL ) | |
| 0x081a016b (DumpFile+0xfc [dumpexe.c:2768]): cmpl $0,0xffffffe0(%ebp) | |
| Wine-dbg>print lpMap | |
| 0x414c5f40 | |
| Wine-dbg>x lpMap | |
| 40007000 | |
| Wine-dbg> x/10x 0x40007000 | |
| x/10x 0x40007000 | |
| 0x40007000 (KERNEL32_NULL_THUNK_DATA+0x37e4fe74): *** Invalid address 0x40007000 (KERNEL32_NULL_THUNK_DATA+0x37e4fe74) | |
| Wine-dbg>quit | |
| $ | |
| ******************************************************************* | |
| The first time through, we find that MapViewOfFile isn't mapping the file | |
| correctly into the virtual address space. Try running again, and step into | |
| MapViewOfFile to figure out what went wrong. | |
| ******************************************************************* | |
| bash$ ./wine -debug './dumpexe.exe -symbol ./dumpexe.exe' | |
| Warning: invalid dir 'e:\test' in path, deleting it. | |
| Win32 task 'W32SXXXX': Breakpoint 1 at 0x081a3450 | |
| Loading symbols from ELF file ./wine... | |
| Loading symbols from ELF file /usr/X11R6/lib/libXpm.so.4.6... | |
| Loading symbols from ELF file /usr/X11R6/lib/libSM.so.6.0... | |
| Loading symbols from ELF file /usr/X11R6/lib/libICE.so.6.0... | |
| Loading symbols from ELF file /usr/X11R6/lib/libXext.so.6.0... | |
| Loading symbols from ELF file /usr/X11R6/lib/libX11.so.6.0... | |
| Loading symbols from ELF file /lib/libm.so.5.0.5... | |
| Loading symbols from ELF file /lib/libc.so.5.2.18... | |
| Loading symbols from ELF file /lib/ld-linux.so.1... | |
| Loading symbols from Win32 file ./dumpexe.exe... | |
| Stopped on breakpoint 1 at 0x081a3450 (_mainCRTStartup) | |
| In 32 bit mode. | |
| *** Invalid address 0x414c5ff8 (KERNEL32_NULL_THUNK_DATA+0x3930ee6c) | |
| 0x081a3450 (_mainCRTStartup): movl %fs:0,%eax | |
| Wine-dbg>b DumpFile:2767 | |
| Breakpoint 2 at 0x081a0156 (DumpFile+0xe7 [dumpexe.c:2767]) | |
| Wine-dbg>c | |
| Dump File: ./dumpexe.exe | |
| Stopped on breakpoint 2 at 0x081a0156 (DumpFile+0xe7 [dumpexe.c:2767]) | |
| Enter path to file dumpexe.c: ../de | |
| 2767 lpMap = (LPSTR) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); | |
| 0x081a0156 (DumpFile+0xe7 [dumpexe.c:2767]): pushl $0x0 | |
| Wine-dbg>step | |
| 390 0385 stdcall MapViewOfFile(long long long long long) MapViewOfFile | |
| 0x080d793c (KERNEL32_385 [kernel32.spec:390]): pushl %ebp | |
| Wine-dbg>step | |
| 223 if (!debugging_relay) return; | |
| 0x080c83dc (RELAY_DebugCallFrom32+0xc [relay.c:223]): cmpw $0,0x644a | |
| Wine-dbg> | |
| 244 } | |
| 0x080c848e (RELAY_DebugCallFrom32+0xbe [relay.c:244]): leal 0xfffffff4(%ebp),%esp | |
| Wine-dbg> | |
| 103 return MapViewOfFileEx(handle,access,offhi,offlo,size,0); | |
| 0x080911a4 (MapViewOfFile+0x14 [file.c:103]): pushl $0x0 | |
| Wine-dbg> | |
| 113 FILEMAP_OBJECT *fmap = (FILEMAP_OBJECT*)handle; | |
| 0x080911cf (MapViewOfFileEx+0xf [file.c:113]): movl 0x8(%ebp),%esi | |
| Wine-dbg>n | |
| 115 if (!size) size = fmap->size; | |
| 0x080911d2 (MapViewOfFileEx+0x12 [file.c:115]): testl %ebx,%ebx | |
| Wine-dbg>list | |
| list | |
| 115 if (!size) size = fmap->size; | |
| 116 if (!size) size = 1; | |
| 117 return mmap ((caddr_t)st, size, fmap->prot, | |
| 118 MAP_ANON|MAP_PRIVATE, | |
| 119 FILE_GetUnixHandle(fmap->hfile), | |
| 120 offlo); | |
| 121 } | |
| 122 | |
| 123 /*********************************************************************** | |
| 124 * UnmapViewOfFile (KERNEL32.385) | |
| 125 */ | |
| Wine-dbg>x size | |
| 00000000 | |
| Wine-dbg>n | |
| 116 if (!size) size = 1; | |
| 0x080911d9 (MapViewOfFileEx+0x19 [file.c:116]): testl %ebx,%ebx | |
| Wine-dbg>x size | |
| 00000000 | |
| Wine-dbg>n | |
| 117 return mmap ((caddr_t)st, size, fmap->prot, | |
| 0x080911e2 (MapViewOfFileEx+0x22 [file.c:117]): pushl %eax | |
| Wine-dbg>x size | |
| 00000000 | |
| Wine-dbg>info local | |
| MapViewOfFileEx:handle == 0x08e48c90 | |
| MapViewOfFileEx:access == 0x00000004 | |
| MapViewOfFileEx:offhi == 0x00000000 | |
| MapViewOfFileEx:offlo == 0x00000000 | |
| MapViewOfFileEx:size == 0x00000000 | |
| MapViewOfFileEx:st == 0x00000000 | |
| MapViewOfFileEx:offlo optimized into register $eax | |
| MapViewOfFileEx:size optimized into register $ebx | |
| MapViewOfFileEx:st optimized into register $edi | |
| MapViewOfFileEx:fmap optimized into register $esi | |
| Wine-dbg>print $ebx | |
| 0x0001 | |
| Wine-dbg>bt | |
| bt | |
| Backtrace: | |
| =>0 0x080911e2 (MapViewOfFileEx+0x22 [file.c:117]) | |
| 1 0x080911b0 (MapViewOfFile+0x20(handle=0x8e48c90, access=0x4, offhi=0x0, offlo=0x0, size=0x0) [file.c:104]) | |
| 2 0x08104ab5 (CallFrom32_stdcall_5+0x25 [callfrom32.s]) | |
| 3 0x081a0168 (DumpFile+0xf9(lpFileName=0x414c61ed) [dumpexe.c:2767]) | |
| 4 0x081a0c35 (main+0x410(argc=0x3, argv=0x414c61cc) [dumpexe.c:3078]) | |
| 5 0x081a3514 (_mainCRTStartup+0xc4) | |
| 6 0x0810549f (Code_Start+0x13 [callto32.s]) | |
| 7 0x0802fdac (TASK_CallToStart+0x8c [task.c:373]) | |
| Wine-dbg> | |
| ******************************************************************* | |
| Notice that you can step through the thunks into our own transfer | |
| routines. You will notice that the source line displays as something | |
| like: | |
| 390 0385 stdcall MapViewOfFile(long long long long long) MapViewOfFile | |
| This is just the source line from the spec file that caused the transfer | |
| routine to be generated. From this you can step again, and you step | |
| into the relay logging code - keep stepping and you eventually step into | |
| the actual function that does the dirty work. | |
| At this point an examination of the source to the Win32 program | |
| and an examination of the source to win32/file.s showed where the problem | |
| was. When you specify 0 for the size of the object in CreateFileMapping, | |
| it is supposed to use the entire size of the file as the size of the | |
| object. Instead we were just blindly copying the number over. | |
| ******************************************************************* | |
| Wine-dbg>b main | |
| Breakpoint 1 at 0x080108c0 (main [dbgmain.c:213]) | |
| Wine-dbg>print breakpoints[1] | |
| {addr={type=0x08043000, seg=0, off=134285504}, addrlen=' ', opcode='U', enabled=1, skipcount=0, in_use=1} | |
| Wine-dbg> print breakpoints[1].enabled | |
| 1 | |
| Wine-dbg>set breakpoints[0].enabled = 0 | |
| Wine-dbg>print breakpoints[0].enabled | |
| 0 | |
| Wine-dbg>print type_hash_table[1]->type | |
| STRUCT | |
| Wine-dbg>print type_hash_table[1] | |
| 0x08072020 | |
| Wine-dbg>print *type_hash_table[1] | |
| print *type_hash_table[1] | |
| {type=STRUCT, next=0x00000000, name="LOGPALETTE", un={basic={basic_type=8, output_format=" V M", basic_size=-128, b_signed=0}, bitfield={bitoff=8, nbits=0, basetype=0x081d56c0}, pointer={pointsto=0x00000008}, funct={rettype=0x00000008}, array={start=8, end=136140480, basictype=0x08043e80}, structure={size=8, members=0x081d56c0}, enumeration={members=0x00000008}}} | |
| Wine-dbg> | |
| ******************************************************************* | |
| This example shows how you can print out various data structures. | |
| Note that enumerated types are displayed in the symbolic form, and strings | |
| are displayed in the expected manner. | |
| You can use the set command to set more or less anything. Note | |
| however that you cannot use enumerated types on the RHS of the expression. | |
| ******************************************************************* | |
| Wine-dbg>list | |
| 2986 if( argc <= 1 ) | |
| 2987 { | |
| 2988 Usage(argv[0]); | |
| 2989 } | |
| 2990 | |
| 2991 for( i = 1; i < argc; i++ ) | |
| 2992 { | |
| 2993 if( strncmp(argv[i], "-dos", sizeof("-dos") - 1) == 0 ) | |
| 2994 { | |
| 2995 DmpCtrl.bDumpDOSHeader = TRUE; | |
| 2996 } | |
| Wine-dbg>b 2993 | |
| Breakpoint 3 at 0x081a8861 (main+0x3c [dumpexe.c:2993]) | |
| Wine-dbg>condition 3 i == 2 | |
| Wine-dbg>c | |
| Stopped on breakpoint 3 at 0x081a8861 (main+0x3c [dumpexe.c:2993]) | |
| 2993 if( strncmp(argv[i], "-dos", sizeof("-dos") - 1) == 0 ) | |
| 0x081a8861 (main+0x3c [dumpexe.c:2993]): pushl $0x4 | |
| Wine-dbg>print i | |
| 2 | |
| Wine-dbg>print argv[i] | |
| "./dumpexe.exe" | |
| ******************************************************************* | |
| This example shows how to use conditional breakpoints. | |
| Here is another one that demonstrates another cool feature | |
| conditional breakpoints that involve a function call: | |
| condition 3 strcmp(argv[i], "./dumpexe.exe") == 0 | |
| ******************************************************************* | |
| Wine-dbg>list | |
| 2986 if( argc <= 1 ) | |
| 2987 { | |
| 2988 Usage(argv[0]); | |
| 2989 } | |
| 2990 | |
| 2991 for( i = 1; i < argc; i++ ) | |
| 2992 { | |
| 2993 if( strncmp(argv[i], "-dos", sizeof("-dos") - 1) == 0 ) | |
| 2994 { | |
| 2995 DmpCtrl.bDumpDOSHeader = TRUE; | |
| 2996 } | |
| Wine-dbg>b 2993 | |
| Breakpoint 3 at 0x081a8861 (main+0x3c [dumpexe.c:2993]) | |
| Wine-dbg>condition 3 strcmp(argv[i], "./dumpexe.exe") == 0 | |
| Wine-dbg>info break | |
| Breakpoints: | |
| 1: y 0x081ab450 (_mainCRTStartup) | |
| 2: y 0x081a882e (main+0x9 [dumpexe.c:2986]) | |
| 3: y 0x081a8861 (main+0x3c [dumpexe.c:2993]) | |
| stop when ( strcmp(( argv[i] ), "./dumpexe.exe") == 0 ) | |
| Wine-dbg>c | |
| Stopped on breakpoint 3 at 0x081a8861 (main+0x3c [dumpexe.c:2993]) | |
| 2993 if( strncmp(argv[i], "-dos", sizeof("-dos") - 1) == 0 ) | |
| 0x081a8861 (main+0x3c [dumpexe.c:2993]): pushl $0x4 | |
| Wine-dbg>print i | |
| 2 | |
| Wine-dbg>print argv[i] | |
| "./dumpexe.exe" | |
| Wine-dbg> |