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

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

相关文章

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

Java利用@SneakyThrows注解提升异常处理效率详解

《Java利用@SneakyThrows注解提升异常处理效率详解》这篇文章将深度剖析@SneakyThrows的原理,用法,适用场景以及隐藏的陷阱,看看它如何让Java异常处理效率飙升50%,感兴趣的... 目录前言一、检查型异常的“诅咒”:为什么Java开发者讨厌它1.1 检查型异常的痛点1.2 为什么说

Python利用PySpark和Kafka实现流处理引擎构建指南

《Python利用PySpark和Kafka实现流处理引擎构建指南》本文将深入解剖基于Python的实时处理黄金组合:Kafka(分布式消息队列)与PySpark(分布式计算引擎)的化学反应,并构建一... 目录引言:数据洪流时代的生存法则第一章 Kafka:数据世界的中央神经系统消息引擎核心设计哲学高吞吐

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Java异常捕获及处理方式详解

《Java异常捕获及处理方式详解》异常处理是Java编程中非常重要的一部分,它允许我们在程序运行时捕获并处理错误或不预期的行为,而不是让程序直接崩溃,本文将介绍Java中如何捕获异常,以及常用的异常处... 目录前言什么是异常?Java异常的基本语法解释:1. 捕获异常并处理示例1:捕获并处理单个异常解释:

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

MySQL中处理数据的并发一致性的实现示例

《MySQL中处理数据的并发一致性的实现示例》在MySQL中处理数据的并发一致性是确保多个用户或应用程序同时访问和修改数据库时,不会导致数据冲突、数据丢失或数据不一致,MySQL通过事务和锁机制来管理... 目录一、事务(Transactions)1. 事务控制语句二、锁(Locks)1. 锁类型2. 锁粒

Python调用LibreOffice处理自动化文档的完整指南

《Python调用LibreOffice处理自动化文档的完整指南》在数字化转型的浪潮中,文档处理自动化已成为提升效率的关键,LibreOffice作为开源办公软件的佼佼者,其命令行功能结合Python... 目录引言一、环境搭建:三步构建自动化基石1. 安装LibreOffice与python2. 验证安装

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python进行JSON和Excel文件转换处理指南

《Python进行JSON和Excel文件转换处理指南》在数据交换与系统集成中,JSON与Excel是两种极为常见的数据格式,本文将介绍如何使用Python实现将JSON转换为格式化的Excel文件,... 目录将 jsON 导入为格式化 Excel将 Excel 导出为结构化 JSON处理嵌套 JSON: