Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tech Tips


Tech Tips

Download the code for this issue

Tech Tips

By Edited by George Frazier

Formatting in the Watch Window

by Bret Pehrson
[email protected]

While debugging, I often find it necessary to check the return value from various Windows and COM functions that return standardized error codes (usually HRESULT). However, I quickly tire of having to copy the result value to the clipboard, and then paste it in the ErrLookup applet to find out what the actual meaning is. Furthermore, I don't particularly like peppering code with temporary variables to store a temporary result to check during debugging. To that end, I've found the following quick and easy techniques to evaluate result codes in a completely nonintrusive fashion.

Under most function calling conventions and return types, the function return value is stored in the eax register at the machine-code level. Therefore, you can almost always determine the function return value by examining the eax register in the register's debug window. However, as just described, a simple number isn't always that beneficial, especially considering complex COM error codes (0x8007000e, for example).

Fortunately, Visual Studio's Watch and QuickWatch debugging windows support a number of watch variable formatting options, as well as the capability to display the various machine registers. Armed with that information, it becomes a trivial task to examine function return codes by adding eax to the Watch debugging window. Formatting the result is equally as simple: Simply append ",hr" to the variable and the debugger will attempt to interpret the variable as type HRESULT and display a meaningful error code such as S_OK, E_OUTOFMEMORY, and so on. So, to view formatted function return values, simply add "eax,hr" to the watch window and see results similar to that shown in Figure 1.

The formatting options don't stop there, though. See Table 1 for an abbreviated list of Visual Studio supported formatting options.

Of particular note is the wm format option. This translates numeric Windows messages and converts them into their corresponding plain-text names. For example, while debugging your Windows message handler, add "uMessage,wm" to your watch window. If the uMessage variable contains 0x0010, you would see WM_CLOSE displayed — that sure makes deciphering Windows messages easy!

How to (Painlessly) Use Dr. Watson Logs to Debug Crashes

by Pablo Presedo
[email protected]

My family always told me it never hurt to review how to use Dr. Watson to debug blue screens. I remembered this when an application I was working with was crashing on a Win98 machine. I was trying to find information on how to interpret the stack dump displayed in the details section of the crash dialog box. The reason I wanted to know this information was because Dr. Watson was failing to write the crash information to its log file. It complained about trying to write to four different files with cryptic names. In doing my research, I failed to find any information on how the stack dump information was laid out. I did discover a lot of people asking about how to use the information provided by the crash dialog box or Dr. Watson log files.

The crash dialog box does not provide as much information as the Dr. Watson log file. There are two important keys to being able to use the information provided by Dr. Watson:

  1. You need to have map files for your application and any DLLs they use.

  2. You need to know the load address for your application and any DLLs it uses.

Map files are generated when you specify the appropriate link line option depending on which compiler you are using. For Borland, use -m and with Visual C++, use -map. With the map files you will be able to translate an address from a crash into a function within your program.

How do you determine the load address for your application and any DLLs it uses? The easiest way is to rebase your DLLs so you can be fairly sure that they load where you want them to. The other advantage of rebasing is that it can speed up the loading of your application, because now the DLLs will not have to be relocated if their default load address conflicts with another previously loaded DLL.

Now that you have done the setup work, you are ready to recreate your crash and take advantage of the information provided by Dr. Watson. If you are working with a Win9x operating system, by default it creates .wlg files in the c:\windows\drwatson directory and will number them sequentially. If you are debugging a crash under NT/Win2K/XP, it will create the Dr. Watson log file in c:\Document and Settings\All Users\Documents\DrWatson. In either case, you can open the options dialog for Dr. Watson to confirm the log file location. If you open the Dr. Watson log created by Win9x with something other than Dr. Watson, the file will have some unreadable information. The information we are interested in will be in human-readable form.

Locate your crash and look for the EIP register. The EIP register will contain the address of the last instruction being executed. You can look up that value in your list of load addresses. Take the load address from the EIP register value and then subtract 0x1000 from that value. You need to subtract 0x1000 from the value because the offset values in the map file do not start from the beginning of the image, but from the end of the NT image header. Once you have this number, you are ready to go looking through your map files. In the map files, search for the string "Publics by value." Here you will have a list of symbols by offset value. Look for a value that is greater than or equal to (but not larger than) the next value. Once you find this value, you will know within which function you have crashed.

Part of the Microsoft Visual C++ 6.0 map file is shown in Table 2(a), available online. If your offset is 258, then your crash has occurred inside of a memcpy. Part of a Dr. Watson log is seen in Table 2(b), available online: If you look at the register values for the mov where the fault occurred, you would see that the EDI register is NULL. In this intentional crash, I tried to memcpy to a NULL pointer. You can also look for that offset within the line numbers section to find an exact line number.

Finally, note the following section of a Microsoft Visual C++ 6.0 map file in Table 2(c), available online. You will see that the offset for the memcpy translates to line 109 in the memcpy.asm file. The Borland map file will look the same, but will not have the line number information.

More on Do/While Statements, Macros, and the Meaning of Life

by Norman Reid
[email protected]

