程序4-4 代码功能
lidt IDT_POINTER(%rip)
...
setup_IDT: // ignore_int(%rip) = 0x1111 2222 3333 4444
#首先ignore_int中断服务程序的偏移地址(OFFSET)就可以使用lea指令取出ignore_int标号的基地址
#这个基地址要被拆分成第0-15位和第48-63位,分别放在EAX和EDX寄存器中
#然後在將EAX的值加載到中斷描述符的低位,將EDX的值加載到中斷描述符的高位。
leaq ignore_int(%rip), %rdx ;假如 ignore_int 函数 的地址为 %rdx = 0x2222 3333 4444 5555
movq $(0x08 << 16), %rax ; 段选择子 %rax = 0x0000 0000 0008 0000 = 0b 0000 0000 0000 1000 位数: 0-1 RPL 请求特权级 1-2 TI 指示目标段描述符所在描述符表类型 3-15 用于索引目标段描述符 2 #段选择符,我们要选用代码段的段选择符,所以我们使用0008h号GDT段选择符。
movw %dx, %ax ; 段内偏移 15:00 %ax = ignore_int 函数的低16位 %ax = 0x5555 %rax = 0x0000 0000 0008 5555
movq $(0x8E00 << 32), %rcx ; %rcx = 0x0000 8E00 0000 0000 = 0b0000 0000 0000 0000 1000 1110 0000 0000 32-34 IST ( Interrupt Stack Table,中断枝表)是IA-32e模式为任务状态段引人的新型战指针,其功能与 RSP相同,只不过IST切换中断棋指针时不会考虑特权级切换。 35-39:0 40-43:Type 第40-43位为段描述符类型标志(TYPE),我们设置的是1110.即将此段描述符标记为“386中断门”。 44-44:0 45-46:DPL 描述符特权级 47-48:P 指定调用门描述符是否有效
addq %rcx, %rax ; %rax = 0x0000 8E00 0008 5555 %rcx = 0x0000 8E00 0000 0000
movl %edx, %ecx ; %ecx = ignore_int 函数的低32位 0x4444 5555 %rcx = 0x0000 8E00 4444 5555
shrl $16, %ecx ; %ecx = 0x0000 4444 %rcx = 0x0000 8E00 0000 4444
shlq $48, %rcx ; %rcx = 4444 0000 0000 0000
addq %rcx, %rax ; %rax = 0x4444 8E00 0008 5555
shrq $32, %rdx ; %rdx = 0x0000 0000 2222 3333
leaq IDT_Table(%rip), %rdi ;
mov $256, %rcx
rp_sidt:
movq %rax, (%rdi)
movq %rdx, 8(%rdi)
addq $0x10, %rdi
dec %rcx
jne rp_sidt
·模块setup_IDT负责往IDT(中断描述符表)填入中断描述符(单个表项占用16Byte),中断描述符的格式参考下图:
ignore_int:
/*#CLD指令即告訴程序si,di向內存地址增大的方向走。*/
cld
pushq %rax
pushq %rbx
pushq %rcx
pushq %rdx
pushq %rbp
pushq %rdi
pushq %rsi
pushq %r8
pushq %r9
pushq %r10
pushq %r11
pushq %r12
pushq %r13
pushq %r14
pushq %r15
movq %es, %rax
pushq %rax
movq %ds, %rax
pushq %rax
movq $0x10, %rax
movq %rax, %ds
movq %rax, %es
leaq int_msg(%rip), %rax
pushq %rax
movq %rax, %rdx
movq $0x00000000, %rsi
movq $0x00ff0000, %rdi
movq $0, %rax
callq color_printk
addq $0x8, %rsp
Loop:
jmp Loop
popq %rax
movq %rax, %ds
popq %rax
movq %rax, %es
popq %r15
popq %r14
popq %r13
popq %r12
popq %r11
popq %r10
popq %r9
popq %r8
popq %rsi
popq %rdi
popq %rbp
popq %rdx
popq %rcx
popq %rbx
popq %rax
iretq
int_msg:
.asciz "Unknown interrupt or fault at RIP\n"
·目前,只写了一个中断处理程序ignore_int,功能是在发生中断时在屏幕显示黑底红字的错误信息"Unknown interrupt or fault at RIP\n",并随即进入死循环;
·根据描述符格式要求,现在利用ignore_int所在的内核数据段选择子0x08以及ignore_int的入口偏移地址,来创建中断描述符,并且往目前的IDT表的全部256个表项都填入这个相同的描述符(即目前无论发什么异常错误,都调用的是ignore_int)
设置TSS
TSS段主要用于任务切换(或特权级切换) 时,保存处理器的寄存器状态,以及切换至对应的 特权级栈空间,从而使得任务返回时能够还原执行现场。 下图是一个32位的任务状态段的内部结 构。 (16位的TSS段只用在Intel286处理中。 )
32位模式下的tss
TSS被分为两部分 : 动态区域和静态区域。 当任务在切换过程中挂起时, 处理器会将执行现场保 存在动态区域内,下图归纳了32位 TSS的动态区域。
64位模式下的tss
下图的TSS描述符位功能非常好理解, 而且功能也并无变化,但TSS的内部结构却发生革命性的 变化。 既然TSS不再需要保存和还原程序 (或任务)的执行现场环境,那么它只负责不同特权级间的栈切换工作 。
下图是IA-32e模式TSS的 内部结构 。
为 TSS 设置 IST
void set_tss64(unsigned long rsp0,unsigned long rsp1,unsigned long rsp2,unsigned long ist1,unsigned long ist2,unsigned long ist3,
unsigned long ist4,unsigned long ist5,unsigned long ist6,unsigned long ist7)
{
*(unsigned long *)(TSS64_Table+1) = rsp0;
*(unsigned long *)(TSS64_Table+3) = rsp1;
*(unsigned long *)(TSS64_Table+5) = rsp2;
*(unsigned long *)(TSS64_Table+9) = ist1;
*(unsigned long *)(TSS64_Table+11) = ist2;
*(unsigned long *)(TSS64_Table+13) = ist3;
*(unsigned long *)(TSS64_Table+15) = ist4;
*(unsigned long *)(TSS64_Table+17) = ist5;
*(unsigned long *)(TSS64_Table+19) = ist6;
*(unsigned long *)(TSS64_Table+21) = ist7;
}
set_tss64(_stack_start, _stack_start, _stack_start, 0xffff800000007c00, 0xffff800000007c00, 0xffff800000007c00, 0xffff800000007c00, 0xffff800000007c00, 0xffff800000007c00, 0xffff800000007c00);
IST和IDT的实际使用示例
/*
*/
#define _set_gate(gate_selector_addr,attr,ist,code_addr) \
do \
{ unsigned long __d0,__d1; \
__asm__ __volatile__ ( "movw %%dx, %%ax \n\t" \
"andq $0x7, %%rcx \n\t" \
"addq %4, %%rcx \n\t" \
"shlq $32, %%rcx \n\t" \
"addq %%rcx, %%rax \n\t" \
"xorq %%rcx, %%rcx \n\t" \
"movl %%edx, %%ecx \n\t" \
"shrq $16, %%rcx \n\t" \
"shlq $48, %%rcx \n\t" \
"addq %%rcx, %%rax \n\t" \
"movq %%rax, %0 \n\t" \
"shrq $32, %%rdx \n\t" \
"movq %%rdx, %1 \n\t" \
:"=m"(*((unsigned long *)(gate_selector_addr))) , \
"=m"(*(1 + (unsigned long *)(gate_selector_addr))),"=&a"(__d0),"=&d"(__d1) \
:"i"(attr << 8), \
"3"((unsigned long *)(code_addr)),"2"(0x8 << 16),"c"(ist) \
:"memory" \
); \
}while(0)
void sys_vector_init()
{
set_trap_gate(0,1,divide_error);
set_trap_gate(1,1,debug);
set_intr_gate(2,1,nmi);
set_system_gate(3,1,int3);
set_system_gate(4,1,overflow);
set_system_gate(5,1,bounds);
set_trap_gate(6,1,undefined_opcode);
set_trap_gate(7,1,dev_not_available);
set_trap_gate(8,1,double_fault);
set_trap_gate(9,1,coprocessor_segment_overrun);
set_trap_gate(10,1,invalid_TSS);
set_trap_gate(11,1,segment_not_present);
set_trap_gate(12,1,stack_segment_fault);
set_trap_gate(13,1,general_protection);
set_trap_gate(14,1,page_fault);
//15 Intel reserved. Do not use.
set_trap_gate(16,1,x87_FPU_error);
set_trap_gate(17,1,alignment_check);
set_trap_gate(18,1,machine_check);
set_trap_gate(19,1,SIMD_exception);
set_trap_gate(20,1,virtualization_exception);
//set_system_gate(SYSTEM_CALL_VECTOR,7,system_call);
}
sys_vector_init 方法里面的所有异常的IST均设置为1,其代表IST1,值为0xffff800000007c00,在发生异常时,会系统会自动将rsp指针设置为 0xffff800000007c00 用于保存寄存器的值!
思考,为什么执行 i = 1/0 时,系统会找到序号为0的中断,而不产生其他中断序号?
异常事件和中断请求的 检测 都是在某一条指令执行过程中进行的,显然由硬件完成;
在 CPU 根据 CS 和 EIP 取下条指令之前,会根据检测的结果判断是否进入中断响应阶段;
异常和中断的响应也都是在某一条指令执行过程中或执行结束时进行的,显然也由硬件完成;