sos-code-article6.75/hwcore/cpu_context.c

408 lines
12 KiB
C

/* Copyright (C) 2005 David Decotigny
Copyright (C) 2000-2004, The KOS team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/assert.h>
#include <sos/klibc.h>
#include <drivers/bochs.h>
#include <drivers/x86_videomem.h>
#include <hwcore/segment.h>
#include "cpu_context.h"
/**
* Here is the definition of a CPU context for IA32 processors. This
* is a SOS convention, not a specification given by the IA32
* spec. However there is a strong constraint related to the x86
* interrupt handling specification: the top of the stack MUST be
* compatible with the 'iret' instruction, ie there must be the
* err_code (might be 0), eip, cs and eflags of the destination
* context in that order (see Intel x86 specs vol 3, figure 5-4).
*
* @note IMPORTANT: This definition MUST be consistent with the way
* the registers are stored on the stack in
* irq_wrappers.S/exception_wrappers.S !!! Hence the constraint above.
*/
struct sos_cpu_state {
/* (Lower addresses) */
/* These are SOS convention */
sos_ui16_t gs;
sos_ui16_t fs;
sos_ui16_t es;
sos_ui16_t ds;
sos_ui16_t cpl0_ss; /* This is ALWAYS the Stack Segment of the
Kernel context (CPL0) of the interrupted
thread, even for a user thread */
sos_ui16_t alignment_padding; /* unused */
sos_ui32_t eax;
sos_ui32_t ebx;
sos_ui32_t ecx;
sos_ui32_t edx;
sos_ui32_t esi;
sos_ui32_t edi;
sos_ui32_t ebp;
/* MUST NEVER CHANGE (dependent on the IA32 iret instruction) */
sos_ui32_t error_code;
sos_vaddr_t eip;
sos_ui32_t cs; /* 32bits according to the specs ! However, the CS
register is really 16bits long */
sos_ui32_t eflags;
/* (Higher addresses) */
} __attribute__((packed));
/**
* The CS value pushed on the stack by the CPU upon interrupt, and
* needed by the iret instruction, is 32bits long while the real CPU
* CS register is 16bits only: this macro simply retrieves the CPU
* "CS" register value from the CS value pushed on the stack by the
* CPU upon interrupt.
*
* The remaining 16bits pushed by the CPU should be considered
* "reserved" and architecture dependent. IMHO, the specs don't say
* anything about them. Considering that some architectures generate
* non-zero values for these 16bits (at least Cyrix), we'd better
* ignore them.
*/
#define GET_CPU_CS_REGISTER_VALUE(pushed_ui32_cs_value) \
( (pushed_ui32_cs_value) & 0xffff )
/**
* Structure of an interrupted Kernel thread's context
*/
struct sos_cpu_kstate
{
struct sos_cpu_state regs;
} __attribute__((packed));
/**
* THE main operation of a kernel thread. This routine calls the
* kernel thread function start_func and calls exit_func when
* start_func returns.
*/
static void core_routine (sos_cpu_kstate_function_arg1_t *start_func,
sos_ui32_t start_arg,
sos_cpu_kstate_function_arg1_t *exit_func,
sos_ui32_t exit_arg)
__attribute__((noreturn));
static void core_routine (sos_cpu_kstate_function_arg1_t *start_func,
sos_ui32_t start_arg,
sos_cpu_kstate_function_arg1_t *exit_func,
sos_ui32_t exit_arg)
{
start_func(start_arg);
exit_func(exit_arg);
SOS_ASSERT_FATAL(! "The exit function of the thread should NOT return !");
for(;;);
}
sos_ret_t sos_cpu_kstate_init(struct sos_cpu_state **ctxt,
sos_cpu_kstate_function_arg1_t *start_func,
sos_ui32_t start_arg,
sos_vaddr_t stack_bottom,
sos_size_t stack_size,
sos_cpu_kstate_function_arg1_t *exit_func,
sos_ui32_t exit_arg)
{
/* We are initializing a Kernel thread's context */
struct sos_cpu_kstate *kctxt;
/* This is a critical internal function, so that it is assumed that
the caller knows what he does: we legitimally assume that values
for ctxt, start_func, stack_* and exit_func are allways VALID ! */
/* Setup the stack.
*
* On x86, the stack goes downward. Each frame is configured this
* way (higher addresses first):
*
* - (optional unused space. As of gcc 3.3, this space is 24 bytes)
* - arg n
* - arg n-1
* - ...
* - arg 1
* - return instruction address: The address the function returns to
* once finished
* - local variables
*
* The remaining of the code should be read from the end upward to
* understand how the processor will handle it.
*/
sos_vaddr_t tmp_vaddr = stack_bottom + stack_size;
sos_ui32_t *stack = (sos_ui32_t*)tmp_vaddr;
/* If needed, poison the stack */
#ifdef SOS_CPU_STATE_DETECT_UNINIT_KERNEL_VARS
memset((void*)stack_bottom, SOS_CPU_STATE_STACK_POISON, stack_size);
#elif defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW)
sos_cpu_state_prepare_detect_kernel_stack_overflow(stack_bottom, stack_size);
#endif
/* Simulate a call to the core_routine() function: prepare its
arguments */
*(--stack) = exit_arg;
*(--stack) = (sos_ui32_t)exit_func;
*(--stack) = start_arg;
*(--stack) = (sos_ui32_t)start_func;
*(--stack) = 0; /* Return address of core_routine => force page fault */
/*
* Setup the initial context structure, so that the CPU will execute
* the function core_routine() once this new context has been
* restored on CPU
*/
/* Compute the base address of the structure, which must be located
below the previous elements */
tmp_vaddr = ((sos_vaddr_t)stack) - sizeof(struct sos_cpu_kstate);
kctxt = (struct sos_cpu_kstate*)tmp_vaddr;
/* Initialize the CPU context structure */
memset(kctxt, 0x0, sizeof(struct sos_cpu_kstate));
/* Tell the CPU context structure that the first instruction to
execute will be that of the core_routine() function */
kctxt->regs.eip = (sos_ui32_t)core_routine;
/* Setup the segment registers */
kctxt->regs.cs
= SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE); /* Code */
kctxt->regs.ds
= SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Data */
kctxt->regs.es
= SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Data */
kctxt->regs.cpl0_ss
= SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Stack */
/* fs and gs unused for the moment. */
/* The newly created context is initially interruptible */
kctxt->regs.eflags = (1 << 9); /* set IF bit */
/* Finally, update the generic kernel/user thread context */
*ctxt = (struct sos_cpu_state*) kctxt;
return SOS_OK;
}
#if defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW)
void
sos_cpu_state_prepare_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt,
sos_vaddr_t stack_bottom,
sos_size_t stack_size)
{
sos_size_t poison_size = SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW;
if (poison_size > stack_size)
poison_size = stack_size;
memset((void*)stack_bottom, SOS_CPU_STATE_STACK_POISON, poison_size);
}
void
sos_cpu_state_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt,
sos_vaddr_t stack_bottom,
sos_size_t stack_size)
{
unsigned char *c;
int i;
/* On SOS, "ctxt" corresponds to the address of the esp register of
the saved context in Kernel mode (always, even for the interrupted
context of a user thread). Here we make sure that this stack
pointer is within the allowed stack area */
SOS_ASSERT_FATAL(((sos_vaddr_t)ctxt) >= stack_bottom);
SOS_ASSERT_FATAL(((sos_vaddr_t)ctxt) + sizeof(struct sos_cpu_kstate)
<= stack_bottom + stack_size);
/* Check that the bottom of the stack has not been altered */
for (c = (unsigned char*) stack_bottom, i = 0 ;
(i < SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) && (i < stack_size) ;
c++, i++)
{
SOS_ASSERT_FATAL(SOS_CPU_STATE_STACK_POISON == *c);
}
}
#endif
/* =======================================================================
* Public Accessor functions
*/
sos_vaddr_t sos_cpu_context_get_PC(const struct sos_cpu_state *ctxt)
{
SOS_ASSERT_FATAL(NULL != ctxt);
/* This is the PC of the interrupted context (ie kernel or user
context). */
return ctxt->eip;
}
sos_vaddr_t sos_cpu_context_get_SP(const struct sos_cpu_state *ctxt)
{
SOS_ASSERT_FATAL(NULL != ctxt);
/* On SOS, "ctxt" corresponds to the address of the esp register of
the saved context in Kernel mode (always, even for the interrupted
context of a user thread). */
return (sos_vaddr_t)ctxt;
}
void sos_cpu_context_dump(const struct sos_cpu_state *ctxt)
{
char buf[128];
snprintf(buf, sizeof(buf),
"CPU: eip=%x esp=%x eflags=%x cs=%x ds=%x ss=%x err=%x",
(unsigned)ctxt->eip, (unsigned)ctxt, (unsigned)ctxt->eflags,
(unsigned)GET_CPU_CS_REGISTER_VALUE(ctxt->cs), (unsigned)ctxt->ds,
(unsigned)ctxt->cpl0_ss,
(unsigned)ctxt->error_code);
sos_bochs_putstring(buf); sos_bochs_putstring("\n");
sos_x86_videomem_putstring(23, 0,
SOS_X86_VIDEO_FG_BLACK | SOS_X86_VIDEO_BG_LTGRAY,
buf);
}
/* =======================================================================
* Public Accessor functions TO BE USED ONLY BY Exception handlers
*/
sos_ui32_t sos_cpu_context_get_EX_info(const struct sos_cpu_state *ctxt)
{
SOS_ASSERT_FATAL(NULL != ctxt);
return ctxt->error_code;
}
sos_vaddr_t
sos_cpu_context_get_EX_faulting_vaddr(const struct sos_cpu_state *ctxt)
{
sos_ui32_t cr2;
/*
* See Intel Vol 3 (section 5.14): the address of the faulting
* virtual address of a page fault is stored in the cr2
* register.
*
* Actually, we do not store the cr2 register in a saved
* kernel thread's context. So we retrieve the cr2's value directly
* from the processor. The value we retrieve in an exception handler
* is actually the correct one because an exception is synchronous
* with the code causing the fault, and cannot be interrupted since
* the IDT entries in SOS are "interrupt gates" (ie IRQ are
* disabled).
*/
asm volatile ("movl %%cr2, %0"
:"=r"(cr2)
: );
return cr2;
}
/* =======================================================================
* Backtrace facility. To be used for DEBUGging purpose ONLY.
*/
sos_ui32_t sos_backtrace(const struct sos_cpu_state *cpu_state,
sos_ui32_t max_depth,
sos_vaddr_t stack_bottom,
sos_size_t stack_size,
sos_backtrace_callback_t * backtracer,
void *custom_arg)
{
int depth;
sos_vaddr_t callee_PC, caller_frame;
/*
* Layout of a frame on the x86 (compiler=gcc):
*
* funcA calls funcB calls funcC
*
* ....
* funcB Argument 2
* funcB Argument 1
* funcA Return eip
* frameB: funcA ebp (ie previous stack frame)
* ....
* (funcB local variables)
* ....
* funcC Argument 2
* funcC Argument 1
* funcB Return eip
* frameC: funcB ebp (ie previous stack frame == A0) <---- a frame address
* ....
* (funcC local variables)
* ....
*
* The presence of "ebp" on the stack depends on 2 things:
* + the compiler is gcc
* + the source is compiled WITHOUT the -fomit-frame-pointer option
* In the absence of "ebp", chances are high that the value pushed
* at that address is outside the stack boundaries, meaning that the
* function will return -SOS_ENOSUP.
*/
if (cpu_state)
{
callee_PC = cpu_state->eip;
caller_frame = cpu_state->ebp;
}
else
{
/* Skip the sos_backtrace() frame */
callee_PC = (sos_vaddr_t)__builtin_return_address(0);
caller_frame = (sos_vaddr_t)__builtin_frame_address(1);
}
for(depth=0 ; depth < max_depth ; depth ++)
{
/* Call the callback */
backtracer(callee_PC, caller_frame + 8, depth, custom_arg);
/* If the frame address is funky, don't go further */
if ( (caller_frame < stack_bottom)
|| (caller_frame + 4 >= stack_bottom + stack_size) )
return depth;
/* Go to caller frame */
callee_PC = *((sos_vaddr_t*) (caller_frame + 4));
caller_frame = *((sos_vaddr_t*) caller_frame);
}
return depth;
}