Much like any powerful tool with a command line interface, the WinDBG expression syntax can sometimes seem quite cryptic. In the interest of trying to unlock the power of the command line interface, in this article we’re going to cover a fundamental WinDBG concept that is key to understanding those long, strange commands: the expression evaluators.
As it turns out, there are two built in expression evaluators in WinDBG, the MASM evaluator and the C++ evaluator. In order to better understand the differences, we’ll first discuss the MASM evaluator in detail and then move on to the C++ evaluator.
Please note that this article is not going to attempt to be definitive and list every possible operator or feature of the expression evaluators. We’re simply going to focus on what we find to be the most useful in the hopes of getting you started. However, once you’ve gotten your feet wet full details are available in the WinDBG documentation.
The MASM expression evaluator is the expression evaluator enabled by default. The main thing that you have to remember when dealing with the MASM expression evaluator is that every expression you type results in nothing but an address. So, for example, MASM has no idea what data type your variable is, it just knows the address of your local variable. In addition, in the case of symbols the address returned is the address of the symbol and not the contents of the address.
We can see exactly what this means if we give the evaluate expression command (?) the name of a local:
1: kd> ?irpSp
If we then cross reference that with the dv output, we see that the value returned here is indeed the address of the variable and not the pointer contents:
1: kd> dv /v /t irpSp
There are a couple of other interesting features of the MASM expression evaluator. The first is that the default radix used by the evaluator is hex, thus all values passed to the expression evaluator are first assumed to be hex values:
1: kd> n
You can change the default radix for the MASM evaluator with the n command, or you can override the radix for individual values. This is done by using one of the following prefixes:
0x for hexadecimal
There’s also another override which might come in handy, !. By prefixing a value with ! you’re telling the MASM evaluator that the following value is a symbol name and not a hexadecimal number. This can be helpful if you have an ambiguous local variable name, such as fcb:
1: kd> ? fcb
Lastly, there’s the poi operator in MASM that you’ll likely find useful. As mentioned, MASM gives you the address of a symbol but not the contents. In most cases, you’ll find yourself more interested in the contents of the address. Take for example a local NTSTATUS value, if you give the name of the variable to the expression evaluator command you will get the local variable address:
1: kd> ?status
In order to see the NTSTATUS value you will need to dereference that address, which is exactly what poi does:
1: kd> ?poi(status)
The C++ expression evaluator provides lots of added features over the MASM expression evaluator. The most important feature is that the evaluator is type aware. This means that you are able to access your variables in the same way as you are in your C/C++ code. In addition, in contrast to the MASM evaluator, symbol expressions result not in the address of the symbol but the contents of the address, thus there is no need for the poi operator.
In order to begin playing with the C++ evaluator, we’ll need to switch the current evaluator from MASM to C++:
1: kd> .expr /s c++
Now we can try looking at our status variable again:
1: kd> ?status
And we can immediately start to see the differences; for example, instead of the address of the local variable we’re given the contents.
What’s also particularly important to know about the C++ evaluator is that all numeric values default to decimal, regardless of what your default radix is. There is no way to change this, which means that any hex value must have an explicit 0x prefix:
1: kd> n
As mentioned, the C++ evaluator also allows for you to treat your variables the way you would in your C/C++ code. For example, I have a local PFILE_OBJECT variable and I can easily dereference fields of that structure:
1: kd> ? fileObject->DeviceObject
This idea also extends out to pointer arithmetic, since the evaluator is aware of the types of your pointers. Let’s see what happens if we add one to the PWCHAR Buffer field of our UNICODE_STRING above:
1: kd> ?fileObject->FileName.Buffer+1
The last nice feature that we’ll discuss is the ability to cast virtual addresses into structure types and then dereference fields of the structure. Here we take an address, cast it as an ETHREAD, then view a field:
1: kd> ? ((nt!_ethread *)0x81d11020)->Win32StartAddress
As you can see, there are probably reasons to use the MASM evaluator in some cases and the C++ evaluator in others. Due to the MASM evaluator’s symbol evaluation and respect of the default radix, it’s likely to be the evaluator that you want to use on a daily basis. In fact, the WinDBG documentation recommends sticking to the MASM evaluator for day to day use. However, the C++ evaluator can be handy and lead to much more powerful WinDBG scripting.
Thus, the question becomes how to incorporate both evaluators into your normal usage? We’ve already seen the first choice in switching between evaluators, which is to use the .expr command to change the default evaluator:
1: kd> .expr /q
That’s cumbersome though and you need to remember to switch back. The more convenient option is to leave MASM as the default evaluator but perform an evaluator override when you want to evaluate an express with C++. This is done with the
_@@_ prefix, which has three forms:
@@c++(expression) - Evaluate expression with the C++ evaluator
@@masm() - Evaluate expression with the MASM evaluator