The Way of the great learning involves manifesting virtue, renovating the people, and abiding by the highest good.

2008年8月10日星期日

SEH A Crash Course on the Depths of Win32™ Structured Exception Handling

Click Here to Install Silverlight
United States Change | All Microsoft Sites




MSDN Home | Developer Centers | Library | Downloads | Code Center | Subscriptions | MSDN Worldwide

Search for

All of MSDNAll LibraryCode & DownloadsMSDN MagazineSupport & KB

Advanced Search
MSJ HomeJanuary 1997SearchSource CodeBack IssuesSubscribeReader ServicesWrite to UsMSDN MagazineMIND ArchiveMagazine Newsgroup MSDN Home > MSJ > January 1997
January 1997



--------------------------------------------------------------------------------

A Crash Course on the Depths of Win32™ Structured Exception Handling


At its heart, Win32 structured exception handling is an operating system-provided service. All the docs you're likely to find about SEH describe one particular com-piler's runtime library wrapping around the operating system implementation. I'll strip SEH to its most fundamental concepts.


Matt Pietrek

This article assumes you're familiar with C++, Win32

Code for this article: Exception.exe (33KB)

Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at NuMega Technologies Inc., and can be reached at mpietrek@tiac.com.



Of all the facilities provided by Win32® operating systems, perhaps the most widely used but underdocumented is structured exception handling (SEH). When you think of Win32 structured exception handling, you probably think of terms like _try, _finally, and _except. You can find good descriptions of SEH in just about any competent Win32 book (even the remedial ones). Even the Win32 SDK has a fairly complete overview of using structured exception handling with _try, _finally, and _except.
With all this documentation, where do I get off saying that SEH is underdocumented? At its heart, Win32 structured exception handling is an operating system-provided service. All the documentation you're likely to find about SEH describes one particular compiler's runtime library wrapping around the operating system implementation. There's nothing magical about the keywords _try, _finally, or _except. Microsoft's OS and compiler groups defined these keywords and what they do. Other C++ compiler vendors have simply gone along with their semantics. While the compiler SEH layer tames the nastiness of raw operating system SEH, it's had the effect of keeping the raw operating system SEH details from public view.
I've received numerous email messages from people who have needed to implement compiler-level SEH and couldn't find much in the way of documentation for the operating system facilities. In a rational world, I'd be able to point to the runtime library sources for Visual C++ or Borland C++ and be done with it. Alas, for some unknown reason, compiler-level SEH seems to be a big secret. Neither Microsoft nor Borland provide the source code for the innermost layer of their SEH support.
In this article, I'll strip structured exception handling down to its most fundamental concepts. In doing so, I'll separate what the OS provides from what compilers provide via code generation and runtime library support. When I dive into the code for key operating system routines, I'll use the Intel version of Windows NT® 4.0 as my base. Most of what I'll describe is equally applicable on other processors, however.
I'm going to avoid the issue of true C++ exception handling, which uses catch() instead of _except. Under the hood, true C++ exception handling is implemented very similarly to what I'll describe here. However, true C++ exception handling has some additional complexities that would just cloud the concepts I want to cover.
In digging through obscure .H and .INC files to piece together what constitutes Win32 SEH, one of the best sources of information turned out to be the IBM OS/2 header files (particularly BSEXCPT.H). This shouldn't be too surprising if you've been in this business for a while. The SEH mechanisms described here were defined back when Microsoft was still working on OS/2. For this reason, you'll find SEH under Win32 and OS/2 to be remarkably similar.



SEH in the Buff
Since the details of SEH can be overwhelming if taken all at once, I'll start out small and work my way up through the layers. If you've never worked with structured exception handling before, you're in good shape; you have no preconceived notions. If you've used SEH before, try to clear your head of words like _try, GetExceptionCode, and EXCEPTION_EXECUTE_HANDLER. Pretend that this is all new to you. Take a deep breath. Are you ready? Good.
Imagine I told you that when a thread faults, the operating system gives you an opportunity to be informed of the fault. More specifically, when a thread faults, the operating system calls a user-defined callback function. This callback function can do pretty much whatever it wants. For instance, it might fix whatever caused the fault, or it might play a Beavis and Butt-head .WAV file. Regardless of what the callback function does, its last act is to return a value that tells the system what to do next. (This isn't strictly true, but it's close enough for now.)
Given that the system calls you back when your code makes a mess, what should the callback function look like? In other words, what sort of information would you want to know about the exception? It really doesn't matter because Win32 has made up your mind for you. An exception callback function looks like this:


EXCEPTION_DISPOSITION
__cdecl _except_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext
);



This prototype, which comes from the standard Win32 header file EXCPT.H, looks a little overwhelming at first. If you take it slowly, it's really not so bad. For starters, ignore the return type (EXCEPTION_DISPOSITION). What you basically have is a function called _except_handler that takes four parameters.
The first parameter to an _except_handler callback is a pointer to an EXCEPTION_RECORD. This structure is defined in WINNT.H, shown below:


typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;



The ExceptionCode parameter is the number that the operating system assigned to the exception. You can see a list of various exception codes in WINNT.H by searching for #defines that start with "STATUS_". For example, the code for the all-too-familiar STATUS_ACCESS_VIOLATION is 0xC0000005. A more complete set of exception codes can be found in NTSTATUS.H from the Windows NT DDK. The fourth element in the EXCEPTION_RECORD structure is the address where the exception occurred. The remaining EXCEPTION_RECORD fields can be ignored for the moment.
The second parameter to the _except_handler function is a pointer to an establisher frame structure. This is a vital parameter in SEH, but for now you can ignore it.
The third parameter to the _except_handler callback is a pointer to a CONTEXT structure. The CONTEXT structure is defined in WINNT.H and represents the register values of a particular thread. Figure 1 shows the fields of a CONTEXT structure. When used for SEH, the CONTEXT structure represents the register values at the time of the exception. Incidentally, this CONTEXT structure is the same structure used with the GetThreadContext and SetThreadContext APIs.
The fourth and final parameter to the _except_handler callback is called the DispatcherContext. It also can be ignored for the moment.
To briefly recap thus far, you have a callback function that's called when an exception occurs. The callback takes four parameters, three of which are pointers to structures. Within these structures, some fields are important, others not so important. The key point is that the _except_handler callback function receives a wealth of information, such as what type of exception occurred and where it occurred. Using this information, the exception callback needs to decide what to do.
While it's tempting for me to throw together a quickie sample program that shows the _except_handler callback in action, there's still something missing. In particular, how does the operating system know where to call when a fault occurs? The answer is yet another structure called an EXCEPTION_REGISTRATION. You'll see this structure throughout this article, so don't skim past this part. The only place I could find a formal definition of an EXCEPTION_REGISTRATION was in the EXSUP.INC file from the Visual C++ runtime library sources:

_EXCEPTION_REGISTRATION struc
prev dd ?
handler dd ?
_EXCEPTION_REGISTRATION ends



You'll also see this structure referred to as an _EXCEPTION_REGISTRATION_RECORD in the definition of the NT_TIB structure from WINNT.H. Alas, nowhere is an _EXCEPTION_REGISTRATION_RECORD defined, so all I have to work from is the assembly language struc definition in EXSUP.INC. This is just one example of what I meant earlier when I said that SEH was underdocumented.
In any event, let's return to the question at hand. How does the OS know where to call when an exception occurs? The EXCEPTION_REGISTRATION structure consists of two fields, the first of which you can ignore for now. The second field, handler, contains a pointer to an _except_ handler callback function. This gets you a little closer, but now the question becomes, where does the OS look to find the EXCEPTION_REGISTRATION structure?
To answer this question, it's helpful to remember that structured exception handling works on a per-thread basis. That is, each thread has its own exception handler callback function. In my May 1996 column, I described a key Win32 data structure, the thread information block (aka the TEB or TIB). Certain fields of this data structure are the same between Windows NT, Windows® 95, Win32s, and OS/2. The first DWORD in a TIB is a pointer to the thread's EXCEPTION_REGISTRATION structure. On the Intel Win32 platform, the FS register always points to the current TIB. Thus, at FS:[0] you can find a pointer to an EXCEPTION_REGISTRATION structure.
Now I'm getting somewhere! When an exception occurs, the system looks at the TIB of the faulting thread and retrieves a pointer to an EXCEPTION_REGISTRATION structure. In this structure is a pointer to an _except_handler callback function. The operating system now knows enough to call the _except_handler function, as shown in Figure 2.



Figure 2 _except_handler_function


