Introduction
Let's start with the reason why I
wrote this article. One day, a colleague asked me to help him debug
a problem he had. So I was watching him stepping in
his code, when I noticed the following line:
int test = GetLastError();
He did this, because he wanted to know the error code, if the previous
function failed. He was adding this line every time he wanted to know the error
code. I advised him to remove all those lines and use the @ERR pseudoregister in
his watch window. He didn't know what it was and asking around in the office, a
lot of other people didn't. So I came up with this article for people who have never
heard of pseudoregisters.
What is a pseudoregister anyway?
A pseudoregister is not an actual hardware
register, but is displayed as though it were a hardware register. With a
pseudoregister, you can see and use certain values (error codes,
thread information block...) in the debugger.
Let's have a look at the @ERR pseudoregister. Fire up your debugger with your
favourite home-written application. Put a breakpoint in your code so that the
debugger will break execution. Open the watch window if it isn't already (do
this by right clicking on some empty toolbar space, and select "Watch" from this
list). Add @ERR in this watch window. You should see 0 in the Value column. Now
step through your code, and watch this value. It will always show the
GetLastError() number for the current thread. So if something goes wrong in your
code, this value will change.
If you want to test this, but your code doesn't
have any errors, I advise to put some in (but don't forget to remove them
afterwards). You can insert something like this:
FILE *fp = fopen("c:\\a_file_that_does_not_exist.txt", "r");
If you step over this line, you'll see that the @ERR value changed to 2. Go to Tools->Error Lookup to see
what this error value means ("The system cannot find the file specified" if you
were wondering). Lazy bums like me, and smart lads / lasses like you can change
the @ERR pseudoregister to @ERR,hr . Doing this will change the value of the
pseudoregister to the error string. Now you even don't have to lookup the error.
I leave the @ERR,hr in the watch window all the time.
Conditional Expressions
Pseudoregisters can also
be used in conditional expressions. To try this out, put following lines after
the fopen:
if (fp)
{
fclose(fp);
}
Put a breakpoint on the if (fp) line. Go to Edit->Breakpoints (or
press Alt-F9). Select the breakpoint you just inserted and press the "Condition"
button. Here, you can enter the @ERR==2 condition. Now start the debugger. The
debugger will break on this breakpoint if fopen() failed because it couldn't find
the file. If the file does exist, the debugger won't break, even if it
encountered another error (say error 4: could not open the file). Try this out by
running the code (not stepping) after creating, and deleting the
"a_file_that_does_not_exist.txt" file on c:\.
Just for the very curious (and
otherwise totally irrelevant to this article) : what does @ERR do? How does it
get the error number? As it turns out, @ERR does exactly the same thing as
GetLastError() does. These functions have a whopping 3 lines of assembly code:
mov eax,fs:[00000018h]
mov eax,dword ptr [eax+34h]
ret
So @ERR grabs the DWORD at
offset 0x34 in the thread environment block pointed to by fs:[18h].
The @TIB pseudoregister
The @ERR pseudoregister is not the only one that exists. Another important pseudoregister
is @TIB. This is the thread information block for the current thread and is
extremely helpful in multi-threaded debugging. If you place a breakpoint in a
function that is called by multiple threads, the debugger will break execution
every time no matter which thread passes the breakpoint. Even if you're stepping
through your code, the debugger can jump to the breakpoint if another thread
called the function. To solve this, you'll need to do the following. If
execution breaks in the thread you want, add @TIB in the watch window. You will
see some value like "0x7ffa6000" or "2147115008" in regular display. Go to the
breakpoint menu (Alt-F9) and select the breakpoint. You can now add the
@TIB==0x7ffa6000 condition filter. Doing this, the debugger will only break
execution for this thread. All other threads using the same function will not
result in a break.
This doesn't work in Windows 98 though. For Windows 98,
you'll need to look at the Intel CPU FS register, which is unique for each
thread. You can use the expression @FS==value
Complete list of pseudoregisters
|
Pseudoregister |
Description |
|
@ERR |
Last error value; the same value returned by the GetLastError() API function |
|
@TIB |
Thread information block for the current thread; necessary because the debugger doesn't handle the "FS:0" format |
|
@CLK |
Undocumented clock register; usable only in the Watch window |
|
@EAX, @EBX, @ECX, @EDX, @ESI, @EDI, @EIP, @ESP, @EBP, @EFL |
Intel CPU registers |
|
@CS, @DS, @ES, @SS, @FS, @GS |
Intel CPU segment registers |
|
@ST0, @ST1, @ST2, @ST3, @ST4, @ST5, @ST6, @ST7 |
Intel CPU floating-point registers |
[Table from "Debugging Applications" by John Robbins]
Acknowledgements
John Robbins for his "Debugging Applications" book
| You must Sign In to use this message board. |
|
|
 |
|
 |
