👻
security
  • 计算机技术
  • OWASP TOP 10
  • 名词解释
  • 1
    • 常见端口利用
    • F5 big-ip从环境搭建到漏洞复现
    • 红队资源
  • About
    • APT
      • 海莲花(APT-C-00)
        • 样本分析
      • 毒云藤(APT-C-01)
        • 大规模钓鱼攻击活动披露
        • 2020上半年针对我重要机构定向攻击活动揭秘
      • 响尾蛇(T-APT-04)
        • 利用WebSocket隧道的新型攻击活动披露
      • 蔓灵花(APT-C-08)
        • 移动平台攻击活动揭露
      • 蓝宝菇(APT-C-12)
        • 组织使用云存储技术发起的最新攻击活动披露
      • 双尾蝎组织(APT-C-23)
        • 针对中东地区的最新攻击活动
      • Lazarus(APT-C-26)
        • 暴风行动 -利用MATA框架针对数字货币行业的攻击活动揭秘
      • Fancy Bear(APT-C-28)
        • 携小众压缩包诱饵对北约、中亚目标的定向攻击分析
      • 肚脑虫组织(APT-C-35)
        • 使用升级版数字武器针对周边地区的攻击活动
        • 针对巴基斯坦的攻击活动
      • 拍拍熊(APT-C-37)
      • 军刀狮(APT-C-38)
      • 蓝色魔眼(APT-C-41)
        • 组织首次针对我国重要机构定向攻击活动披露
      • 美人鱼(Infy)
        • 使用最新的Foudre后门进行攻击活动的分析
    • 各类靶场讲解
      • sqli-labs
      • upload-labs
      • xss-labs
    • CISP题库
    • Docker
      • Docker基线
        • docker基线-概述
        • 推荐一
        • 推荐二
        • 推荐三
        • 推荐四
        • 推荐五
        • 推荐六
      • 命令与选项
      • 基于Docker的固件模拟
      • 固件相关
      • Docker 私有仓库搭建
      • 基础命令的背后
      • 渗透思路调研
      • Docker容器环境检测方法【代码】
    • 浏览器
    • markdown
    • 密码学
    • 内网渗透TIPS
    • 网络扫描
    • 正则表达式
  • 操作系统
    • Android
      • APK终端安全分析法
      • 应用审计指南
        • 通用审计方法
    • IOS
      • 应用审计指南
    • Linux
      • 反弹shell
      • 基线检查
      • SHELL编程
      • 实战技能
    • windows
      • BACKDOOR with 权限维持
      • 磁盘取证实验
      • 基线检查
      • 免杀抓取明文
      • payload下载方式
      • powershell
      • 日志分析
        • 分析工具
      • Untitled
  • 数据库
    • db2
    • mysql
      • webshell写入
      • 基础知识
      • 核心技术
      • 高级应用
    • oracle
      • webshell写入
    • SQLserver
      • webshell写入
  • 中间件
    • apache
      • 基线检查
      • 日志审计
    • iis
      • 基线检查
      • 7.5解析绕过漏洞
    • nginx
      • 基线检查
    • tomcat
      • 基线检查
  • 编程语言
    • C
    • Java
      • webshell
        • 查杀Java web filter型内存马
        • Filter/Servlet型内存马的扫描抓捕与查杀
        • 基于内存 Webshell 的无文件攻击技术研究
        • 基于tomcat的内存 Webshell 无文件攻击技术
        • Tomcat 内存马检测
      • 代码审计
      • 代码审计指南
      • 浅析Java命令执行
      • 相关框架简介及漏洞
    • PHP
      • 代码审计
      • 破解DVWA-admin密码
      • webshell
        • 常见php一句话webshell解析
        • PHP Webshell Hidden Learning
        • Webshell免杀研究
        • Webshell那些事-攻击篇
        • 过D盾webshell分享
      • 相关框架简介及漏洞
    • python
      • 安全编码规范-代码审计
      • 编码规范
      • fishc
      • 某教程涉及脚本
      • POC编写相关
      • python秘籍
        • 上半部分
        • 下半部分
      • 安全方面的内容
        • Python Opcode逃逸笔记
        • 虚拟机逃逸
      • with-EXCEL
      • 相关框架简介及漏洞
      • 源码剖析
        • 多线程和GIL锁
        • Set容器
        • 统一内存管理
        • 信号处理机制
        • 循环垃圾回收器
        • 字符串对象PyStringObject
        • 整数对象PyIntObject
        • 字节码和虚拟机
    • 汇编
    • Javascript
      • Tampermonkey Script
  • AIGC
    • howtouse
  • 网络
    • CCNA
  • 漏洞类型及讲解
    • 综合
    • 技术分享
      • 暴力破解与信息泄露
      • 信息泄露漏洞_java
      • sqli-with-java
      • python远程命令执行与SSRF
    • SQL-Injectoin
    • Cross-Site Scripting
      • 跨站的艺术-XSS入门与介绍
      • 跨站的艺术-XSS Fuzzing 的技巧
      • 给开发者的终极XSS防护备忘录
      • AngularJS特性的 XSS
    • 文件操作
      • 文件包含
  • how-to-use
    • Acunetix(AWVS)
      • 安装到使用
      • 编写AWVS脚本探测web services
      • 简单分析-web方面
      • 流量分析特征
    • burpsuite
      • 导出报告方式
      • captcha-killer
      • FAKE-IP
      • JSFind
      • 编写插件绕过WAF
    • Cobalt Strike
      • Cobalt Strike Powershell过360+Defender上线
    • FOFA
    • GDB
    • PowerSh
      • 获得Powershell命令的历史记录
      • 深入分析PowerShell的两面性
      • 内网渗透利器之PowerSploit
      • PoC:滥用PowerShell Core
      • 如何绕过PowerShell访问限制并实现PowerShell代码执行
      • 工具包
      • 无powershell运行powershell方法总结
    • sheji
    • sqlmap
      • Atlas修改SQLMap tampers 绕过WAF/IDS/IPS
      • 内核分析
      • 检测剖析
      • tamper
      • UDF
      • --os-shell
      • sqlmapapi
      • with burp
      • 网络特征
    • Matlab
    • Metasploit
      • 与Powershell
    • NESSUS
      • 流量分析特征
      • Untitled
    • Network MapTools
      • 流量特征修改
      • 识别主机指纹
    • waf
      • ngx-lua-waf
      • modsecurity