With the minimal pieces finally put together, I wrote a small program to demonstrate this very simple description of OS-level structured exception handling. Figure 3 shows MYSEH.CPP, which has only two functions. Function main uses three inline ASM blocks. The first block builds an EXCEPTION_REGISTRATION structure on the stack via two PUSH instructions ("PUSH handler" and "PUSH FS:[0]"). The PUSH FS:[0] saves the previous value of FS:[0] as part of the structure, but that's not important at the moment. The significant thing is that there's an 8-byte EXCEPTION_REGISTRATION structure on the stack. The very next instruction (MOV FS:[0],ESP) makes the first DWORD in the thread information block point at the new EXCEPTION_REGISTRATION structure.
If you're wondering why I built the EXCEPTION_REGISTRATION structure on the stack rather than using a global variable, there's a good reason. When you use a compiler's _try/_except syntax, the compiler also builds the EXCEPTION_REGISTRATION struct on the stack. I'm simply showing you a simplified version of what a compiler would do if you used _try/_except.
Back to function main, the next __asm block intentionally causes a fault by zeroing out the EAX register (MOV EAX,0), and then uses the register's value as a memory address, which the next instruction writes to (MOV [EAX],1). The final __asm block removes this simple exception handler: first it restores the previous contents of FS:[0], and then it pops the EXCEPTION_REGISTRATION record off the stack (ADD ESP,8).
Now, pretend that you're running MYSEH.EXE and you'll see what happens. When the MOV [EAX],1 instruction executes, it causes an access violation. The system looks at the FS:[0] in the TIB and finds a pointer to the EXCEPTION_REGISTRATION structure. In the structure is a pointer to the _except_handler function in MYSEH.CPP. The system then pushes the four required parameters (which I described earlier) onto the stack and calls the _except_handler function.
Once inside _except_handler, the code first indicates "Yo! I made it here!" via a printf statement. Next, _except_handler fixes the problem that caused the fault. That is, the EAX register points to a memory address that can't be written to (address 0). The fix is to change the EAX value in the CONTEXT structure so that it points to a location where writing is allowed. In this simple program, a DWORD variable (scratch) has been designated for just this purpose. The last act of the _except_handler function is to return the value ExceptionContinueExecution, which is defined in the standard EXCPT.H file.
When the operating system sees that ExceptionContinueExecution was returned, it interprets this to mean that you've fixed the problem and the faulting instruction should be restarted. Since my _except_handler function tweaked the EAX register to point to valid memory, the MOV EAX,1 instruction works the second time and function main continues normally. See, that wasn't so complicated, was it?


Moving In a Little Deeper
With this simplest of scenarios behind us, let's go back and fill in some of the blanks. While this exception callback is great, it's not a perfect solution. In an application of any size, it would be exceedingly messy to write a single function to handle exceptions that could occur anywhere in your application. A much more workable scenario would be to have multiple exception handling routines, each one customized to a particular section of your application. Wouldn't you know it, the operating system provides just this functionality.
Remember the EXCEPTION_REGISTRATION structure that the system uses to find the exception callback function? The first member of this structure, which I ignored earlier, is called prev. It's really a pointer to another EXCEPTION_REGISTRATION structure. This second EXCEPTION_REGISTRATION structure can have a completely different handler function. What's more, its prev field can point to a third EXCEPTION_REGISTRATION structure, and so on. Simply put, there's a linked list of EXCEPTION_REGISTRATION structures. The head of the list is always pointed to by the first DWORD in the thread information block (FS:[0] on Intel-based machines).
What does the operating system do with this linked list of EXCEPTION_REGISTRATION structures? When an exception occurs, the system walks the list of structures and looks for an EXCEPTION_REGISTRATION whose handler callback agrees to handle the exception. In the case of MYSEH.CPP, the handler callback agreed to handle the exception by returning the value ExceptionContinueExecution. The exception callback can also decline to handle the exception. In this case, the system moves on to the next EXCEPTION_REGISTRATION structure in the list and asks its exception callback if it wants to handle the exception. Figure 4 shows this process. Once the system finds a callback that handles the exception, it stops walking the linked list of EXCEPTION_REGISTRATIONs.
To show an example of an exception callback that doesn't handle an exception, check out MYSEH2.CPP in Figure 5. To keep the code simple, I cheated a bit and used a little compiler-level exception handling. Function main just sets up a _try/_except block. Inside the _try block is a call to the HomeGrownFrame function. This function is very similar to the code in the earlier MYSEH program. It creates an EXCEPTION_REGISTRATION record on the stack and points FS:[0] at it. After establishing the new handler, the function intentionally causes a fault by writing to a NULL pointer:



*(PDWORD)0 = 0;



The exception callback function, again called _except_ handler, is quite different than the earlier version. The code first prints out the exception code and flags from the ExceptionRecord structure that the function received as a pointer parameter. The reason for printing out the exception flags will become clear later. Since this _except_handler function has no intention of fixing the offending code, the function returns ExceptionContinueSearch. This causes the operating system to continue its search at the next EXCEPTION_REGISTRATION record in the linked list. For now, take my word for it; the next installed exception callback is for the _try/_except code in function main. The _except block simply prints out "Caught the exception in main()". In this case, handling the exception is as simple as ignoring that it happened.
A key point to bring up here is execution control. When a handler declines to handle an exception, it effectively declines to decide where control will eventually resume. The handler that accepts the exception is the one that decides where control will continue after all the exception handling code is finished. This has an important implication that's not immediately obvious.
When using structured exception handling, a function may exit in an abnormal manner if it has an exception handler that doesn't handle the exception. For instance, in MYSEH2 the minimal handler in the HomeGrownFrame function didn't handle the exception. Since somebody later in the chain handled the exception (function main), the printf after the faulting instruction never executes. In some ways, using structured exception handling is similar to using the setjmp and longjmp runtime library functions.
If you run MYSEH2, you'll find something surprising in the output. It seems that there's two calls to the _except_handler function. The first call is certainly understandable, given what you know so far. But why the second?


Home Grown handler: Exception Code: C0000005 Exception Flags 0
Home Grown handler: Exception Code: C0000027 Exception Flags 2
EH_UNWINDING
Caught the Exception in main()



There's obviously a difference: compare the two lines that start with "Home Grown Handler." In particular, the exception flags are zero the first time though, and 2 the second time. This brings me to the subject of unwinding. To jump ahead a bit, when an exception callback declines to handle an exception, it gets called a second time. This callback doesn't happen immediately, though. It's a bit more complicated then that. I'll need to refine the exception scenario one final time.
When an exception occurs, the system walks the list of EXCEPTION_REGISTRATION structures until it finds a handler for the exception. Once a handler is found, the system walks the list again, up to the node that will handle the exception. During this second traversal, the system calls each handler function a second time. The key distinction is that in the second call, the value 2 is set in the exception flags. This value corresponds to EH_UNWINDING. (The definition for EH_UNWINDING is in EXCEPT.INC, which is in the Visual C++ runtime library sources, but nothing equivalent appears in the Win32 SDK.)
What does EH_UNWINDING mean? When an exception callback is invoked a second time (with the EH_UNWINDING flag), the operating system is giving the handler function an opportunity to do any cleanup it needs to do. What sort of cleanup? A perfect example is that of a C++ class destructor. When a function's exception handler declines to handle an exception, control typically doesn't exit from that function in a normal manner. Now, consider a function with a C++ class declared as a local variable. The C++ specification says that the destructor must be called. The second exception handler callback with the EH_UNWINDING flag is the opportunity for the function to do cleanup work such as invoking destructors and _finally blocks.
After an exception is handled and all the previous exception frames have been called to unwind, execution continues wherever the handling callback decides. Remember though that it's just not enough to set the instruction pointer to the desired code address and plunge ahead. The code where execution resumes expects that the stack and frame pointer (the ESP and EBP registers on Intel CPUs) are set to their values within the stack frame that handled the exception. Therefore, the handler that accepts a particular exception is responsible for setting the stack and frame pointers to values that they had in the stack frame that contains the SEH code that handled the exception.



Figure 6 Unwinding from an Exception


In more general terms, the act of unwinding from an exception causes all things on the stack below the handling frame's stack region to be removed. It's almost as if those functions were never called. Another effect of unwinding is that all EXCEPTION_REGISTRATIONs in the list prior to the one that handled the exception are removed from the list. This makes sense, as these EXCEPTION_REGISTRATIONs are typically built on the stack. After the exception has been handled, the stack and frame pointers will be higher in memory than the EXCEPTION_REGISTRATIONs that were removed from the list. Figure 6 shows what I'm talking about.