Raja Venkataraman's article on do/while Macros for C++ (WDM, February 2003) gives an interesting solution to the writing of macros. However, he fails to explain what the real problem is or why his solution works.

The real problem is the pesky semicolon after the macro call. A macro call is not a function call (even though it may look like one in the code), but rather a text substitution. The inclusion of the semicolon after the macro call in his example is really the inclusion of a NULL statement. So one solution to the problem is not to put the semicolon after the macro call.

if (x)
MY_ASSERT_ONE (y)
    // NO ';' - this is a macro
else
MY_ASSERT_ONE (z)   
    // NO ';' - this is a macro

Another is to surround the macro call with braces.

if (x)
{
MY_ASSERT_ONE (y);    // note: this is a macro
}
else
{
MY_ASSERT_ONE (z);    // note: this is a macro
}

The reason his solution appears to work is that the do/while statement is the only statement in C++ (and C, for that matter) that contains a keyword after a code block. By omitting the final semicolon of the do/while statement in his macro, the semicolon after the macro call completes the statement. Of course, this method fails if the user omits the semicolon from the macro call as in the first example.

Because macro calls look so much like function calls, most programmers automatically put a semicolon after them. So Mr. Venkataraman's solution will work most of the time. Taking advantage of this quirk in the C++ language, however, is a kludge, not a good macro programming practice as Mr. Venkataraman calls it. By surrounding a macro with a do/while statement, Mr. Venkataraman is making the macro much more difficult to read. If this is a known coding convention, then users will know to ignore this kludgy coding. However, if they are not aware of the convention, they will spend a lot of wasted time trying to figure out why this loop is only executed once.

Because the macro writer can never be sure how the user will code the macro call, there is no 100-percent perfect solution to this problem. If errors occur with a macro call, the user needs to look at the macro to determine the best solution to fix the problem, be it a set of extra braces or the elimination or addition of a semicolon.

Remembering that macros are code substitutions, not function calls, will help the user determine how to best fix compilation errors when dealing with macro calls.

Visual SourceSafe and ".NET" Interoperability

by Matthew Wilson
[email protected]

When I created a directory on my drive, with a matching folder in Visual SourceSafe, within which to host my .NET projects, I was irritated to learn that Visual SourceSafe 6.0 has a multitude of problems when dealing with a project (or a directory) that begins with a '.'. In other words, it was going to prevent me from using ".NET" and force something like "dotNet" or "DNet."

Being given to persistence in such circumstances, I tried various things, but to no avail. I was about to give up when it occurred to me to try Visual SourceSafe 5.0, which I keep around because Version 6.0 has another irritating trait in getting confused when manipulating local-only files from within a recursive "Project Differences" view.

It worked a treat, and the project is named ".NET". I was wryly amused to see that seven-year-old technology was needed to enable me to use the latest and greatest in the way I required.

Beware of GetFullPathNameW

by Matthew Wilson
[email protected]

The Win32 function GetFullPathName enables you to translate a relative path (i.e., ".", "\Bin\whereis.exe", "Debug") to an absolute one. It has the following signature:

  DWORD WINAPI
  GetFullPathName(LPCTSTR lpFileName,
                  DWORD nBufferLength,
                  LPTSTR lpBuffer,
                  LPTSTR *lpFilePart);

and is called in the following way:

  TCHAR   sz[1 + _MAX_PATH];
  LPTSTR  fileName;

  GetFullPathName(".",
                  sz,
                  NUM_ELEMENTS(sz),
                  &fileName);

The fact that it returns the fileName part enables one to use it to split paths on the drive\dir - name.exe junction. This technique can then be useful to extract the drive\directory of a given file, as follows:

  TCHAR   location[1 + _MAX_PATH];
  LPTSTR  fileName;

  GetFullPathName(... some file path ...,
                  location,
                  NUM_ELEMENTS(location),
                  &fileName);
  *fileName = '\0';

An extension of this technique is to use the path to be processed as the buffer — in effect saying "strip the file from this path" — as in:

  LPTSTR  fileName;

  GetFullPathName(somePath,
                  somePath,
                  NUM_ELEMENTS(somePath),
                  &fileName);
  *fileName = '\0';

Nothing in the MSDN documentation suggests that this is not valid, and it works fine on Windows 95 machines, and on NT (NT4, 2000, XP) machines when the build is ANSI (meaning that GetFullPathName resolves to GetFullPathNameW). However, something about the implementation of GetFullPathNameW causes an access violation. (No doubt the ANSI variant is insulated from this because it will be implemented in terms of GetFullPathNameW and therefore its arguments and return values must be translated to and from the Unicode forms.) Hence, the recommended safe way to strip filenames would be to use the following form:

  TCHAR   dummy[1 + _MAX_PATH];
  LPTSTR  fileName;

  GetFullPathName(somePath,
                  dummy,
                  NUM_ELEMENTS(dummy),
                  &fileName);
  somePath[fileName - &dummy[0]] = '\0';

Listing 1 shows a sample program that demonstrates the bug.


George Frazier is a software engineer in the System Design and Verification group at Cadence Design Systems Inc. and has been programming for Windows since 1991. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.