由 GitBook 提供支持
在本页
  • Python信号处理机制
  • 信号机制的初始化
  • 信号产生时Python的动作
  • Python信号的语义
  • 信号实例
  • 主线程才能捕获信号
  • 信号可能只被处理一次
  • Python信号的特殊性

这有帮助吗?

  1. 编程语言
  2. python
  3. 源码剖析

信号处理机制

上一页统一内存管理下一页循环垃圾回收器

最后更新于4年前

这有帮助吗?

Python信号处理机制

本篇的信号处理机制不是指Python的signal模块的使用,而是指Python解释器本身如何处理信号以及如何实现signal模块。Python解释器处理信号机制需要做好两件事情:

  1. Python解释器与操作系统有关信号的交互

  2. Python解释器实现信号语义的API接口和模块

img

大体上,Python解释器对信号的实现总体思路比较简单。Python解释器对信号做一层封装,在这层封装中处理信号,以及信号发生时的回调函数,使之能够纳入整个Python虚拟机的运行中。我们先从信号的初始化开始一点点揭露整个运作机制。

信号机制的初始化

信号机制的初始化是在Python初始化整个解释器时开始的,Python在初始化函数中调用initsigs来进行整个系统以及singal模块的初始化。

// python/pythonrun.c  
void
Py_InitializedEx(int install_sigs)
{
        ....
    
        if (install_sigs)   // 初始化时install_sigs==1
        initsigs(); /* Signal handling stuff, including initintr() */
        
        ....
}
// 代码中PyOS_xxx系列都是Python解释器直接对系统调用的封装
static void
initsigs(void)
{
#ifdef SIGPIPE
    PyOS_setsig(SIGPIPE, SIG_IGN);     // 忽略SIGPIPE  
#endif
#ifdef SIGXFZ
    PyOS_setsig(SIGXFZ, SIG_IGN);      // 忽略SIGXFZ 
#endif
#ifdef SIGXFSZ
    PyOS_setsig(SIGXFSZ, SIG_IGN);     // 忽略SIGXFSZ  file size exceeded
#endif
    PyOS_InitInterrupts(); /* May imply initsignal() */
}
// python/modules/singalmodle.c
void
PyOS_InitInterrupts(void)
{
    initsignal();
    _PyImport_FixupExtension("signal", "signal");
}

直接进入到singalmodule.c中看signal模块以及信号的初始化 。