Help! Nobody Handled It!
So far, I've implicitly assumed that the operating system always finds a handler somewhere in the linked list of EXCEPTION_REGISTRATIONs. What happens if nobody steps up to the plate? As it turns out, this almost never happens. The reason is that the operating system sneaks in a default exception handler for each and every thread. The default handler is always the last node in the linked list, and it always chooses to handle the exception. Its actions are somewhat different than a normal exception callback routine, as I'll show later.
Let's look at where the system inserts the default, last resort exception handler. This obviously needs to occur very early in the thread's execution, before any user code executes. Figure 7 shows some pseudocode I wrote for BaseProcessStart, an internal routine in the Windows NT KERNEL32.DLL. BaseProcessStart takes one parameter, the address of the thread's entry point. BaseProcessStart runs in the context of the new process and calls the entry point to kick off execution of the first thread in the process.
In the pseudocode, notice that the call to lpfnEntryPoint is wrapped within an _try and _except construct. This _try block is what installs the default, last resort exception handler in the linked list of handlers. All subsequent registered exception handlers will be inserted ahead of this handler in the list. If the lpfnEntryPoint function returns, the thread ran to completion without causing an exception. When this happens, BaseProcessStart calls ExitThread to terminate the tread.
On the other hand, what if the thread faults and no other exception handlers handle it? In this case, control goes to the code inside the parens after the _except keyword. In BaseProcessStart, this code calls the UnhandledExceptionFilter API, which I'll come back to later. For now, the key point is that the UnhandledExceptionFilter API contains the meat of the default exception handler.
If UnhandledExceptionFilter returns EXCEPTION_EXECUTE_HANDLER, the _except block in BaseProcessStart executes. All the _except block code does is terminate the current process by calling ExitProcess. Thinking about it for a second, this makes sense; it's common knowledge that if a program causes a fault and nobody handles the fault, the system terminates the process. What you're seeing in the pseudocode is exactly where and how this happens.
There's one final addition to what I've just described. If the thread that faulted is running as a service and is for a thread-based service, the _except block code doesn't call ExitProcess—it calls ExitThread instead. You wouldn't want to terminate the entire service process just because one service went bad.
So what does the default exception handler code in UnhandledExceptionFilter do? When I ask this question at seminars, very few people can guess what the default behavior of the operating system is when an unhandled exception occurs. With a very simple demonstration of the default handler's behavior, things usually click and people understand. I simply run a program that intentionally causes a fault, and point out the results (see Figure 8).




Figure 8 Unhandled Exception Dialog


At a high level, UnhandledExceptionFilter displays a dialog telling you that a fault occurred. At that point, you're given the opportunity to either terminate or debug the faulting process. Much more happens behind the scenes, and I'll describe these things towards the end of the article.
As I've shown, when an exception occurs, user-written code can (and often does) get executed. Likewise, during an unwind operation, user-written code can execute. This user-written code may have bugs and could cause another exception. For this reason, there are two other values that an exception callback can return: ExceptionNestedException and ExceptionCollidedUnwind. While obviously important, this is pretty advanced stuff and I'm not going to dwell on it here. It's difficult enough to understand the basic facts.


Compiler-level SEH
While I've made occasional reference to _try and _except, just about everything I've written about so far is implemented by the operating system. However, in looking at the contortions of my two small programs that used raw system SEH, a compiler wrapper around this functionality is definitely in order. Let's now see how Visual C++ builds its structured exception handling support on top of the system-level SEH facilities.
Before moving on, it's important to remember that another compiler could do something completely different with the raw system-level SEH facilities. Nothing says that a compiler must implement the _try/_except model that the Win32 SDK documentation describes. For example, the upcoming Visual Basic® 5.0 uses structured exception handling in its runtime code, but the data structures and algorithms are completely different from what I'll describe here.
If you read through the Win32 SDK documentation on structured exception handling, you'll come across the following syntax for a so called "frame-based" exception handler:



try {
// guarded body of code
}
except (filter-expression) {
// exception-handler block
}



To be a bit simplistic, all of the code within a try block in a function is protected by an EXCEPTION_REGISTRATION that's built on the function's stack frame. On function entry, the new EXCEPTION_REGISTRATION is put at the head of the linked list of exception handlers. After the end of the _try block, its EXCEPTION_REGISTRATION is removed from the head of the list. As I mentioned earlier, the head of the exception handler chain is kept at FS:[0]. Thus, if you're stepping through assembly language in a debugger and you see instructions such as


MOV DWORD PTR FS:[00000000],ESP



or


MOV DWORD PTR FS:[00000000],ECX



you can be pretty sure that the code is setting up or tearing down a _try/_except block.
Now that you know that a _try block corresponds to an EXCEPTION_REGISTRATION structure on the stack, what about the callback function within the EXCEPTION_ REGISTRATION? Using Win32 terminology, the exception callback function corresponds to the filter-expression code. To refresh your memory, the filter-expression is the code in parens after the _except keyword. It's this filter-expression code that decides whether the code in the subsequent {} block will execute.
Since you write the filter-expression code, you get to decide if a particular exception will be handled at this particular point in your code. Your filter-expression code might be as simple as saying "EXCEPTION_EXECUTE_ HANDLER." Alternatively, the filter-expression might invoke a function that calculates p to 20 million places before returning a code telling the system what to do next. It's your choice. The key point: your filter-expression code is effectively the exception callback that I described earlier.
What I've just described, while reasonably simple, is nonetheless a rose-colored-glasses view of the world. The ugly reality is that things are more complicated. For starters, your filter-expression code isn't called directly by the operating system. Rather, the exception handler field in every EXCEPTION_REGISTRATION points to the same function. This function is in the Visual C++ runtime library and is called __except_handler3. It's __except_handler3 that calls your filter-expression code, and I'll come back to it a bit later.
Another twist to the simplistic view that I described earlier is that EXCEPTION_REGISTRATIONs aren't built and torn down every time a _try block is entered or exits. Instead, there's just one EXCEPTION_REGISTRATION created in any function that uses SEH. In other words, you can have multiple _try/_except constructs in a function, yet only one EXCEPTION_REGISTRATION is created on the stack. Likewise, you might have a _try block nested within another _try block in a function. Still, Visual C++ creates only one EXCEPTION_REGISTRATION.
If a single exception handler (that is, __except_handler3) suffices for the whole EXE or DLL, and if a single EXCEPTION_REGISTRATION handles multiple _try blocks, there's obviously more going on here than meets the eye. This magic is accomplished through data in tables that you don't normally see. However, since the whole purpose of this article is to dissect structured exception handling, let's take a look at these data structures.


The Extended Exception Handling Frame
The Visual C++ SEH implementation doesn't use the raw EXCEPTION_REGISTRATION structure. Instead, it adds additional data fields to the end of the structure. This additional data is critical to allowing a single function (__except_handler3) to handle all exceptions and route control to the appropriate filter-expressions and _except blocks throughout the code. A hint to the format of the Visual C++ extended EXCEPTION_REGISTRATION structure is found in the EXSUP.INC file from the Visual C++ runtime library sources. In this file, you'll find the following (commented out) definition:



;struct _EXCEPTION_REGISTRATION{
; struct _EXCEPTION_REGISTRATION *prev;
; void (*handler)(PEXCEPTION_RECORD,
; PEXCEPTION_REGISTRATION,
; PCONTEXT,
; PEXCEPTION_RECORD);
; struct scopetable_entry *scopetable;
; int trylevel;
; int _ebp;
; PEXCEPTION_POINTERS xpointers;
;};



You've seen the first two fields, prev and handler, before. They make up the basic EXCEPTION_REGISTRATION structure. What's new are the last three fields: scopetable, trylevel, and _ebp. The scopetable field points to an array of structures of type scopetable_entries, while the trylevel field is essentially an index into this array. The last field, _ebp, is the value of the stack frame pointer (EBP) before the EXCEPTION_REGISTRATION was created.
It's not coincidental that the _ebp field becomes part of the extended EXCEPTION_REGISTRATION structure. It's included in the structure via the PUSH EBP instruction that most functions begin with. This has the effect of making all of the other EXCEPTION_REGISTRATION fields accessible as negative displacements from the frame pointer. For example, the trylevel field is at [EBP-04], the scopetable pointer is at [EBP-08], and so on.
Immediately below its extended EXCEPTION_REGISTRATION structure, Visual C++ pushes two additional values. In the DWORD immediately below, it reserves space for a pointer to an EXCEPTION_POINTERS structure (a standard Win32 structure). This is the pointer returned when you call the GetExceptionInformation API. While the SDK documentation implies that GetException Information is a standard Win32 API, the truth is that GetExceptionInformation is a compiler-intrinsic function. When you call the function, Visual C++ generates the following:


MOV EAX,DWORD PTR [EBP-14]



Just as GetExceptionInformation is a compiler intrinsic function, so is the related function GetExceptionCode. GetExceptionCode just reaches in and returns the value of a field from one of the data structures that GetExceptionInformation returns. I'll leave it as an exercise for the reader to figure out exactly what's going on when Visual C++ generates the following instructions for GetExceptionCode:


