zoukankan      html  css  js  c++  java
  • Linux Kernel Stack【转】

    转自:https://www.cnblogs.com/gm-201705/p/9863960.html

    整理一些杂乱的内容。以下x86架构。

    Linux 内核栈大小

    内核栈大小是固定的,默认为8k,曾经有选项可以设置为4k栈。由于大小固定,申请过大的栈内存,或者函数调用层次过深,都可能导致栈溢出。

    关注默认4k还是8k栈,社区曾有过长时间讨论。

    其中8k栈的缺点如下:

    1. 浪费内存。

    2. 由于内核4k分页,要创建一个内核栈就需要申请2块连续的4k页。当内存碎片严重,尤其内存紧张的时候,申请8k的连续内存,要比4k困难的多。

    但貌似4k栈带来的麻烦更大,内核中许多bug都由4k栈太小,发生溢出导致的。

    因此内核从 2.6.37 版本开始,便移除了对4k栈的支持,见 commit : dcfa726280116dd31adad37da940f542663567d0

    Linux内核栈布局

    栈地址是逆增长的,thread_info 结构位于栈的底部,即低地址处。

    top     +----------------+
            | return vals    |
            |   & local vars |
            | ...            |
            |                |
            |                |
            | 0's            |
            | thread_info    |
    bottom  +----------------+
    

    从内核栈布局可以的到,如果在栈上申请内存过多,则会下溢破坏 thread_info 结构。在绕过 pxn 的时候,有一个办法是修改进程的 addr_limit 值,这个值在 thread_info 中。由于内核栈固定8k的特性,要计算 thread_info 位置,只需要将 sp 指针的后13位清0,即 sp & ~(THREAD_SIZE-1) 即可。

    thread_info_addr = sp & ~(THREAD_SIZE-1)

    struct thread_info {
        struct task_struct  *task;
        struct exec_domain  *exec_domain;
        __u32                flags;
        __u32                status;
        __u32                cpu;
        int                  preempt_count;
        mm_segment_t         addr_limit;
        struct restart_block restart_block;
        void __user         *sysenter_return;
    #ifdef CONFIG_X86_32
        unsigned long        previous_esp;
        __u8                 supervisor_stack[0];
    #endif
        int                  uaccess_err;
    };
    

    Stack 使用安全

    由申请栈内存过多、过大,或函数调用层次太深导致的溢出问题非常隐蔽,因此这是内核编码中需注意的地方。同时有许多工具来检查这类BUG:

    1. CONFIG_FRAME_WARN

    这是一个内核配置选项,默认为1024,在内核编译时传递给gcc的“-Wframe-larger-than=xxx”选项,当编译器检测到栈使用大于阙值时,会产生一条编译告警:

      ...
      CC      ipc/msg.o
      CC      ipc/sem.o
     .../linux-3.0.y/ipc/sem.c: In function 'semctl_main.clone.7':
     .../linux-3.0.y/ipc/sem.c:1021:1: warning: the frame size of 520 bytes is larger than 256 bytes
     .../linux-3.0.y/ipc/sem.c: In function 'sys_semtimedop':
     .../linux-3.0.y/ipc/sem.c:1514:1: warning: the frame size of 472 bytes is larger than 256 bytes
      CC      ipc/shm.o
      CC      ipc/ipcns_notifier.o
    

    2. checkstack.pl

    checkstack.pl是内核源码中的一个Perl脚本,用于执行静态的栈分析,使用方法如下:

    $(CROSS_COMPILE)objdump -d vmlinux | scripts/checkstack.pl [arch]
    

    其中arch支持arm, mips and x86等架构。注意其参数,是一个.S的汇编代码通过pipe输入checkstack.pl的

    $ arm-eabi-objdummp -d vmlinux -o vmlinux-arm.S
    $ cat vmlinux-arm.S | scripts/checkstack.pl arm
    0x0012c858 nlmclnt_reclaim [vmlinux-arm.o]:             720
    0x0025748c do_tcp_getsockopt.clone.11 [vmlinux-arm.o]:  552
    0x00258d04 do_tcp_setsockopt.clone.14 [vmlinux-arm.o]:  544
    ...
    

    3.CONFIG_DEBUG_STACK_USAGE

    同样是一个内核选项,用于输出每个进程的栈使用情况。它的原理是在内核栈创建时使用’0’初始化,再通过计算thread_info结构到第一个非0位置的大小,获取栈使用情况。

    可以通过 dmesg 查看栈使用情况:

    # dmesg | grep greatest                                                       
    kworker/u:0 used greatest stack depth: 10564 bytes left                         
    busybox used greatest stack depth: 9512 bytes left                              
    busybox used greatest stack depth: 9504 bytes left                              
    grep used greatest stack depth: 9372 bytes left                                 
    init used greatest stack depth: 9028 bytes left 
    

    为什么dmesg中会有栈使用情况,看下CONFIG_DEBUG_STACK_USAGE的具体功能:

    • 首先在进程创建时,将进程栈填充为0(kernel/fork.c)
    • sysrq ‘t’时,显示空闲内存大小,这是通过 stack_not_used()调用实现(kernel/sched.c)
    • 定义check_stack_usage(),每次low-water时,进行printks打印
      • low-water是所有栈全局共享的
      • check_stack_usage()只有在进程退出时调用,因此只有在进程退出时才会发现栈使用的问题
    • stack_not_used()在include/linux/sched.h文件中定义,他输出从thread_info到第一个非0位置的内存大小

    也可以通过 ‘t’ sysrq,得到当前运行进程栈实时的使用情况:

    $ echo t >/proc/sysrq-trigger
    $ dmesg | grep -v [[]
      task                PC stack   pid father
    init            S 802af8b0   932     1      0 0x00000000
    kthreadd        S 802af8b0  2496     2      0 0x00000000
    ksoftirqd/0     S 802af8b0  2840     3      2 0x00000000
    kworker/0:0     S 802af8b0  2776     4      2 0x00000000
    kworker/u:0     S 802af8b0  2548     5      2 0x00000000
    ...
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    vue2.0阻止事件冒泡
    IconFont 图标制作和使用
    Gulp入门教程
    伪类实现特殊图形,一个span加三角形
    Vue渲染列表,在更新data属性后,列表未更新问题
    理解Array.prototype.slice.call(arguments)
    ;(function(){ //代码})(); 自执行函数开头为什么要加;或者!
    Hexo 搭建博客 本地运行 常见报错及解决办法
    说说JSON和JSONP,也许你会豁然开朗
    数组去重的常用方法
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13692101.html
Copyright © 2011-2022 走看看