内核处理信号对应用层堆栈的影响

2024-06-18 09:08

本文主要是介绍内核处理信号对应用层堆栈的影响,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

by alert7 < alert7@xfocus.org >
主页: http://www.xfocus.org/  http://www.whitecell.org/
时间:2003年8月1日
好久没有为组织做点贡献了,真有点过意不去:(
本文着重点在内核信号处理对应用层堆栈的影响上,其他的一些在处理信号细节上被忽略。
至于本文是否跟安全相关,那就是仁者见仁智者见智了。
1 发送信号过程:
发送信号的过程比接收信号的过程简单的多。当应用层用KILL命令向某个进程发送进程的时候,
内核只在进程task_struct的sigpending结构中安排一个信号位。
2 接收信号过程
信号处理的时机。
当某个进程有悬而未决的信号的时候,内核就会调用do_signal函数
do_signal做一些其他功能上的事情,真正递送一个信号是在handle_signal函数。于是在最后
do_signal函数调用了handle_signal真正递送一个信号。当然要想到达这一步需要一些条件。比如说
应用层已经声明要处理该信号,信号不是些不可捕获的信号等等...
重点中的重点,我们来看看handle_signal函数
/*
* OK, we're invoking a handler
*/    
static void
handle_signal(unsigned long sig, struct k_sigaction *ka,
          siginfo_t *info, sigset_t *oldset, struct pt_regs * regs)
{
....
    /* Set up the stack frame */
    if (ka->sa.sa_flags & SA_SIGINFO)
        setup_rt_frame(sig, ka, info, oldset, regs);
    else
        setup_frame(sig, ka, oldset, regs);
.....
}
去掉一些我们不想关心的东西,代码就剩下上面这些。
以上函数setup_rt_frame和setup_frame就是内核在应用层的堆栈上安排信号堆栈帧的过程,就是我们所
要关注的。setup_rt_frame和setup_frame雷同,我们就来分析下setup_frame函数。
static void setup_frame(int sig, struct k_sigaction *ka,
            sigset_t *set, struct pt_regs * regs)
{
    struct sigframe *frame;
    int err = 0;
    frame = get_sigframe(ka, regs, sizeof(*frame)); //决定要使用应用层堆栈的地址
    if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) //判断是否可写
        goto give_sigsegv;
    err |= __put_user((current->exec_domain
                   && current->exec_domain->signal_invmap
                   && sig < 32
                   ? current->exec_domain->signal_invmap[sig]
                   : sig),
                  &frame->sig);
    if (err)
        goto give_sigsegv;
    
    /*保存寄存器信号到&frame->sc和&frame->fpstate中*/
    err |= setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
    if (err)
        goto give_sigsegv;
    if (_NSIG_WORDS > 1) {
        err |= __copy_to_user(frame->extramask, &set->sig[1],
                      sizeof(frame->extramask));
    }
    if (err)
        goto give_sigsegv;
    /* Set up to return from userspace.  If provided, use a stub
       already in userspace.  */
    if (ka->sa.sa_flags & SA_RESTORER) {
        err |= __put_user(ka->sa.sa_restorer, &frame->pretcode);
    } else {
        /*把frame->retcod的地址放到&frame->pretcode中,这样当信号处理函数返回时候就会*/
        /*跳到frame->retcode地址去执行代码了*/
        err |= __put_user(frame->retcode, &frame->pretcode);
        /* This is popl %eax ; movl $,%eax ; int $0x80 */
        err |= __put_user(0xb858, (short *)(frame->retcode+0));
        err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2));
        err |= __put_user(0x80cd, (short *)(frame->retcode+6));
        /*以上在frame->retcode上安排了popl %eax ; movl $,%eax ; int $0x80指令*/
    }
    if (err)
        goto give_sigsegv;
    /* Set up registers for signal handler */
    regs->esp = (unsigned long) frame; //让应用层的esp指向frame;
    regs->eip = (unsigned long) ka->sa.sa_handler;//EIP为信号处理函数
    set_fs(USER_DS);
    regs->xds = __USER_DS;
    regs->xes = __USER_DS;
    regs->xss = __USER_DS;
    regs->xcs = __USER_CS;
    regs->eflags &= ~TF_MASK;
#if DEBUG_SIG
    printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n",
        current->comm, current->pid, frame, regs->eip, frame->pretcode);
#endif
    return;
give_sigsegv:
    if (sig == SIGSEGV)
        ka->sa.sa_handler = SIG_DFL;
    force_sig(SIGSEGV, current);
}
到此,内核在应用层的堆栈上就安排了一个帧,我们来看一下一个实际的例子。
[alert7@redhat73 sigal]$ cat test.c
test ()
        {
        printf("test");
return;
        }