PyMODINIT_FUNC
initsignal(void)
{
    PyObject *m, *d, *x;
    int i;
#ifdef WITH_THREAD
    main_thread = PyThread_get_thread_ident();
    main_pid = getpid();
#endif
    /* Create the module and add the functions */
    // 初始化signal模块
    m = Py_InitModule3("signal", signal_methods, module_doc);
    if (m == NULL)
        return;
    /* Add some symbolic constants to the module */
    d = PyModule_GetDict(m);
    // 将SIG_DFL、SIGIGN 转化成Python整数对象
    x = DefaultHandler = PyLong_FromVoidPtr((void *)SIG_DFL);
    if (!x || PyDict_SetItemString(d, "SIG_DFL", x) < 0)
        goto finally;
    x = IgnoreHandler = PyLong_FromVoidPtr((void *)SIG_IGN);
    if (!x || PyDict_SetItemString(d, "SIG_IGN", x) < 0)
        goto finally;
    x = PyInt_FromLong((long)NSIG);
    if (!x || PyDict_SetItemString(d, "NSIG", x) < 0)
        goto finally;
    Py_DECREF(x);
    /*
    * 获取signal模块中的默认中断处理函数,
    * 实际就是 signal_default_int_handler    
    */
    x = IntHandler = PyDict_GetItemString(d, "default_int_handler");
    if (!x)
        goto finally;
    Py_INCREF(IntHandler);
    /*
    * 初始化Python解释器中的Handler,
    * 这个数组存储每个用户自定义的信号处理函数
    * 以及标志是否发生该信号的标志。
    */
    Handlers[0].tripped = 0;
    for (i = 1; i < NSIG; i++) {
        void (*t)(int);
        t = PyOS_getsig(i);
        Handlers[i].tripped = 0;
        if (t == SIG_DFL)
            Handlers[i].func = DefaultHandler;
        else if (t == SIG_IGN)
            Handlers[i].func = IgnoreHandler;
        else
            Handlers[i].func = Py_None; /* None of our business */
        Py_INCREF(Handlers[i].func);
    }
    
    /* 
    * 为 SIGINT 设置Python解释器的信号处理函数signal_handler
    * signal_handler 也会成为Python解释器与用户自定义处理函数的桥梁
    */
    if (Handlers[SIGINT].func == DefaultHandler) {
        /* Install default int handler */
        Py_INCREF(IntHandler);
        Py_DECREF(Handlers[SIGINT].func);
        Handlers[SIGINT].func = IntHandler;
        old_siginthandler = PyOS_setsig(SIGINT, signal_handler);
    }
// 实现signal模块中的各个 SIGXXX 信号值和名称
#ifdef SIGHUP                  
    x = PyInt_FromLong(SIGHUP);
    PyDict_SetItemString(d, "SIGHUP", x);
    Py_XDECREF(x);
#endif
    ....
    if (!PyErr_Occurred())
        return;
    /* Check for errors */
  finally:
    return;
}

可以看到Python将用户自定义信号处理函数保存在Handler数组中,而实际上向系统注册signal_handler函数。这个signal_handler函数成为信号发生时沟通Python解释器和用户自定义信号处理函数的桥梁。可以从signal.signal的实现中清楚的看到这一点。

// python/signalmodule.c
static PyObject *
signal_signal(PyObject *self, PyObject *args)
{
    PyObject *obj;
    int sig_num;
    PyObject *old_handler;
    void (*func)(int);
    if (!PyArg_ParseTuple(args, "iO:signal", &sig_num, &obj))
        return NULL;
#ifdef MS_WINDOWS
    /* Validate that sig_num is one of the allowable signals */
    switch (sig_num) {
        case SIGABRT: break;
#ifdef SIGBREAK
        /* Issue #10003: SIGBREAK is not documented as permitted, but works
           and corresponds to CTRL_BREAK_EVENT. */
        case SIGBREAK: break;
#endif
        case SIGFPE: break;
        case SIGILL: break;
        case SIGINT: break;
        case SIGSEGV: break;
        case SIGTERM: break;
        default:
            PyErr_SetString(PyExc_ValueError, "invalid signal value");
            return NULL;
    }
#endif
#ifdef WITH_THREAD
    // 只有主函数才能设置信号处理函数
    if (PyThread_get_thread_ident() != main_thread) {
        PyErr_SetString(PyExc_ValueError,
                        "signal only works in main thread");
        return NULL;
    }
#endif
    if (sig_num < 1 || sig_num >= NSIG) {
        PyErr_SetString(PyExc_ValueError,
                        "signal number out of range");
        return NULL;
    }
    if (obj == IgnoreHandler)
        func = SIG_IGN;
    else if (obj == DefaultHandler)
        func = SIG_DFL;
    else if (!PyCallable_Check(obj)) {
        PyErr_SetString(PyExc_TypeError,
"signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object");
                return NULL;
    }
    else
        // 除了signal.SIG_IGN和signal.SIG_DFL之外
        // Python解释器向系统注册的都是signal_handler函数
        func = signal_handler;
    if (PyOS_setsig(sig_num, func) == SIG_ERR) {
        PyErr_SetFromErrno(PyExc_RuntimeError);
        return NULL;
    }
    // 把实际的用户自定义信号处理函数,放入对应的Handler数组中
    // tripped标记对应信号值的信号是否发生了
    old_handler = Handlers[sig_num].func;
    Handlers[sig_num].tripped = 0;
    Py_INCREF(obj);
    Handlers[sig_num].func = obj;
    if (old_handler != NULL)
        return old_handler;
    else
        Py_RETURN_NONE;
}

