July, 2008

  • The Old New Thing

    Don't be helpless: You can put things together, it doesn't have to be a single command

    • 29 Comments

    Humans are distinguished among all animal species by their advanced development of and heavy reliance on tools. Don't betray your ancestors. Use those tools you have.

    For example, during the debugging of a thread pool problem, it looked like somebody did a PostThreadMessage to a thread pool thread and left the message unprocessed after the thread pool function returned. Who could it have been? Well, one idea was to see if there were any DLLs in the system which called both QueueUserWorkItem and PostThreadMessage.

    I did a little legwork and contributed the following analysis to the mail thread:

    Of all the DLLs loaded into the process, the following call PostThreadMessage:

    SHLWAPI.dll 77D72436 221 PostThreadMessageA
    SHELL32.dll 77D78596 222 PostThreadMessageW
    ole32.dll 77D78596 222 PostThreadMessageW
    ... (list trimmed; you get the idea) ...

    Of those DLLs, these also call QueueUserWorkItem:

    shlwapi.dll
    shell32.dll
    ... (list trimmed; you get the idea) ...

    Astounded, somebody wanted to know how I came up with that list.

    Nothing magic. You have the tools, you have a brain, so connect the dots.

    The lm debugger command lists all the DLLs loaded into the process. Copy the output from the debugger window and paste it into a text file. Now write a little script that takes each line of the text file and does a link /dump /imports on the corresponding DLL. I happen to prefer perl for this sort of thing, but you can use a boring batch file if you like.

    for /f %i in (dlls.txt) do ^
    @echo %i & link /dump /imports %i | findstr PostThreadMessage
    

    Scrape the results off the screen, prune out the misses, and there you have it.

    "I tried that, but the result wasn't in the same format as what you posted."

    Well, yeah. There's no law that says that I can't manually reformat the data before presenting it in an email message. Since there were only a dozen hits, it's not worth writing a script to do that type of data munging. Typing "backspace, home, up-arrow" twelve times is a lot faster than writing a script to take the output of the above batch file and turn it into the output I used in the email message.

    Another boring batch file filters the list to those DLLs that also call QueueUserWorkItem. Writing it (or a script in your favorite language) is left as an exercise.

    No rocket science here. Just taking a bunch of tools and putting them together to solve a problem. That's what your brain is for, after all.

  • The Old New Thing

    What does each country claim for its own?

    • 202 Comments

    One of the things that fascinates me is how each country's view of history is clouded by its own chauvinism. I was reminded of this when researchers were able to reconstruct the original recording from a phonautograph which predated Edison's phonograph, thereby adding another claim to the mix of who invented sound recording.

    I think the most contentious invention belongs to human flight. It seems that every country on the planet has a claim to being the pioneer in this field. I'm particularly amused that both France and Brazil claim Alberto Santos-Dumont as their own. Failure is an orphan.

    When I visited Portugal, I asked one of the professors, "What is it that students in Portugal are taught is Portugal's greatest contribution to humanity?"

    The professor had to stop and think for a while before formulating an answer.

    "Portugal has not fared very well of late economically. Our best years were long ago. I would say that our greatest contribution was our accomplishments during the Age of Discoveries."

    My question to you, dear reader, is to tell us what students in your country are taught are your country's greatest achievements, or alternatively, what students believe them to be. These beliefs need not be based in fact. I'm more interested in what it is that people want you to believe whether or not it's actually true.

    For starters, here's my list of what students are taught (or end up believing) are the great accomplishments of the United States:

    • Democracy (even though it existed for millennia prior, and some might argue whether what we have today still counts as one)
    • Powered flight (The Wright Brothers)
    • The telephone (Alexander Graham Bell)
    • The light bulb, phonograph, and motion pictures (Thomas Edison)
    • The camera (George Eastmann)
    • The elevator (Elisha Otis)
    • The automobile (Henry Ford)

    Many of these are contested, and two of them are flat-out wrong: Elisha Otis did not invent the elevator, but he made them popular in the United States thanks to safety improvements. Similarly, Henry Ford did not invent the automobile but he made them popular and affordable in the United States by using an assembly line.

  • The Old New Thing

    When I double-click an Excel spreadsheet, Excel opens but the document doesn't

    • 39 Comments

    Sometime last year, we got a report from a customer that whenever he double-clicks an Excel spreadsheet, Excel starts up, but the document isn't loaded. Instead, he gets an error message saying that document could not be found. He has to go to the Open dialog and open the spreadsheet manually. This report was routed to the shell team, since it appeared to be an Explorer problem.

    We studied the file type registration for Excel documents; those were fine. We suggested some breakpoints, but everything looked good there, too. Even ddespy reported that the "Open" command was being sent to Excel correctly. So far as the shell was concerned, it was sending the command that Excel registered as the "Please send me this command when you want to open a document," and yet when Explorer sent that very command, Excel responded with "Huh? What are you talking about?"

    This indicated that an investigation of Excel itself was in order, and an hour later, the problem was identified. Under Excel Options, Advanced, General, there is an option called "Ignore other applications that use Dynamic Data Exchange", and the customer had enabled this option. The consequences of enabling this option are described in Knowledge Base article Q211494: Since Excel was configured to ignore DDE requests, it ignored the DDE request that came from Explorer.

    A misconfigured copy of Excel resulted in an error message that by all indications looked like an Explorer bug. That's pretty much par for the course in Explorer-ville.

  • The Old New Thing

    The sign that a trend is over: It shows up in a movie

    • 12 Comments

    George Mannes talks with Mike Pesca on his observation that once a financial trend shows up in a movie or television show, it's over.

  • The Old New Thing

    Be careful what you name your mailing list

    • 17 Comments

    Some time ago, I recommended exercising caution when choosing the name for your product group. The same caution applies to the name of your mailing list. Thanks to the large number of spammers out there, creating a mailling list whose account name is a word from the dictionary is just asking for trouble.

    When you create a new mailing list at Microsoft, the mailing list, by default, accepts mail from outside the company. Most people don't realize this; as a result, when a message comes in to a mailing list from outside Microsoft, people on the mailing list may reply to it, unaware that the person on the "From" line was not a Microsoft employee. I'm sure you can pull all sorts of fun social engineering attacks this way.

    Of course, the real question is why the default is to accept mail from outside Microsoft in the first place. Shouldn't the principle of "secure by default" apply here? Mailing lists should by default reject mail that arrives from the outside.

    Alas, it's even worse than that. The mechanism for changing a mailing list to "Microsoft-only" is not obvious. (It used to be "virtually impossible" but now it's just "hard to find".) Unfortunately, the people who run the system for maintaining Microsoft's myriad mailing lists have said that it's too much work to change the default, so we're going to be stuck with the insecure default for the indefinite future. But at least I can send out a "heads-up" to people who create new mailing lists.

    Update: I've heard a rumor that the default is now to reject mail from outside the company.

  • The Old New Thing

    How can SIGINT be safely delivered on the main thread?

    • 20 Comments

    Commenter AnotherMatt wonders why Win32 console programs deliver console notifications on a different thread. Why doesn't it deliver them on the main thread?

    Actually, my question is the reverse. Why does unix deliver it on the main thread? It makes it nearly impossible to do anything of consequence inside the signal handler. The main thread might be inside the heap manager (holding the heap critical section) when the signal is raised. If the signal handler tried to access the heap, it would deadlock with itself if you're lucky, or just corrupt the heap if you aren't.

    For example, consider this signal handler:

    void catch_int(int sig_num)
    {
        /* re-set the signal handler again to catch_int, for next time */
        signal(SIGINT, catch_int);
        /* and print the message */
        printf("Don't do that");
        fflush(stdout);
    }
    

    What happens if the signal is raised while the main program is executing its own fflush, say after it had already flushed half the buffer? If two threads called fflush, the second caller would wait for the first to complete. But here, it's all coming from within the same thread; the second caller can't wait for the first caller to return, since the first caller can't run until the second caller returns!

    (Note also that this signal handler potentially modifies errno, which can lead to "impossible" bugs in the main program.)

    Win32 doesn't believe in interrupt user-mode code with other user-mode code asynchronously because it makes it impossible to reason about the state of the process. Delivering the console notification on a second thread means that if the second thread tries to access the heap while the first thread is inside the heap manager, the second thread will dutifully wait for the heap to stabilize before it goes ahead and starts mucking with it.

  • The Old New Thing

    Why seventh grade students want to go to weddings

    • 22 Comments

    My friend the seventh-grade teacher is getting married this summer, and when her students learned about the impending nuptials, they couldn't contain their excitement.

    The students asked her if there was going to be a chocolate fountain. Because you can't not have a chocolate fountain. When they learned that, no, there will not be a chocolate fountain, the students lobbied hard for her to reconsider and please, you have to have a chocolate fountain. The concept of a fancy party without a chocolate fountain defied comprehension. I mean, isn't a chocolate fountain the definition of a fancy party?

    Naturally, the students also wanted to be invited as guests. They are after all, the people the teacher spends the most time with, right? They shared so many experiences: They went on field trips together, they cheered when the school cross country team defeated its longtime rival, they were even there for the great Fire Drill of February '08. How could they possibly be left out?

    The reasons for wanting to attend were different between the boys and the girls. The boys were basically in it so they could camp out by the chocolate fountain and nibble on sweets all day. The girls were drawn in by the opportunity to witness the pageantry of a wedding, the bride in a beautiful gown, the romance of exchanging vows with one's True Love. Oh, and then to camp out by the chocolate fountain and nibble on sweets all day.

  • The Old New Thing

    Simulating a drop, part two

    • 14 Comments

    Last time, we wrote a tiny program to simulate dropping a file on another file, but we discovered that it didn't work for dropping a file onto Mail Recipient.MAPIMail. The reason, as you no doubt instantly recognized, is that the MAPIMail handler creates a worker thread, and we're exiting the process before the worker thread has finished its work.

    And as you no doubt know by now, the solution is to use the SHSetInstanceExplorer function. Let's bring back the ProcessReference class and use it to solve our process lifetime problem.

    int __cdecl wmain(int argc, WCHAR **argv)
    {
     ProcessReference ref;
     ...
    

    Compile this program and run it with the command line

    fakedrop c:\autoexec.bat "%USERPROFILE%\SendTo\Mail Recipient.MAPIMail"
    

    to watch our process reference save the day.

    Oh wait, it didn't help. What's going on?

    Run this under the debugger and you'll see an interesting exception:

    (918.110): Unknown exception - code 80010012 (first chance)
    

    A little hunting through winerror.h reveals what this exception means:

    //
    // MessageId: RPC_E_SERVER_DIED_DNE
    //
    // MessageText:
    //
    //  The callee (server [not server application]) is not available and
    //  disappeared; all connections are invalid. The call did not execute.
    //
    #define RPC_E_SERVER_DIED_DNE            _HRESULT_TYPEDEF_(0x80010012L)
    

    Huh? What's this RPC stuff? Oh wait, COM uses RPC. That should be a clue.

    Notice that our ProcessReference's destructor runs after we have already uninitialized COM. When we invoked the IDropTarget::Drop method on the MAPIMail drop target, it kicked off a background thread to do the work, and in order to access the parameters from the background thread, it had to do a bit of marshalling, with the help of the functions with ridiculously long names CoMarshalInterThreadInterfaceInStream and the only slightly less ridiculous CoGetInterfaceAndReleaseStream. But since we tear down COM immediately, when the background thread gets around to asking, "Okay, and what was that file name?" the thread that has the answer (the main thread) has already shut down COM. Hence the "server died" error.

    To fix this, we need to fix the scope of the process reference object:

     if (argc == 3 && SUCCEEDED(CoInitialize(NULL))) {
      ProcessReference ref;
      ...
    

    Compile this program and run it with the same command line and... it still doesn't work! But this time you definitely know why: The destructor is running at the wrong time.

    I leave it as an exercise to fix the destructor timing problem. To see whether you got it right, run the fakedrop command line again. When you successfully get an email message, then you know you've got it.

  • The Old New Thing

    Reading a contract from the other side: Simulating a drop

    • 8 Comments

    Most people, when they think of the IDropTarget interface, think only of implementing a drop target. But you can read the contract from the other side, because the description of how a drag source interacts with a drop target tells you how to be a drag source.

    To summarize, the sequence of drop target operations go like this:

    • IDropTarget::DragEnter is called to indicate that an object has been dragged into the drop target. If the drop target returns a failure code, then the drop operation ends immediately.
    • Otherwise, IDropTarget::DragOver calls are made to advise the drop target as to the object's location.
    • If the user completes the drop operation, then call IDropTarget::Drop. Otherwise call IDropTarget::Leave. A drop operation can fail to complete because the user hit the Escape key, for example, or dragged the mouse out of the drop target.

    Let's write a simple program that drops one file onto another.

    #include <windows.h>
    #include <shlobj.h>
    #include <shellapi.h>
    #include <tchar.h>
    
    ... Insert the function GetUIObjectOfFile here ...
    
    int __cdecl wmain(int argc, WCHAR **argv)
    {
     if (argc == 3 && SUCCEEDED(CoInitialize(NULL))) {
      IDataObject *pdto;
      if (SUCCEEDED(GetUIObjectOfFile(NULL, argv[1],
                             IID_IDataObject, (void**)&pdto))) {
       IDropTarget *pdt;
       if (SUCCEEDED(GetUIObjectOfFile(NULL, argv[2],
                              IID_IDropTarget, (void**)&pdt))) {
        POINTL pt = { 0, 0 };
        DWORD dwEffect = DROPEFFECT_COPY | DROPEFFECT_LINK;
        if (SUCCEEDED(pdt->DragEnter(pdto, MK_LBUTTON,
                                     pt, &dwEffect))) {
         dwEffect &= DROPEFFECT_COPY | DROPEFFECT_LINK;
         if (dwEffect) {
          pdt->Drop(pdto, MK_LBUTTON, pt, &dwEffect);
         } else {
          pdt->DragLeave();
         }
        }
        pdt->Release();
       }
       pdto->Release();
      }
      CoUninitialize();
     }
     return 0;
    }
    

    This is a pretty straightforward implementation of the host side of the drag/drop protocol. Run this program with the full paths to two files, the first being the file to drop, and the second being the file you want to drop it onto. (Modifying this program to accept relative paths is left as an exercise for the reader.) For example, you might try

    fakedrop c:\autoexec.bat c:\windows\notepad.exe
    

    Now, sure, dropping a file on a program is nothing exciting. You could've just run the program with the file as the command line argument, after all. But that's looking at it too narrowly; you are simulating a drop operation, after all. For example, you can drop a file onto a shortcut to a printer, and the file will print; or you can drop a file onto a folder and it will be copied there (since we specified DROPEFFECT_COPY | DROPEFFECT_LINK, but folders prefer copy to link if the Ctrl+Shift keys are not held down); or you can drop a file onto the Mail Recipient.MAPIMail shortcut in your "Send To" folder to create a mail message with the file as an attachment.

    Oh wait, that last example with Mail Recipient.MAPIMail doesn't work. We'll look at why next time, although I suspect you already know the reason.

  • The Old New Thing

    Things other people have written that have amused me

    • 9 Comments

    I occasionally post things I've written that have amused other people, but today I'm going to share something Betsy Aoki wrote that amused me. On one of our internal mailing lists, somebody wondered why we don't use email spam filters to attack comment spam and trackback spam. My point was that the goal of email spam is very different from the goals of comment spam and trackback spam:

    • Email spam is about tricking the recipient into reading your message.
    • Comment spam is about tricking search engines into giving a higher ranking to the pages you link to.
    • Trackback spam is about stealing somebody else's content and republishing it, with the intent of driving traffic to your fake site and generate advertising revenue.

    I concluded with the remark, "I am not aware of any email spam that takes messages that I wrote and forwards them back to me."

    Betsy replied, "I encounter this all the time. It's called working at Microsoft."

Page 1 of 4 (40 items) 1234