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
@@() - Evaluate the expression with the opposite of the evaluator that should be used
The third option is a bit tricky and less clear than the first two options. It simply looks to see what evaluator should be used for the current expression and uses the opposite. It basically just saves you some typing if you know what expression evaluator would be used and you want the opposite.
We can see this in action in a goofy example. Let’s see what we’d get if we added four to the length of the file name in the file object. We’re currently in the MASM expression evaluator, so we’ll need to perform an override to get the name length out of the file object:
1: kd> .expr
Because we already knew that we were in the MASM evaluator, we could have omitted the c++ from the @@ prefix:
1: kd> ? 4 + @@(fileObject->FileName.Length)
While most of the time we’re working with whatever the default expression evaluator is, there are three circumstances in which you will always get the C++ evaluator by default.
The first is when using the ?? command, which is used specifically for evaluating C++ expressions. This command is nice because it goes beyond showing just the value of the expression and shows typed information. So, instead of seeing the file name buffer address in our earlier examples we can dump out the full Unicode string structure:
1: kd> .expr
You are allowed to specify MASM expressions to this command, you just need to wrap them in a @@masm() or @@() prefix. Confusingly enough, @@() works here because that simply chooses the opposite evaluator from what would be used, which in this case is C++:
1: kd> ?? status
The other two places where you get the C++ expression evaluator are the Watch and Locals windows. This is what allows you to type addresses as structures and browse them via the GUI in those windows.
The debugger syntax is indeed rich and full of nuances, which can make diving in a bit of a challenge at first. Hopefully this intro to the expression evaluators provides a good starting point for some experimentation of your own.
Article link: http://xnerv.wang/the-tale-of-two-evaluators-understanding-masm-and-cpp-expression-evaluators-in-windbg/
Reprinted from: The Tale of Two Evaluators: Understanding MASM and C++ Expression Evaluators in WinDbg