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

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

相关文章

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

SpringBoot中的404错误:原因、影响及解决策略

《SpringBoot中的404错误:原因、影响及解决策略》本文详细介绍了SpringBoot中404错误的出现原因、影响以及处理策略,404错误常见于URL路径错误、控制器配置问题、静态资源配置错误... 目录Spring Boot中的404错误:原因、影响及处理策略404错误的出现原因1. URL路径错

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题小结

《SpringBoot整合ShedLock处理定时任务重复执行的问题小结》ShedLock是解决分布式系统中定时任务重复执行问题的Java库,通过在数据库中加锁,确保只有一个节点在指定时间执行... 目录前言什么是 ShedLock?ShedLock 的工作原理:定时任务重复执行China编程的问题使用 Shed

Redis如何使用zset处理排行榜和计数问题

《Redis如何使用zset处理排行榜和计数问题》Redis的ZSET数据结构非常适合处理排行榜和计数问题,它可以在高并发的点赞业务中高效地管理点赞的排名,并且由于ZSET的排序特性,可以轻松实现根据... 目录Redis使用zset处理排行榜和计数业务逻辑ZSET 数据结构优化高并发的点赞操作ZSET 结

微服务架构之使用RabbitMQ进行异步处理方式

《微服务架构之使用RabbitMQ进行异步处理方式》本文介绍了RabbitMQ的基本概念、异步调用处理逻辑、RabbitMQ的基本使用方法以及在SpringBoot项目中使用RabbitMQ解决高并发... 目录一.什么是RabbitMQ?二.异步调用处理逻辑:三.RabbitMQ的基本使用1.安装2.架构

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea