Section 6.5. Exception Processing


6.5. Exception Processing

Figure 69 shows a high-level view of exception processing. Recall from earlier discussion that the vectors reside in physical memory starting at location 0x0. Consider the example of an instruction access exception, which is caused when the effective address of an instruction fails to translate to a virtual address. As listed in Table 51, the vector offset for this exception is 0x400. Consequently, the processor executes the code at physical address 0x400 to handle this exception. Most exception handlers simply save GPR13 and GPR11, set an interrupt code in GPR11, and jump to .L_exception_entry() [osfmk/ppc/lowmem_vectors.s] for further processing. For some exceptions, such as system reset (0x100), system call (0xC00), and trace (0xD00), the first-level exception handlers perform more work. Nevertheless, there exists a code path from each handler to .L_exception_entry().

Figure 69. A high-level view of exception processing in the xnu kernel


Figure 610 shows the code structure of .L_exception_entry(). It first saves a variety of contextan operation whose implementation is different for 32-bit and 64-bit processors. The address labeled as extPatch32 contains an unconditional branch instruction to 64-bit-specific code. This branch must not be taken on a 32-bit processorinstead, execution should continue with the 32-bit code that follows this instruction. As we saw in Chapter 5, the kernel performs boot-time memory patching of instructions and data. In this case, the kernel would replace the unconditional branch with a no-op instruction at boot time on a 32-bit processor.

Figure 610. Common code for exception processing

; osfmk/ppc/lowmem_vectors.s ; Caller's GPR13 is saved in SPRG2 ; Caller's GPR11 is saved in SPRG3 ; Exception code is in GPR11 ; All other registers are live ; Interrupts are off ; VM is off ; In 64-bit mode, if supported ; .L_exception_entry:             .globl EXT(extPatch32) LEXT(extPatch32)             b extEntry64 ; Patched to a no-op if 32-bit             ...             ; 32-bit context saving             ...             b xcpCommon  ; Join common interrupt processing             ; 64-bit context saving extEntry64:             ...             b xcpCommon  ; Join common interrupt processing             ; All of the context is now saved             ; We will now get a fresh save area             ; Thereafter, we can take an interrupt xcpCommon:             ...             ; Save some more aspects of the context, such as some             ; floating-point and vector status             ...             ; Done saving all of the context             ; Start filtering the interrupts Redrive:             ...             ; Use the exception code to retrieve the next-level exception             ; handler from xcpTable             ...             ; Load the handler in CTR             ...             bctr ; Go process the exception

Once .L_exception_entry() has saved all the context, it refers to an exception vector filter table called xcpTable, which too is defined in osfmk/ppc/lowmem_vectors.s and resides in low memory (the first 32KB of physical memory). The common exception-handling code in .L_exception_entry() uses the incoming exception code to look up the handler in the filter table, after which it branches to the handler. Table 69 lists the exception handlers corresponding to the various exception codes set by the exception vectors. For example, the codes T_INTERRUPT (vector offset 0x500), T_DECREMENTER (vector offset 0x900), T_SYSTEM_MANAGEMENT (vector offset 0x1400), and T_THERMAL (vector offset 0x1700) are all channeled to code labeled as PassUpRupt, which leads to a higher-level interrupt handler. Similarly, traps (various exception codes) and system calls (the T_SYSTEM_CALL exception code) are channeled to the PassUpTrap and xcpSyscall labels, respectively. Figure 611 depicts a simplified view of the processing of traps, interrupts, and system calls.

Table 69. Table-Driven Exception Filtering

Exception Code

Handler

Notes

T_IN_VAIN

EatRupt

Restore state, return from interrupt

T_RESET

PassUpTrap

Handled by thandler() [osfmk/ppc/hw_exception.s]

T_MACHINE_CHECK

MachineCheck

MachineCheck() implemented in osfmk/ppc/lowmem_vectors.s

T_DATA_ACCESS

EXT(handlePF)

handlePF() implemented in osfmk/ppc/hw_vm.s

T_INSTRUCTION_ACCESS

EXT(handlePF)

 

T_INTERRUPT

PassUpRupt

Handled by ihandler() [osfmk/ppc/hw_exception.s]

T_ALIGNMENT

EXT(AlignAssist)

AlignAssist() implemented in osfmk/ppc/Emulate.s

T_PROGRAM

EXT(Emulate)

Emulate() implemented in osfmk/ppc/Emulate.s

T_FP_UNAVAILABLE

PassUpFPU

Handled by fpu_switch() [osfmk/ppc/cswtch.s]

T_DECREMENTER

PassUpRupt

 

T_IO_ERROR

PassUpTrap

 

T_RESERVED

PassUpTrap

 

T_SYSTEM_CALL

xcpSyscall

Handled locally in the case of a "CutTrace" system call, by FirmwareCall() [osfmk/ppc/Firmware.s] in the case of other firmware calls, and by shandler() [bsd/dev/ppc/systemcalls.c] in the case of normal system calls

T_trACE

PassUpTrap

 

T_FP_ASSIST

PassUpTrap

 

T_PERF_MON

PassUpTrap

 

T_VMX

PassUpVMX

Handled by vec_switch() [osfmk/ppc/cswtch.s]

T_INVALID_EXCP0

PassUpTrap

 

T_INVALID_EXCP1

PassUpTrap

 

T_INVALID_EXCP2

PassUpTrap

 

T_INSTRUCTION_BKPT

PassUpTrap

 

T_SYSTEM_MANAGEMENT

PassUpRupt

 

T_ALTIVEC_ASSIST

EXT(AltivecAssist)

AltivecAssist() implemented in osfmk/ppc/AltiAssist.s

T_THERMAL

PassUpRupt

 

T_INVALID_EXCP5

PassUpTrap

 

T_INVALID_EXCP6

PassUpTrap

 

T_INVALID_EXCP7

PassUpTrap

 

T_INVALID_EXCP8

PassUpTrap

 

T_INVALID_EXCP9

PassUpTrap

 

T_INVALID_EXCP10

PassUpTrap

 

T_INVALID_EXCP11

PassUpTrap

 

T_INVALID_EXCP12

PassUpTrap

 

T_INVALID_EXCP13

PassUpTrap

 

T_RUNMODE_TRACE

PassUpTrap

 

T_SIGP

PassUpRupt

 

T_PREEMPT

PassUpTrap

 

T_CSWITCH

conswtch

conswtch() implemented in osfmk/ppc/lowmem_vectors.s

T_SHUTDOWN

PassUpRupt

 

T_CHOKE

PassUpAbend

Handled by chandler() [osfmk/ppc/hw_exception.s]

T_DATA_SEGMENT

EXT(handleDSeg)

handleDSeg() implemented in osfmk/ppc/hw_vm.s

T_INSTRUCTION_SEGMENT

EXT(handleISeg)

handleISeg() implemented in osfmk/ppc/hw_vm.s

T_SOFT_PATCH

WhoaBaby

WhoaBaby() implemented in osfmk/ppc/lowmem_vectors.ssimply an infinite loop

T_MAINTENANCE

WhoaBaby

 

T_INSTRUMENTATION

WhoaBaby

 

T_ARCHDEP0

WhoaBaby

 

T_HDEC

EatRupt

 


Figure 611. Processing of traps, interrupts, and system calls


Table 69 lists some handlers with the EXT() macro, which is defined in osfmk/ppc/asm.h. This macro simply adds an underscore prefix to its argument, allowing assembly code to refer to the corresponding external symbol while maintaining the C language name (without the underscore) for visual consistency.


Examples of exception handlers[5] shown in Figure 611 and Table 69 include PassUpTrap, PassUpRupt, EatRupt, xcpSyscall, and WhoaBaby. Let us briefly look at each of these.

[5] In many cases, the handlers are labels in assembly code.

  • PassUpTrap loads the address of thandler() [osfmk/ppc/hw_exception.s] in GPR20 and branches to PassUp. When thandler() executes, virtual memory is turned on, but interrupts are turned off.

  • PassUpRupt loads the address of ihandler() [osfmk/ppc/hw_exception.s] in GPR20 and branches to PassUp. As is the case with thandler(), ihandler() also executes with virtual memory turned on and interrupts turned off.

  • EatRupt is the main place for returning from an interrupt. It is also used if the interrupt has already been handled and nothing further needs to be done. For example, if it is found during page-fault processing that the exception has already been handled (say, due to another fault for the same page), the page-fault handler will return a T_IN_VAIN, which is handled by EatRupt. Other examples of this situation include software assistance of floating-point and VMX.

  • xcpSyscall handles system calls depending on their types, such as whether they are normal or special system calls. In the case of normal system calls, xcpSyscall loads the address of shandler() [osfmk/ppc/hw_exception.s] in GPR20 and branches to PassUp. We will discuss system call types and their processing in Section 6.6.

  • WhoaBaby is for exceptions that must not occur during the normal functioning of the operating system. Its handler is extremely simplemerely an infinite loop.

    ; osfmk/ppc/lowmem_vectors.s WhoaBaby:   b      . ; open the hood and wait for help

PassUp [osfmk/ppc/lowmem_vectors.s] places the exception code in GPR3 and the next-level exception handler's address in SRR0. It also switches segment registers between the kernel and the user. It finally executes the rfid instruction (rfi on 32-bit processors) to launch the exception handler.

6.5.1. Hardware Interrupts

A true hardware interrupt can only occur because of a device that has a physical connectionan interrupt linefrom itself to the system's interrupt controller. Such a connection may involve a device controller. A PCI device is a good example: An interrupt line connects a PCI device slot to the PCI controller, which connects it to the interrupt controller. When the system boots, Open Firmware assigns one or more interrupts to a PCI device. The interrupts used by the device are listed in an array called IOInterruptSpecifiers in the device's I/O Registry node. When the device causes a hardware interrupt, it is signaled to the processor by setting an interrupt identifier bit and asserting the processor's external interrupt input signal, causing an external interrupt exception (vector offset 0x500, exception code T_INTERRUPT). As we saw earlier, processing of this exception will eventually lead to the ihandler() function. Moreover, as shown in Figure 611, other exception codes such as T_DECREMENTER and T_SHUTDOWN also lead to ihandler().