MOV EAX,DWORD PTR [EBP-14]
MOV EAX,DWORD PTR [EAX]
MOV EAX,DWORD PTR [EAX]



Returning to the extended EXCEPTION_REGISTRATION structure, 8 bytes before the start of the structure, Visual C++ reserves a DWORD to hold the final stack pointer (ESP) after all the prologue code has executed. This DWORD is the normal value of the ESP register as the function executes (except of course when parameters are being pushed in preparation for calling another function).
If it seems like I've dumped a ton of information onto you, I have. Before moving on, let's pause for just a moment and review the standard exception frame that Visual C++ generates for a function that uses structured exception handling:


EBP-00 _ebp
EBP-04 trylevel
EBP-08 scopetable pointer
EBP-0C handler function address
EBP-10 previous EXCEPTION_REGISTRATION
EBP-14 GetExceptionPointers
EBP-18 Standard ESP in frame



From the operating system's point of view, the only fields that exist are the two fields that make up a raw EXCEPTION_REGISTRATION: the prev pointer at [EBP-10] and the handler function pointer at [EBP-0Ch]. Everything else in the frame is specific to the Visual C++ implementation. With this in mind, let's look at the Visual C++ runtime library routine that embodies compiler level SEH, __except_handler3.


__except_handler3 and the scopetable
While I'd dearly love to point you to the Visual C++ runtime library sources and have you check out the __except_handler3 function yourself, I can't. It's conspicuously absent. In its place, you'll have to make due with some pseudocode for __except_handler3 that I cobbled together (see Figure 9).
While __except_handler3 looks like a lot of code, remember that it's just an exception callback like I described at the beginning of this article. It takes the identical four parameters as my homegrown exception callbacks in MYSEH.EXE and MYSEH2.EXE. At the topmost level, __except_handler3 is split into two parts by an if statement. This is because the function can be called twice, once normally and once during the unwind phase. The larger portion of the function is devoted to the non-unwinding callback.
The beginning of this code first creates an EXCEPTION_POINTERS structure on the stack, initializing it with two of the __except_handler3 parameters. The address of this structure, which I've called exceptPtrs in the pseudocode, is placed at [EBP-14]. This initializes the pointer that the GetExceptionInformation and GetExceptionCode functions use.
Next, __except_handler3 retrieves the current trylevel from the EXCEPTION_REGISTRATION frame (at [EBP-04]). The trylevel variable acts as an index into the scopetable array, which allows a single EXCEPTION_REGISTRATION to be used for multiple _try blocks within a function, as well as nested _try blocks. Each scopetable entry looks like this:




typedef struct _SCOPETABLE
{
DWORD previousTryLevel;
DWORD lpfnFilter
DWORD lpfnHandler
} SCOPETABLE, *PSCOPETABLE;


The second and third parameters in a SCOPETABLE are easy to understand. They're the addresses of your filter-expression and the corresponding _except block code. The previous tryLevel field is bit trickier. In a nutshell, it's for nested try blocks. The important point here is that there's one SCOPETABLE entry for each _try block in a function.
As I mentioned earlier, the current trylevel specifies the scopetable array entry to be used. This, in turn, specifies the filter-expression and _except block addresses. Now, consider a scenario with a _try block nested within another _try block. If the inner _try block's filter-expression doesn't handle the exception, the outer _try block's filter-expression must get a crack at it. How does __except_handler3 know which SCOPETABLE entry corresponds to the outer _try block? Its index is given by the previousTryLevel field in a SCOPETABLE entry. Using this scheme, you can create arbitrarily nested _try blocks. The previousTryLevel field acts as a node in a linked list of possible exception handlers within the function. The end of the list is indicated by a trylevel of 0xFFFFFFFF.
Returning to the __except_handler3 code, after it retrieves the current trylevel the code points to the corresponding SCOPETABLE entry and calls the filter- expression code. If the filter-expression returns EXCEPTION_CONTINUE_SEARCH, __except_handler3 moves on to the next SCOPETABLE entry, which is specified in the previousTryLevel field. If no handler is found by traversing the list, __except_handler3 returns DISPOSITION_CONTINUE_SEARCH, which causes the system to move on to the next EXCEPTION_REGISTRATION frame.
If the filter-expression returns EXCEPTION_EXECUTE_HANDLER, it means that the exception should be handled by the corresponding _except block code. This means that any previous EXCEPTION_REGISTRATION frames have to be removed from the list and the _except block needs to be executed. The first of these chores is handled by calling __global_unwind2, which I'll describe later on. After some other intervening cleanup code that I'll ignore for the moment, execution leaves __except_handler3 and goes to the _except block. What's strange is that control never returns from the _except block, even though __except_handler3 makes a CALL to it.
How is the current trylevel set? This is handled implicitly by the compiler, which performs on-the-fly modifications of the trylevel field in the extended EXCEPTION_REGISTRATION structure. If you examine the assembler code generated for a function that uses SEH, you'll see code that modifies the current trylevel at [EBP-04] at different points in the function's code.
How does __except_handler3 make a CALL to the _except block code, yet control never returns? Since a CALL instruction pushes a return address onto the stack, you'd think this would mess up the stack. If you examine the generated code for an _except block, you'll find that the first thing it does is load the ESP register from the DWORD that's 8 bytes below the EXCEPTION_REGISTRATION structure. As part of its prologue code, the function saves the ESP value away so that an _except block can retrieve it later.



The ShowSEHFrames Program
If you're feeling a bit overwhelmed at this point by things like EXCEPTION_REGISTRATIONs, scopetables, trylevels, filter-expressions, and unwinding, so was I at first. The subject of compiler-level structured exception handling does not lend itself to learning incrementally. Much of it doesn't make sense unless you understand the whole ball of wax. When confronted with a lot of theory, my natural inclination is to write code that applies the concepts I'm learning. If the program works, I know that my understanding is (usually) correct.
Figure 10 is the source code for ShowSEHFrames.EXE. It uses _try/_except blocks to set up a list of several Visual C++ SEH frames. Afterwards, it displays information about each frame, as well as the scopetables that Visual C++ builds for each frame. The program doesn't generate or expect any exceptions. Rather, I included all the _try blocks to force Visual C++ to generate multiple EXCEPTION_ REGISTRATION frames, with multiple scopetable entries per frame.
The important functions in ShowSEHFrames are WalkSEHFrames and ShowSEHFrame. WalkSEHFrames first prints out the address of __except_handler3, the reason for which will be clear in a moment. Next, the function obtains a pointer to the head of the exception list from FS:[0] and walks each node in the list. Each node is of type VC_EXCEPTION_REGISTRATION, which is a structure that I defined to describe a Visual C++ exception handling frame. For each node in the list, WalkSEHFrames passes a pointer to the node to the ShowSEHFrame function.
ShowSEHFrame starts by printing the address of the exception frame, the handler callback address, the address of the previous exception frame, and a pointer to the scopetable. Next, for each scopetable entry, the code prints out the previous trylevel, the filter-expression address, and the _except block address. How do I know how many entries are in a scopetable? I don't really. Rather, I assume that the current trylevel in the VC_EXCEPTION_REGISTRATION structure is one less than the total number of scopetable entries.
Figure 11 shows the results of running ShowSEHFrames. First, examine every line that begins with "Frame:". Notice how each successive instance shows an exception frame that's at a higher address on the stack. Next, on the first three Frame: lines, notice that the Handler value is the same (004012A8). Looking at the beginning of the output, you'll see that this 004012A8 is none other than the address of the __except_handler3 function in the Visual C++ runtime library. This proves my earlier assertion that a single entry point handles all exceptions.
You may be wondering why there are three exception frames using __except_handler3 as their callback since ShowSEHFrames plainly has only two functions that use SEH. The third frame comes from the Visual C++ runtime library. The code in CRT0.C from the Visual C++ runtime library sources shows that the call to main or WinMain is wrapped in an _try/_except block. The filter-expression code for this _try block is found in the WINXFLTR.C file.
Returning to ShowSEHFrames, the Handler for the last Frame: line contains a different address, 77F3AB6C. Poking around a bit, you'll find that this address is in KERNEL32.DLL. This particular frame is installed by KERNEL32.DLL in the BaseProcessStart function that I described earlier.



Unwinding
Let's briefly recap what unwinding means before digging into the code that implements it. Earlier, I described how potential exception handlers are kept in a linked list, pointed to by the first DWORD of the thread information block (FS:[0]). Since the handler for a particular exception may not be at the head of the list, there needs to be an orderly way of removing all exception handlers in the list that are ahead of the handler that actually deals with the exception.
As you saw in the Visual C++ __except_handler3 function, unwinding is performed by the __global_unwind2 RTL function. This function is just a very thin wrapper around the undocumented RtlUnwind API:



__global_unwind2(void * pRegistFrame)
{
_RtlUnwind( pRegistFrame,
&__ret_label,
0, 0 );
__ret_label:
}



While RtlUnwind is a critical API for implementing compiler-level SEH, it's not documented anywhere. While technically a KERNEL32 function, the Windows NT KERNEL32.DLL forwards the call to NTDLL.DLL, which also has an RtlUnwind function. I was able to piece together some pseudocode for it, which appears in Figure 12.
While RtlUnwind looks imposing, it's not hard to understand if you methodically break it down. The API begins by retrieving the current top and bottom of the thread's stack from FS:[4] and FS:[8]. These values are important later as sanity checks to ensure that all of the exception frames being unwound fall within the stack region.
RtlUnwind next builds a dummy EXCEPTION_RECORD on the stack and sets the ExceptionCode field to STATUS_UNWIND. Also, the EXCEPTION_UNWINDING flag is set in the ExceptionFlags field of the EXCEPTION_RECORD. A pointer to this structure will later be passed as a parameter to each exception callback. Afterwards, the code calls the _RtlpCaptureContext function to create a dummy CONTEXT structure that also becomes a parameter for the unwind call of the exception callback.
The remainder of RtlUnwind traverses the linked list of EXCEPTION_REGISTRATION structures. For each frame, the code calls the RtlpExecuteHandlerForUnwind function, which I'll cover later. It's this function that calls the exception callback with the EXCEPTION_UNWINDING flag set. After each callback, the corresponding exception frame is removed by calling RtlpUnlinkHandler.
RtlUnwind stops unwinding frames when it gets to the frame with the address that was passed in as the first parameter. Interspersed with the code I've described is sanity-checking code to ensure that everything looks okay. If some sort of problem crops up, RtlUnwind raises an exception to indicate what the problem was, and this exception has the EXCEPTION_NONCONTINUABLE flag set. A process isn't allowed to continue execution when this flag is set, so it must terminate.


Unhandled Exceptions
Earlier in the article, I put off a full description of the UnhandledExceptionFilter API. You normally don't call this API directly (although you can). Most of the time, it's invoked by the filter-expression code for KERNEL32's default exception callback. I showed this earlier in the pseudocode for BaseProcessStart.
Figure 13 shows my pseudocode for UnhandledExceptionFilter. The API starts out a bit strangely (at least in my opinion). If the fault is an EXCEPTION_ACCESS_ VIOLATION, the code calls _BasepCheckForReadOnlyResource. While I haven't provided pseudocode for this function, I can summarize it. If the exception occurred because a resource section (.rsrc) of an EXE or DLL was written to, _BasepCurrentTopLevelFilter changes the faulting page's attributes from its normal read-only state, thereby allowing the write to occur. If this particular scenario occurs, UnhandledExceptionFilter returns EXCEPTION_ CONTINUE_EXECUTION and execution restarts at the faulting instruction.
The next task of UnhandledExceptionFilter is to determine if the process is being run under a Win32 debugger. That is, the process was created with the DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS flag. UnhandledExceptionFilter uses the NtQueryInformationProcess Function that I describe in this month's Under the Hood column to tell if the process is being debugged. If so, the API returns EXCEPTION_CONTINUE_SEARCH, which tells some other part of the system to wake up the debugger process and tell it that an exception occurred in the debuggee.
Next on UnhandledExceptionFilter's plate is a call to the user-installed unhandled exception filter, if present. Normally, there isn't a user-installed callback, but one can be installed via the SetUnhandledExceptionFilter API. I've also provided pseudocode for this API. The API simply bashes a global variable with the new user callback address, and returns the value of the old callback.
With the preliminaries out of the way, UnhandledExceptionFilter can get down to its primary job: informing you of your ignominious programming blunder with the ever- stylish Application Error dialog. There are two ways that this dialog can be avoided. The first is if the process has called SetErrorMode and specified the SEM_NOGPFAULTERRORBOX flag. The other method is to have the Auto value under the AeDebug registry key set to 1. In this case, UnhandledExceptionFilter skips the Application Error dialog and automatically fires up whatever debugger is specified in the Debugger value of the AeDebug key. If you're familiar with "just in time debugging," this is where the operating system supports it. More on this later.
In most cases, neither of these dialog avoidance conditions are true and UnhandledExceptionFilter calls the NtRaiseHardError function in NTDLL.DLL. It's this function that brings up the Application Error dialog. This dialog waits for you to hit the OK button to terminate the process, or Cancel to debug it. (Maybe it's just me, but hitting Cancel to launch a debugger seems a little backward.)
If you hit OK in the Application Error dialog box, UnhandledExceptionFilter returns EXCEPTION_EXECUTE_HANDLER. The code that called UnhandledExceptionFilter usually responds by terminating itself (as you saw in the BaseProcessStart code). This brings up an interesting point. Most people assume that the system terminates a process with an unhandled exception. It's actually more correct to say that the system sets up things so that an unhandled exception causes the process to terminate itself.
The truly interesting code in UnhandledExceptionFilter executes if you select Cancel in the Application Error dialog, thereby bringing up a debugger on the faulting process. The code first calls CreateEvent to make an event that the debugger will signal after it has attached to the faulting process. This event handle, along with the current process ID, is passed to sprintf, which formats the command line used to start the debugger. Once everything is prepared, UnhandledExceptionFilter calls CreateProcess to start the debugger. If CreateProcess succeeds, the code calls NtWaitForSingleObject on the event created earlier. This call blocks until the debugger process signals the event, indicating that it has attached to the faulting process successfully. There are other little bits and pieces to the UnhandledExceptionFilter code, but I've covered the important highlights here.



Into the Inferno
If you've made it this far, it wouldn't be fair to finish without completing the entire circuit. I've shown how the operating system calls a user-defined function when an exception occurs. I've shown what typically goes on inside of those callbacks, and how compilers use them to implement _try and _catch. I've even shown what happens when nobody handles the exception and the system has to do the mopping up. All that remains is to show where the exception callbacks originate from in the first place. Yes, let's plunge into the bowels of the system and see the beginning stages of the structured exception handling sequence.
Figure 14 shows some pseudocode I whipped up for KiUserExceptionDispatcher and some related functions. KiUserExceptionDispatcher is in NTDLL.DLL and is where execution begins after an exception occurs. To be 100 percent accurate, what I just said isn't exactly true. For instance, in the Intel architecture an exception causes control to vector to a ring 0 (kernel mode) handler. The handler is defined by the interrupt descriptor table entry that corresponds to an exception. I'm going to skip all that kernel mode code and pretend that the CPU goes straight to KiUserExceptionDispatcher upon an exception
The heart of KiUserExceptionDispatcher is its call to RtlDispatchException. This kicks off the search for any registered exception handlers. If a handler handles the exception and continues execution, the call to RtlDispatchException never returns. If RtlDispatchException returns, there are two possible paths: either NtContinue is called, which lets the process continues, or another exception is raised. This time, the exception isn't continuable, and the process must terminate.
Moving on to the RtlDispatchExceptionCode, this is where you'll find the exception frame walking code that I've referred to throughout this article. The function grabs a pointer to the linked list of EXCEPTION_REGISTRATIONs and iterates over every node, looking for a handler. Because of the possibility of stack corruption, the routine is very paranoid. Before calling the handler specified in each EXCEPTION_REGISTRATION, the code ensures that the EXCEPTION_REGISTRATION is DWORD-aligned, within the thread's stack, and higher on the stack than the previous EXCEPTION_REGISTRATION.
RtlDispatchException doesn't directly call the address specified in the EXCEPTION_REGISTRATION structure. Instead, it calls RtlpExecuteHandlerForException to do the dirty work. Depending on what happens inside RtlpExecuteHandlerForException, RtlDispatchException either continues walking the exception frames or raises another exception. This secondary exception indicates that something went wrong inside the exception callback and that execution can't continue.
The code for RtlpExecuteHandlerForException is closely related to another function, RtlpExecutehandlerForUnwind. You may recall that I mentioned this function earlier when I described unwinding. Both of these "functions" simply load the EDX register with different values before sending control to the ExecuteHandler function. Put another way, RtlpExecuteHandlerForException and RtlpExecutehandlerForUnwind are separate front ends to a common function, ExecuteHandler.
ExecuteHandler is where the handler field from the EXCEPTION_REGISTRATION is extracted and called. Strange as it may seem, the call to the exception callback is itself wrapped by a structured exception handler. Using SEH within itself seems a bit funky but it makes sense if you ponder it for a moment. If an exception callback causes another exception, the operating system needs to know about it. Depending on whether the exception occurred during the initial callback or during the unwind callback, ExecuteHandler returns either DISPOSITION_NESTED_ EXCEPTION or DISPOSITION_COLLIDED_UNWIND. Both are basically "Red Alert! Shut everything down now!" kind of codes.
If you're like me, it's hard to keep all of the functions associated with SEH straight. Likewise, it's hard to remember who calls who. To help myself, I came up with the diagram shown in Figure 15.
Now, what's the deal with setting EDX before getting to the ExecuteHandler code? It's simple, really. ExecuteHandler uses whatever's in EDX as the raw exception handler if something goes wrong while calling the user-installed handler. It pushes the EDX register onto the stack as the handler field for a minimal EXCEPTION_REGISTRATION structure. In essence, ExecuteHandler uses raw structured exception handling like I showed in the MYSEH and MYSEH2 programs.



