分享
 
 
 

linux下糟糕的异常处理方式

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

linux下发生异常,芯片会自动产生一个异常中断。在这异常中断处理程序中会判断异常来自用户程序或者内核,如果是发生在用户程序,那么会产生一个异常信号,再根据异常信号的回调函数通知用户程序发生异常。如果发生在内核里面,那么就会搜索内核模块的异常结构表,找到相应的处理调用地址,修改异常中断的返回地址为异常处理的地址,中断返回的时候程序就跳到异常处理程序处理执行了。但具体这两种处理方法都很糟糕,下面简要分析一下。

linux系统把所有进程数据结构都放于内核,这就增加了一些不必要的切换时间。 linux可以通过系统调用,安装信号的回调函数,这回调函数指针存放在内核的进程数据结构里面。这点windows处理得比较好,windows把进程数据结构分成了两部分,一部分敏感数据放于内核的进程数据结构里面,加以保护,另一部分不敏感数据就放于用户空间,这样当访问那些不加保护的数据时,就不用切换到内核,节约了时间。像windows下异常处理,也是一种回调函数,但因为结构放于用户空间,安装的时候就很方便,也节约切换时间。

上面那一点只是效率问题,但linux内核的异常处理那才是糟糕。先介绍一下linux内核的异常处理结构吧,看明白了你自然就知道糟糕到什么程度了。要了解这,显然应该是先从异常中断入手。下面主要是x86芯片的一些处理,但别的芯片下的也应该差不多。

文件:entry.S:

ENTRY(general_protection)

pushl $ SYMBOL_NAME(do_general_protection)

jmp error_code

这是异常中断入口,显然会执行do_general_protection。

文件traps.c:

asmlinkage void do_general_protection(struct pt_regs * regs, long error_code)

{

if (regs->eflags & VM_MASK)

goto gp_in_vm86;

/*

虚拟8086下发生的异常否

*/

if (!(regs->xcs & 3))

goto gp_in_kernel;

/*

内核发生的异常否

*/

current->tss.error_code = error_code;

current->tss.trap_no = 13;

force_sig(SIGSEGV, current);

/*

用户程序发生的异常,产生异常信号,

根据异常信号的句柄回调处理函数

*/

return;

gp_in_vm86:

lock_kernel();

handle_vm86_fault((struct kernel_vm86_regs *) regs, error_code);

/*

虚拟8086的处理

*/

unlock_kernel();

return;

gp_in_kernel:

{

unsigned long fixup;

fixup = search_exception_table(regs->eip);

/*

根据异常时的eip搜索异常结构链

找到处理程序地址

*/

if (fixup) {

regs->eip = fixup;

/*

找到异常处理地址,修改中断返回地址,中断返回时跳到异常处理程序处

*/

return;

}

die("general protection fault", regs, error_code);

/*

没找到异常处理程序地址,显示内核异常信息后死机

*/

}

}

搜索异常处理程序代码文件extable.c:

extern const struct exception_table_entry __start___ex_table[];

extern const struct exception_table_entry __stop___ex_table[];

unsigned long search_exception_table(unsigned long addr)

{

unsigned long ret;

#ifndef CONFIG_MODULES

/* There is only the kernel to search. */

ret = search_one_table(__start___ex_table, __stop___ex_table-1, addr);

if (ret) return ret;

#else

/* The kernel is the last "module" -- no need to treat it special. */

struct module *mp;

for (mp = module_list; mp != NULL; mp = mp->next) {

if (mp->ex_table_start == NULL)

continue;

ret = search_one_table(mp->ex_table_start,

mp->ex_table_end - 1, addr);

if (ret) return ret;

}

#endif

return 0;

}

static inline unsigned long

search_one_table(const struct exception_table_entry *first,

const struct exception_table_entry *last,

unsigned long value)

{

while (first <= last) {

const struct exception_table_entry *mid;

long diff;

mid = (last - first) / 2 + first;

diff = mid->insn - value;

if (diff == 0)

return mid->fixup;

else if (diff < 0)

first = mid+1;

else

last = mid-1;

}

return 0;

}