int main(int argv,char **argc) {
         char buf[256];
        signal(10,test);
        while(1);
}
[alert7@redhat73]
(gdb) b main
Breakpoint 1 at 0x8048501
(gdb) r dd dd
Starting program: /home/alert7/sigal/test dd dd
Breakpoint 1, 0x08048501 in main ()
(gdb)
Breakpoint 2 at 0x42029098
(gdb) c
Continuing.
(gdb) x/5i 0x42029098
0x42029098 <__restore>: pop    %eax
0x42029099 <__restore+1>:       mov    $0x77,%eax
0x4202909e <__restore+6>:       int    $0x80
0x420290a0 <__restore+8>:       mov    (%esp,1),%ebx
0x420290a3 <__restore+11>:      ret
(gdb) i reg esp ebp eip
esp            0xbffff748       0xbffff748
ebp            0xbffffb38       0xbffffb38
eip            0x4202909e       0x4202909e
(gdb) x/50x $esp-8 //$esp-8就是内核构造的一个信号帧
0xbffff740:     0x42029098      0x0000000a      0x00000000      0x00000000
0xbffff750:     0x0000002b      0x0000002b      0xbffffba4      0x40013020
0xbffff760:     0xbffffb38      0xbffffa20      0x4213030c      0xbffffc00
0xbffff770:     0x08049752      0xbffffb2c      0x00000001      0x00000000
0xbffff780:     0x08048570      0x00000023      0x00000346      0xbffffa20
0xbffff790:     0x0000002b      0x00000000      0x00000000      0x00000000
0xbffff7a0:     0x4000083e      0x400005b8      0x40000218      0x400131e8
0xbffff7b0:     0x00000003      0x40013e48      0x00000003      0x42009e38
0xbffff7c0:     0x40013d68      0x0d1fc7ae      0x0d1fc7ae      0xbffff890
0xbffff7d0:     0x40013bc8      0x4200f624      0x00000000      0x00000000
0xbffff7e0:     0x42009e38      0x40013bc8      0x00000000      0x00000000
0xbffff7f0:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff800:     0x00000000      0x00000000
struct sigframe
{
    char *pretcode;            //这里为0x42029098,在该程序中,ka->sa.sa_flags 有 SA_RESTORER标志,
                    //所以没有在堆栈中安排指令,而是使用了一个现成的地址
    int sig;            //信号为10
    struct sigcontext sc;
    struct _fpstate fpstate;
    unsigned long extramask[_NSIG_WORDS-1];
    char retcode[8];
};
struct sigcontext {
    unsigned short gs, __gsh;//0,0
    unsigned short fs, __fsh;//0,0
    unsigned short es, __esh;//0x2b,0
    unsigned short ds, __dsh;//0x2b,0
    unsigned long edi;        //0xbffffba4
    unsigned long esi;        //0x40013020
    unsigned long ebp;        //0xbffffb38
    unsigned long esp;        //0xbffffa20
    unsigned long ebx;        //0x4213030c
    unsigned long edx;        //0xbffffc00
    unsigned long ecx;        //0x08049752
    unsigned long eax;        //0xbffffb2c
    unsigned long trapno;        //0x00000001
    unsigned long err;        //0x00000000
    unsigned long eip;        //0x08048570
    unsigned short cs, __csh;    //0x23,0
    unsigned long eflags;        //0x00000346
    unsigned long esp_at_signal;    //0xbffffa20
    unsigned short ss, __ssh;    //0x2b,0
    struct _fpstate * fpstate;    //0x00000000
    unsigned long oldmask;        //0x00000000
    unsigned long cr2;        //0x00000000
};
内核在应用层的堆栈上安了一个帧后,当一返回到应用态的时候就跳到信号处理函数test去执行了。
此时图一 ①,应用层的堆栈多了一个帧,如下:
**********************************************************************************
图一
        (内存高址)
        +--------------------------------------+
        | ...                                  |  
        +--------------------------------------+
        | char retcode[8]                      | 8个字节
        +--------------------------------------+
        | long extramask[_NSIG_WORDS-1];       |  
        +--------------------------------------+
        | struct _fpstate fpstate;           |  
        +--------------------------------------+
        |  struct sigcontext sc;           |  
        +--------------------------------------+  <---------esp指向这里 ③
        | int sig;                   |
        +--------------------------------------+  <---------esp指向这里 ②
        | char *pretcode;               |
        +--------------------------------------+  <---------esp指向这里 ①
        |  ...                       |  
        +--------------------------------------+
        (内存低址)
