The "Go" tools
     The GoAsm manual

for those new to ....

symbolic debugging

by Jeremy Gordon -

This file is intended for those interested in 32 bit assembler programming, in particular for Windows.

I can't recommend too strongly that you learn how to use a debugger. If your programs assemble and link properly but do not work you will remain in the dark about the reason for this until you watch them working under debugger control. Often, one debugging session will show up an error instantly and can save hours of time trying out various different things. When programming for Windows you need a good Windows debugger designed for the job.

What is a debugger?
What are symbols?
What is a symbolic debugger?
Debugging in Win32 - special requirements
Debugging in Win32 - special techniques
Other techniques to try (instead of debugging)

What is a debugger?
A debugger is able to run another program (the "debuggee") in closely controlled conditions. This enables you to single step through the debuggee's code, that is, the processor will execute a single instruction at a time, and you can watch the effect of this on the debuggee's registers and flags, stack and memory areas. Usually a debugger allows you to choose whether to trace into CALLS or execute but jump over them. A debugger will also allow you to set breakpoints where you can stop execution and proceed from there, or to run the debuggee until something goes wrong.

What are symbols?
Symbols are data and code labels in your source code. A data label relates to an address in the data section and is given when the data is declared. For example,
SmallBuffer DD 20h DUP 0
creates a buffer of 32 dwords initialised to zero, with a label "SmallBuffer". This buffer can then be referred to as "SmallBuffer" throughout your source code. A code label relates to an address in the code section and may be given as follows:-
This will establish the label "Procedure66" in the code section. The procedure can then be called using the code
CALL Procedure66
A data or code label does not have to be at the start of the area of code or data. You can divide data areas so that the label refers only to part of it, and a code label can be on any line in the code section.

What is a symbolic debugger?
A symbolic debugger knows the addresses of the symbols and is able to display them in the disassembly.For example here the debugger shows in the disassembly the code label at the beginning of the procedure, and a second label a couple of lines below. Data references in the disassembly are shown by data label. A symbolic debugger may also use the code labels to allow the user to establish breakpoints, and may display the contents of memory by reference to data labels. The symbols are known to the debugger either because the symbol information is embedded in the executable, or kept in a separate file. This is done at link-time and is achieved by the linker, which if asked, will sort the labels in the object files and put them in the executable file (or in a separate file) to be read by the debugger as symbols.

Debugging in Win32 - special requirements
A Win32 debugger needs to be especially designed to monitor and deal with system events, messages and actions. In particular, it must be able to monitor and report on the messages to and from the debuggee, since this is the main way the operating system controls and communicates with the debuggee. Messages may be sent by the system either directly to the debuggee or via the message queue (picked up in the message loop). When waiting for a return from GetMessage in the message loop, the debugger must be able to catch and monitor execution elsewhere in the debuggee caused by messages sent to window procedures. The debugger must also be able to debug multi-threaded applications, and monitor the activities of each thread and show the inter-action between threads. It must be able to display virtual memory and stack areas and register values and identify those areas of memory unique to the operating system. It must be able to trace execution into Dlls. It must be able to deal with exceptions caused by the debuggee allowing the debuggee to continue without crashing, and it must be able to report errors reported by the apis. Finally the debugger must be able to close the debuggee in a system-friendly manner.

Special techniques when debugging in Win32
These are some of the things you should be able to do with a well-designed Win32 debugger. You should be able to set a breakpoint to run the debuggee until any message or until a particular message, or until a particular message to a particular window procedure. View the sequence and detail of messages. Study the execution of new threads and see how Windows shares processor time between threads. View the contents of the stack in detail. Monitor api errors. Change the (ordinary, floating point or mmx) register values or flags at run-time to correct errors or to check your code. Break into continuous loops. View and check the opcodes produced by your assembler or compiler, and view and check any executable code as assembler mnemonics. View as code or as data the memory areas and memory represented by symbols are loaded. View the debuggee and its Dlls (the executable images) as loaded in memory. Search the debuggee's memory context and shared memory for specific strings or values and view the results. View the debuggee and Dlls resources as loaded in memory and (subject to copyright) extract useful resources from executable files.

Other techniques to try (instead of debugging)

  • Examine your code
    Often there is no substitute for a hard look at your source code to see where the error has occurred. This will be made much easier if you add and test your source code incrementally. In other words add small chunks of code bit by bit, and then test it, rather than adding large sections, which might contain more than one fault which may interact with each other.
  • Retrace the steps prior to the bug
    Try to note the sequence of events leading up to the bug each time the fault occurs. Then carry out that sequence again to check that the fault then occurs. Try to isolate the fault by removing some of the steps or by taking other steps. Get the sequence as short as you can. This process will help to find the most likely culprit for the fault, and reduce the procedures that you need to check.
  • Slow down the system at run-time for draw problems
    If you have a Windows drawing problem where you suspect Windows is drawing something to the screen and then drawing over it with something else, try slowing down your programs by inserting, at an appropriate point:-
    PUSH 1000
    CALL Sleep
    This calls the API Sleep, with a delay of 1,000 milliseconds.
    This API will act only on the thread by which it is called, so if you suspect that another thread is writing over material from the first thread this technique should make this apparent. [Note it is not a good idea in any Windows application to have more than one thread responsible for drawing to the screen or managing windows since this is bound to confuse the system].
    If the over-write is occurring within your own code in response to the WM_PAINT message, then insert the Sleep API call at appropriate places in your painting process to discover exactly where the over-write is occurring.
    Windows will sometimes draw over what you have tried to draw to the screen. This might occur, for example, if the system is of the belief that another window should appear on top of the draw (ie. another window is higher in the Z-order) or if the system believes the area you have drawn onto is "invalid" (requires painting because it has moved or been uncovered). Bear in mind that the system will not do any further drawing in a window until the thread which has created that window returned from its WndProc or until DefWindProc is called. So you can insert the Sleep API prior to doing this.
  • Remove parts of your code
    This is another useful technique to identify over-write problems, whether to the screen or to memory. Remove the suspected code using a colon at the front of the line of code, so that the assembler ignores that line. See if it corrects the problem. Of course, you have to be careful to ensure that all necessary register and memory values are provided to later lines of code, and that the equillibrium of the stack is maintained.
  • Add a visible tester to your code at the point of error
    Here you arrange for a "Tester window" to appear on the screen when a particular part of the code is reached. In the example code which you can view (click on the link below), the tester window displays in hex the value of the EAX register. It saves all the registers and flags and is therefore transparent to the program. If the tester is called again it adds another line to the tester window with the value of EAX at that time. Each line is numbered and can be viewed by scrolling. There are two things to be cautious about here. Firstly be careful using this method if you have painting and drawing problems since the tester itself may cause your application to carry out further painting and drawing. Secondly, when inserting the tester in a secondary thread, don't call the tester directly from that thread. Instead, send a message to the main window and call the tester from there to avoid inter-thread problems.
     Tester window code (GoAsm syntax).
  • Dedicate a key during your application's development which shows the tester window
    Here you respond to a specific key and WM_KEYDOWN by calling the tester. You can load into EAX whatever memory value you want to study. When your program is finished and ready for publication ensure that this code is removed. This is a quick way to view memory values without having to start the debugger. Again you can call the tester as many times as you like, the results will simply scroll in the window. This technique can also be used to check the results of your coding in more detail. For example, when the key is pressed arrange to call the function under test, passing it various values. Read the result to check it is working alright.

  • Copyright © Jeremy Gordon 2002-2003
    Back to top