程序8-2 linux/kernel/traps.c


  1 /*

  2  *  linux/kernel/traps.c

  3  *

  4  *  (C) 1991  Linus Torvalds

  5  */

  6

  7 /*

  8  * 'Traps.c' handles hardware traps and faults after we have saved some

  9  * state in 'asm.s'. Currently mostly a debugging-aid, will be extended

 10  * to mainly kill the offending process (probably by giving it a signal,

 11  * but possibly by killing it outright if necessary).

 12  */

    /*

     * 在程序asm.s中保存了一些状态后,本程序用来处理硬件陷阱和故障。目前主要用于调试目的,

     * 以后将扩展用来杀死遭损坏的进程(主要是通过发送一个信号,但如果必要也会直接杀死)。

     */

 13 #include <string.h>       // 字符串头文件。主要定义了一些有关内存或字符串操作的嵌入函数。

 14

 15 #include <linux/head.h>   // head头文件,定义了段描述符的简单结构,和几个选择符常量。

 16 #include <linux/sched.h>  // 调度程序头文件,定义了任务结构task_struct、初始任务0的数据,

                              // 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。

 17 #include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义。

 18 #include <asm/system.h>   // 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。

 19 #include <asm/segment.h>  // 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。

 20 #include <asm/io.h>       // 输入/输出头文件。定义硬件端口输入/输出宏汇编语句。

 21

    // 以下语句定义了三个嵌入式汇编宏语句函数。有关嵌入式汇编的基本语法见本程序列表后的说明。

    // 用圆括号括住的组合语句(花括号中的语句)可以作为表达式使用,其中最后的__res是其输出值。

    // 23行定义了一个寄存器变量__res。该变量将被保存在一个寄存器中,以便于快速访问和操作。

    // 如果想指定寄存器(例如eax),那么我们可以把该句写成“register char __res asm("ax");”。

    // 取段seg中地址addr处的一个字节。

    // 参数:seg - 段选择符;addr - 段内指定地址。

    // 输出:%0 - eax (__res);输入:%1 - eax (seg)%2 - 内存地址 (*(addr))

 22 #define get_seg_byte(seg,addr) ({ \

 23 register char __res; \

 24 __asm__("push %%fs;mov %%ax,%%fs;movb %%fs:%2,%%al;pop %%fs" \

 25         :"=a" (__res):"0" (seg),"m" (*(addr))); \

 26 __res;})

 27

    // 取段seg中地址addr处的一个长字(4字节)。

    // 参数:seg - 段选择符;addr - 段内指定地址。

    // 输出:%0 - eax (__res);输入:%1 - eax (seg)%2 - 内存地址 (*(addr))

 28 #define get_seg_long(seg,addr) ({ \

 29 register unsigned long __res; \

 30 __asm__("push %%fs;mov %%ax,%%fs;movl %%fs:%2,%%eax;pop %%fs" \

 31         :"=a" (__res):"0" (seg),"m" (*(addr))); \

 32 __res;})

 33

    // fs段寄存器的值(选择符)。

    // 输出:%0 - eax (__res)

 34 #define _fs() ({ \

 35 register unsigned short __res; \

 36 __asm__("mov %%fs,%%ax":"=a" (__res):); \

 37 __res;})

 38

    // 以下定义了一些函数原型。

 39 void page_exception(void);                   // 页异常。实际是page_faultmm/page.s14)。

 40

 41 void divide_error(void);                     // int0kernel/asm.s20)。

 42 void debug(void);                            // int1kernel/asm.s54)。

 43 void nmi(void);                              // int2kernel/asm.s58)。

 44 void int3(void);                             // int3kernel/asm.s62)。

 45 void overflow(void);                         // int4kernel/asm.s66)。

 46 void bounds(void);                           // int5kernel/asm.s70)。

 47 void invalid_op(void);                       // int6kernel/asm.s74)。

 48 void device_not_available(void);             // int7kernel/sys_call.s158)。

 49 void double_fault(void);                     // int8kernel/asm.s98)。

 50 void coprocessor_segment_overrun(void);      // int9kernel/asm.s78)。

 51 void invalid_TSS(void);                      // int10kernel/asm.s132)。

 52 void segment_not_present(void);              // int11kernel/asm.s136)。

 53 void stack_segment(void);                    // int12kernel/asm.s140)。

 54 void general_protection(void);               // int13kernel/asm.s144)。

 55 void page_fault(void);                       // int14mm/page.s14)。

 56 void coprocessor_error(void);                // int16kernel/sys_call.s140)。

 57 void reserved(void);                         // int15kernel/asm.s82)。

 58 void parallel_interrupt(void);               // int39kernel/sys_call.s295)。

 59 void irq13(void);                            // int45 协处理器中断处理(kernel/asm.s86)。

 60 void alignment_check(void);                  // int46kernel/asm.s148)。

 61

    // 该子程序用来打印出错中断的名称、出错号、调用程序的EIPEFLAGSESPfs段寄存器值、

    // 段的基址、段的长度、进程号pid、任务号、10字节指令码。如果堆栈在用户数据段,则还

    // 打印16字节的堆栈内容。这些信息可用于程序调试。

 62 static void die(char * str,long esp_ptr,long nr)

 63 {

 64         long * esp = (long *) esp_ptr;

 65         int i;

 66

 67         printk("%s: %04x\n\r",str,nr&0xffff);

    // 下行打印语句显示当前调用进程的CS:EIPEFLAGSSS:ESP的值。参照错误!未找到引用源。可知,这里esp[0]

    // 即为图中的esp0位置。因此我们把这句拆分开来看为:

    // (1) EIP:\t%04x:%p\n  -- esp[1]是段选择符(cs),esp[0]eip

    // (2) EFLAGS:\t%p      -- esp[2]eflags

    // (3) ESP:\t%04x:%p\n  -- esp[4]是原ssesp[3]是原esp

 68         printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",

 69                 esp[1],esp[0],esp[2],esp[4],esp[3]);

 70         printk("fs: %04x\n",_fs());

 71         printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));

 72         if (esp[4] == 0x17) {             // 若原ss值为0x17(用户栈),则还打印出

 73                 printk("Stack: ");        // 用户栈中的4个长字值(16字节)。

 74                 for (i=0;i<4;i++)

 75                         printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));

 76                 printk("\n");

 77         }

 78         str(i);                 // 取当前运行任务的任务号(include/linux/sched.h210行)。

 79         printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i); // 进程号,任务号。

 80         for(i=0;i<10;i++)

 81                 printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));

 82         printk("\n\r");

 83         do_exit(11);            /* play segment exception */

 84 }

 85

    // 以下这些以do_开头的函数是asm.s中对应中断处理程序调用的C函数。

 86 void do_double_fault(long esp, long error_code)

 87 {

 88         die("double fault",esp,error_code);

 89 }

 90

 91 void do_general_protection(long esp, long error_code)

 92 {

 93         die("general protection",esp,error_code);

 94 }

 95

 96 void do_alignment_check(long esp, long error_code)

 97 {

 98     die("alignment check",esp,error_code);

 99 }

