在Linux系统中怎样判断栈是否溢出

摘自海思文档:

linux系统中共使用了4种堆栈:

l  第1种是系统引导初始化时临时使用的堆栈;

l  第2种是进入保护模式之后提供内核程序初始化使用的堆栈,位于内核代码地址空间固定位置处。该堆栈也是后来任务0使用的用户态堆栈;

l  第3种是每个任务通过系统调用,执行内核程序时使用的堆栈,我们称之为任务的内核态堆栈。每个任务都有自己独立的内核态堆栈;

l  第4种是任务在用户态执行的堆栈,位于任务(进程)逻辑地址空间近末端处。

使用多个栈或在不同情况下使用不同栈的主要原因有两个。首先是由于从实模式进入保护模式,使得CPU对内存寻址访问方式发生了变化,因此需要重新调整设置栈区域。另外,为了解决不同CPU特权级共享使用堆栈带来的保护问题,执行0级的内核代码和执行3级的用户代码需要使用不同的栈。当一个任务进入内核态运行时,就会使用其TSS段中给出的特权级0的堆栈指针tss.ss0、tss.esp0,即内核栈。原用户栈指针会被保存在内核栈中。而当从内核态返回用户态时,就会恢复使用用户态的堆栈


当一个进程运行在内核态时(比如通过系统调用),它就将开始使用它自己的内核栈,如果内核栈大小为8K,那么此时的触发的中断处理也将使用这个栈,如果内核栈大小为4K,那么此时的触发的中断处理则将使用单独的内核栈。

由于内核栈大小固定且比较小,很容易出现内核栈溢出的情况,所以不能在内核代码里使用递归调用(除非你非常清楚它递归的层次,但仍建议将递归改为循环,因为谁也不知道将来哪一天递归的层次是否会发生变化),也不建议使用较大或大小未知的栈变量(比如动态数组)等。由于task_struct和内核栈共用同一块内存区域,所以内核栈溢出最直接的后果就是把task_struct结构体踩坏,在linux下,这个结构体是至关重要的,每一个进程都是由这个task_struct数据结构来定义,它也就是我们通常所说的PCB,它是用来对进程进行控制的唯一手段,也是最有效的手段;到了kernel 2.6.x之后,和内核栈共用同一块内存区域的不再是task_struct,而是结构体thread_info,不过由于thread_info结构体的第一个字段就是task_struct指针,所以内核栈溢出的话同样会损坏task_struct,因为此时task指针指向一个不可预知的地址,相应的task_struct结构体各个字段数据当然也就都是垃圾数据了。看如下图的堆栈结构更直观:




do_fork()时,内核使用slab分配了task_struct结构体tsk;

threadinfo不需要单独分配,因为threadinfo在该线程的栈的空间里(最底下)。也就是分配了栈空间,就有threadinfo的位置了。

ti = alloc_pages(GFP_KERNEL, 1);//order=1代表8K的空间,ti代表threadinfo上面一句相对于分配了8K的栈的空间,同时threadinfo就占据在这个空间的开始部分。threadinfo的大小估算了下有752B大小(不精确),然后do_fork后面在每个threadinfo后面放了个MAGIC数值,便于观察是否栈溢出覆盖掉threadinfo。

task_struct有个stack字段(void *stack),它指向自己的stack的起始地址。

tsk.stack = ti;//也就是通过task_struct的stack就找到了threadinfo的首地址(指针)。同时threadinfo里有个task字段(struct task_struct  *task),它指向自己的这个task_struct。也就是它们两个互相交叉引用!

那么我们则么判断栈是否溢出呢?内核判断栈的结束地方如下:

static inline unsigned long*end_of_stack(struct task_struct *p)
{
    return (unsigned long *)(task_thread_info(p) +1);

}

也就是thread_info的上面就是栈的结束,上面指的是地址向上。这里的加1实际上偏移了一个structthreadinfo大小。

内核在栈底(end ofstack)保存了一个STACK_END_MAGIC数值。

我们获取了栈底的这个地址,读取出的数值是否是这个STACK_END_MAGIC数值,就可以很好的判断是否栈溢出了!具体的修改方法举例如下:

在kernel/softirq.c中的asmlinkage void __do_softirq(void)函数中加下面红色字体的代码

。。。。。。

do {

       if (pending & 1) {

            int printkflg= 0;

            static int ncount = 0;

            unsigned long *sp_magic = NULL;

            unsigned inttmp=current_thread_info();

            unsigned int vec_nr = h - softirq_vec;

           int prev_count = preempt_count();

           kstat_incr_softirqs_this_cpu(vec_nr);

            sp_magic =(unsigned long *)((struct thread_info *)tmp +1);

            if(current_thread_info()->task->pid!= 0)

            {

                register unsigned long sp asm("sp");

                unsigned int  tmp2=sp;

                static int minstack=0xFFFFFFFF;

 

                if((*sp_magic != 0x57AC6E9D))ncount++;

 

                if( (tmp2 - tmp < minstack)|| ((*sp_magic != 0x57AC6E9D) && (ncount < 2)))

                {

                    minstack = tmp2 - tmp;

                    printkflg = 1;

                    printk("-----sp_magic= 0x%x, 0x%x, 0x%x\n",*sp_magic,0x57AC6E9D,sp_magic);

                    printk("----------DumpAt %s %d, StackS %p(id=%d)-- %p ?= %08x ---------\n",

                            __FUNCTION__,__LINE__, current_thread_info(), current_thread_info()->task->pid,&tmp, sp);

                }

            }

            trace_softirq_entry(vec_nr);

           h->action(h);

           trace_softirq_exit(vec_nr);

            if(current_thread_info()->task->pid!= 0)

            {

                if( (printkflg) || ((*sp_magic!= 0x57AC6E9D) && (ncount < 2)))

                {

                    register unsigned long spasm ("sp");

                    unsigned int  tmp2=sp;

 

                    printkflg = 0;

                    printk("-----sp_magic= 0x%x, 0x%x, 0x%x\n",*sp_magic,0x57AC6E9D,sp_magic);

                    printk("----------Dump At%s %d, StackS %p(id=%d)-- %p ?= %08x ---------\n",

                            __FUNCTION__,__LINE__, current_thread_info(), current_thread_info()->task->pid,&tmp, sp);

                    dump_stack();

                }

            }

。。。。。。




猜你喜欢

转载自blog.csdn.net/vc66vcc/article/details/80540813
今日推荐