Conclusion
Structured exception handling is a wonderful Win32 feature. Thanks to the supporting layers that compilers like Visual C++ put on top of it, the average programmer can benefit from SEH with a relatively small investment in learning. However, at the operating system level, things are more complicated than the Win32 documentation would lead you to believe.
Unfortunately, not much has been written about system-level SEH to date because almost everyone considers it an extremely difficult subject. The lack of documentation on the system-level details hasn't helped. In this article, I've shown that system-level SEH revolves around a relatively simple callback. If you understand the nature of the callback, and then build additional layers of understanding on top of that, system-level structured exception handling really isn't so hard to grasp.




From the January 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.



--------------------------------------------------------------------------------

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.


Manage Your Profile |Legal |Contact us |MSDN Flash Newsletter
© 2008 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy Statement (Updated)

2008年8月2日星期六

mac系统

无图版
» 您尚未 登录 注册 | 搜索 | 社区服务 | 资讯站 | 软件站
社区服务
银行
勋章中心
道具中心
彩票中心
论坛FTP
系统之家论坛 » MAC OS X » 【教学】【转贴】手把手教你在 PC上安装MAC OS. 包括基本软件与双系统使用

« 1 2345» Pages: ( 1/25 total )

投 票
本页主题: 【教学】【转贴】手把手教你在 PC上安装MAC OS. 包括基本软件与双系统使用 打印 | 加为IE收藏 | 收藏主题 | 上一主题 | 下一主题





pengdongdd



做老爸真的很幸福!!

级别: 版主
精华: 1
发帖: 1682
威望: 738 点
黄金: 99 金币
功勋: 2 点
在线时间:1053(小时)
注册时间:2007-04-04
最后登录:2008-08-02
【教学】【转贴】手把手教你在 PC上安装MAC OS. 包括基本软件与双系统使用
摘要:4月初,苹果公司出人意料地发布了一款名为“BootCamp”的小软件,使用它可以在苹果笔记本上安装Windows XP,并使之与Mac OS X共存,这让更多想拥有苹果却担心使用问题的用户雀跃欢呼。下面,让我们一起看看,如何在PC上安装好看并且好用的Mac OS X系统吧。

进入2006年,随着采用英特尔处理器的全新iMac、Mac mini以及MacBook的全面上市,Mac和PC在硬件上的差异已经越来越少,二者软件的互通也越来越清晰。4月初,苹果公司出人意料地发布了一款名为“BootCamp”的小软件,使用它可以在苹果笔记本上安装Windows XP,并使之与Mac OS X共存。这一软件的发布引起了轩然大波,一方面让Mac OS X的忠诚Fans痛斥苹果的“变节”,另一方面却让更多想拥有苹果却担心使用问题的用户雀跃欢呼。

苹果与PC之间的大门打开了,不过它是单向的,苹果用户可以使用Windows系统,却没有为PC用户发布Mac OS X系统。值得庆幸的是,有人对最新的Mac OS X 10.4.6的安装程序进行了修改,使它能够安装在普通PC上,而且它的运行效率已经和采用英特尔处理器的Mac计算机没有多大的差距,甚至可以使之作为一些人的主要操作系统了。

下面,让我们一起看看,如何在PC上安装好看并且好用的Mac OS X系统吧。

Part1 刻录和分区,安装Mac OS X前的准备工作

首先当然需要一台电脑,而且这台电脑的配置不能太低,运行Windows XP都不流畅的电脑肯定玩不转Mac OS X。有一个硬性的要求就是,这台PC的处理器必须支持SSE2或SSE3指令集,不过诸如Pentium 4、Pentium M或基于二者的Celeron处理器以及Sempron和Athlon 64处理器都符合这一要求。也就是说,2005年以来的主流电脑基本上都能安装。

除此之外,内存最少要求256MB、硬盘最少要有4GB,如果要从光盘进行安装的话,还需要一台DVD光驱。如果使用的是nVIDIA的显卡,那么暂时没有适用的驱动,在使用过程中可能会遇到一些小问题。

安装分为两种情况:一种是在全新硬盘上安装,另一种是在已经安装了Windows系统的硬盘上进行安装。安装方式也分为两种,一种是利用光盘进行安装,另一种是通过虚拟机进行安装,前者需要刻录DVD光盘但是速度较快,后者不用刻录光盘但是速度比较慢,并且需要安装虚拟机软件,因此并不推荐。本文以通过刻录好的DVD光盘在已经安装有Windows操作系统的硬盘上进行安装为例进行说明。

1.刻录光盘。从网络上下载到的通常是一个ISO格式光盘镜像文件,注意检查相应的版本说明,尽量选择最新的版本,因为越新的版本集成的补丁越多,安装时出现错误的概率也越小。然后使用Nero之类的刻录软件直接用刻录镜像的方式将其刻录成光盘即可。

曾经有人说使用可擦写的光盘不能进行安装,可是笔者使用DVD+RW顺利完成了安装而且至今使用正常,倒是有一张DVD+R光盘不能进行安装,因此如果你在安装过程中提示光盘出错,那么不妨换一种光盘重新刻录试试。

2.硬盘分区。Mac OS X的一个“另类”之处在于它使用的是HFS+分区格式,Windows和Linux都不支持这种分区,甚至连一些常用的磁盘工具也不能识别它们,因此必须在安装之前为Mac OS X准备好可用的分区。

