结合源代码分析一个完整的中断过程【转】

转自:http://blog.csdn.net/rosetta/article/details/49454021 

转载请注明出处:http://blog.csdn.net/rosetta   

结合源代码分析一个完整的中断过程

  此文详细描述了中断产生到中断处理程序执行、中断处理程序返回以及中断描述符初始化整个过程,结合linux-0.00源代码片断学习将会更直观易懂。

   中断是指当前执行程序或任务在执行到某处时出现一个事件,该事件需要程序处理。这种事件会导致程序会从当前运行程序转移到被称为中断(或异常)处理程序中执行。中断可以由硬件发生,也可以由软件调用int n指令产生。异常发生在CPU执行一条指令时,检测到一个出错条件时发生,例如被0除出错。

       那么中断产生时,当CPU去执行中断处理程序之前如何保存当前程序的信息?需要保存的信息有哪些,以便在处理完中断处理程序后恢复执行之前的程序?再者CPU怎么去找中断处理程序的?

       下面以int n产生中断为例,以linux-0.00中的head.s源码为实例,并使用bochs单步调试,理论联系实际,分析从中断产生到执行并返回整个过程。   

       Boch调试环境及linux-0.00运行环境搭建可参考《Linux-0.00运行环境搭建》

       从head.s如下代码处开始分析。完整的代码可参考《一个简单多任务内核实例的分析 》

[cpp] view plain copy
 
  1. 236 task0:  
  2.   
  3. 237    movl $0x17, %eax  
  4.   
  5. 238    movw %ax, %ds  
  6.   
  7. 239    movb $65, %al              /*print 'A' 65*/  
  8.   
  9. 240    int $0x80  
  10.   
  11. 241     movl $0xfff, %ecx  
  12.   
  13. 242 1: loop 1b  
  14.   
  15. 243    jmp task0  
  16.   
  17.    

       运行bochs 调试脚本,按以下方法找到task0的地址,因为head程序在物理地址0x00处,所以使用objdump显示的偏移地址+段地址0x00还是其本身,即最终的断点位置。

       在源代码目录执行:

[html] view plain copy
 
  1. [root@xxx linux-0.00-rh9]# objdump -d -j.text -t ./head.o|grep task0  
  2.   
  3. 000010e0  l      .text     00000000 task0  

 

       所以在bochs中直接下断到0x10e0处。

  在执行int 0x80前先停下观察cpu相应寄存器及栈的数据。

  栈中状态:

 

  可以看到此时的cs=0x000f(62行iret返回后设置的cs值),eip=0x10e9(当前即将执行的int 0x80代码),如果这里不产生中断的话下一条代码将是执行0x10eb,但这里使用int 0x80 产生了一个中断,从而去调用IDT中断描述符表中索引为0x80(第128个)的描述符。

       使用跟进函数指令s跟进中断处理程序并显示当前cpu、栈的情况。

 

 

   寄存器中主要变化的是cs,eip,ss,esp,而栈的变化也很大,那么他们是如何变化的呢?

 

  1. eip=0x10e9 => 0x0166

       eip的变化很好理解,调用int0x80之前的指令0x10e9处跳到了现在的0x0166处,之前指令的后一条将要执行的指令地址0x10eb存在了栈中(用于中断返回)。

  2.  cs=0x000f => 0x0008

  那么cs为何变成了0x0008呢?注意此时是保护模式下,所以0x0008其实为段选择符。

  因为此时已经跳转到了IDT中第0x80个描述符,那么0x80处的描述符长何样?

[html] view plain copy
 
  1. 36 # setup timer & system call interruptdescriptors.  
  2.   
  3.  37    movl $0x00080000, %eax  
  4.   
  5.  38     movw $timer_interrupt, %ax  
  6.   
  7.  39     movw $0x8E00, %dx  
  8.   
  9.  40     movl $0x08, %ecx              # The PC default timer int.  
  10.   
  11.  41     lea idt(,%ecx,8), %esi  
  12.   
  13.  42     movl %eax,(%esi)  
  14.   
  15.  43     movl %edx,4(%esi)  
  16.   
  17.  44     movw$system_interrupt, %ax  
  18.   
  19.  45    movw $0xef00, %dx  
  20.   
  21.  46    movl $0x80, %ecx  
  22.   
  23.  47    lea idt(,%ecx,8), %esi  
  24.   
  25.  48    movl %eax,(%esi)  
  26.   
  27.  49    movl %edx,4(%esi)  

       如上代码,因为IDT中的每一项中断描述符都为8字节,以上第37行,44~49行就是设置IDT表中第0x80个位置处的中断描述符,其内容:eax为低4字节,其中eax的高16位为段选择符0x0008(即刚才cs的变化,由0x000f => 0x0008),eax的低16位为中断处理程序system_interrupt偏移地址;dx为中断描述符类型及DPL等,此时的中断描述符类型为0xf=1111为陷阱门描述符。

  3.ss=0x0017=> 0x0010, esp=0x0bd8 => 0xe4c

  代码中的tss0内容为:

[html] view plain copy
 
  1. 205tss0:   .long 0             /* back link */  
  2.   
  3. 206     .long krn_stk0, 0x10        /* esp0, ss0 */  
  4.   
  5. 207     .long 0, 0, 0, 0, 0     /* esp1, ss1, esp2, ss2, cr3 */  
  6.   
  7. 208     .long 0, 0, 0, 0, 0     /* eip, eflags, eax, ecx, edx */  
  8.   
  9. 209     .long 0, 0, 0, 0, 0     /* ebx esp, ebp, esi, edi */  
  10.   
  11. 210     .long 0, 0, 0, 0, 0, 0      /* es, cs, ss, ds, fs, gs */  
  12.   
  13. 211     .long LDT0_SEL, 0x8000000   /* ldt, trace bitmap */  
  14.   
  15. 212  
  16.   
  17. 213     .fill 128,4,0  
  18.   
  19. 214 krn_stk0:  
  20.   
  21. 215 #   .long 0  
  22.   
  23.    

  使用objdump查看head.o中的krn_stak0地址:

[html] view plain copy
 
  1. [root@plmlinux-0.00-rh9]# objdump -d -j .text -t ./head.o|grep krn_stk0  
  2.   
  3. 00000e60  l       .text     00000000krn_stk0  
  4.   
  5. 00000e60<krn_stk0>:  

       4.栈的变化

  ss的值加载了tss任务段描述符中的ss0字段,为0x10;esp也是加载其中的esp0字段,所以应该是0xe60,但是因为该栈已经被使用过了,所以导致esp减至0xe4c。

       栈中从0xe60开始至0xe4c处都是什么内容?此时看下system_interrupt是如何返回的?

[html] view plain copy
 
  1. 152 .align 2  
  2.   
  3. 153system_interrupt:  
  4.   
  5. 154     push %ds  
  6.   
  7. 155     pushl %edx  
  8.   
  9. 156     pushl %ecx  
  10.   
  11. 157     pushl %ebx  
  12.   
  13. 158     pushl %eax  
  14.   
  15. 159     movl $0x10, %edx  
  16.   
  17. 160     mov %dx, %ds  
  18.   
  19. 161     call write_char  
  20.   
  21. 162     popl %eax  
  22.   
  23. 163     popl %ebx  
  24.   
  25. 164     popl %ecx  
  26.   
  27. 165     popl %edx  
  28.   
  29. 166     pop %ds  
  30.   
  31. 167     iret  

       发现其最后是iret,而执行此指令会导致:

[html] view plain copy
 
  1. eip <= ss:esp  esp=esp+4 ; eip <= 0x10eb  
  2.   
  3. cs <= ss:esp  esp=esp+4; cs <= 0x000f  
  4.   
  5. eflags <= ss:esp esp=esp+4; eflags <= 246  
  6.   
  7. esp <= ss:esp  esp=esp+4; esp <= 0x0bd8  
  8.   
  9. ss <= ss:esp   esp=esp+4; ss <= 0x0017  

 

   所以,栈中保存的内容都是中断前的信息,以便返回时使用。

   System_interrupt中断处理程序调用iret返回后将继续执行int 0x80后面的指令cs:eip=0x00f:0x10eb。

  此文仅描述中断方面的知识,对于任务task的切换和加载、tss任务段描述符未做说明,此部分内容将在下一篇文章中记录。

参考文献:

《Linux内核完全剖析》赵炯

posted @ 2016-03-15 16:02  Sky&Zhang  阅读(376)  评论(0编辑  收藏  举报