设置中断门与陷阱门

通过前面中断的实验我们知道LABEL_IDT是放到内存中的一张表,里面是一个一个的门描述符,对应相应的中断向量。我们的工作就是在描述符中填充相应的内容。为了能在c中设置中断门,我们在head.S中添加.globl LABEL_IDT,这样在c中我们就可以使用了,我们在main.c中是这样声明的:

typedef struct desc_struct {
unsigned long a,b;
} desc_table[256];
extern desc_table LABEL_IDT;

然后定义了一个set_intr_gate,参数n代表中断向量号,参数addr代表中断服务函数的地址。

#define set_intr_gate(n,addr) \
_set_gate(&LABEL_IDT[n],14,0,addr)

下面看一下_set_gate的实现:

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

c嵌入汇编的格式为:

asm("汇编语句"

    :输出寄存器

    :输入寄存器

    :会被修改的寄存器);
把输出寄存器和输入寄存器统一编号,分别对应%0,%1,%2 ..... 顺序为:先输出再输入,从左到右(从上到下,从左到右)。

"i","o","d",这些是寄存器加载代码,"i"表示立即数,"o"表示使用内存地址并可以加偏移值,"d"使用寄存器edx,"a"使用寄存器eax。

重点看输入寄存器:

: "i" ((short)(0x8000+(dpl<<13)+(type<<8))), \ 

%0:输入项是立即数,0x8000对应P=1,(dpl<<13)对应DPL=0,(type<<8)对应TYPE=0xe,386中断门

         "o"(*((char *) (gate_addr))), \

%1:gate_addr低4字节,对应LABEL_IDT[n] 低4字节

         "o"(*(4+(char *) (gate_addr))), \

%2:gate_addr高4字节,对应LABEL_IDT[n] 高4字节

         "d"((char *) (addr)),

把偏移地址放到edx,对应函数divide_error的地址。

         "a"(0x00080000))

把0x00080000放到eax

总结:

eax=0x00080000,edx=&divide_error,%0=访问,%2= LABEL_IDT[n]低4字节,%2= LABEL_IDT[n] 高4字节

 

上面我们得到了所有的值,那么汇编语句无非就是把它们放到合适的位置,那具体应该放那呢?下面的两张图描述了一个中断门中的内容,也就是LABEL_IDT[n]中应该填的内容。

 设置中断门与陷阱门_第1张图片

 

  

P:存在位,占1位。P=1表示段在内存中存在;P=0表示段在内存中不存在

DPL:特权级,占2位。0~3

S: 1位。S=0,系统段或门描述符;S=1,数据段或代码段描述符。

TYPE:描述符类型,占4位。0xe表示386中断门,0xf表示386陷阱门。

 

通过上面的两张图我们知道,无非也就是在  LABEL_IDT[n]填对应的数据,不过在这里用%1和%2表示LABEL_IDT[n]而已。下面解释一下汇编语句:

        "movw%%dx,%%ax\n\t" \

edx的低16位放到eax的低16位,eax=0x00080000|divide_error地址的低16位

         "movw %0,%%dx\n\t" \

%0的内容放到dx寄存器,edx高16为divide_error地址的高16位,edx低16为访问权字段的内容

         "movl %%eax,%1\n\t" \

%1=eax,即0x00080000|divide_error地址的低16位,对应selector为0x0008

         "movl %%edx,%2" \

%1=edx,即divide_error地址的高16位|访问权字段的内容

 

为了加深印象我们用c再重写一下_set_gate函数:

typedef	unsigned int	u32;
typedef	unsigned short	u16;
typedef	unsigned char	u8;	
typedef struct s_gate
{
	u16	offset_low;
	u16	selector;	
	u8	dcount;		 
	u8	attr;		
	u16	offset_high;
}GATE;
	
void _set_gate(GATE *gate_addr,u8 type,u8 dpl,void (*addr)())	
{
	gate_addr->offset_low	= ((u32)addr) & 0xFFFF;
	gate_addr->selector	= 0x08; 
	gate_addr->dcount		= 0;
	gate_addr->attr		= 0x80 | type | (dpl << 5);//0x80对应图中的存在位P
	gate_addr->offset_high	= ((u32)addr >> 16) & 0xFFFF;
		
}


 我们写一个函数,让一个变量每次加1,然后打印出来。

int i =0;
void do_divide_error(long esp, long error_code)
{
  i++;
  disp_int(i);
  outb(0x20,0x20);

然后在main函数中添加如下语句,32是时钟中断,do_divide_error是中断服务函数。
 set_intr_gate(32,&do_divide_error);
 sti();

不过你可能很失望,因为打印了一个00000001h就停止了。

下面讲一下中断门和陷阱门的区别

通过中断门的转移和通过陷阱门的转移之间的差别只是对IF标志的处理。对于中断门,在转移过程中把IF置为0,使得在处理程序执行期间屏蔽掉INTR中断(当然,在中断处理程序中可以人为设置IF标志打开中断,以使得在处理程序执行期间允许响应可屏蔽中断);对于陷阱门,在转移过程中保持IF位不变,即如果IF位原来是1,那么通过陷阱门转移到处理程序之后仍允许INTR中断。因此,中断门最适宜于处理中断,而陷阱门适宜于处理异常。

因为上面我们用的是中断门,在转移过程中把IF置为0,屏蔽了INTR中断,所以我们看到只打印了一个00000001h就停止了。我们只要在此处用陷阱门就会不停的打印了,其实中断门和陷阱门真的很像,稍加修改即可。

#define set_trap_gate(n,addr) \
 _set_gate(&idt[n],15,0,addr)

使用:

set_trap_gate(32,&do_divide_error);
sti();

在之前实验的时候把中断门和陷阱门搞反了,还怀疑是c代码返回时用了iret,objdump后发现根本不是那么回事,所以使用的时候一定要小心了。

为了让中断门正常工作我们添加一个asm.S,我们先保存所有寄存器的值,然后调用中断处理函数,最后将恢复寄存器,执行iret返回,代码如下:

.globl divide_error
divide_error:
	pushl $do_divide_error
no_error_code:
	xchgl %eax,(%esp)//eax寄存器与esp指向的地址的内容互换(如eax=do_divide_error的地址),栈顶存放eax的内容
	pushl %ebx//相当于eax,ebx,ecx依次入栈,所以有最后的popl	%eax,然后将错误处理函数(如:do_divide_error)的地址放到eax寄存器
	pushl %ecx
	pushl %edx
	pushl %edi
	pushl %esi
	pushl %ebp
	push	%ds
	push	%es
	push	%fs
	pushl	$0//将0作为错误码入栈,作为函数参数,对应error_code
	lea		44(%esp),%edx//我们push了11个寄存器,每个占4个字节,共44个字节,所以开始时esp的值为esp+44
	pushl	%edx//edx入栈,其实就是原来的esp入栈,作为函数参数,对应esp
	movl	$0x20,%edx//初始化段寄存器ds、es和fs,加载内核数据段选择符
	mov	%dx,%ds
	mov	%dx,%es
	mov	%dx,%fs
	call *%eax//调用c参数,如do_divide_error
	addl	$8,%esp
	pop	%fs
	pop	%es
	pop	%ds
	popl	%ebp
	popl	%esi
	popl	%edi
	popl	%edx
	popl	%ecx
	popl	%ebx
	popl	%eax
	iret


同时中断门修改如下:

 set_intr_gate(32,&do_divide_error);
 sti();

你可能感兴趣的:(设置中断门与陷阱门)