已经安装好系统的硬盘一般都没有可用空间了,我们可以将其他分区的可用空间集中到一起,并建立一个新分区。这个过程可以用PQ Magic、Acronis Disk Director来完成,不过更推荐使用Partition Manager(下载地址:http://www.onlinedown.net/soft/25982.htm),因为它还可以完成下面必须的另一步操作,而其他软件大都不能同时完成两个任务。
具体操作方法为:运行Partition Manager,选中已经存在的分区,点击工具栏上的“调整大小”按钮,将分区的空间缩小。注意缩小的时候要尽量将可用空间留在靠前的位置,即用鼠标从左往右拖动游标(如图1)。挪出几处可用空间后,再将已有的分区向后移动,将可用空间集成到一起,等可用空间到6GB以上时,建立一个分区。此时这个分区应该还是逻辑分区,将它转换成主分区(如图2)。


图1 分离出一块空的硬盘空间


图2 将目标分区设置为主分区


之所以进行这一步操作,是因为当分区起始位置超过硬盘的前1024柱面后有可能会出现不能引导的情况。不过此时的分区格式还不能为Mac OS X所识别,需要进行进一步修改。右击新建好的主分区,选择“修改”、“更改分区ID”,然后在弹出的窗口中输入“af”(不要引号),此时这个分区就变成Mac OS X使用的HFS+格式了(如图3)。

图3 在系统ID处输入AF




将Mac OS X安装光盘放入光驱,做好正式安装的准备吧!

Part2 按部就班,系统安装要注意驱动问题

启动电脑,设置为由光盘启动,如果引导成功的话会出现输入引导参数的提示,几秒钟后自动进入安装程序。

安装所需的时间取决于电脑的配置及光驱的速度。笔者使用的笔记本配置为:Celeorn M 370(1.5GHz/2MB缓存)、1GB内存、80GB 7200转硬盘、8X康宝驱动器、Buffalo WLI-G54无线网卡,整个安装过程耗时约30分钟,比安装Windows XP略快一些。

刚进入安装程序后屏幕会显示出一个圆形的旋转光标,此时不能进行任何操作,光驱读取大概3分钟后才会正式进入安装界面,所以在这个过程中不要着急。

第一步是要求用户选择安装程序的语言(如图4)。我们可以看到Mac OS X支持包括简体中文和繁体中文在内的15种语言,而相比之下微软则为每一种语言的Windows制作了不同的安装程序。另外一个很明显的不同就是安装Mac OS X并不需要序列号。产生这种差异的原因我们已经提过,那是因为苹果靠硬件赚钱而微软靠的就是软件,所以还是可以理解的。需要说明的是,在此处选择的语言只是安装过程中所使用,如果你希望安装好的系统使用英文或者其他语言,可以随时进行修改。


图4 选择安装程序的语言


接下来是对我们前面为Mac OS X准备好的分区进行格式化。打开顶部的“实用工具”菜单,然后选择“磁盘工具”,就会弹出一个新的窗口(如图5)。这个窗口左侧会显示出硬盘已有的分区,所有分区都会显示为灰色。如果前面的操作正确的话,点击第二个分区(可能会显示为disk0s2),底部的信息栏中会显示其分区格式为“Mac OS扩展(日志式)”,选中它之后在右侧的窗口中选择“抹掉”,然后确认后完成操作并返回安装程序(如图6)。

图5 调出“磁盘工具”

图6 “抹掉”磁盘分区

这里需要说明的是,Mac OS X可以读取NTFS分区但不能进行写操作,如果想和Windows系统进行数据交换的话,最好将一个NTFS分区转换为FAT32分区,或者在Windows系统中安装MacDrive来对HFS+分区进行读写操作(后文会进行详细介绍)。
接下来就是许可协议了,主要是对软件的使用权限进行描述,直接点击同意即可。再接着是选择目标磁盘,直接选择刚才已经格式化好的分区即可(如图7)。
图7 选择安装的目标分区


由于前面的操作基本上都是一路回车,所以到了这一步也很容易让人产生错觉,笔者就因此而多做了两次无用功,安装好的系统根本无法启动。在安装类型选择界面上,点击下方左侧的“自定”按钮,会弹出一个组件列表供选择安装。

在这个列表中,“打印机驱动程序”是可以选择安装的,如果全部不安装的话可以节省大约2GB硬盘空间;“本地化语言”中保留的语言可以供你在安装好系统后选择使用,一般情况下保留简体中文即可;“X11”对于普通用户完全可以不安装,如此一来比默认的安装方式可以省下2GB左右空间,以后在它上面安装其他应用软件也不用为硬盘发愁了(如图8)。

图8 修改要安装的程序列表

保证系统可以运行的关键一步是在这个列表中选择合适的补丁。使用英特尔或AMD处理器要分别选择对应的补丁,此外还有针对GMA900显示芯片、创新USB声卡、Radeon Mobility 显示芯片、VIA与ATI的ATA总线以及无线网卡等设备的补丁,如果使用了相应的设备也一定要勾选上,否则以后再修改时会麻烦许多倍(如图9)。
图9 选择针对硬件的软件包(补丁)




现在越来越多的电脑(尤其是笔记本电脑)都标配了无线网卡,不过只有采用Brodcomm芯片的无线网卡才可以在Mac OS X系统中正常使用,如果是其他芯片的话就不得不拉根网线才能上网了。

一切设置妥当后就可以点击“安装”按钮了。苹果还会正式安装之前对安装光驱进行检查,这一步会耗费很长的时间,如果你确认自己刻录的光盘没有问题的话,可以直接跳过进入复制文件的环节。复制速度视电脑配置而定,在笔者的笔记本上大约用了15分钟。

文件复制完成后,系统还会花大约5分钟来进行一次最后的优化,然后系统会进行重启,整个安装过程到这一步就算是真正完成了。另外需要提醒一下的是,在重新启动之前记得把光盘拿出来,否则可能会忽略一些存在的问题。

Part3 注册账户,成功登录Mac OS X

Mac OS X成功完成安装,在重启后会播放一段听起来振奋人心的音乐,而且在这个过程中无法实现静音,如果是在晚上进行安装的话要做好心理准备哦^_^。

音乐过后开始设置账户信息。系统首先会询问用户的国家信息,点击“Show All”后才会显示出China;然后会提示是否需要从另一台Mac计算机传输账户信息,选择第三项“Do not transfer my information”以后点击继续;至于键盘布局,选择默认的英文(U.S.)即可。

如果你安装了无线网络补丁并且生效的话,系统会显示出已经检测到的无线网络并要求你进行设置,比如连接到哪个热点以及WEP密码等。

接下来是Apple ID窗口,不用输入任何内容直接点击继续;注册信息一栏中,除了First Name和Last Name之外其他的信息都可以任意填写,不过任何一栏都不能为空;再下来苹果还会询问用户的身份职业情况,去掉下面的复选框后继续,就会进入建立账户界面(如图10)。需要提醒的是,这个密码一定要记住,以后在安装软件和进行系统设置时经常会用到(如果忘记的话也可以通过安装光盘来修改)。


图10 建立用户帐户


最后苹果还会问你是否需要购买.Mac服务,两个对话框中都直接选择最后一项不购买,设置过程就算告一段落了。看看Mac OS X清爽的界面,是不是非常有成就感呢(如图11)?

图11 终于进入了Mac OS X的主界面



正式进入系统后你会发现默认是英文界面,不要着急,前面我们已经说过,Mac OS X可以随意修改语言,点击屏幕下方的Dock中从右向左数第三个图标,会打开系统预置窗口(如图12);然后图标第一行的International图标打开区域设置窗口,从左侧的列表中将简体中文拖动到最上方(如图13),点击屏幕左上角的苹果菜单,注销后就会变成亲切的简体中文界面了

图12 调出系统预置窗口

图13 将“简体中文”拖到最上方





Mac OS X中的许多设置都是即时生效,在设置窗口中甚至没有“确定”按钮,这对于习惯了Windows的用户可能需要一段时间来适应。

Part4 启动设置,让WinXP和Mac随意切换

到此为止,我们的硬盘上已经有了Windows和Mac OS X两个操作系统,不过由于两个系统处在两个不同的硬盘分区中,而只有一个分区可以设为激活并引导系统,如果每次想切换系统都修改激活分区的话,岂不是太麻烦了吗?

不要着急,通往罗马的路当然不止一条。如果你很少使用Mac OS X,那么可以进入Windows系统,将Mac OS X安装光盘上的Chain0这个文件复制到C盘根目录,然后用记事本打开boot.ini文件(默认属性为隐藏),在启动菜单[operating systems]字段下加入一行:

C:\Chain0="Mac OS X"

引号内的内容可以自行设置,在Windows XP启动时会显示出一个菜单供用户选择。不过使用这种方法的缺陷在于,在这个启动菜单中选择了Mac OS X之后,Mac OS X还会再出一个菜单需要选择,操作上自然是麻烦了一点。

如果你对Mac OS X比较满意想让它留在硬盘上与Windows共同为你服务,而且想一劳永逸的话,就需要使用第三方软件来配合了,在众多多重引导软件中,笔者推荐OS Selector(下载地址:http://www.onlinedown.net/soft/25934.htm)。OS Selector是Acronis Disk Director软件中的一个组件,最多支持在同一台电脑上安装100个以上的操作系统(不过估计很少有人会这么做),安装过程非常简单,只需一路回车即可。

运行OS Selector后它会自动搜索硬盘上已经存在的操作系统,并显示在图形化的列表中。尽管它可能不能识别这个修改版本的Mac OS X,将它显示为Unknown(如图14),但是并不影响它的正常启动,选择这个Unknown的系统仍然可以正常引导Mac OS X。选中Unknown系统的图标,然后在左侧的任务列表中选择“Properties”,然后在弹出的窗口中对这个系统进行重命名及更改图标等操作。可惜的是,OS Selector默认的图标库中并不包含Mac OS X的图标,如果想追求完美的话可以自行修改它的安装文件,这些图标都是以PNG格式存储在安装包中的。

图14 OS Selector将Mac OS X识别为“Unknown”


OS Selector默认的显示分辨率为800X600,在CRT上显示的效果尚可,可是到了液晶显示器上面就差强人意了。在OS Selector主界面的工具栏中找到Option图标并点击,然后选择“Display Properties”(显示属性),在右侧设置适合显示器的分辨率及色深后应用,这样看起来就非常舒服了(如图15)。
图15 设置OS Selector的显示选项

前面提到,Mac OS X不能对NTFS分区进行写操作,但是可以对FAT32分区进行读写。如果你担心在Mac OS X下操作时会因为误操作伤及FAT32分区上的东西,那么可以在OS Selector中为不同的系统设置不同的分区隐藏选项。方法是选中系统,然后点击“Properties”,然后在左侧选择“Partitions”,在右侧的窗口中就能对分区设置隐藏了(如图16)。
图16 为不同的系统设置不同的分区隐藏选项



OS Selector有三种工作方式,一般情况下建议使用延时模式。进入OS Selector选项设置窗口,在左侧选择“Startup Options”,在右侧选择“With timeout sec”(如图17),然后返回主界面选择一个系统作为默认引导项。
图17 填写延时启动的时间





Part5 利用软件,实现WinXP和Mac OS的数据互访

Mac OS X本身就可以识别Windows XP所使用的FAT32和NTFS分区,区别在于对NTFS只能读取而FAT32可以读写操作,所以在Mac OS X下基本上不存在访问Windows数据的问题。

然而反过来,要在Windows系统下访问Mac OS X分区的文件,则必须借助第三方软件(如前面提到的MacDrive,下载地址http://www.onlinedown.net/soft/31914.htm)来实现。虽然一般情况下没有这种需求,但是在对系统进行修改时,使用这种方式却比使用Mac OS X的字符界面终端要直观方便许多。

安装MacDrive后会要求重新启动以加载驱动程序,重启后会在系统托盘中显示一个图标,点击后即可弹出MacDrive的向导式主界面(图18)。在这个界面上点击“View Mac Disks in My Computer”会在“我的电脑”中打开一个HFS+分区窗口,“Burn a Mac CD or DVD”用来刻录苹果格式的光盘;“Partition a Mac hard disk”可以设置HFS+分区,“Change MacDrive Options”则可以对MacDrive软件本身进行一些设置。
图18 用MacDrive来让Windows访问Max OS X的分区


有时候虽然加载了MacDrive驱动,但是Windows XP并不会将HSF+分区显示在“我的电脑”列表中,这时我们可以使用Partition Manager对该分区进行加载。需要注意的是,MacDrive加载硬盘后默认可以进行写操作,在进行删除等操作时要小心不要破坏了HFS+分区的数据,否则可能造成Mac OS X系统不能引导。

Part6 取代Windows?Mac OS X的实用性分析

一直以来,人们不接受Mac OS X的主要原因都是可用的软件太少。这一局面到了今天已经大为改观,Adobe与前Macromedia公司的系列软件一直就有Mac版本,微软也为Mac平台发布了专门的Office软件(不过在文本样式方面还没有做到100%兼容),至于邮件收发、音乐播放等方面,Mac OS X的iMail、iTunes比之Windows平台下的软件有过之而无不及(如图19)。

图19 在Mac OS X中收发邮件




对于大部分人来讲,QQ、MSN、Yahoo通和Google Talk等即时交流工具比大型软件更为重要,而这些问题在修改版的Mac OS X上也得到了很好的解决。比如登录腾讯QQ可以使用iQQ、登录其他IM软件可以使用Adium,其功能及易用性都是非常出色。

Adium是一款集多种协议于一身的IM软件,通过它可以在Mac平台上使用诸如MSN、ICQ、雅虎通等多种平台,而且可以将各平台的联系人集中在一起,使用起来非常方便(如图20)。使用iQQ时则要注意下载最新的Universal版本(即兼容使用PowerPC处理器和英特尔处理器的Mac系统),目前惟一的两个不足之处是不能使用自定义表情和传输文件,不过并不算是太大的问题(如图21)。至于下载工具,无论是http、ftp、BT还是eMule,都有比较好用的软件来代替
图20 Adium集成了多项聊天功能



图21 iQQ简介的界面,是不是想起了曾经界面清爽的QQ?



iQQ的下载地址是:http://lumaqq.linuxsir.org/main/?q=node/95,Adium的下载地址是:http://www.macosx.com.cn/104/ 308.html。


另本人推荐如安装XP,MAC双系统请使用Tboot这个引导,用法很简单,把tboot拷到C盘的根目录下,在Boot.ini 里加上一句 c:\tboot = "Mac OSX 86" 保存好就行了,但是要保证Mac是安装到逻辑分区的第一个分区。
如安装Vista,Mac的话请使用easybcd这个软件来修改引导。
另Mac OSX86 10.4.8 Jas破解版(带PPE1)光盘镜像在论坛FTP已经有的下了


[ 此贴被pengdongdd在2007-10-11 09:31重新编辑 ]

CPU: Intel E6750 2.66GHz
主 板: gigabyte P35-DS4 all work
内 存: A-DATA 红龙DDRII800 2GB×2
显 卡: 影驰 8800GS 384MB QE,CI work
系 统: Dell Vista,Leopard,Tiger
硬 盘: ST3500320AS for Mac,WD2500YS for Vista

Posted: 2007-05-22 15:03 | [楼 主]

免注册






级别: 火炎天使
精华: 0
发帖: 689
威望: 439 点
黄金: 17 金币
功勋: 0 点
在线时间:8(小时)
注册时间:2006-12-14
最后登录:2008-07-02
不错,感谢提供。
Posted: 2007-05-24 10:46 | 1 楼

yongdiz






级别: 死亡天使
精华: 0
发帖: 385
威望: 172 点
黄金: 318 金币
功勋: 0 点
在线时间:106(小时)
注册时间:2006-11-14
最后登录:2008-07-29
喜欢它的界面。
Posted: 2007-05-25 00:52 | 2 楼

沙漠之泪






级别: 乐园天使
精华: 0
发帖: 83
威望: 30 点
黄金: 32 金币
功勋: 0 点
在线时间:59(小时)
注册时间:2006-01-28
最后登录:2008-08-02
界面不错,我喜欢,学习了
Posted: 2007-05-25 10:36 | 3 楼

spoor






级别: 乐园天使
精华: 0
发帖: 31
威望: 10 点
黄金: 40 金币
功勋: 0 点
在线时间:0(小时)
注册时间:2007-04-30
最后登录:2008-03-16
学习了!
很想安装!
Posted: 2007-05-26 11:44 | 4 楼

幽冥公子




级别: 守护天使
精华: 0
发帖: 242
威望: 68 点
黄金: 2 金币
功勋: 0 点
在线时间:53(小时)
注册时间:2007-04-30
最后登录:2008-04-18
很是谢谢楼主啊!
找了很久1

给我一张毛片,我能撬起整个地球......

Posted: 2007-05-29 14:51 | 5 楼

baishancuen






级别: 乐园天使
精华: 0
发帖: 49
威望: 29 点
黄金: 1 金币
功勋: 0 点
在线时间:10(小时)
注册时间:2006-09-14
最后登录:2008-07-26
怎么教程中间有一段是重复的啦?是不是想让我们多看几次好让我们记得注,老大你真幽默。

田地里辛勤耕耘的农民

Posted: 2007-05-30 20:19 | 6 楼

hnhytank






级别: 乐园天使
精华: 0
发帖: 50
威望: 29 点
黄金: 86 金币
功勋: 0 点
在线时间:4(小时)
注册时间:2007-05-30
最后登录:2008-07-22
好东西,不知道对计算机有什么要求?
Posted: 2007-05-31 18:38 | 7 楼

xiangyu






级别: 实习天使
精华: 0
发帖: 8
威望: 5 点
黄金: 3 金币
功勋: 0 点
在线时间:0(小时)
注册时间:2006-06-23
最后登录:2008-05-19
我觉得,如果只是喜欢界面的话还不如安装个美化的主题。
Posted: 2007-06-01 10:36 | 8 楼

hycad






级别: 乐园天使
精华: 0
发帖: 37
威望: 22 点
黄金: 7 金币
功勋: 0 点
在线时间:0(小时)
注册时间:2006-04-10
最后登录:2008-03-18
不错,苹果电脑没钱买,并且也不懂得怎么用
Posted: 2007-06-02 14:31 | 9 楼

« 1 2345» Pages: ( 1/25 total ) 快速跳至 |- IT前沿 |- 集成光盘 |- 集成制作交流 |- nLite/vLite讨论 |- 系统评测 |- 快速装机 |- 亮的精简版 |- 无限的未知 |- 海上生明月 |- MAC OS X |- Unix/Linux >> 资源天下 |- 软件精华 |- 绿色软件 |- 美化资源 |- 手机PDA |- 免费资源 |- 交易资源 |- 精品资源 |- 我要上传 >> 技术资讯 |- 求助解答 |- 技术交流 |- Vista专题 |- Server 2008 |- 服务器技术 |- 硬件交流 |- 软件点评 |- 认证考试 |- 数据库技术 >> 休闲娱乐 |- 休闲驿站 |- 靓图共赏 |- 影视交流 |- 音乐Zone |- 动漫游戏 |- 体育沙龙 >> 站务管理 |- 站务公告 |- 违规处理 |- 申请审核 |- 建议交流 |- 投诉举报 |- 回收中心 |- 论坛议会
系统之家论坛 » MAC OS X


文章版权归作者所有,系统之家拥有展示权!网友言论不代表本站观点
本站资料均为网友推荐发布而来,如有侵犯您的版权,请联系我们
Copyright © 2004-08 PHPWind Code 系统之家

Total 0.018982(s) query 4, Gzip enabled 辽ICP备05013013号 Contact us | Wap | Top