• 学内核之二十一:系统调用栈结构分析


    目录

    一 构建分析环境

    二 栈的位置

    三 栈开头8字节

    四 寄存器环境

    五 R4和R5

    六 如何确定系统调用的具体函数


    一 构建分析环境

    为了分析方便,做了如下测试环境
    内核实现一个简单的创建字符设备的驱动
    应用层实现一个c程序,操作为打开内核创建的字符设备文件
    内核在处理open设备文件的接口中,将指针设置为空,并在该空指针上赋值。这样,就触发内核的空指针异常,输出oops及相关堆栈。

    为了说明方便,将内核的栈信息单独提取出来

    1. <0>dee0: beceeccc c00cd8f4 00000041 c004897c cf050790 cf5ab990 ea7ef579 00000006
    2. <0>df00: cd826015 c0718100 00000000 cf401c38 ce2c3bc8 00000101 00000004 0000003e
    3. <0>df20: 00000000 00000000 00000000 ffffff9c cd826000 00000ff0 c0776c30 000105f0
    4. <0>df40: 00000000 00000000 ffffff9c cd826000 00000005 00000003 ffffff9c cd826000
    5. -1 -2 -3 -4 -5 -6
    6. <0>df60: 00000005 c00bec3c c0714080 c071f780 cd0e2480 00000000 c0000000 00000024
    7. -7 -8 -9 r4 r5 r6 r7 r8
    8. <0>df80: 00000100 00000001 cd3cc000 0001056c 00000000 00010354 00000005 c000e6a8
    9. r9 lr r4 r5 r0 0 r1 1 -18 2 3
    10. <0>dfa0: cd3cc000 c000e4e0 0001056c 00000000 000105f0 00000000 00000001 00000000
    11. 4 5 6 7 8 9 10 11
    12. <0>dfc0: 0001056c 00000000 00010354 00000005 00000000 00000000 b6f8b000 beceeccc
    13. 12 13 sp 14 lr 15 pc 16 cpsr 17-old r0 rev rev
    14. <0>dfe0: 00000000 beceecb4 000104fc b6df902c 60080010 000105f0 00000000 00000000
    15. <4>[<bf8f5074>] (second_open [debug_for_syscall_statck]) from [<c00c35fc>] (chrdev_open+0xd0/0x190)
    16. <4>[<c00c35fc>] (chrdev_open) from [<c00bd790>] (do_dentry_open+0x1d8/0x2f0)
    17. <4>[<c00bd790>] (do_dentry_open) from [<c00cbe44>] (do_last+0x6b0/0xc5c)
    18. <4>[<c00cbe44>] (do_last) from [<c00cc4a8>] (path_openat+0xb8/0x640)
    19. <4>[<c00cc4a8>] (path_openat) from [<c00cd8f4>] (do_filp_open+0x2c/0x88)
    20. <4>[<c00cd8f4>] (do_filp_open) from [<c00bec3c>] (do_sys_open+0x104/0x1c8)
    21. <4>[<c00bec3c>] (do_sys_open) from [<c000e4e0>] (ret_fast_syscall+0x0/0x38)

    上面,已对栈的信息做了标注。下面看这些标注如何得来。

    二 栈的位置

    参考之前对oops异常的分析。主要是内核栈占用两个页面,共8KB,一头上threadinfo,一头是内核栈栈底。栈向下增长,从高地址到低地址。

    三 栈开头8字节

    内核栈预留了8个字节
    这是内核设计保留的,具体原因参考内核的修改记录

    https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git/commit/?id=415395e19fd197ce4f248902dba54f4065af547c

    1.   Always leave 8 bytes free at the top of the kernel stack.  This
    2.    prevents the stack becoming completely empty when do_exit() is
    3.    called from an exiting nfsd() thread, and causing the wrong
    4.    pointer to be returned from current_thread_info()


    代码中也是如此定义栈开始位置的。

    ./arch/arm/include/asm/thread_info.h:#define THREAD_START_SP            (THREAD_SIZE - 8)

    上面标记为rev的两个位置

    寄存器环境

    接下来18个位置,为寄存器环境保存用。占用大小根据pg_regs定义来,72字节
    具体入栈操作在entry-common.S中

    1. .align 5
    2. ENTRY(vector_swi)
    3. #ifdef CONFIG_CPU_V7M
    4. v7m_exception_entry
    5. #else
    6. sub sp, sp, #S_FRAME_SIZE
    7. stmia sp, {r0 - r12} @ Calling r0 - r12
    8. ARM( add r8, sp, #S_PC )
    9. ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
    10. 。。。。。。

    上述文件在kernel目录的如下位置:

    ./arch/arm/kernel/entry-common.S:ENTRY(vector_swi)

    vector_swi定义了系统调用异常的入口。也就是上层c代码进入c库使用swi指令触发系统调用时,会触发异常,在异常向量表中,执行上述汇编代码
    在上述汇编代码中,保存了栈的18个位置

    其中的7保存了系统调用号

    五 R4和R5

    在调用系统调用接口之前,保存了两个寄存器

    1. local_restart:
    2. ldr r10, [tsk, #TI_FLAGS] @ check for syscall tracing
    3. stmdb sp!, {r4, r5} @ push fifth and sixth args
    4. tst r10, #_TIF_SYSCALL_WORK @ are we tracing syscalls?
    5. bne __sys_trace
    6. cmp scno, #NR_syscalls @ check upper syscall limit
    7. adr lr, BSYM(ret_fast_syscall) @ return address
    8. ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine

    r4 和 r5之后,使用r8保存系统调用表,使用7中的中断号,右移两位,每个调用占用4字节,修改pc寄存器,直接跳转到系统调用中

    跳转之前,将返回地址写入lr寄存器中

    六 如何确定系统调用的具体函数

    sys_call_table确定开始位置

    call.S确定具体函数名

    1. /* 0 */ CALL(sys_restart_syscall)
    2. CALL(sys_exit)
    3. CALL(sys_fork)
    4. CALL(sys_read)
    5. CALL(sys_write)
    6. /* 5 */ CALL(sys_open)
    7. ./arch/arm/include/asm/unistd.h:#define __NR_syscalls (388)

    共有388个项目,所以sys_call_table开始位置保留 388×4大小的空间
    这些空间在vmlinux.o目标中是填充的零。此文件反汇编后,虚拟地址的开始位置为0
    需要查看vmlinux的反汇编。这个反汇编中,虚拟地址开始位置调整为c0000000了,且上述表的内容也有具体内容了。

    1. c000e6a8 <sys_call_table>:
    2. c000e6a8: c002e0d8 ldrdgt lr, [r2], -r8
    3. c000e6ac: c0024c64 andgt r4, r2, r4, ror #24
    4. c000e6b0: c0021940 andgt r1, r2, r0, asr #18
    5. c000e6b4: c00cf1b8 ; <UNDEFINED> instruction: 0xc00cf1b8
    6. c000e6b8: c00cf254 andgt pc, ip, r4, asr r2 ; <UNPREDICTABLE>
    7. c000e6bc: c00cdf74 andgt sp, ip, r4, ror pc
    8. c000e6c0: c00cccd4 ldrdgt ip, [ip], -r4
    9. c000e6c4: c003aa08 andgt sl, r3, r8, lsl #20
    10. c000e6c8: c00cdf90 mulgt ip, r0, pc ; <UNPREDICTABLE>

    这里,5号调用,第六个位置,地址为c00cdf74
    该地址的汇编代码为

    1. c00cdf74 <SyS_open>:
    2. c00cdf74: e6ff3072 uxth r3, r2
    3. c00cdf78: e1a02001 mov r2, r1
    4. c00cdf7c: e1a01000 mov r1, r0
    5. c00cdf80: e3e00063 mvn r0, #99 ; 0x63
    6. c00cdf84: eaffff88 b c00cddac <do_sys_open>

    所以,系统调用最开始调用SyS_open,接着调用do_sys_open
    这就跟上述栈的回溯对应上了

    1. c00cddac <do_sys_open>:
    2. c00cddac: e3a0c040 mov ip, #64 ; 0x40
    3. c00cddb0: e7dfc81c bfi ip, ip, #16, #16
    4. c00cddb4: e012c00c ands ip, r2, ip
    5. c00cddb8: 17eb3053 ubfxne r3, r3, #0, #12
    6. c00cddbc: e92d43f0 push {r4, r5, r6, r7, r8, r9, lr}
    7. c00cddc0: e24dd024 sub sp, sp, #36 ; 0x24

    这个函数里,入栈7个位置,并预留9个位置,这也跟上述栈标记及栈回溯对应上了
      
    关于系统调用时如何通过代码里的宏定义等映射到SyS_open的,后续再看。
      
    基于此,就可以进行栈分析了。
      
    七 其他
    关于汇编里的伪代码 标记 头文件引入 指令集选择  新旧ABI兼容处理 等等,就不记录了,可以结合最终汇编代码,确定一些条件编译的情况

  • 相关阅读:
    超详细MySQL总结[从百草园到三味书屋]
    采用创芯科技canfd实现ros-can通信
    mybatis原理及整合spring原理
    工厂设计模式
    专业软件测评中心:关于软件性能测试的实用建议
    用指针低三位存放额外信息的优化方法
    1、什么是NFT
    探索Native Plugins:开启大模型的技能之门
    什么是CDN?什么是安全加速CDN?有什么优势?
    7、使用Maven:IDEA环境
  • 原文地址:https://blog.csdn.net/wwwyue1985/article/details/132947952