September, 2011

  • The Old New Thing

    Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?


    If you look at the disassembly of functions inside Windows DLLs, you'll find that they begin with the seemingly pointless instruction MOV EDI, EDI. This instruction copies a register to itself and updates no flags; it is completely meaningless. So why is it there?

    It's a hot-patch point.

    The MOV EDI, EDI instruction is a two-byte NOP, which is just enough space to patch in a jump instruction so that the function can be updated on the fly. The intention is that the MOV EDI, EDI instruction will be replaced with a two-byte JMP $-5 instruction to redirect control to five bytes of patch space that comes immediately before the start of the function. Five bytes is enough for a full jump instruction, which can send control to the replacement function installed somewhere else in the address space.

    Although the five bytes of patch space before the start of the function consists of five one-byte NOP instructions, the function entry point uses a single two-byte NOP.

    Why not use Detours to hot-patch the function, then you don't need any patch space at all.

    The problem with Detouring a function during live execution is that you can never be sure that at the moment you are patching in the Detour, another thread isn't in the middle of executing an instruction that overlaps the first five bytes of the function. (And you have to alter the code generation so that no instruction starting at offsets 1 through 4 of the function is ever the target of a jump.) You could work around this by suspending all the threads while you're patching, but that still won't stop somebody from doing a CreateRemoteThread after you thought you had successfully suspended all the threads.

    Why not just use two NOP instructions at the entry point?

    Well, because a NOP instruction consumes one clock cycle and one pipe, so two of them would consume two clock cycles and two pipes. (The instructions will likely be paired, one in each pipe, so the combined execution will take one clock cycle.) On the other hand, the MOV EDI, EDI instruction consumes one clock cycle and one pipe. (In practice, the instruction will occupy one pipe, leaving the other available to execute another instruction in parallel. You might say that the instruction executes in half a cycle.) However you calculate it, the MOV EDI, EDI instruction executes in half the time of two NOP instructions.

    On the other hand, the five NOPs inserted before the start of the function are never executed, so it doesn't matter what you use to pad them. It could've been five garbage bytes for all anybody cares.

    But much more important than cycle-counting is that the use of a two-byte NOP avoids the Detours problem: If the code had used two single-byte NOP instructions, then there is the risk that you will install your patch just as a thread has finished executing the first single-byte NOP and is about to begin executing the second single-byte NOP, resulting in the thread treating the second half of your JMP $-5 as the start of a new instruction.

    There's a lot of patching machinery going on that most people don't even realize. Maybe at some point, I'll get around to writing about how the operating system manages patches for software that isn't installed yet, so that when you do install the software, the patch is already there, thereby closing the vulnerability window between installing the software and downloading the patches.

  • The Old New Thing

    Sending a window a WM_DESTROY message is like prank calling somebody pretending to be the police


    A customer was trying to track down a memory leak in their program. Their leak tracking tool produced the stacks which allocated memory that was never freed, and they all seemed to come from uxtheme.dll, which is a DLL that comes with Windows. The customer naturally contacted Microsoft to report what appeared to be a memory leak in Windows.

    I was one of the people who investigated this case, and the customer was able to narrow down the scenario which was triggering the leak. Eventually, I tracked it down. First, here's the thread that caused the leak:

    DWORD CALLBACK ThreadProc(void *lpParameter)
     // This CreateWindow caused uxtheme to allocate some memory
     HWND hwnd = CreateWindow(...);
     MSG msg;
     while (GetMessage(&msg, NULL, 0, 0)) {
     return 0;

    This thread creates an invisible window whose job is to do something until it is destroyed, at which point the thread is no longer needed. The window procedure for the window looks like this:

                             WPARAM wParam, LPARAM lParam)
     switch (uMsg) {
     ... business logic deleted ...
     case WM_DESTROY:
     return DefWindowProc(hwnd, uMsg, wParam, lParam);

    Sinec this is the main window on the thread, its destruction posts a quit message to signal the message loop to exit.

    There's nothing obviously wrong here that would cause uxtheme to leak memory. And yet it does. The memory is allocated when the window is created, and it's supposed to be freed when the window is destroyed. And the only time we exit the message loop is when the window is destroyed. So how is it that this thread manages to exit without destroying the window?

    The key is how the program signals this window that it should go away.

    void MakeWorkerGoAway()
     // Find the worker window if it is registered
     HWND hwnd = GetWorkerWindow();
     // If we have one, destroy it
     if (hwnd) {
      // DestroyWindow doesn't work for windows that belong
      // to other threads.
      // DestroyWindow(hwnd);
      SendMessage(hwnd, WM_DESTROY, 0, 0);

    The authors of this code first tried destroying the window with DestroyWindow but ran into the problem that you cannot destroy a window that belongs to a different thread. "But aha, since the DestroyWindow function sends the WM_DESTROY message, we can just cut out the middle man and send the message directly."

    Well, yeah, you can do that, but that doesn't actually destroy the window. It just pretends to destroy the window by prank-calling the window procedure and saying "Ahem, um, yeah, this is the, um, window manager? (stifled laughter) And, um, like, we're just calling you to tell you, um, you're being destroyed. (giggle) So, um, you should like pack up your bags and (snort) sell all your furniture! (raucous laughter)"

    The window manager sends the WM_DESTROY message to a window as part of the window destruction process. If you send the message yourself, then you're making the window think that it's being destroyed, even though it isn't. (Because it's DestroyWindow that destroys windows.)

    The victim window procedure goes through its "Oh dear, I'm being destroyed, I guess I'd better clean up my stuff" logic, and in this case, it unregisters the worker window and posts a quit message to the message loop. The message loop picks up the WM_QUIT and exits the thread.

    And that's the memory leak: The thread exited before all its windows were destroyed. That worker window is still there, because it never got DestroyWindow'd. Since the window wasn't actually destroyed, the internal memory used to keep track of the window didn't get freed, and there you have your leak.

    "You just got punk'd!"

    The correct solution is for the MakeWorkerGoAway function to send a message to the worker window to tell it, "Hey, I'd like you to go away. Please call DestroyWindow on yourself." You can invent a private message for this, or you can take advantage of the fact that the default behavior of the WM_CLOSE message is to destroy the window. Since our window procedure doesn't override WM_CLOSE, the message will fall through to DefWindowProc which will convert the WM_CLOSE into a DestroyWindow.

    Now that you understand the difference between destroying a window and prank-calling a window telling it is being destroyed, you might be able to help Arno with his problem.

  • The Old New Thing

    Why is the registry a hierarchical database instead of a relational one?


    Commenter ton asks why the registry was defined as a hierarchical database instead of a relational database.

    Heck, it's not even a hierarchical database!

    The original registry was just a dictionary; i.e., a list of name/value pairs, accessed by name. In other words, it was a flat database.

    .txt txtfile
    txtfile Text Document
    txtfile\DefaultIcon notepad.exe,1
    txtfile\shell open
    txtfile\shell\open\command notepad %1

    If you turned your head sideways and treated the backslashes as node separators, you could sort of trick yourself into believing that this resulted in something vaguely approximating a hierarchical database, and a really lame one at that (since each node held only one piece of data).

    When you choose your data structures, you necessarily are guided by the intended use pattern and the engineering constraints. One important engineering constraint was that you have to minimize memory consumption. All of the registry code fit in 16KB of memory. (Recall that Windows 3.1 had to run on machines with only 1MB of memory.)

    Okay, what is the usage pattern of the registry? As originally designed, the registry was for recording information about file types. We have the file types themselves (txtfile), properties about those file types (DefaultIcon), verbs associated with those file types (open), and verb implementations (command or ddeexec). Some verb implementations are simple (command involves just a single string describing the command line); others are complex (ddeexec requires the execute string, the application, and the topic, plus an optional alternate execute string).

    • Given a file type and a property, retrieve the value of that property.
    • Given a file type and a verb, retrieve information about how to perform that verb.
    • The set of properties can be extended.
    • The set of property schemata can be extended.
    • The set of verbs can be extended.
    • The set of verb implementations can be extended.

    Since the properties and verb implementations can be extended, you can't come up with a single schema that covers everything. For example, over the years, new file type properties have been added such as ContentType, OpenWithList, and ShellNew. The first one is a simple string; the second is a list of strings, and the third is a complex key with multiple variants. Meanwhile, additional verb implementations have been added, such as DropTarget.

    Given the heterogeneity of the data the registry needs to keep track of, imposing some sort of uniform schema is doomed to failure.

    "But you can just update the schemata each time the registration is extended."

    That creates its own problems. For example, to support roaming user profiles, you need a single registry hive to work on multiple versions of the operating system. If version N+1 adds a new schema, but then the profile roams to a machine running version N, then that registry hive will be interpreted as corrupted since it contains data that matches no valid schema.

    "Well, then include the schemata with the roaming profile so that when the older operating system sees the hive, it also sees the updated schemata."

    This is trickier than it sounds, because when the profile roams to the newer operating system, you presumably want the schemata to be upgraded and written back into the user profile. It also assumes that the versioning of the schemata is strictly linear. (What if you roam a user profile from a Windows XP machine to a Server 2003 machine? Neither is a descendant of the other.)

    But what kills this proposal is that it makes it impossible for a program to "pre-register" properties for a future version of the operating system. Suppose a new schema is added in version N+1, like, say, the IDropTarget verb implementation. You write a program that you want to run on version N as well as on version N+1. If your installer tries to register the version N+1 information, it will fail since there is no schema for it. But that means that when the user upgrades to version N+1, they don't get the benefit of the version N+1 feature. In order to get the version N+1 feature to work, they have to reinstall the program so the installer says, "Oh, now I can register the version +1 information."

    "Well, then allow applications to install a new schema whenever they need to."

    In other words, make it a total free-for-all. In which case, why do you need a schema at all? Just leave it as an unregulated collection of name/value pairs governed by convention rather than rigid rules, as long as the code which writes the information and the code which reads it agree on the format of the information and where to look for it.

    Hey, wow, that's what the registry already is!

    And besides, if you told somebody, "Hi, yeah, in order to support looking up four pieces of information about file types, Windows 3.1 comes with a copy of SQL Server," they would think you were insane. That's like using a bazooka to kill a mosquito.

    What are you planning on doing with this relational database anyway? Are you thinking of doing an INNER JOIN on the registry? (Besides, the registry is already being abused enough already. Imagine if it were a SQL server: Everybody would store all their data in it!)

    ton explains one way applications could use this advanced functionality:

    An application would have a table or group of tables in relational style registry. A group of settings would be a row. A single setting would be a column. Is it starting to become clearer now how SQL like statements could now be used to constrain what gets deleted and added? How good is your understanding of SQL and DBMS?

    You know what most application authors would say? They would say "Are you mad? You're saying that I need to create a table with one column for each setting? And this table would have a single row (since I have only one application)? All this just so I can save my window position? Screw it, I'm going back to INI files." What'll happen in practice is that everybody will create a table with two columns, a string called name and a blob called value. Now we've come full circle: We have our flat database again.

    And how would they make sure the name of their table doesn't collide with the name of a table created by another application? Probably by encoding the company name and application name into the name of the table, according to some agreed-upon convention. Like say, the Settings table used by the LitSoft program written by LitWare would be called LitWare_LitSoft_Settings. So querying a value from this table would go something like

    SELECT value FROM PerUser.LitWare_LitSoft_Settings
        WHERE name = "WindowPosition"

    Hey, this looks an awful lot like


    One of ton's arguments for using a relational database is that it permits enforcement of referential integrity. But I would argue that in the general case, you don't want strict enforcement of referential integrity. Suppose you uninstall a program. The uninstaller tries to delete the program registration, but that registration is being referenced by foreign keys in other tables. These references were not created by the application itself; perhaps the shell common dialog created them as part of its internal bookkeeping. If the registry blocked the deletion, then the uninstall would fail. "Cannot uninstall application because there's still a reference to it somewhere." And that reference might be in Bob's user profile, from that time Bob said, "Hey can I log onto your machine quickly? I need to look up something." Bob is unlikely to come back to your machine any time soon, so his user profile is just going to sit there holding a reference to that application you want to uninstall for an awfully long time. "Hi, Bob, can you come by my office? I need you to log on so I can uninstall an app."

    So let's assume it goes the other way: The registry automatically deletes orphaned foreign key rows. (And for hives that are not currently available, it just remembers that those foreign key rows should be deleted the next time they are loaded. Nevermind that that list of "foreign key rows that should be deleted the next time Bob logs on" is going to get pretty long.)

    Now suppose you're uninstalling a program not because you want to get rid of it, but because you're doing an uninstall/reinstall troubleshooting step. You uninstall the program, all the orphaned foreign key rows are automatically deleted, then you reinstall the program. Those orphaned foreign key rows are not undeleted; they remain deleted. Result: You lost some settings. This is the reason why you don't clean up per-user data when uninstalling programs.

    Enforcing referential integrity also means that you can't create anticipatory references. One example of this was given earlier, where you register something on version N even though the feature doesn't get activated until the user upgrades to version N+1. More generally, Program X may want to create a reference to Program Y at installation, even if program Y isn't installed yet. (For example, X is a Web browser and Y is a popular plug-in.) The Program Y features remain dormant, because the attempt by Program X to access Program Y will fail, but once the user installs Program Y, then the Program Y features are magically "turned on" in Program X.

    Consider, as an even more specific example, the "kill bit" database. There, the goal isn't to "turn on" features of Program Y but to turn them off. Imagine if referential integrity were enforced: You couldn't kill an ActiveX control until after it was installed!

  • The Old New Thing

    Throwing garbage on the sidewalk: The sad history of the rundll32 program


    During the development of Windows Vista, the application comaptibility team traced a bunch of issues back to people corrupting the stack by using the rundll32 program to call functions that were not designed to be called by rundll32.

    The problems were often subtle. For example, a batch file which used rundll32 incorrectly ended up hanging because the rundll32 process never returned. The misaligned stack resulted in registers being restored from the stack incorrectly, and then the cleanup code inside rundll32 ends up getting confused and wedging itself. The programs got away with it on previous versions of Windows by sheer luck. The version of the compiler used by Windows Vista contains different optimizations, and it ended up arranging stack variables and using registers differently, and what in previous versions of Windows was some corruption that went largely unnoticed became corruption that resulted in the program getting stuck in an infinite loop. Lucky no longer.

    I was asked to come up with a solution for this problem, to fix the rundll32 program so it was more resilient to people who used it incorrectly. To fix other people's bugs for them.

    The solution: Before calling the function, push a hundred bytes of garbage onto the stack (in case the called function pops too many bytes off the stack) and save the stack pointer in a global variable. After the function returns, restore the stack pointer, in case the called function pops too many or too few bytes off the stack. I think I may even have saved the processor registers in global variables, I forget.

    Do not consider this free license to continue abusing the rundll32 program. When the pet store opens on Sundays, that doesn't mean that it's okay to keep throwing garbage on the sidewalk.

  • The Old New Thing

    Appearing to succeed is a valid form of undefined behavior, but it's still undefined


    A customer requested a clarification on the MSDN documentation for the HeapFree function.

    The MSDN documentation says that if the lpMem parameter is NULL, then the behavior is undefined. Is this true?

    As explicitly stated in MSDN, the behavior is undefined. Observe that the annotation on the lpMem parameter is __in, which means that the parameter must be a non-NULL value provided by the caller. (If NULL were permitted, the annotation would have been __in_opt.)

    Undefined behavior means that anything can happen. The program might crash immediately. It might crash five minutes later. It might send email to your boss saying that you screwed up and then read you Vogon poetry. Or maybe not.

    MSDN says don't do it, so don't do it.

    The customer explained why they were interested in knowing more information about undefined behavior:

    We were interested because there is a mismatch between the semantics of a function we are implementing (where NULL is valid and ignored) and the function HeapFree we are using as the implementation. It looks like Windows Vista returns TRUE if you pass NULL.

    If there is a mismatch in semantics between the function you are implementing and the function you are calling, it is your responsibility as the programmer to bridge the gap. The customer didn't say what function they were implementing, but I'm guessing it was something like operator delete. Since your function accepts NULL but HeapFree doesn't, it is your responsibility to filter out NULL parameters.

    void operator delete(void* ptr) throw ()
     if (ptr != NULL)
      HeapFree(CustomHeap, 0, ptr);

    This concept goes by the fancy name of the Adapter Pattern. The less fancy name is wrapper function.

    And the value returned by HeapFree on Windows Vista is irrelevant. Pretending to succeed is a valid form of undefined behavior, because anything qualifies as undefined behavior.

    (Of course, you can't assume that returning TRUE will always be the result of triggering undefined behavior. After all, if you could rely on it, then it wouldn't be undefined any more!)

  • The Old New Thing

    What's the story with the parameters to the WM_INPUT_DEVICE_CHANGE message?


    A customer found these strange macros in winuser.h:

    #if (_WIN32_WINNT >= 0x0601)
    #define GET_DEVICE_CHANGE_WPARAM(wParam)  (LOWORD(wParam))
    #elif (_WIN32_WINNT >= 0x0501)
    #define GET_DEVICE_CHANGE_LPARAM(lParam)  (LOWORD(lParam))
    #endif /* (_WIN32_WINNT >= 0x0601) */

    According to the documentation for the WM_INPUT_DEVICE_CHANGE message, the wParam is the operation code and the lParam is a handle to the device that changed. Given that definition, the correct macro would be GET_DEVICE_CHANGE_WPARAM. What's up with the bogus GET_DEVICE_CHANGE_LPARAM macro?

    The macro was incorrectly defined in Windows Vista. In the Windows 7 version of the Platform SDK, the correct macro was added, but in order to avoid introducing a breaking change to existing code, the old broken macro remains in place in order to retain bug-for-bug compatibility with existing code.

    Even though the macro didn't work, there is a good possibility that code exists which relied on it anyway. For example, people may have read the documentation, read the macro, realized that the macro was wrong, and worked around the bug like this:

     return OnInputDeviceChange(GET_DEVICE_CHANGE_LPARAM(wParam),

    To avoid breaking this code, the old broken definition remains in the header file. But at least it's defined only if you say that you want the Windows Vista version of the header file, so at least people won't use the bad macro going forward.

  • The Old New Thing

    Why does my single-byte write take forever?


    A customer found that a single-byte write was taking several seconds, even though the write was to a file on the local hard drive that was fully spun-up. Here's the pseudocode:

    // Create a new file - returns quickly
    hFile = CreateFile(..., CREATE_NEW, ...);
    // make the file 1GB
    SetFilePointer(hFile, 1024*1024*1024, NULL, FILE_BEGIN);
    // Write 1 byte into the middle of the file
    SetFilePointer(hFile, 512*1024*1024, NULL, FILE_BEGIN);
    BYTE b = 42;
    / this write call takes several seconds!
    WriteFile(hFile, &b, &nBytesWritten, NULL);

    The customer experimented with using asynchronous I/O, but it didn't help. The write still took a long time. Even using FILE_FLAG_NO_BUFFERING (and writing full sectors, naturally) didn't help.

    The reason is that on NTFS, extending a file reserves disk space but does not zero out the data. Instead, NTFS keeps track of the "last byte written", technically known as the valid data length, and only zeroes out up to that point. The data past the valid data length are logically zero but are not physically zero on disk. When you write to a point past the current valid data length, all the bytes between the valid data length and the start of your write need to be zeroed out before the new valid data length can be set to the end of your write operation. (You can manipulate the valid data length directly with the Set­File­Valid­Data function, but be very careful since it comes with serious security implications.)

    Two solutions were proposed to the customer.

    Option 1 is to force the file to be zeroed out immediately after setting the end of file by writing a zero byte to the end. This front-loads the cost so that it doesn't get imposed on subsequent writes at seemingly random points.

    Option 2 is to make the file sparse. Mark the file as sparse with the FSCTL_SET_SPARSE control code, and immediately after setting the end of file, use the FSCTL_SET_ZERO_DATA control code to make the entire file sparse. This logically fills the file with zeroes without committing physical disk space. Anywhere you actually write gets converted from "sparse" to "real". This does open the possibility that a later write into the middle of the file will encounter a disk-full error, so it's not a "just do this and you won't have to worry about anything" solution, and depending on how randomly you convert the file from "sparse" to "real", the file may end up more fragmented than it would have been if you had "kept it real" the whole time.

  • The Old New Thing

    Does this operation work when impersonating? The default answer is NO


    I'll often see a customer ask for assistance with a scenario like this: "We're having trouble doing X. We're doing X1, X2, and X3, but it looks like we're getting the wrong answer back."

    The next step in the conversation goes something like "There must be something else going on, because X1, X2 and X3 is the correct way of doing X. To demonstrate, I've written the following sample program that illustrates doing X by the X1, X2, X3 technique. When I run it, I get the correct answer. What do you get when you run it?"

    "When we run your program we get the correct answer, but it doesn't work when we do it from our program." And then, as if by afterthought, "Could the problem be that we're impersonating?"

    Ohhhhhh, you're impersonating. Thanks for not mentioning that.

    By default, nothing works when impersonating. Impersonation requires end-to-end awareness. A function might create a worker thread—the worker thread runs with the identity of the process. A function might use a function like Queue­Usere­Work­Item—by default, the work item runs with the identity of the process. (You have to pass WT_TRANSFER_IMPERSONATION if you want the work item to respect impersonation.) A function might send a message to another window—that window will do its work under its own security token, not the token of the sender. A function might invoke a method on a remote COM object—that object will run under its own security token, not the token of the invoker. (COM requires you to call Co­Set­Proxy­Blanket to enable impersonation transfer during marshaling, and the server needs to call CoImpersonateClient. For some reason, this is called cloaking.) The registry keys HKEY_CURRENT_USER and HKEY_CLASSES_ROOT don't work when you're impersonating. (You have to use RegOpenCurrentUser or RegOpenUserClassesRoot.) Functions like SHGetKnownFolderPath have a token parameter which is used when impersonating; if you pass NULL, then it assumes you aren't impersonating.

    The requirements go beyond just code that runs during the execution of the function in question. If you have a function which caches information across calls, the cache needs to be made impersonation-aware so that a value calculated when called while impersonating user X isn't mistakenly used while impersonating user Y.

    In order for impersonation to work, every function all the way down the chain needs to be impersonation-safe. Sure, you might be careful to call QueueUserWorkItem with the WT_TRANSFER_IMPERSONATION flag, and your work item is careful to call SetProxyBlanket on its COM objects, and your COM server is careful to call CoImpersonateClient when servicing the call, but if your COM server then calls a helper object which calls SHGetKnownFolderPath and passes NULL for the impersonation token, then all your careful work has been for naught.

    This is another special case of When you create an object with constraints, you have to make sure everybody who uses the object understands those constraints.

    The Programming Golden Rule can be applied here as well: When you write your own code, do you do this? Since most people who write code do not think about impersonation (indeed, the operating system even encourages not-impersonation-safe coding when it provides conveniences like HKEY_CURRENT_USER) the default answer to "Does this work when I'm impersonating" is "No."

  • The Old New Thing

    Why does my asynchronous I/O complete synchronously?


    A customer was creating a large file and found that, even though the file was opened with FILE_FLAG_OVERLAPPED and the Write­File call was being made with an OVERLAPPED structure, the I/O was nevertheless completing synchronously.

    Knowledge Base article 156932 covers some cases in which asynchronous I/O will be converted to synchronous I/O. And in this case, it was scenario number three in that document.

    The reason the customer's asynchronous writes were completing synchronously is that all of the writes were to the end of the file. It so happens that in the current implementation of NTFS, writes which extend the length of the file always complete synchronously. (More specifically, writes which extend the valid data length are forced synchronous.)

    We saw last time that merely calling Set­End­Of­File to pre-extend the file to the final size doesn't help, because that updates the file size but not the valid data length. To avoid synchronous behavior, you need to make sure your writes do not extend the valid data length. The suggestions provided in yesterday's article apply here as well.

  • The Old New Thing

    Why are the building numbers on Microsoft main campus so erratic?


    Carrie complains that the building numbers on Microsoft main campus are completely random. Why is building 22 near buildings 40 and 41, far, far away from building 24?

    Because the Microsoft campus evolved.

    Many many years ago, the space on which the central Microsoft campus resides was a mill. Eventually it became an office park, and when Microsoft decided to move its headquarters there, it carved out a little wooded area and constructed four buildings, logically numbered 1 through 4.

    Later, the campus expanded, and plans were drawn up for three more buildings, logically numbered (and placed) 5 through 7. Two of those buildings were constructed, but the third was not built for reasons shrouded in mystery. When the campus expanded a third time, the new buildings were numbered 8 through 11. Presumably, at this point, there were still plans to construct Building 7 someday, so the number remained assigned to the planned-but-not-yet-built building. (Even if the Building 7 plans had been abandoned, the number had already been used in the plans submitted to the City of Redmond, and revising them would have entailed additional paperwork for no real benefit aside from satisfying some anal-retentive compulsion to ensure that every number was used. People who worry about this probably are also waiting for DirectX 4.)

    The campus grew, and each time new buildings were added, they received the next available number. The result of this was that buildings with consecutive numbers could very well end up far apart on campus.

    When the Microsoft main campus expanded across highway 520, the people in charge of assigning numbers decided to assign numbers starting at 100 for buildings on the other side of the highway. Mind you, they didn't stick to that plan rigidly, as there are some buildings numbered in the high 90's on that part of the campus.

    Once the idea of assigning non-consecutive numbers was breached, the number-assigning people went to town. There is a cluster of buildings in the 40's, another in the 50's (with Building 50 being an outlier), and another in the 80's.

    So at least the numbers for newer buildings are a bit less crazy. But if you're looking for an older building, you're going to have a rough time of it.

    Maybe if the original building-numbering people had had the foresight to name the buildings after their GPS coordinates.

    Bonus chatter: In 2009, the building-numbering people tried to rename Buildings 116 through 119 to Studios E through H, presumably because they were across the street from Studios A through D. This "Rebranding Project" was largely mocked. (And of course, just to make things confusing, the new names appear to have been assigned randomly.)

    Bonus chatter 2: The original Building 100 was demolished to make way for The Commons. The soon-to-be-displaced residents of Building 100 had a "demolition party" on their last day in the building, wherein they went around spraying graffiti, smashing walls with sledgehammers, that sort of thing.

Page 1 of 3 (26 items) 123