Source to machdep/i386/trap.c


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @[email protected]
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @[email protected]
 */

/*
 * Copyright (c) 1992 NeXT Computer, Inc.
 *
 * Intel386 Family:	Trap handlers.
 *
 * HISTORY
 *
 * 13 April 1992 ? at NeXT
 *	Created.
 */
 
#import <gdb.h>

#import <mach/mach_types.h>
#import <mach/exception.h>

#import <kern/syscall_sw.h>
#import <kern/kdp.h>

#import <vm/vm_kern.h>

#import <machdep/i386/trap.h>
#import <machdep/i386/machdep_call.h>
#import <machdep/i386/cpu_inline.h>
#import <machdep/i386/fp_exported.h>
#import <machdep/i386/sel_inline.h>
#import <machdep/i386/err_inline.h>

#import <sys/kernel.h>
#import <sys/systm.h>
#import <sys/proc.h>
#import <sys/user.h>
#import <sys/syslog.h>

#define N_BACKTRACE		4	// number of backtrace frames to print

static
void
kernel_debug_trap(
	thread_saved_state_t	*saved_state
);
static
kern_return_t
kernel_pagefault(
	vm_offset_t		address,
	except_frame_t		*frame
);
static
boolean_t
kernel_try_recover(
	except_frame_t		*frame
);
static
void
kernel_recover_thread(
	thread_saved_state_t	*saved_state
);

void
_i386_backtrace(
	void			*frame,
	int			nframes
);

void
user_trap(
    thread_saved_state_t	*state
)
{
    except_frame_t		*frame = &state->frame;
    int				trapno;
    thread_t			thread;
    int				_exception, code = 0, subcode = 0;
    vm_offset_t			va;
    kern_return_t		result;
    int				uerror;
    struct proc			*p;
    struct timeval		syst;
    
    thread = current_thread();
    trapno = state->trapno;
    
    if (p = current_proc())
	syst = p->p_stats->p_ru.ru_stime;
    
    switch (trapno) {

    case T_ZERO_DIVIDE:
	_exception	= EXC_ARITHMETIC;
	code		= EXC_I386_ZERO_DIVIDE;
	break;
	
    case T_DEBUG:
	_exception	= EXC_BREAKPOINT;
	code		= EXC_I386_DEBUG;
	break;
	
    case T_BREAKPOINT:
	_exception	= EXC_BREAKPOINT;
	code		= EXC_I386_BREAKPOINT;
	break;
	
    case T_OVERFLOW:
    	_exception	= EXC_SOFTWARE;
	code		= EXC_I386_OVERFLOW;
	break;
	
    case T_BOUNDS_CHECK:
	_exception	= EXC_SOFTWARE;
	code		= EXC_I386_BOUNDS_CHECK;
	break;
	
    case T_INVALID_OPCODE:
	_exception	= EXC_BAD_INSTRUCTION;
	code		= EXC_I386_INVALID_OPCODE;
	break;
	
    case T_NOEXTENSION:
    	fp_noextension(state);
	goto out;
	
    case T_SEGMENT_NOTPRESENT:
    	_exception	= EXC_BAD_INSTRUCTION;
	code		= EXC_I386_SEGMENT_NOTPRESENT;
	subcode		= err_to_error_code(frame->err);
	break;
	
    case T_STACK_EXCEPTION:
	_exception	= EXC_BAD_INSTRUCTION;
	code		= EXC_I386_STACK_EXCEPTION;
	subcode		= err_to_error_code(frame->err);
	break;
	
    case T_GENERAL_PROTECTION:
	_exception	= EXC_BAD_INSTRUCTION;
	code		= EXC_I386_GENERAL_PROTECTION;
	subcode		= err_to_error_code(frame->err);
	break;
	
    case T_PAGE_FAULT:
    	va = cr2();
	result = vm_fault(thread->task->map, trunc_page(va),
			    frame->err.pgfault.wrtflt?
				VM_PROT_READ | VM_PROT_WRITE:
				VM_PROT_READ,
			    FALSE, 0);
	if (result == KERN_SUCCESS)
	    goto out;

	_exception	= EXC_BAD_ACCESS;
	code		= result;
	subcode		= va;
	break;
    
    case T_EXTENSION_FAULT:
    	fp_extension_fault(state);
	goto out;
	
    case T_ALIGNMENT_CHECK:
	_exception	= EXC_SOFTWARE;
	code		= EXC_I386_ALIGNMENT_CHECK;
	break;
	
    default:
    	_exception	= EXC_BAD_INSTRUCTION;
	code		= trapno;
	break;
    }

    exception(_exception, code, subcode);
    /* NOTREACHED */
			    
out:
    if (p && (p->p_flag & P_PROFIL)) {
	int		ticks;
	struct timeval	*tv = &p->p_stats->p_ru.ru_stime;
			
	ticks = ((tv->tv_sec - syst.tv_sec) * 1000 +
		    (tv->tv_usec - syst.tv_usec) / 1000) / (tick / 1000);
	if (ticks)
	    addupc_task(p, frame->eip, ticks);
    }

    thread_exception_return();
    /* NOTREACHED */
}

