The information in this article applies to:
SUMMARY
This article is intended for developers of debugging tools for the Win32s
environment. It covers the issues that should be to taken into
consideration while writing debugging tools for the Win32s environment.
MORE INFORMATIONThe following information is specific to writing a debugger for the Win32s environment. Using the WaitForDebugEvent() API FunctionThe WaitForDebugEvent() API function waits for a debugging event to occur in a process being debugged. Use it to trap debugging events.Because of the non-preemptive nature of Windows version 3.1, it is not possible to guarantee the timeout functionality. For this reason, the dwTimeout parameter was implemented differently in Win32s. In Win32s, if dwTimeout is zero, the WaitForDebugEvent() function behaves as documented in the "Win32 Programmer's Reference." Otherwise, the function waits indefinitely, until a debug event occurs or until a message is received for that process. Make sure the function returns if a message is received, so that the calling process can respond to messages. If WaitForDebugEvent() returns because a debug event has occurred, the return value is TRUE. Otherwise, the return value is FALSE. In Win32, a FALSE return value means failure. Have the calling process use SetLastError() to set the error value to 0 before calling WaitForDebugEvent(). Then if the return value is FALSE and error value returned by GetLastError() is still zero, it means a message arrived. The following code fragment demonstrates the use of WaitForDebugEvent() in the message loop:
Debugging Shared CodeUnder Win32s, all processes run in a single address space. For that reason, if a debugger sets a breakpoint in shared code, all processes will encounter this breakpoint, even those that are not being debugged. For these processes, the debugger should restore the code, let the process execute the restored instruction, and then reset the breakpoint. The problem is that in order to do these operations, the debugger needs a handle to the process thread.The debugger does not have a handle for the process thread of a process it did not create. To get the handle, Win32s supports a new function, OpenThread(), which is not a part of the Win32 API.
Parameter description:
dwThreadId - Specifies the thread identifier of the thread to open.Returns: If the function succeeds, the return value is an open handle of the specified thread; otherwise, it is NULL. To get extended error information, use the GetLastError() API.Comments: The handle returned by OpenThread() can be used in any function that requires a handle to a thread.OpenThread() is exported by KERNEL32.DLL, but is not included in any of the SDK import libraries. To create an import library on a Windows NT development machine:
The following code fragment demonstrates how a debugger can handle breakpoints in the context of a non-debugged process:
This sample code does not contain code to handle error checking and return
values from APIs. The assumption is that a non-debugged process generates a
single step exception only when it is executing the instruction in the
place of the breakpoint. The code for handling the single step exception
does not handle debug registers.
Getting and Setting Thread ContextBecause of architectural differences between Windows NT and Win32s, there is a difference in the way GetThreadContext() and SetThreadContext() work in Win32s. These functions return successfully only if they are called after returning from WaitForDebugEvent() with the EXCEPTION_DEBUG_EVENT value in the dwDebugEventCode field of the DEBUG_INFO structure and before calling ContinueDebugEvent(). At any other point, these APIs fail and GetLastError() returns ERROR_CAN_NOT_COMPLETE.Tracing Through Mixed 16- and 32-bit CodeOccasionally, Win32-based applications switch to 16-bit mode and then go back to 32-bit mode. For example, part of the Windows API is implemented in Win32s by using thunks to connect to Windows version 3.1. That means that in order to call the API, Win32s switches to 16-bit mode, calls the corresponding API on the Windows version 3.1 side, and then returns to 32- bit mode.Most debuggers do not allow tracing through 16-bit code. So when the code is about to switch to 16-bit mode, the debugger should trace over this code. To do so, Win32s supplies the DbgBackTo32 label. All calls to 16-bit code return through this address. The DbgBackTo32 label is exported by W32SKRNL.DLL. At this label, there is a RET instruction. After executing this RET instruction and immediately another following RET instruction, Win32s resumes execution at the application code, at the instruction following the call to the thunked function. So if the debugger determines that the next call is into a thunk function, it can set a breakpoint at DbgBackTo32 and trace over this call. Using Asynchronous StopsThe asynchronous stop key combination was set to CTRL+ALT+F11 in Win32s. It allows a 16-bit debugger to run at the same time as a 32-bit debugger. Each debugger can synchronously stop the other.If the user presses CTRL+ALT+F11 when the executing code is 16-bit code, execution will not be interrupted until it returns to 32-bit code. This way, the debugger does not have to handle 16-bit code. If the user presses CTRL+ALT+F11 when the executing code is 32-bit code, execution is interrupted immediately. Execution is interrupted by generating a single step exception. To handle the case where the user presses CTRL+ALT+F11 while 16-bit code is executing, the address of the exception is at a special Win32s label (W32S_BackTo32). This label is exported by W32SKRNL.DLL and is located a few instructions before DbgBackTo32. For more information on this see the "Tracing Through Mixed 16- and 32-bit Code" above. The code at W32S_BackTo32 is system code and usually debuggers should not allow tracing through system code. But between W32S_BackTo32 and DbgBackTo32, the debugger may allow tracing through this specific code and also through the two following RET instructions. This will bring the user to the point in the application at which CTRL+ALT+F11 was pressed. Identifying System DLLsWhen tracing through application code, it is not desirable to trace into system DLL code. The main reason for this is that in many cases the code goes to 16-bit code. To enable the debugger to distinguish between system and user DLLs, all Win32s system DLLs contain an extra exported symbol called WIN32SYSDLL. The address of this symbol is meaningless. The existence of such a symbol indicates that this is a system DLL.Understanding Linear and Virtual AddressesWin32s uses flat memory address space as does Windows NT, but unlike Windows NT, the base of the code and data segments is not at zero. You must consider this when dealing with linear addresses -- such as hardware debug registers when setting a hardware breakpoint. When setting a hardware breakpoint, you need to add the base of the selector to the virtual address of the breakpoint and set the debug register with this value. If you do not do so, the code will run on Windows NT but not on Win32s.The debugger needs to get the base address of the selectors by using the GetThreadSelectorEntry() function. Similarly, when the hardware breakpoint is encountered, you must subtract the selector base address from the contents of the debug register in order to read the process memory at the breakpoint location. Reading and Writing Process MemoryWhen reading from or writing to process memory, all hardware breakpoints must be disabled. If you do not do so, accessing the memory locations pointed to by the debug registers will trigger the hardware breakpoints.The following code demonstrates how a debugger can read process memory at the location of a read memory hardware breakpoint:
Accessing the Thread Local Storage (TLS)The lpThreadLocalBase field of the CREATE_PROCESS_DEBUG_INFO structure in Windows NT specifies the base address of a per-thread data block. At offset 0x2C within this block, there exists a pointer to an array of LPVOIDs. There is one LPVOID for each DLL/EXE loaded at process initialization, and that LPVOID points to Thread Local Storage (TLS). This gives a debugger access to per-thread data in its debuggee's threads using the same algorithms that a compiler would use.On the other hand, in Win32s, lpThreadLocalBase contains a pointer directly to the array of LPVOIDs, not the pointer to the per-thread data block. Additional query words: 1.10 1.20
Keywords : kbcode |
Last Reviewed: January 13, 2000 © 2000 Microsoft Corporation. All rights reserved. Terms of Use. |