God's in his heaven.
All's right with the world.

0%

The Tale of Two Evaluators: Understanding MASM and C++ Expression Evaluators in WinDbg(转载)

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.

MASM Expression Evaluator

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
2
3
1: kd> ?irpSp

Evaluate expression: -109664056 = f976a8c8

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
2
3
1: kd> dv /v /t irpSp

f976a8c8 struct _IO_STACK_LOCATION * irpSp = 0x825c6fdc

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1: kd> n

base is 16

1: kd> ?1234

Evaluate expression: 4660 = 00001234

1: kd> n 10

base is 10

1: kd> ?1234

Evaluate expression: 1234 = 000004d2

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:

1
2
3
4
5
6
7
0x for hexadecimal

0t for octal

0y binary

0n for decimnal (the 'n' is silent)

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
2
3
4
5
6
7
1: kd> ? fcb

Evaluate expression: 4043 = 00000fcb

1: kd> ? !fcb

Evaluate expression: -109664276 = f976a7ec

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
2
3
1: kd> ?status

Evaluate expression: -138300352 = f7c1b440

In order to see the NTSTATUS value you will need to dereference that address, which is exactly what poi does:

1
2
3
1: kd> ?poi(status)

Evaluate expression: -1073741823 = c0000001

C++ Expression Evaluator

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
2
3
1: kd> .expr /s c++

Current expression evaluator: C++ - C++ source expressions

Now we can try looking at our status variable again:

1
2
3
1: kd> ?status

Evaluate expression: -1073741823 = c0000001

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
2
3
4
5
6
7
8
9
10
11
1: kd> n

base is 16

1: kd> ?1234

Evaluate expression: 1234 = 000004d2

1: kd> ?0x1234

Evaluate expression: 4660 = 00001234

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
2
3
4
5
6
7
1: kd> ? fileObject->DeviceObject

Evaluate expression: -2117322120 = 81cc3a78

1: kd> ? fileObject->FileName.Buffer

Evaluate expression: -505429944 = e1dfc048

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
2
3
1: kd> ?fileObject->FileName.Buffer+1

Evaluate expression: -505429942 = e1dfc04a

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
2
3
1: kd> ? ((nt!_ethread *)0x81d11020)->Win32StartAddress

Evaluate expression: 1979339677 = 75fa539d

Switching Evaluators

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1: kd> .expr /q

Available expression evaluators:

MASM - Microsoft Assembler expressions

C++ - C++ source expressions

Current expression evaluator: MASM - Microsoft Assembler expressions

1: kd> .expr /s c++

Current expression evaluator: C++ - C++ source expressions

1: kd> .expr /s masm

Current expression evaluator: MASM - Microsoft Assembler expressions

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
    2
    3
    4
    5
    6
    7
    1: kd> .expr

    Current expression evaluator: MASM - Microsoft Assembler expressions

    1: kd> ? 4 + @@c++(fileObject->FileName.Length)

    Evaluate expression: 6 = 00000006

    Because we already knew that we were in the MASM evaluator, we could have omitted the c++ from the @@ prefix:

    1
    2
    3
    1: kd> ? 4 + @@(fileObject->FileName.Length)

    Evaluate expression: 6 = 00000006

    When the Default Isn’t the Default

    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    1: kd> .expr

    Current expression evaluator: MASM - Microsoft Assembler expressions

    1: kd> ?? fileObject->FileName

    struct _UNICODE_STRING

    "\"

    +0x000 Length : 2

    +0x002 MaximumLength : 0x38

    +0x004 Buffer : 0xe1dfc048 "\"

    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1: kd> ?? status

    long 0xc0000001

    1: kd> ?? @@masm(status)

    unsigned int64 0xffffffff`f7c1b440

    1: kd> ?? @@(status)

    unsigned int64 0xffffffff`f7c1b440

    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.

    Hopefully A Little Less Cryptic

    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.


    本文地址:http://xnerv.wang/the-tale-of-two-evaluators-understanding-masm-and-cpp-expression-evaluators-in-windbg/
    转载自:The Tale of Two Evaluators: Understanding MASM and C++ Expression Evaluators in WinDbg