void
kernel_trap(
    thread_saved_state_t	*state
)
{
    except_frame_t		*frame = &state->frame;
    int				trapno;
    vm_offset_t			va;
    int				_exception, code = 0, subcode = 0;
    kern_return_t		result;
    int				uerror;
    
    trapno = state->trapno;
    
    switch (trapno) {

    case T_NOEXTENSION:
	/* ignore exception */;
	return;
	
    case T_EXTENSION_FAULT:
	fp_kernel_extension_fault(state);
	return;
	
    case T_DEBUG:
    case T_BREAKPOINT:
	kernel_debug_trap(state);
	return;
	
    case T_PAGE_FAULT:
	va = cr2();
	
	if (va >= KERNEL_LINEAR_BASE)
	    result = kernel_pagefault(va, frame);
	else
	    result = vm_fault(current_task()->map, trunc_page(va),
				frame->err.pgfault.wrtflt?
				    VM_PROT_READ | VM_PROT_WRITE:
				    VM_PROT_READ,
				FALSE, 0);

	if (result == KERN_SUCCESS)
	    return;
    
	if (kernel_try_recover(frame))
	    return;
	    
	if (va < KERNEL_LINEAR_BASE) {
	    /*
	     * This can happen on return to user mode
	     * when a private thread LDT contains
	     * unallocated virtual memory.  Send an
	     * exception RPC, after carefully piecing
	     * the thread state back together.
	     */
	    kernel_recover_thread(state);

	    _exception		= EXC_BAD_ACCESS;
	    code		= result;
	    subcode		= va;
	    exception_from_kernel(_exception, code, subcode);
	    thread_exception_return();
	    /* NOTREACHED */
	}
	break;
	
    case T_GENERAL_PROTECTION:
    case T_SEGMENT_NOTPRESENT:
    case T_STACK_EXCEPTION:
	if (frame->err.normal.tbl == ERR_LDT) {
	    /*
	     * This can happen on return to user mode
	     * when one of the segment registers refers
	     * to a descriptor that is invalid for some
	     * reason or another.  Send an exception RPC,
	     * after carefully piecing the thread state
	     * back together.
	     */
	    kernel_recover_thread(state);

	    _exception	= EXC_BAD_INSTRUCTION;
	    code	= trapno;
	    subcode	= err_to_error_code(frame->err);
	    exception_from_kernel(_exception, code, subcode);
	    thread_exception_return();
	    /* NOTREACHED */
	}
    	break;
	
    case T_INVALID_TSS:
    	if ((frame->eflags & EFL_NT) != 0) {
	    /*
	     * This can happen on return to user mode
	     * when a thread managed to set the NT flag
	     * and then entered the kernel through a call
	     * gate.  The fix is to redo the IRET, since
	     * the NT flag cannot be set at this point (we
	     * just came through a trap gate to get here).
	     */
	     kernel_recover_thread(state);
	     thread_exception_return();
	     /* NOTREACHED */
	}
	break;
    }
    
    if (kernel_try_recover(frame))
    	return;   

#if	GDB
    DoSafeAlert("Kernel Trap", "", FALSE);
    printf("unexpected kernel trap %x eip %x\n", trapno, frame->eip);
    switch (trapno) {
    
    case T_ZERO_DIVIDE:
	_exception = EXC_ARITHMETIC;
	code = EXC_I386_ZERO_DIVIDE;
	break;
    
    case T_OVERFLOW:
	_exception = EXC_SOFTWARE;
	code = EXC_I386_OVERFLOW;
	break;
    
    case T_BOUNDS_CHECK:
	_exception = EXC_ARITHMETIC;
	code = EXC_I386_BOUNDS_CHECK;
	break;
    
    case T_INVALID_OPCODE:
	_exception = EXC_BAD_INSTRUCTION;
	code = EXC_I386_INVALID_OPCODE;
	break;
    
    case T_SEGMENT_NOTPRESENT:
	_exception = EXC_BAD_INSTRUCTION;
	code = EXC_I386_SEGMENT_NOTPRESENT;
	subcode	= err_to_error_code(frame->err);
	break;
    
    case T_STACK_EXCEPTION:
	_exception = EXC_BAD_INSTRUCTION;
	code = EXC_I386_STACK_EXCEPTION;
	subcode	= err_to_error_code(frame->err);
	break;
    
    case T_GENERAL_PROTECTION:
	_exception = EXC_BAD_INSTRUCTION;
	code = EXC_I386_GENERAL_PROTECTION;
	subcode	= err_to_error_code(frame->err);
	break;
	
    case T_PAGE_FAULT:
    	_exception = EXC_BAD_ACCESS;
	code = result;
	subcode = va;
	break;
    
    case T_ALIGNMENT_CHECK:
	_exception = EXC_SOFTWARE;
	code = EXC_I386_ALIGNMENT_CHECK;
	break;
	
    default:
    	_exception = EXC_BAD_INSTRUCTION;
	code = trapno;
	break;
    }

    _i386_backtrace(state->regs.ebp, N_BACKTRACE);

    kdp_raise_exception(_exception, code, subcode, state);
    DoRestore();
    panic("continued after kernel trap");
#else
    printf("unexpected kernel trap %x eip %x\n", trapno, frame->eip);
    panic("kernel trap");
#endif
}