信号产生时Python的动作

当信号产生时,操作系统会调用Python解释器注册的信号处理函数,即上文中的signal_handler函数。这个函数将对应的Handler结构中的信号产生标志tripped设置为1,然后将一个统一信号处理函数trip_signal作为pending_call注册到Python虚拟机的执行栈中。于是,Python在虚拟机执行过程中调用pending_call并执行各个用户自定义的信号处理函数。

static void
signal_handler(int sig_num)
{
    int save_errno = errno;
#if defined(WITH_THREAD) && defined(WITH_PTH)
    if (PyThread_get_thread_ident() != main_thread) {
        pth_raise(*(pth_t *) main_thread, sig_num);
    }
    else
#endif
    {
#ifdef WITH_THREAD
    /* See NOTES section above */
    if (getpid() == main_pid)
#endif
    {
        trip_signal(sig_num);
    }
#ifndef HAVE_SIGACTION
#ifdef SIGCHLD
    /* To avoid infinite recursion, this signal remains
       reset until explicit re-instated.
       Don't clear the 'func' field as it is our pointer
       to the Python handler... */
    if (sig_num != SIGCHLD)
#endif
    /* If the handler was not set up with sigaction, reinstall it.  See
     * Python/pythonrun.c for the implementation of PyOS_setsig which
     * makes this true.  See also issue8354. */
    // 重新设置信号处理
    PyOS_setsig(sig_num, signal_handler);
#endif
    }
    /* Issue #10311: asynchronously executing signal handlers should not
       mutate errno under the feet of unsuspecting C code. */
    errno = save_errno;
}
static void
trip_signal(int sig_num)
{
    // 信号产生了
    Handlers[sig_num].tripped = 1;
    
    // 如果正在处理信号,则不再向Python虚拟机提交
    if (is_tripped)
        return;
    /* Set is_tripped after setting .tripped, as it gets
       cleared in PyErr_CheckSignals() before .tripped. */
    is_tripped = 1;
    // 向Python虚拟机提交pending_call,纳入到整个虚拟机的执行过程中
    Py_AddPendingCall(checksignals_witharg, NULL);
    if (wakeup_fd != -1)
        write(wakeup_fd, "\0", 1);
}
static int
checksignals_witharg(void * arg)
{
    return PyErr_CheckSignals();
}
int
PyErr_CheckSignals(void)
{
    int i;
    PyObject *f;
    // 已经在信号处理中
    if (!is_tripped)
        return 0;
    // 只主线程中处理信号
#ifdef WITH_THREAD
    if (PyThread_get_thread_ident() != main_thread)
        return 0;
#endif
    /*
     * The is_tripped variable is meant to speed up the calls to
     * PyErr_CheckSignals (both directly or via pending calls) when no
     * signal has arrived. This variable is set to 1 when a signal arrives
     * and it is set to 0 here, when we know some signals arrived. This way
     * we can run the registered handlers with no signals blocked.
     *
     * NOTE: with this approach we can have a situation where is_tripped is
     *       1 but we have no more signals to handle (Handlers[i].tripped
     *       is 0 for every signal i). This won't do us any harm (except
     *       we're gonna spent some cycles for nothing). This happens when
     *       we receive a signal i after we zero is_tripped and before we
     *       check Handlers[i].tripped.
     */
    /*
    * 恢复该信号。对于信号处理可能有两种情况:
    * 1. 在is_tripped = 0之前: 信号又发生了,则只在Handler中设置标志位,
    *    不会再次提交到pendingcall,多个信号只处理一次;
    * 2. 在is_tripped = 0之后: 信号又发生了,则会被再次提交到pendingcall
    *    每发生一次信号调用一次信号处理函数。
    */
    is_tripped = 0;
    if (!(f = (PyObject *)PyEval_GetFrame()))
        f = Py_None;
    // 按照信号值从小到大依次调用对应的信号处理函数
    for (i = 1; i < NSIG; i++) {
        if (Handlers[i].tripped) {
            PyObject *result = NULL;
            PyObject *arglist = Py_BuildValue("(iO)", i, f);
            Handlers[i].tripped = 0;
            if (arglist) {
                result = PyEval_CallObject(Handlers[i].func,
                                           arglist);
                Py_DECREF(arglist);
            }
            if (!result)
                return -1;
            Py_DECREF(result);
        }
    }
    return 0;
}