100

101 void do_divide_error(long esp, long error_code)

102 {

103         die("divide error",esp,error_code);

104 }

105

    // 参数是进入中断后被顺序压入堆栈的寄存器值。参见asm.s程序第24--35行。

106 void do_int3(long * esp, long error_code,

107                 long fs,long es,long ds,

108                 long ebp,long esi,long edi,

109                 long edx,long ecx,long ebx,long eax)

110 {

111         int tr;

112

113         __asm__("str %%ax":"=a" (tr):"" (0));               // 取任务寄存器值ètr

114         printk("eax\t\tebx\t\tecx\t\tedx\n\r%8x\t%8x\t%8x\t%8x\n\r",

115                 eax,ebx,ecx,edx);

116         printk("esi\t\tedi\t\tebp\t\tesp\n\r%8x\t%8x\t%8x\t%8x\n\r",

117                 esi,edi,ebp,(long) esp);

118         printk("\n\rds\tes\tfs\ttr\n\r%4x\t%4x\t%4x\t%4x\n\r",

119                 ds,es,fs,tr);

120         printk("EIP: %8x   CS: %4x  EFLAGS: %8x\n\r",esp[0],esp[1],esp[2]);

121 }

122

123 void do_nmi(long esp, long error_code)

124 {

125         die("nmi",esp,error_code);

126 }