Not all devices cause true hardware interrupts. A USB device, for example, generates an "interrupt" by sending a message on the USB bus without involving the system's interrupt controller.


ihandler() ensures the integrity of the interrupt stack, marks it busy, and calls the higher-level interrupt() function [osfmk/ppc/interrupt.c], which disables preemption and performs different operations depending on the specific exception code it is called with.

// osfmk/ppc/interrupt.c struct savearea * interrupt(int type, struct savearea *ssp, unsigned int dsisr, unsigned int dar) {     ...     disable_preemption();     ...     switch (type) {         case T_DECREMENTER:             ...         break;         case T_INTERRUPT:             ...         break;         ...         default: #if MACH_KDP || MACH_KDB             if (!Call_Debugger(type, ssp)) #endif             unresolved_kernel_trap(type, ssp, dsisr, dar, NULL);             break;     }     enable_preemption();     return ssp; }


In the case of a T_DECREMENTER exception code, interrupt() calls rtclock_intr() [osfmk/ppc/rtclock.c]the real-time clock device interrupt function. interrupt() also checks whether the current thread has its quick-activation single-shot timer set; if so, it checks whether the timer has expired, in which case it is cleared. The kernel's virtual machine monitor facility uses this timer.

In the case of a T_INTERRUPT exception code, interrupt() increments the count of incoming interrupts and calls the platform-specific interrupt handler function referred to in the per-processor structure. The type of this handler function (IOInterruptHandler) is defined in iokit/IOKit/IOInterrupts.h.

typedef void (* IOInterruptHandler)(void *target,                                     void *refCon,                                     void *nub,                                     int   source);


In the case of a T_SHUTDOWN exception code, which is generated by a special system call (a so-called firmware callsee Section 6.8.8.1), interrupt() calls cpu_doshutdown() [osfmk/ppc/cpu.c].

If an invalid exception code is sent to ihandler() for processing, it will either panic the system or drop into the debugger if one is available. The panic will be accompanied by an "Unresolved kernel trap . . ." message.


6.5.2. Miscellaneous Traps

The low-level trap handlerthandler() [osfmk/ppc/hw_exception.s]performs different operations depending on the specific trap. A system call exception may end up in the trap handler if there is nothing for the system call to do except generate a trap. The trap handler may jump to the interrupt handler if it finds that it is running on an interrupt stack. thandler() eventually calls the higher-level TRap() function [osfmk/ppc/trap.c] to process traps.

// osfmk/ppc/trap.c struct savearea * trap(int trapno, struct savearea *ssp, unsigned int dsisr, addr64_t dar) {     int exception;     ...     exception = 0;     ...     if (/* kernel mode */) {         // Handle traps originating from the kernel first         // Examples of such traps are T_PREEMPT, T_PERF_MON, T_RESET         // Various traps should never be seen here         // Panic if any of these traps are encountered         ...     } else {         /* user mode */         // Handle user mode traps         ...     }     // The 'exception' variable may have been set during trap processing     if (exception) {         doexception(exception, code, subcode);     }     ...     if (/* user mode */) {         // If an AST is needed, call ast_taken()         // Repeat until an AST is not needed     }     return ssp; }


There are several criteria for the invalidity of a trap's occurrence. For example, T_IN_VAIN should never be seen by trap() because it should have been disposed of by EatRupt in osfmk/ppc/lowmem_vectors.s. Note that TRap() determines whether a trap originated from the user mode or kernel mode by looking at the contents of the SRR1 in the save area. It uses the USER_MODE() macro [osfmk/ppc/proc_reg.h] for this purpose.

// osfmk/ppc/proc_reg.h #define ENDIAN_MASK(val,size) (1 << ((size-1) - val)) ... #define MASK32(PART)          ENDIAN_MASK(PART ## _BIT, 32) ... #define MASK(PART)            MASK32(PART) ... #define MSR_PR_BIT            17 ... #define USER_MODE(msr)        (msr & MASK(MSR_PR) ? TRUE : FALSE)


The Choker

In the case of fatal errors, an exception of type T_CHOKE is generated. This exception results in chandler() [osfmk/ppc/hw_exception.s]the choke handlerbeing called. The choke handler "chokes" the system by going into an infinite loop that increments GPR31 by 1 in each iteration. If thandler() or ihandler() detects that the kernel or interrupt stack, respectively, is invalid, it invokes a special firmware system call for choking the system by setting the external interrupt value to T_CHOKE.


6.5.3. System Calls

The remaining type of exception is for system calls. As noted earlier, system calls are well-defined entry points into the kernel typically used by user-level programs. The next section covers the details of the Mac OS X system call mechanism.




Mac OS X Internals. A Systems Approach
Mac OS X Internals: A Systems Approach
ISBN: 0321278542
EAN: 2147483647
Year: 2006
Pages: 161
Authors: Amit Singh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net