static
kern_return_t
kernel_pagefault(
    vm_offset_t			va,
    except_frame_t		*frame
)
{
    thread_t			thread = current_thread();
    vm_map_t			map;
    kern_return_t		result;

    if (thread == THREAD_NULL)
	map = kernel_map;
    else {
	map = thread->task->map;
	if (vm_map_pmap(map) != kernel_pmap)
	    map = kernel_map;
    }

    result = vm_fault(map, trunc_page(va - KERNEL_LINEAR_BASE),
			frame->err.pgfault.wrtflt?
			    VM_PROT_READ | VM_PROT_WRITE:
			    VM_PROT_READ,
			FALSE, 0);

    return (result);
}

static
void
kernel_debug_trap(
    thread_saved_state_t	*state
)
{
    except_frame_t		*frame = &state->frame;
    int				code;
    dr6_t			debug = (dr6_t) { 0 };
    extern void			unix_syscall_(void),
    				mach_kernel_trap_(void),
				machdep_call_(void);
    
    // Clear debug status
    set_dr6(debug);
    
    if (state->trapno == T_DEBUG) {
	if (	frame->eip == (unsigned int)unix_syscall_	||
		frame->eip == (unsigned int)mach_kernel_trap_ 	||
		frame->eip == (unsigned int)machdep_call_	) {
	    current_thread()->pcb->trace = TRUE;
	    frame->eflags &= ~EFL_TF;
	    return;
	}
	code = EXC_I386_DEBUG;
    }
    else
    	code = EXC_I386_BREAKPOINT;

#if	GDB
    kdp_raise_exception(EXC_BREAKPOINT, code, 0, state);
#else
    panic("kernel debug trap");
#endif
}

static
boolean_t
kernel_try_recover(
    except_frame_t	*frame
)
{
    vm_offset_t		recover = current_thread()->recover;
				
    if (recover != 0) {
        frame->eip = recover;
	frame->cs = KCS_SEL;
	frame->eflags &= ~EFL_DF;
    }
    else
	return (FALSE);
	
    current_thread()->recover = 0;
    
    return (TRUE);
}

static
void
kernel_recover_thread(
    thread_saved_state_t	*state
)
{
    thread_saved_state_t	*user_regs = USER_REGS(current_thread());
    vm_offset_t			stack_ptr = (vm_offset_t)&state->frame.esp;

    user_regs->frame.err = state->frame.err;
    
    if (stack_ptr > (vm_offset_t)&user_regs->regs.ds) {

    	user_regs->regs.eax = state->regs.eax;
    	user_regs->regs.ecx = state->regs.ecx;
    	user_regs->regs.edx = state->regs.edx;
    	user_regs->regs.ebx = state->regs.ebx;
    	user_regs->regs.ebp = state->regs.ebp;
    	user_regs->regs.esi = state->regs.esi;
    	user_regs->regs.edi = state->regs.edi;
	
	user_regs->regs.ds = state->regs.ds;
	user_regs->regs.es = state->regs.es;
    	user_regs->regs.fs = state->regs.fs;
    	user_regs->regs.gs = state->regs.gs;
    }
    else if (stack_ptr > (vm_offset_t)&user_regs->regs.es) {
	user_regs->regs.es = state->regs.es;
    	user_regs->regs.fs = state->regs.fs;
    	user_regs->regs.gs = state->regs.gs;
    }
    else if (stack_ptr > (vm_offset_t)&user_regs->regs.fs) {
    	user_regs->regs.fs = state->regs.fs;
    	user_regs->regs.gs = state->regs.gs;
    }
    else if (stack_ptr > (vm_offset_t)&user_regs->regs.gs)
    	user_regs->regs.gs = state->regs.gs;
}

static inline
void
check_for_trace(
    thread_saved_state_t	*state
)
{
    thread_t			thread = current_thread();
    
    if (thread->pcb->trace) {
	thread->pcb->trace = FALSE;
	state->frame.eflags |= EFL_TF;
    }
}

void
machdep_call(
    thread_saved_state_t	*state
)
{
    except_frame_t		*frame = &state->frame;
    regs_t			*regs = &state->regs;
    int				trapno, nargs;
    machdep_call_t		*entry;
    
    check_for_trace(state);
    
    trapno = regs->eax;
    if (trapno < 0 || trapno >= machdep_call_count) {
	regs->eax = (unsigned int)kern_invalid();

	thread_exception_return();
	/* NOTREACHED */
    }
    
    entry = &machdep_call_table[trapno];
    nargs = entry->nargs;

    if (nargs > 0) {
	int			args[nargs];

	if (copyin(frame->esp + sizeof (int),
		    args,
		    nargs * sizeof (int))) {

	    regs->eax = KERN_INVALID_ADDRESS;

	    thread_exception_return();
	    /* NOTREACHED */
	}

	asm volatile("
	    1:
	    mov (%2),%%eax;
	    pushl %%eax;
	    sub $4,%2;
	    dec %1;
	    jne 1b;
	    mov %3,%%eax;
	    call *%%eax;
	    mov %%eax,%0"
	    
	    : "=r" (regs->eax)
	    : "r" (nargs),
		"r" (&args[nargs - 1]),
		"g" (entry->routine)
	    : "ax", "cx", "dx", "sp");
    }
    else
	regs->eax = (unsigned int)(*entry->routine)();

    thread_exception_return();
    /* NOTREACHED */
}

void
mach_kernel_trap(
    thread_saved_state_t	*state
)
{
    except_frame_t		*frame = &state->frame;
    regs_t			*regs = &state->regs;
    int				trapno, nargs;
    mach_trap_t			*entry;
    
    check_for_trace(state);
    
    trapno = -(int)regs->eax;
    if (trapno < 0 || trapno >= mach_trap_count) {
	regs->eax = (unsigned int)kern_invalid();

	thread_exception_return();
	/* NOTREACHED */
    }
    
    entry = &mach_trap_table[trapno];
    nargs = entry->mach_trap_arg_count;

    if (nargs > 0) {
	int			args[nargs];

	if (copyin(frame->esp + sizeof (int),
		    args,
		    nargs * sizeof (int))) {

	    exception(EXC_BAD_ACCESS,
	    		KERN_INVALID_ADDRESS,
			frame->esp + sizeof (int));
	    /* NOTREACHED */
	}

	asm volatile("
	    1:
	    mov (%2),%%eax;
	    pushl %%eax;
	    sub $4,%2;
	    dec %1;
	    jne 1b;
	    mov %3,%%eax;
	    call *%%eax;
	    mov %%eax,%0"
	    
	    : "=r" (regs->eax)
	    : "r" (nargs),
		"r" (&args[nargs - 1]),
		"g" (entry->mach_trap_function)
	    : "ax", "cx", "dx", "sp");
    }
    else
	regs->eax = (unsigned int)(*entry->mach_trap_function)();

    thread_exception_return();
    /* NOTREACHED */
}

void
unix_syscall(
    thread_saved_state_t	*state
)
{
    except_frame_t		*frame = &state->frame;
    regs_t			*regs = &state->regs;
    thread_t			thread = current_thread();
    struct uthread		*uthread;
    struct sysent		*callp;
    struct proc			*p;
    struct timeval		syst;
    int				nargs, error = 0;
    unsigned short		code;
    caddr_t			params;
	int rval[2];
    
    check_for_trace(state);

    uthread = thread->_uthread;
    uthread->uu_ar0 = (int *)state;
    if (p = current_proc())	
	syst = p->p_stats->p_ru.ru_stime;
    
    code = regs->eax;
    params = (caddr_t)frame->esp + sizeof (int);
    callp = (code >= nsysent) ? &sysent[63] : &sysent[code];
    if (callp == sysent) {
	code = fuword(params);
	params += sizeof (int);
	callp = (code >= nsysent) ? &sysent[63] : &sysent[code];
    }
    
    if ((nargs = (callp->sy_narg * sizeof (int))) &&
	    (error = copyin(params, (caddr_t)uthread->uu_arg, nargs)) != 0) {
	regs->eax = error;
	frame->eflags |= EFL_CF;
	thread_exception_return();
	/* NOTREACHED */
    }
    
    rval[0] = 0;
    rval[1] = regs->edx;

	error = (*(callp->sy_call))(p, (caddr_t)uthread->uu_arg, rval);
	
	if (error == ERESTART) {
		state->frame.eip -= 7;
	}
	else if (error != EJUSTRETURN) {
		if (error) {
	    state->regs.eax = error;
	    state->frame.eflags |= EFL_CF;	/* carry bit */
		} else { /* (not error) */
	    state->regs.eax = rval[0];
	    state->regs.edx = rval[1];
	    state->frame.eflags &= ~EFL_CF;
		} 
	}
	/* else  (error == EJUSTRETURN) { nothing } */

    thread_exception_return();
    /* NOTREACHED */
	
}


void
check_for_ast(
    thread_saved_state_t	*state
)
{
    thread_t			thread = current_thread();
    struct proc			*p = current_proc();

    while (TRUE) {
	ast_t			reasons;
	int			mycpu = cpu_number();
	int					signum;

    	cli();		// disable interrupts
	
	reasons = ast_needed(mycpu);	

	if (p) {		    
	    if ((p->p_flag & P_OWEUPC) && (p->p_flag & P_PROFIL)) {
		addupc_task(p, state->frame.eip, 1);
		p->p_flag &= ~P_OWEUPC;
	    }
	    
	    ast_off(mycpu, AST_UNIX);
	    
	    if (CHECK_SIGNALS(p, thread, thread->_uthread)) {
			signum = issignal(p);
			if (signum)
				postsig(signum);
			cli();
	    }
	}

	ast_off(mycpu, reasons);

	if (thread_should_halt(thread))
	    thread_halt_self();
	else
	if ((reasons & AST_BLOCK) ||
		csw_needed(thread, current_processor())) {
		p->p_stats->p_ru.ru_nivcsw++;
	    thread_block_with_continuation(thread_exception_return);
	}
	else
	if (reasons & AST_FP_EXTEN)
	    fp_ast(thread);
	else
	    break;
    }
}

typedef struct _cframe_t {
    struct _cframe_t	*prev;
    unsigned		caller;
    unsigned		args[0];
} cframe_t;


#define MAX_FRAME_DELTA		65536

void
_i386_backtrace(
    void			*_frame,
    int				nframes
)
{
	cframe_t	*frame = (cframe_t *)_frame;
	int i;

	for (i=0; i<nframes; i++) {
	    if ((vm_offset_t)frame < VM_MIN_KERNEL_ADDRESS ||
	        (vm_offset_t)frame > VM_MAX_KERNEL_ADDRESS) {
		goto invalid;
	    }
	    safe_prf("frame %x called by %x ",
		frame, frame->caller);
	    safe_prf("args %x %x %x %x\n",
		frame->args[0], frame->args[1],
		frame->args[2], frame->args[3]);
	    if ((frame->prev < frame) ||	/* wrong direction */
	    	((frame->prev - frame) > MAX_FRAME_DELTA)) {
		goto invalid;
	    }
	    frame = frame->prev;
	}
	return;
invalid:
	safe_prf("invalid frame pointer %x\n",frame->prev);
}