**********************************************************************************
由于内核是让应用程序跳到信号处理函数的,所以不象一般的调用会把当前的EIP压入堆栈,所以现在
esp指向的pretcode的值将来信号处理完就返回到那里去了。此时ESP情况如图一 ② 的情况
当test信号处理函数完成时候,将返回到frame->pretcode也就是0x42029098的地址去执行,在这里0x42029098地址代码如下:
0x42029098 <__restore>:        pop    %eax    //弹出frame->sig,这里为10
0x42029099 <__restore+1>:       mov    $0x77,%eax
0x4202909e <__restore+6>:       int    $0x80    //请求sys_sigreturn系统调用
当执行完以上三条指令的时候,应用层的堆栈就变成了 ③ 的情况了。
忽略切入内核的细节,sys_sigreturn系统调用被调用。下面是该函数的实现细节。
asmlinkage int sys_sigreturn(unsigned long __unused)
{
    struct pt_regs *regs = (struct pt_regs *) &__unused;
    struct sigframe *frame = (struct sigframe *)(regs->esp - 8);//取得frame地址,-8是为了补上ret和pop
                                                            //这两个指令分别弹出的pretcode和sig
                                    //看看上面的图会更清楚
    sigset_t set;
    int eax;
    if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
        goto badframe;
    if (__get_user(set.sig[0], &frame->sc.oldmask)
        || (_NSIG_WORDS > 1
        && __copy_from_user(&set.sig[1], &frame->extramask,
                    sizeof(frame->extramask))))
        goto badframe;
    sigdelsetmask(&set, ~_BLOCKABLE);
    spin_lock_irq(&current->sigmask_lock);
    current->blocked = set;
    recalc_sigpending(current);
    spin_unlock_irq(&current->sigmask_lock);
    
    /*把frame保存的一些信息恢复出来,修改regs一些寄存器*/    
    if (restore_sigcontext(regs, &frame->sc, &eax))
        goto badframe;
    return eax;
badframe:
    force_sig(SIGSEGV, current);
    return 0;
}    
restore_sigcontext函数好象也没有什么好说的,等到sys_sigreturn函数返回,regs的一些寄存器又恢复到信号来之前的值了。
所以等到内核态在返回到应用态的时候,又恢复到原来的地址去执行了。
参考资料:
    linux 2.4.18 kernel src
-------the end--------

这篇关于内核处理信号对应用层堆栈的影响的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1071808

相关文章

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

无线路由器哪个品牌好用信号强? 口碑最好的三个路由器大比拼

《无线路由器哪个品牌好用信号强?口碑最好的三个路由器大比拼》不同品牌在信号覆盖、稳定性和易用性等方面各有特色,如何在众多选择中找到最适合自己的那款无线路由器呢?今天推荐三款路由器让你的网速起飞... 今天我们来聊聊那些让网速飞起来的路由器。在这个信息爆炸的时代,一个好路由器简直就是家庭网编程络的心脏。无论你

电脑显示hdmi无信号怎么办? 电脑显示器无信号的终极解决指南

《电脑显示hdmi无信号怎么办?电脑显示器无信号的终极解决指南》HDMI无信号的问题却让人头疼不已,遇到这种情况该怎么办?针对这种情况,我们可以采取一系列步骤来逐一排查并解决问题,以下是详细的方法... 无论你是试图为笔记本电脑设置多个显示器还是使用外部显示器,都可能会弹出“无HDMI信号”错误。此消息可能

如何安装HWE内核? Ubuntu安装hwe内核解决硬件太新的问题

《如何安装HWE内核?Ubuntu安装hwe内核解决硬件太新的问题》今天的主角就是hwe内核(hardwareenablementkernel),一般安装的Ubuntu都是初始内核,不能很好地支... 对于追求系统稳定性,又想充分利用最新硬件特性的 Ubuntu 用户来说,HWEXBQgUbdlna(Har

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Python中处理NaN值的技巧分享

《Python中处理NaN值的技巧分享》在数据科学和数据分析领域,NaN(NotaNumber)是一个常见的概念,它表示一个缺失或未定义的数值,在Python中,尤其是在使用pandas库处理数据时,... 目录NaN 值的来源和影响使用 pandas 的 isna()和 isnull()函数直接比较 Na

详解Python中通用工具类与异常处理

《详解Python中通用工具类与异常处理》在Python开发中,编写可重用的工具类和通用的异常处理机制是提高代码质量和开发效率的关键,本文将介绍如何将特定的异常类改写为更通用的ValidationEx... 目录1. 通用异常类:ValidationException2. 通用工具类:Utils3. 示例文