July, 2010

  • The Old New Thing

    Decoding the parameters of a thrown C++ exception (0xE06D7363)

    • 10 Comments

    Special preview content for my TechReady talk later today. I'd like to claim it was planned this way, but actually it was just a coincidence.

    The Visual C++ compiler uses exception code 0xE06D7363 for C++ exceptions. Here's how you can decode the other parameters. (Handy if you're debugging a crash dump.)

    Note that this information falls under the category of implementation detail. There is no guarantee that this method will continue to work in the future, so don't write code that relies on it. It's just a debugging tip.

    When the C++ exception is raised, the exception code is 0xE06D7363 and there are three (possibly four) parameters.

    • Parameter 0 is some internal value not important to the discussion.
    • Parameter 1 is a pointer to the object being thrown (sort of).
    • Parameter 2 is a pointer to information that describes the object being thrown.
    • Parameter 3 is the HINSTANCE of the DLL that raised the exception. (Present only on 64-bit Windows.)

    The object being thrown is pretty much the object being thrown, except that sometimes there is some junk in front that you have to skip over. Once you figure out what it is, you can dump it. (I haven't bothered trying to figure out exactly how much; I just dump bytes and figure out the correct start of the object by inspection.) But what is it? That's what Parameter 2 tells you, but in a very roundabout way.

    Take Parameter 2 and go to the fourth DWORD and treat it as a pointer. (On 64-bit systems, you have to add this value to the HINSTANCE passed as Parameter 3 to convert it to a pointer.)

    Next, go to the second DWORD and treat it as a pointer. (Again, on 64-bit systems, it's really an offset from the HINSTANCE.)

    Next, go to the second DWORD and treat it as a pointer. (64-bit systems: you know the drill.)

    Finally, skip over the first two void*s and the rest is the class name.

    Here's a picture, rendered in high-tech ASCII line drawing. Pointer-sized fields are marked with an asterisk, and fields whose value are unknown or not important are marked with tildes.

    EXCEPTION_RECORD
    +----------+
    | E06D7363 |
    +----------+
    |  ~~~     |
    +----------+
    |* ~~~     |
    +----------+
    |* ~~~     |
    +----------+
    | 3 or 4   |
    +----------+
    |* ~~~     |
    +----------+
    |*Object   |
    +----------+     +---+
    |*       ------> |~~~|
    +----------+     +---+
    |*HINSTANCE|     |~~~|
    +----------+     +---+
                     |~~~|
                     +---+    +---+
                     | -----> |~~~|
                     +---+    +---+    +---+
                              | -----> |~~~|
                              +---+    +---+    +----------+
                                       | -----> |*   ~~~   |
                                       +---+    +----------+
                                                |*   ~~~   |
                                                +----------+
                                                |Class name|
                                                +----------+
    

    "When in doubt, add another level of indirection" appears to be the mantra here.

    Here's a real-world example I had to debug. This came from a crash dump in a third-party application reported via Windows Error Reporting, so all debugging has to be done without source code or symbols.

    0:008> .exr 00000000`015dede0
    ExceptionAddress: 000007fefd23bb5d (KERNEL32!RaiseException+0x39)
       ExceptionCode: e06d7363 (C++ EH exception)
      ExceptionFlags: 00000001
    NumberParameters: 4 // this is running on 64-bit Windows
       Parameter[0]: 0000000019930520
       Parameter[1]: 00000000015def30 // object being thrown
       Parameter[2]: 00000000100cefa8 // magic Parameter 2
       Parameter[3]: 0000000010000000 // HINSTANCE
    

    According to the cookbook, we follow Parameter 2:

    0:008> dd 00000000100cefa8 l4
    00000000`100cefa8  00000000 00000000 00000000 000cefc8
                                                  ^^^^^^^^
    

    and take the fourth DWORD. Since this is a 64-bit machine, we add it to the HINSTANCE before dumping. (If this were a 32-bit machine, we would just dump it directly.)

    0:008> dd 100cefc8 l2
    00000000`100cefc8  00000005 000ceff8
                                ^^^^^^^^
    

    Now we take the second DWORD (add the HINSTANCE since this is a 64-bit machine) and then dump it again:

    0:008> dd 100ceff8 l2
    00000000`100ceff8  00000001 000d6670
                                ^^^^^^^^
    

    Okay, we're within striking distance now. Since this is a 64-bit machine, we add the HINSTANCE to the offset. And on all platforms, we add two pointers (which is 0x10 on a 64-bit machine and 8 on a 32-bit machine). The result should be an ASCII string representing the class name:

    0:008> da 100d6670+10
    00000000`100d6680  ".PEAVCResourceException@@"
    

    If you ignore the decorations, you see that this is telling you that the object thrown was a CResource­Exception.

    And for old time's sake, here's a 32-bit version I just made up now.

    0:000> .exr 0008f2e4
    ExceptionAddress: 7671b046 (kernel32!RaiseException)
       ExceptionCode: e06d7363 (C++ EH exception)
      ExceptionFlags: 00000001
    NumberParameters: 3 // 32-bit platform
       Parameter[0]: 19930520
       Parameter[1]: 0008f384 // object being thrown
       Parameter[2]: 10cfed60 // magic Parameter 2
    0:000> dd 10cfed60 l4
    10cfed60  00000000 00000000 00000000 10db297c
    0:000> dd 10db297c l2
    10db297c  00000004 10db2990
    0:000> dd 10db2990 l2
    10db2990  00000001 10dbccac
    0:000> da 10dbccac+8
    10dbccb4  ".PAVCFileException@@"
    

    Anyway, back to the original problem: Knowing that the object being thrown was a CResource­Exception was a big help, because that's a class used by MFC, so I have additional information as to what it does and how it's used. This turns out to have been the necessary foothold to identify the source of the problem, which will be the subject of a future write-up.

  • The Old New Thing

    Holy cow, those TechReady attendees really love their tchotchkes

    • 16 Comments

    I was at the Ask the Experts event last night at TechReady11, and if I didn't know better, I would have thought the purpose of Ask the Experts was for attendees to wander the room collecting the coolest swag they could get their hands on as quickly as possible. My table was equipped with about two dozen Windows 7 frisbees, and the moment they came out of the box, they disappeared into the hands of passers-by, most of whom didn't even bother reading the sign on the table much less make eye contact with me.

    The table next to mine started with a mountain of mugs, but it wasn't long before it was reduced to a molehill.

    To try to convince people at least to make eye contact with me, I hid the remaining frisbees under the table, handing them only to people who actually stopped to ask a question, or at least tell an interesting story.

    After the frisbees were gone, the swag fairies dropped a few dozen battery-powered light stick thingies. They didn't disappear as quickly, perhaps because the initial surge of swag-hunters had subsided.

    I was kind of surprised at how aggressively people went after the swag. This is, after all, a Microsoft internal event. You'd think these people would be jaded by now, having been surrounded by Microsoft-branded doodads for years.

  • The Old New Thing

    Why is my icon being drawn at the wrong size when I call DrawIcon?

    • 6 Comments

    Some time ago I had a problem with icon drawing. When I tried to draw an icon with Draw­Icon it ended up being drawn at the wrong size. A call to Get­Icon­Info confirmed that the icon was 48×48, but it drew at 32×32.

    The answer is documented in a backwards sort of way in the Draw­Icon­Ex function, which says at the bottom,

    To duplicate DrawIcon (hDC, X, Y, hIcon), call DrawIconEx as follows:

    DrawIconEx (hDC, X, Y, hIcon, 0, 0, 0, NULL,
                DI_NORMAL | DI_COMPAT | DI_DEFAULTSIZE); 
    

    Aha, if you use Draw­Icon, then the icon size is ignored and it is drawn with DI_DEFAULT­SIZE.

    The fix, therefore, was to switch to the Draw­Icon­Ex function so I could remove the DI_DEFAULT­SIZE flag, thereby permitting the icon to be drawn at its actual size.

    - DrawIcon(hdc, pt.x, pt.y, hico);
    + DrawIconEx(hdc, pt.x, pt.y, hico, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT);
    

    A bonus quirk of the DI_DEFAULT­SIZE flag (and therefore of the Draw­Icon function) is that the drawing is done at the default icon size, even if you asked it to draw a cursor.

  • The Old New Thing

    The frustration of people who have already decided on the solution and won't let you derail them with your annoying questions

    • 22 Comments

    I illustrate this frustration with an actual mail thread (suitably redacted) which I was an observer to. It's a long thread because that's part of the frustration.

    From: Adam

    I am looking for some expert advice here on finding a better solution to our performance problem with Product P. Here are the details.

    [Here follow the details on a problem and three proposed solutions. Feature F is mentioned briefly and rejected because "it will be a problem because of Condition C."]

    From: Bob

    This approach is prone to a lot of trouble.

    Please be more specific about what was wrong with Feature F.

    From: Adam

    We were concerned about Scenario S when we are in Condition C.

    From: Bob

    But Condition C is exactly why Feature F was developed.

    From: Charles

    How big is Scenario S? I'm part of a team that is even worse than Condition C, and we haven't noticed any problems. But maybe your Scenario S is a lot bigger than ours.

    From: David

    Stepping back a bit and emphasizing Charles's point: You rejected Feature F because "it will be a problem." Have you actually tried it? Set up a testbed with Condition C, turn on Feature F, and try it out. Do you actually see problems you predict? As with any performance problem, the first thing you have to do is measure. Information like "A job of size X takes M minutes to run to completion" gives us something to work from.

    From: Adam

    Actually, I know what Charles's situation is, and our Condition C is worse than Charles's. And we found that when we ran Scenario S on a default configuration, it was really painful.

    From: Edward

    Can you be more specific about what the pain was?

    From: Adam

    Please focus on the topic we originally asked and let the experts help us solve our performance problem.

    From: Edward

    I am the Product P expert who designed and implemented Feature F. I would like to help you. The measurement suggested by David would help everyone understand your situation. Feature F was specifically designed to improve performance in Condition C. If you assume that it will not work without even trying it, then the first thing you need to do is turn it on and try it and measure it.

    By the way, your second proposed solution is missing some important steps. If you want to continue along that plan, then you should refer to the Scenario Z section of this whitepaper I wrote (part of the Product P Resource Kit) and use that as a template.

    From: Adam

    Thanks for your great whitepaper. It's really useful! I will test it in our lab next week.

    That was the last we heard from Adam.

    It's sort of the whitepaper version of somebody who is just looking for the magic thing to type without trying to understand why it works or what its advantages, disadvantages, and limitations are.

    My guess is that Adam is just going to plunge ahead with his proposed solution #2 even though it's probably not the best solution. He already decided what he was going to do and just wanted somebody to sign off on his solution. He wasn't interested in learning about other options, as demonstrated by his blind rejection of Feature F (which was in fact specifically designed to address his situation).

    It was also strange hearing Adam demand that we avoid annoying tangents (like trying to understand what exactly the problem is) and let the experts solve his problem. Even though everybody involved in the discussion knew far more about Product P than he does, and that he singled out the most qualified expert for his scolding.

  • The Old New Thing

    Hardware backward compatibility: The finicky floppy drive

    • 26 Comments

    I think the behavior is more petulant than finicky, but finicky is alliterative.

    Back in the days of Windows 95, I was talking with the person responsible for, among other things, the floppy disk driver, and I learned about a particular driver hack that was needed to work around a flaw in a very common motherboard chipset.

    Apparently the floppy disk controller in this chipset was very picky about how you talked to it. If the very first command it receives after power-on is a read request, and there is no disk in the drive, the controller chip hangs unrecoverably. Issuing a reset to the chip has no effect. It's gone. You have to power-cycle the machine and try again.

  • The Old New Thing

    Why didn't Windows XP auto-elevate programs beyond those named setup.exe?

    • 43 Comments

    Commenter J-F has a friend who wonders why Windows XP didn't auto-elevate all installers but rather only the ones named setup.exe. (Perhaps that friend's name is Josh, who repeated the question twelve days later.)

    Remember what the starting point was. In Windows 2000, nothing was auto-elevated.

    Before adding a feature, you have to know what problem the feature is trying to solve. The problem is improving the experience for non-administrators who want to install software. When they try to install a program and forget to use the Run as feature, then instead of proceeding halfway through the installer and then getting an Access denied error, do the Run as for them automatically.

    Knowing whether the user is running an installer that requires elevation requires a degree of semantic analysis beyond what you want to add to the Create­Process code path. Hey, here's a program called PRONT4.EXE. Is it an installer? Turns out that it is. And then there are the programs that might be installers, depending on what other command line switches you provide.

    Given that you're reduced to a heuristic, you have to decide what the acceptable rates of false positives and false negatives will be. If you guess wrong and think a program requires administrator privileges when it doesn't, then you've screwed over all the non-administrators who want to use the program. "I used to be able to run this program, but now when I try, I'm asked for the administrator password, which I do not know. Windows broke my program." The effect of a false positive is My program stops working.

    On the other hand, if you fail to detect a program that requires being run with administrator privileges, the behavior is the same as before: The user gets an Access denied error. The effect of a false negative is No change.

    Given that the cost of a false positive is huge and the cost of a false negative is zero, you can see that the math says to use a conservative heuristic. The heuristic is that a program named setup.exe will be treated as an installation program, and nothing else.

    Windows was under no obligation to auto-detect installation programs. Indeed, according to the strict interpretation of operating system design, it shouldn't do this. If the user says to run this program at the current privilege level, then you darned well better run the program with the current privilege level. The treatment of programs named setup.exe is really just a compatibility hack, a courtesy to make your life a little bit easier.

    It's a case of giving somebody five dollars and being asked why you didn't give them ten.

    Starting in Windows Vista, applications can specify via a manifest whether they want to run at the privilege level the user requested (requested­Execution­Level level="as­Invoker") or always to elevate to administrator (requestedExecution­Level level="require­Administrator"). Hopefully, all new applications will specify their elevation requirements explicitly, and the heuristic will be necessary only for old programs.

  • The Old New Thing

    MSDN content is also available as a Web service

    • 19 Comments

    Unless you've been living under a rock, by now you know about MSDN's low bandwidth view (aka ScriptFree) and lightweight view. But there are other views too, like PDA view (for when you want to look up MSDN documentation on your phone?), Robot view, printer-friendly view, unstyled HTML view... (See that first link above for more details.)

    But in addition to all the views, you can go directly to the back-end that drives all the data: The MSDN/TechNet Publish System (MTPS) Content Service. With that interface, you can request the back-end data and format it any way you like. Here's an MSDN Magazine article which builds a documentation viewer, and the author of that article also developed a command-line tool which returns the content as a man page. (Update: Looks like the command-line tool no longer exists. Sorry, all you retro-heads out there.)

  • The Old New Thing

    If I'm not supposed to call IsBadXxxPtr, how can I check if a pointer is bad?

    • 21 Comments

    Some time ago, I opined that Is­Bad­Xxx­Ptr should really be called Crash­Program­Randomly and you really should just let the program crash if somebody passes you a bad pointer. It is common to put pointer validation code at the start of functions for debugging purposes (as long as you don't make logic decisions based on whether the pointer is valid). But if you can't use Is­Bad­Xxx­Ptr, how can you validate the pointer?

    Well, to validate a write pointer, write to it. To validate a read pointer, read from it. If the pointer is invalid, you'll crash, and at a predictable location, before the function has gotten halfway through its processing (making post-mortem debugging more difficult). Here are the functions I used:

    // Make sure to disable compiler optimizations in these functions
    // so the code won't be removed by the optimizer.
    void DebugValidateWritePtr(void *p, size_t cb)
    {
     memcpy(p, p, cb);
    }
    
    void DebugValidateReadPtr(void *p, size_t cb)
    {
     memcmp(p, p, cb);
    }
    

    To verify that a buffer can be written to, we write to it by copying it to itself. Similarly, to verify that a buffer can be read, we read from it by comparing it to itself. The result of the operation is not important; we are interested in the side-effect of the memory access itself.

    Note that the Debug­Validate­Write­Ptr function is not thread-safe: If another thread modifies the buffer while we are copying it to itself, the write may be lost. But code that does this violates one of the ground rules for programming (specifically the parameter stability requirements). Of course, if your function has specific behavior requirements beyond the ground rules, then that helper function may not work for you. I'm just putting it out there as a courtesy.

  • The Old New Thing

    I will be speaking at TechReady11

    • 5 Comments

    Microsoft has an internal conference known as TechReady, the eleventh edition of which will take place in Seattle next week.

    Having learned that the way to get me to show up at your conference is to invite me, the TechReady folks invited me to give a talk on advanced debugging techniques. I wasn't their first choice, though. This talk is a regular event at TechReady, but the usual presenter (Lav Pivo) will not be available; I'm a substitute. Lav usually knocks everybody dead with his presentation, so I've got big shoes to fill.

    I've been trying to squeeze preparing for this talk in amongst the things I actually get paid to do, and I hope I'll get everything done before the conference organizers start yelling at me for not having gone through all the checklist items on schedule.

    If you're a Microsoft employee at TechReady, you can watch me sweat and panic on Friday at 1pm. I'll also try to make it to Ask the Experts on Wednesday night.

    Update: The presentation is technically a Chalk Talk (code CLICT400, and no I don't know what those letters stand for, though the CT probably stands for Chalk Talk), in case you were wondering why you can't find it in the session list.

  • The Old New Thing

    Things I've written that have amused other people, Episode 7

    • 10 Comments

    A customer asked for advice on how to accomplish something, the details of which are not important, except to say that what they were trying to do was far more complicated than the twenty-word summary would suggest. And I wasn't convinced that it was a good idea, sort of like asking for advice on how to catch a baseball in your teeth or pick all the cheese off your cheeseburger.

    I explained several of the pitfalls of their approach, the ones that I could think of off the top of my head, things they need to watch out for or take precautions against, and I concluded with the sentence, "This idea is fraught with peril, and I fear that my answers to your questions will be interpreted as approval rather than reluctant assistance."

    That sentence immediately went into many people's Raymond-quotes file.

Page 1 of 3 (28 items) 123