这里面的PyErr_CheckSignals函数也会被其他模块调用直接信号的处理。例如,在file.read读取文件过程中中断,Python对调用该函数进行信号处理。至此,可以看到整个信号处理的流程:

  1. 初始化signal模块,将对应的操作系统信号值、函数转化成Python对象

  2. 用户设置信号就向操作系统注册函数signal_handler,并将用户自定义信号处理函数设置到对应的Handler数组中

  3. 当信号发生时,操作系统调用signal_handler设置tripped=1,然后调用trip_signal将统一处理函数checksignals_witharg作为pendingcall注册到Python虚拟机的执行栈中。

  4. Python虚拟机在处理pendingcall时调用checksignals_withargs,从而信号处理函数得以执行。

  5. 另外,Python其他模块可以直接调用PyErr_CheckSignals进行信号处理。

Python信号的语义

通过注释以及代码剖析可以归纳Python的信号语义:

  • 只有主线程能够设置、捕获和处理信号

  • 信号设置一直有效(signal_handler中会再次注册信号处理函数)

  • 多次信号,可能会被合并处理一次

  • 按照信号值从小到大处理

信号实例

主线程才能捕获信号

import threading                                                                 
import signal                                                                    
import time                                                                      
                                                                                 
SIG = []                                                                         
                                                                                 
def sig_handler(*args):                                                          
    SIG.append(args)                                                             
                                                                                 
signal.signal(signal.SIGUSR1, sig_handler)                                       
signal.signal(signal.SIGUSR2, sig_handler)                                       
signal.signal(signal.SIGSYS, sig_handler)                                        
                                                                                 
class MyThread(threading.Thread):                                                
    def run(self, *args):                                                        
        start = time.time()                                                      
        while True:                                                              
            if time.time() > start + 10:                                         
                break                                                            
        print 'In Thread:', SIG                                                          
                                                                                 
t = MyThread()                                                                   
t.start()                                                                        
print 'start thread:', t                                                         
t.join()                                                                         
print 'In Main:', SIG
root@ubuntu:/home/python# python test_signal_main_thread.py
start thread: <MyThread(Thread-1, started 140229890316032)>
In Main: []
In Thread: []          # [1]
  • [1] Python中的线程都是分离的,因此主线程很快退出。信号不能发送到主线程,因此不能被执行。

信号可能只被处理一次

import threading
import signal
import time
SIG = []
def sig_handler(*args):
    SIG.append(args)
signal.signal(signal.SIGUSR1, sig_handler)
signal.signal(signal.SIGUSR2, sig_handler)
signal.signal(signal.SIGSYS, sig_handler)
class MyThread(threading.Thread):
    def run(self, *args):
        start = time.time()
        while True:
            if time.time() > start + 10:
                break
        print 'In Thread:', SIG
t = MyThread()
t.start()
print 'start thread:', t
t.join()
print 'In Main:', SIG
# python test_signal_signo.py       # kill -10  2856; kill -10 2856; kill -12 2856
start thread: <MyThread(Thread-1, started 140351081834240)>
In Thread: []       # [1]
In Main: [(10, <frame object at 0x16e2d50>), (12, <frame object at 0x16e2d50>)] # [2]
  • [1] 主线程的t.join一直阻塞,因此在子线程没有退出前不能处理信号。(C语言的信号处理是可以打断堵塞信号的)

  • [2] 信号在有机会处理之前发生了两次信号,但是只处理了一次。

Python信号的特殊性

Python的信号语义与Linux的C语言的信号语义有一些不同。

  • Python信号的处理函数会一直有效;而Linux除非特殊设置否则信号处理函数默认只调用一次就被恢复

  • Python信号只能在主线程中设置、捕获和处理

  • Python信号不能打断堵塞操作(因为信号发生时子线程在运行)