trap.c & Asm.s
trap.c和Asm.s主要完成的是系统中断和陷阱的初始化定义。
注:在80386体系的CPU中中断描述符表替代了中断向量表,IDT的描述符可以是中断门、陷阱门或者任务门。IDT中的中断门和陷阱门的定义如下:BYTE0/1(偏移底字节),BYTE2/3(选择子)、BYTE4/5(属性)、BYTE6/7 (偏移高字节)。通过两个字节的段描述符合四个字节的偏移就可以找到相应的目标代码地址。
因此在看trap.c和Asm.s的代码前,首先要分析下一部分system.h中的代码,如下:
#define _set_gate(gate_addr,type,dpl,addr) \__asm__ ("movw %%dx,%%ax\n\t" \ //下面四行汇编就是完成IDT描述符的填充 "movw %0,%%dx\n\t" \ "movl %%eax,%1\n\t" \ "movl %%edx,%2" \ : \ //无输出 : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \//%0参数为IDT的BYTE4,5 "o" (*((char *) (gate_addr))), \//%1变量为内存gate_addr,即门描述符的低四字节 "o" (*(4+(char *) (gate_addr))), \ //%2变量为内存gate_addr+4 "d" ((char *) (addr)),"a" (0x00080000))//edx = addr, eax = 0x00080000; 0x0008为选择字,即对应GDT中的第二个描述符,代码段 #define set_intr_gate(n,addr) \ //n为中断索引 _set_gate(&idt[n],14,0,addr) // 14 0x0e属于中断门,DPL设置为0不允许用户态直接访问#define set_trap_gate(n,addr) \ _set_gate(&idt[n],15,0,addr) // 15 0x0f属于属于陷阱门,DPL设置为0不允许用户态直接访问#define set_system_gate(n,addr) \ _set_gate(&idt[n],15,3,addr) //调用门,DPL为3允许用户态访问
在trap.c和Asm.s中具体设置陷阱比较类似,以除零出错为例:
在trap.c的init函数中 set_trap_gate(0, ÷_error)。divider_error函数定义在Asm.s中如下:
divide_error: pushl $do_divide_error //do_divide_error陷阱处理函数入栈
do_divide_error定义在trap.c中,如下:
void do_divide_error(long esp, long error_code){ die("divide error",esp,error_code);//打印当前出错进程的信息}
其他代码类似,诸如int 3中断或堆栈段错误。
int3: pushl $do_int3 jmp no_error_codestack_segment: pushl $do_stack_segment jmp error_code
注:这里要值得注意的是do_XX函数的调用,是通过在no_error_code或者error_code中利用iret和堆栈实现的。其中no_error_code要比error_code少入栈一个参数error_code,即我们看到的do_divide_error中的第二个参数。如下:
error_code: xchgl %eax,4(%esp) # error code <-> %eax xchgl %ebx,(%esp) # &function <-> %ebx pushl %ecx pushl %edx pushl %edi pushl %esi pushl %ebp push %ds push %es push %fs pushl %eax # error code lea 44(%esp),%eax # offset //地址esp+44 赋值给eax,44是堆栈中寄存器长度和,参考赵博的书 pushl %eax movl $0x10,%eax mov %ax,%ds mov %ax,%es mov %ax,%fs call *%ebx //调用具体的函数,C语言的入参是放在堆栈里的; addl $8,%esp //跳过error code和function这8字节 pop %fs pop %es pop %ds popl %ebp popl %esi popl %edi popl %edx popl %ecx popl %ebx popl %eax iret