看看上面搜索异常处理程序的算法就知道了,有个异常模块表,保存会发生异常时候的eip和异常处理程序指针,发生异常的时候就根据异常时候的eip搜索表里面的eip,发现相等就找到了异常处理指针。这是什么意思呢,就是说你编写的内核程序必须精确的知道哪条指令可能会发生异常,要求真够高的。想想windows下的异常编程是多么轻松?程序员只需要知道哪一段程序可能出现异常,就只需要一个括号一个异常语句保护这段程序就是了。

光看上面算法可能对其这异常的处理还没怎么有感性认识,那么我们再看看其内核的异常形式、编写方式吧。

下面我们再看看exception.txt的一些说明:

查看内核模块表:

> Sections:

> Idx Name Size VMA LMA File off Algn

> 0 .text 00098f40 c0100000 c0100000 00001000 2**4

> CONTENTS, ALLOC, LOAD, READONLY, CODE

> 1 .fixup 000016bc c0198f40 c0198f40 00099f40 2**0

> CONTENTS, ALLOC, LOAD, READONLY, CODE

> 2 .rodata 0000f127 c019a5fc c019a5fc 0009b5fc 2**2

> CONTENTS, ALLOC, LOAD, READONLY, DATA

> 3 __ex_table 000015c0 c01a9724 c01a9724 000aa724 2**2

> CONTENTS, ALLOC, LOAD, READONLY, DATA

> 4 .data 0000ea58 c01abcf0 c01abcf0 000abcf0 2**4

> CONTENTS, ALLOC, LOAD, DATA

> 5 .bss 00018e21 c01ba748 c01ba748 000ba748 2**2

> ALLOC

> 6 .comment 00000ec4 00000000 00000000 000ba748 2**0

> CONTENTS, READONLY

> 7 .note 00001068 00000ec4 00000ec4 000bb60c 2**0

> CONTENTS, READONLY

看模块__ex_table就是会出现异常的一些程序eip,fixup就是相应的异常处理程序地址。这显然这异常结构是静态的,与windows动态的链表形式有很大的分别。

再看看异常程序的编写,下面是get_user(c, buf)的一段代码:

switch ((sizeof(*(buf)))) {

case 1:

__asm__ __volatile__(

"1: mov" "b" " %2,%" "b" "1\n"

/*

这语句可能发生异常

*/

"2:\n"

".section .fixup,\"ax\"\n"

"3: movl %3,%0\n"

/*

异常处理程序

*/

" xor" "b" " %" "b" "1,%" "b" "1\n"

" jmp 2b\n"

".section __ex_table,\"a\"\n"

" .align 4\n"

" .long 1b,3b\n"

/*

1b,3b就是对应1:,3:就是可能会发生异常的eip与异常后的处理程序

*/

".text" : "=r"(__gu_err), "=q" (__gu_val): "m"((*(struct __large_struct *)

( __gu_addr )) ), "i"(- 14 ), "0"( __gu_err )) ;

break;

看看上面代码,是不是要严格的知道哪条指令可能会产生异常?你也清楚了linux整个异常处理方式了吧。编写是不是也太麻烦?弄不好你不知道哪条语句会发生异常,而执行到那发生了异常,那么系统就会出现可怕的异常提示后死机了。

这点完全可以用一个链表的方式处理,哪段程序可能发生异常,就安装异常处理程序指针到链表里面,执行完这段代码就从链表里面删除这段异常处理代码指针,如果这段代码发生异常,系统的异常中断处理程序只需要调用这链表的异常处理程序指针就是了,不要匹配发生异常时的eip。linux的处理主要是因为处理成了一个静态的链表,而不是动态的,这就使得需要确定发生异常时的异常处理指针,所以就增加了一个检测eip完全相等的匹配条件,而这就造成了程序编写上的苦难。这处理方式不用我说你就会知道是多么的糟糕了,弄不好就会留下一些会让系统崩溃的地方呢。

附exception.txt:

Kernel level exception handling in Linux 2.1.8

Commentary by Joerg Pommnitz

When a process runs in kernel mode, it often has to access user

mode memory whose address has been passed by an untrusted program.

To protect itself the kernel has to verify this address.

In older versions of Linux this was done with the

int verify_area(int type, const void * addr, unsigned long size)

function.

This function verified that the memory area starting at address

addr and of size size was accessible for the operation specified

in type (read or write). To do this, verify_read had to look up the

virtual memory area (vma) that contained the address addr. In the

normal case (correctly working program), this test was successful.

It only failed for a few buggy programs. In some kernel profiling

tests, this normally unneeded verification used up a considerable

amount of time.

To overcome this situation, Linus decided to let the virtual memory

hardware present in every Linux-capable CPU handle this test.

How does this work?

Whenever the kernel tries to access an address that is currently not

accessible, the CPU generates a page fault exception and calls the

page fault handler

void do_page_fault(struct pt_regs *regs, unsigned long error_code)

in arch/i386/mm/fault.c. The parameters on the stack are set up by

the low level assembly glue in arch/i386/kernel/entry.S. The parameter

regs is a pointer to the saved registers on the stack, error_code

contains a reason code for the exception.

do_page_fault first obtains the unaccessible address from the CPU

control register CR2. If the address is within the virtual address

space of the process, the fault probably occurred, because the page

was not swapped in, write protected or something similar. However,

we are interested in the other case: the address is not valid, there

is no vma that contains this address. In this case, the kernel jumps

to the bad_area label.

There it uses the address of the instruction that caused the exception

(i.e. regs->eip) to find an address where the execution can continue

(fixup). If this search is successful, the fault handler modifies the

return address (again regs->eip) and returns. The execution will

continue at the address in fixup.

Where does fixup point to?

Since we jump to the contents of fixup, fixup obviously points

to executable code. This code is hidden inside the user access macros.

I have picked the get_user macro defined in include/asm/uaccess.h as an

example. The definition is somewhat hard to follow, so let's peek at

the code generated by the preprocessor and the compiler. I selected

the get_user call in drivers/char/console.c for a detailed examination.

The original code in console.c line 1405:

get_user(c, buf);

The preprocessor output (edited to become somewhat readable):

(

{

long __gu_err = - 14 , __gu_val = 0;

const __typeof__(*( ( buf ) )) *__gu_addr = ((buf));

if (((((0 + current_set[0])->tss.segment) == 0x18 ) ||

(((sizeof(*(buf))) <= 0xC0000000UL) &&

((unsigned long)(__gu_addr ) <= 0xC0000000UL - (sizeof(*(buf)))))))

do {

__gu_err = 0;

switch ((sizeof(*(buf)))) {

case 1:

__asm__ __volatile__(

"1: mov" "b" " %2,%" "b" "1\n"

"2:\n"

".section .fixup,\"ax\"\n"

"3: movl %3,%0\n"

" xor" "b" " %" "b" "1,%" "b" "1\n"

" jmp 2b\n"

".section __ex_table,\"a\"\n"

" .align 4\n"

" .long 1b,3b\n"

".text" : "=r"(__gu_err), "=q" (__gu_val): "m"((*(struct __large_struct *)

( __gu_addr )) ), "i"(- 14 ), "0"( __gu_err )) ;

break;

case 2:

__asm__ __volatile__(

"1: mov" "w" " %2,%" "w" "1\n"

"2:\n"

".section .fixup,\"ax\"\n"

"3: movl %3,%0\n"

" xor" "w" " %" "w" "1,%" "w" "1\n"

" jmp 2b\n"

".section __ex_table,\"a\"\n"

" .align 4\n"

" .long 1b,3b\n"

".text" : "=r"(__gu_err), "=r" (__gu_val) : "m"((*(struct __large_struct *)

( __gu_addr )) ), "i"(- 14 ), "0"( __gu_err ));

break;

case 4:

__asm__ __volatile__(

"1: mov" "l" " %2,%" "" "1\n"

"2:\n"

".section .fixup,\"ax\"\n"

"3: movl %3,%0\n"

" xor" "l" " %" "" "1,%" "" "1\n"

" jmp 2b\n"

".section __ex_table,\"a\"\n"

" .align

[1] [2] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有