127

128 void do_debug(long esp, long error_code)

129 {

130         die("debug",esp,error_code);

131 }

132

133 void do_overflow(long esp, long error_code)

134 {

135         die("overflow",esp,error_code);

136 }

137

138 void do_bounds(long esp, long error_code)

139 {

140         die("bounds",esp,error_code);

141 }

142

143 void do_invalid_op(long esp, long error_code)

144 {

145         die("invalid operand",esp,error_code);

146 }

147

148 void do_device_not_available(long esp, long error_code)

149 {

150         die("device not available",esp,error_code);

151 }

152

153 void do_coprocessor_segment_overrun(long esp, long error_code)

154 {

155         die("coprocessor segment overrun",esp,error_code);

156 }

157

158 void do_invalid_TSS(long esp,long error_code)

159 {

160         die("invalid TSS",esp,error_code);

161 }

162

163 void do_segment_not_present(long esp,long error_code)

164 {

165         die("segment not present",esp,error_code);

166 }

167

168 void do_stack_segment(long esp,long error_code)

169 {

170         die("stack segment",esp,error_code);

171 }

172

173 void do_coprocessor_error(long esp, long error_code)

174 {

175         if (last_task_used_math != current)

176                 return;

177         die("coprocessor error",esp,error_code);

178 }

179

180 void do_reserved(long esp, long error_code)

181 {

182         die("reserved (15,17-47) error",esp,error_code);

183 }

184

    // 下面是异常(陷阱)中断程序初始化子程序。设置它们的中断调用门(中断向量)。

    // set_trap_gate()set_system_gate()都使用了中断描述符表IDT中的陷阱门(Trap Gate),

    // 它们之间的主要区别在于前者设置的特权级为0,后者是3。因此断点陷阱中断int3、溢出中断

    // overflow 和边界出错中断 bounds 可以由任何程序调用。 这两个函数均是嵌入式汇编宏程序,

    // 参见include/asm/system.h,第36行、39行。

185 void trap_init(void)

186 {

187         int i;

188

189         set_trap_gate(0,&divide_error);     // 设置除操作出错的中断向量值。以下雷同。

190         set_trap_gate(1,&debug);

191         set_trap_gate(2,&nmi);

192         set_system_gate(3,&int3);           /* int3-5 can be called from all */

193         set_system_gate(4,&overflow);       /* int3-5 可以被所有程序执行 */

194         set_system_gate(5,&bounds);

195         set_trap_gate(6,&invalid_op);

196         set_trap_gate(7,&device_not_available);

197         set_trap_gate(8,&double_fault);

198         set_trap_gate(9,&coprocessor_segment_overrun);

199         set_trap_gate(10,&invalid_TSS);

200         set_trap_gate(11,&segment_not_present);

201         set_trap_gate(12,&stack_segment);

202         set_trap_gate(13,&general_protection);

203         set_trap_gate(14,&page_fault);

204         set_trap_gate(15,&reserved);

205         set_trap_gate(16,&coprocessor_error);

206         set_trap_gate(17,&alignment_check);

 

    // 下面把int17-47的陷阱门先均设置为reserved,以后各硬件初始化时会重新设置自己的陷阱门。

207         for (i=18;i<48;i++)

208                 set_trap_gate(i,&reserved);

 

    // 设置协处理器中断0x2d45)陷阱门描述符,并允许其产生中断请求。设置并行口中断描述符。

209         set_trap_gate(45,&irq13);

210         outb_p(inb_p(0x21)&0xfb,0x21);          // 允许8259A主芯片的IRQ2中断请求。

211         outb(inb_p(0xA1)&0xdf,0xA1);            // 允许8259A从芯片的IRQ13中断请求。

212         set_trap_gate(39,&parallel_interrupt);  // 设置并行口1的中断0x27陷阱门描述符。

213 }

214