.. use ERR,hr in the watch window (the formatting code hr stands for HRESULT).
Example: File does not exist instead of just 2. Better, no? Sometimes it's the little things that make the difference.
Caveat: only known (by me) to work in Visual Studio 2005, but most likely also works in (some) older VS versions.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Works of course, and that's why it's also in the article
----------------------- New and improved: kwakkelflap.com"Hey, Eddie, can I pour you a beer?" "A little early, isn't it, Richy?" "For a beer?" "No, for stupid questions."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Oh, yes, so it is. Apologies.
Nice photo, by the way. Is that really you, or is it Ade Edmundson? Spitting image, anyway.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
You've told me something I never heard about before. And it is so useful in my daily work. Thank you very much!
Cheers, Jay
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Such Techniques are always required but hardly come to know on time.I am very thankful to you that i got what i was looking since long time. Any technique to debug while concurrent threads are running and error may be in any one threads depending upon the input primary threads.
Moyeen Software Design Engineer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
hi, This is a nice article , which would definitely help in reducing the time to check up the return codes of functions. Thank you. Also waiting for the next installment of the same
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
 | Kooool  RVS Kumar | 20:09 4 Aug '03 |
|
|
 |
|
 |
is there a chance to get something similar to:
mymessage,wm = displays the name of the window message... mymessage,?? = displays my own message name that i declare in my programm.
is there something like autoexp.dat or usertype.dat?
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
No, sorry. You can use the [hresult] section (in 7.0 and later) to add your own msgs to the ",hr" list, which is a hacky way of doing that, but your msgs numbers would have to not conflict with real error message numbers.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
is i search more on the web and see there is no chance to get it. so i write it in a small dump way ... but it helps 
CString StringDSMessages(int &dsMessage) { CString pTemp;
switch(dsMessage)
...
return pTemp; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The table "Complete list of pseudo registers" is not strictly accurate: only the first three are pseudo-registers: all the others are real registers.
For 7.0 and beyond we (the debugger team) are encouraging the use of $ as a prefix for pseudo registers and @ for real registers. In 7.0 and 7.1 both are supported for both types, but that might change in a future release. $ was added as a prefix in 7.0: older versions only supported @.
I know I am being pedantic here, but I was so surprised to see everyone's reactions to these features. I personally implemented all of those you have here, plus the other ones missing from the article. Glad you like them!
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Thanks for your comments! I'll be updating the article soon. I only have VC 6, so I'll look for 7.0 and have all items in the article corrected.
----------------------- New and improved: kwakkelflap.com My second CP article: MAP files[^]
while (!:bob:.IsDrunk()) { :bob:.Drink( :beer: ); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
Thanks for this article. Would you please aslo tell that when @EAx, @EBX ... are used? I know that they are CPU registers, but sometimes they are used for special purpose for example as you ( or somebody else ) mentioned @EAX is used for the returned value of a function. Is there any special use for the other registers?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I don't have an explanation ready right now. I'm working on an update on the article and I'll include an explanation of the other pseudoregisters.
----------------------- New and improved: kwakkelflap.com My second CP article: MAP files[^]
while (!:bob:.IsDrunk()) { :bob:.Drink( :beer: ); }
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Read Matt Pietrek's great article about assembly language.
In Fig 1: EAX Multipurpose. Return values from a function are usually stored in EAX. Low 16 bits are referenced as AX. AX can be further subdivided into AL (the low 8 bits), and AH (the upper 8 bits of AX).
EBX Multipurpose. Low 16 bits are referenced as BX. BX can be further subdivided into BL (the low 8 bits), and BH (the upper 8 bits of BX).
http://www.microsoft.com/msj/0298/hood0298.htm
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
 | VC5?  Anonymous | 7:07 2 Dec '02 |
|
 |
How about VC5? None of @ERR and @TIB work for VC, while EAX,EBX,... etc are OK. Is it limited only to VC6 and later versions?
THANKS
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The @ERR and @TIB pseudoregisters only work starting from VC6. Some new pseudoregisters I have yet to mention (@HANDLES, see a few messages below) only work in VC7. I should edit the article and mention which pseudoregisters works in which VC version.
----------------------- New and improved: kwakkelflap.com My first CP article: Pseudoregisters[^]
while (!:bob:.IsDrunk()) { :bob:.Drink( :beer: ); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
It might also be useful to format the pseudoregisters in the watch window. Except from standard (simple printf) formats, the most useful symbol would be hr (HRESULT or Win32 error code) and wm (windows message number).
Example:
@err,hr will produce S_OK if @err=0x00000000 dummy,wm will produce WM_CLOSE if dummy=0x0010
Another thing that seems undocumented would be the pointer expander:
pChar,10 will show an 10 char expanded array.
Cheers /moliate
Two o'clock and walking through familiar London - Or what was familiar London before the cursor deleted certain certainties - I watch a suit and tie man giving suck to the Psion Organizer lodged in his breast pocket its serial interface like a cool mouth hunting his chest for sustenance, familiar feeling, and I'm watching my breath steam in the air.
Neil Gaiman - Cold Colours
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|