穿越两大空间的调用栈

2023-09-21 16:19:48

人是有经历的,软件也如此。

简历记录着一个人的经历,而调用栈(call stack)则记录着软件的经历。看一个人的简历可以快速了解一个人。观察调用栈,则可以快速理解软件。

因为此,我非常喜欢看软件的调用栈。每当看到一个漂亮的调用栈,我常常如获至宝,端详许久。

903e8df14e457ffc54667144633ccf8b.png

因为对调试技术的热爱,这些年,我花了很多时间在调试器上。特别是开发了NDB调试器(Nano Code Debugger)。

对于NDB调试器,我很看重的一个功能当然也是调用栈,因为这个命令太重要了。上图的WIFI网络调用栈就是使用NDB显示出的。图中的lk是Linux Kernel的缩写,bcmdhd是博通公司(Broadcom)的WIFI驱动名字。

对于上面的调用栈,已经包含了丰富的信息,可以从中学到很多东西,但是,它还不完美,因为它只有内核空间的部分,缺少用户空间的部分。

52eeeb13e4aadd68c6e9e7bb9307a196.png

软件世界的两大空间一阳一阴,与古老的道家哲学有着惊人的相似。

系统调用是沟通两大空间的一种重要机制。现代CPU一般都有专门的指令来执行系统调用。SVC指令是ARM CPU的系统调用指令。

比如下面的这段shmget函数的反汇编指令中,+16的位置便是SVC指令,它的前一条指令将0xC2赋值给x8,0xC2是shmget这个系统调用的唯一编号。

=> 0x0000007ff7f20f58 <+0>:     sxtw    x0, w0
   0x0000007ff7f20f5c <+4>:     sxtw    x2, w2
   0x0000007ff7f20f60 <+8>:     mov     x3, #0x0                        // #0
   0x0000007ff7f20f64 <+12>:    mov     x8, #0xc2                       // #194
   0x0000007ff7f20f68 <+16>:    svc     #0x0
   0x0000007ff7f20f6c <+20>:    cmn     x0, #0x1, lsl #12
   0x0000007ff7f20f70 <+24>:    b.hi    0x7ff7f20f78 <shmget+32>  // b.pmore
   0x0000007ff7f20f74 <+28>:    ret
   0x0000007ff7f20f78 <+32>:    adrp    x1, 0x7ff7fa1000 <__libio_codecvt+136>
   0x0000007ff7f20f7c <+36>:    ldr     x1, [x1, #3608]
   0x0000007ff7f20f80 <+40>:    mrs     x2, tpidr_el0
   0x0000007ff7f20f84 <+44>:    neg     w3, w0
   0x0000007ff7f20f88 <+48>:    mov     w0, #0xffffffff                 // #-1
   0x0000007ff7f20f8c <+52>:    str     w3, [x2, x1]
   0x0000007ff7f20f90 <+56>:    ret

CPU执行到svc指令后,便会像蹦极一样“自由下坠”,坠落到内核空间的陷阱处理函数,即图1中的el0_svc。

9月上旬去了趟庐山,在太乙峰下和参加纳秒级优化的小伙伴度过了几天忙碌而又快乐的时光。回上海后,又忙着LINUX研习班上海站。两个研习班结束后,距离国庆假期就只有一周多了,没有什么大的安排。思考一番,我决定用这个时间来实现NDB的“跨界调用栈”。

7874d04058a7bf1bb6637884b19ad096.png

我喜欢用PPT来记录工作过程,既方便记录截图,也方便记录文字,还可以做各种标注。精彩的转为讲义,就可以分享给小伙伴们。

调用栈的原理不难,就是从栈寻找软件的历史状态。但因为每个函数用的栈空间大小不一,所以实际实现是有各种困难的。

对于跨界调用栈,困难有以下几个:

- 用户空间的栈和内核栈不连续,是两个栈,要找到用户空间栈的位置

- 恢复用户空间寄存器的状态

- 加载用户空间模块

要解决上面的问题,需要有一个得心应手的环境。好在挥码枪和GDK8就在手边,基础的内核调试功能已经工作的很稳定。

但仍有一个棘手的问题就是要选个合适的系统调用做突破口。刚开始我用vfs_read,但是设断点后,频繁命中。改为vfs_write后,还是如此。

思考一番,改为使用shmget,庐山研习班上演示跨进程通信时刚好使用了这个系统调用,示例代码叫uking(幽王之意,源于烽火戏诸侯的故事),是现成的。

将本来在x86上的幽王复制到GDK8,编译成功后,使用gdb开始调试,在执行svc指令前停下来,观察它的寄存器。

(gdb) info r
x0             0x73616d69       1935764841
x1             0x400    1024
x2             0x3a4    932
x3             0x0      0
x4             0x0      0
x5             0x0      0
x6             0x7ff7fa3b00     549621218048
x7             0x4001001010000  1125968643162112
x8             0xc2     194
x9             0xffffffffffffffff       -1
x10            0x8000000000000000       -9223372036854775808
x11            0x4001001010000  1125968643162112
x12            0x0      0
x13            0x7ff7ffe048     549621588040
x14            0x7ff7ffc218     549621580312
x15            0x7ff7ffc158     549621580120
x16            0x5555566fa8     366503948200
x17            0x7ff7f20f58     549620682584
x18            0x3      3
x19            0x5555555f00     366503878400
x20            0x0      0
x21            0x5555555b50     366503877456
x22            0x0      0
x23            0x0      0
x24            0x0      0
x25            0x0      0
x26            0x0      0
x27            0x0      0
x28            0x0      0
x29            0x7ffffff330     549755810608
x30            0x5555555d60     366503877984
sp             0x7ffffff330     0x7ffffff330
pc             0x7ff7f20f68     0x7ff7f20f68 <shmget+16>
cpsr           0x80200000       [ EL=0 SS N ]
fpsr           0x0      0
fpcr           0x0      0

在执行svc指令前,在内核空间对ksys_shmget设置断点。

内核空间准备好断点后,在用户空间单步执行svc指令,内核空间的断点顺利命中。

接下来,使用k命令激发调用栈功能,一边调试NDB的代码(debug the debugger),一边分析栈上的数据。

d82c0277ee6d72b790b1d5a4031ccc73.png

上图右侧x0开始一段内存区是所谓的陷阱帧(Trap Frame),它记录着CPU从用户空间切到内核空间后的寄存器状态,这个状态里面就有我要找的用户空间栈位置。

人工分析清楚后,要落实到代码上。NDB的符号模块叫NDW,其中包含着繁琐的DWARF符号格式解析逻辑。

经过2天多的攻坚战,昨晚7点时,跨界栈回溯开始工作了,可以跨越“鸿沟”找libc模块了。

e51697b50d0b8dc177197aa824cde641.png

libc是用户空间的核心模块,是发起系统调用的地方。

今天上午解决了几个小问题后,第一个完整的扩展栈回溯显示出来了。

c28f81774a9af48261b7d85c15fa269d.png

上图中,栈帧3-4是两个空间的分界。

有了跨界栈回溯能力后,我又设置了vfs_write断点,这一次,就可以看清楚是哪些应用在写文件了。比如下面这一次是gnome的gmain工作线程因为检查版本在写文件。

kn 100
 # Child-SP          RetAddr           Call Site
00 ffffff80`0b89be20 ffffff80`0828dca4 lk!vfs_write [fs/read_write.c @ 535]
01 ffffff80`0b89be70 ffffff80`0828dd34 lk!ksys_write+0x64 [fs/read_write.c @ 601]
02 ffffff80`0b89be80 ffffff80`08098f6c lk!__arm64_sys_write+0x14 [fs/read_write.c @ 610]
03 ffffff80`0b89beb0 ffffff80`080990a8 lk!el0_svc_common.constprop.0+0x64 [./arch/arm64/include/asm/current.h @ 19]
04 ffffff80`0b89beb8 ffffff80`08083d08 lk!el0_svc_handler+0x28 [arch/arm64/kernel/syscall.c @ 164]
05 0000007f`93d0c640 0000007f`94376a90 lk!el0_svc+0x8 [arch/arm64/kernel/entry.S @ 941]
06 0000007f`93d0c648 0000007f`944a3590 libc!__GI___libc_write+0x70 [../sysdeps/unix/sysv/linux/write.c @ 27]
07 0000007f`93d0c678 0000007f`94457f28 libglib_2_0_so_0_5600_4!glib_check_version+0x2a0
08 0000007f`93d0c6b8 0000007f`9445bc90 libglib_2_0_so_0_5600_4!g_list_sort_with_data+0x340
09 0000007f`93d0c6d8 0000007f`9445bfdc libglib_2_0_so_0_5600_4!g_main_context_dispatch+0x1d0
0a 0000007f`93d0c768 0000007f`9445c07c libglib_2_0_so_0_5600_4!g_main_context_dispatch+0x51c
0b 0000007f`93d0c7c8 0000007f`9445c0cc libglib_2_0_so_0_5600_4!g_main_context_iteration+0x34
0c 0000007f`93d0c7e8 0000007f`94484a64 libglib_2_0_so_0_5600_4!g_main_context_iteration+0x84
0d 0000007f`93d0c808 0000007f`9420a088 libglib_2_0_so_0_5600_4!g_test_get_filename+0x1b4
0e 0000007f`93d0c828 0000007f`943840cc libpthread_2_27_so!__pthread_get_minstack+0x13e0
0f 00000000`00000000 00000008`00000008 libc!thread_start+0xc [../sysdeps/unix/sysv/linux/aarch64/clone.S @ 81]

调试器是软件生产的基础工具,也是解决复杂软件问题的关键工具。用了2天多时间为NDB增加了期待已久的功能,让用户在调试时可以观察到贯通两大空间的调用栈,化天堑变通途,我觉得还是非常值得的。

这个功能成功后,我立刻将截图发到了兰舍群。软件技术没有止境,欢迎更多的伙伴加入兰舍群一起成长。

对于NDB的用户,可以通过如下步骤体验这个功能:

x lk!vfs_read

ba e4 lk!vfs_read

.reload /user

k

如果ndb提示缺少so文件,那么可以最好提前从目标机复制到主机。

(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)

*************************************************

正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生

扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物

2343db679417baba2f2540a5dbf1839a.png

也欢迎关注格友公众号

8c855a509828a6b558bd455295b81760.jpeg

更多推荐

芯科科技第二代平台的所有蓝牙片上系统均可支持蓝牙技术联盟的新功能和新标准

中国,北京-2023年9月21日–致力于以安全、智能无线连接技术,建立更互联世界的全球领导厂商SiliconLabs(亦称“芯科科技”,NASDAQ:SLAB),今日宣布其支持蓝牙技术联盟(BluetoothSIG)针对蓝牙网状网络(BluetoothMesh)实现的新功能增强,以及他们新的网络照明控制(NLC)标准,

【从0学习Solidity】17. 库合约 站在巨人的肩膀上

【从0学习Solidity】17.库合约站在巨人的肩膀上博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!本文收录于不写代码没饭吃的学习汇报系

RHCE---时间服务器

文章目录目录文章目录前言一.安装时间服务器软件初始化系统二.配置时间服务器的服务端三.配置时间服务器的客户端四.远程连接服务器前言Linux中的时间服务器是指NTP服务器,NTP是NetworkTimeProtocol的缩写,即网络时间协议。NTP服务器可以提供精确的时间信息,从而使得网络上的所有设备都能够同步时间,确

表名注解/主键注解/字段注解/乐观锁注解[MyBatis-Plus系列] - 第486篇

悟纤:师傅,脑瓜疼~师傅:徒儿这是怎么了?​悟纤:师傅,你了解冷暴力吗?师傅:略懂略懂。悟纤:那冷暴力是怎么定义的?师傅:冷暴力是暴力的一种,其表现形式多为通过冷淡、轻视、放任、疏远和漠不关心,致使他人精神上和心理上受到侵犯和伤害。冷暴力是目前为社会公认的会对行为相对人造成心理、精神伤害的行为。悟纤:有点抽象呢。师傅:

爬虫代理在数据采集中的应用详解

随着互联网技术的不断发展,数据采集已经成为了各个行业中必不可少的一项工作。在数据采集的过程中,爬虫代理的应用越来越受到了重视。本文将详细介绍爬虫代理在数据采集中的应用。什么是爬虫代理?爬虫代理是指利用代理服务器来隐藏真实的IP地址,从而保护数据采集者的隐私和安全。在数据采集中,使用爬虫代理可以带来以下几个好处:防止被封

QT之QListWidget的介绍

QListWidget常用成员函数1、成员函数介绍2、例子显示图片和按钮的例子1、成员函数介绍1)QListWidget(QWidget*parent=nullptr)构造函数,创建一个新的QListWidget对象。2)voidaddItem(constQString&label)在列表末尾添加一个项目,项目标签为l

【python百炼成魔】Python循环语句:嵌套循环

前言文章目录前言嵌套循环嵌套的用途嵌套循环案例1.输出一个三行四列的矩形2.打印直角三角形3.写一个九九乘法表总结嵌套循环Python中的嵌套循环是指在一个循环体内部包含另一个循环。通过嵌套循环,我们可以在外部循环的每次迭代中,执行内部循环的所有迭代。嵌套循环可以帮助我们处理一些复杂的问题。嵌套循环的逻辑:forxin

算法leetcode|76. 最小覆盖子串(rust重拳出击)

文章目录76.最小覆盖子串:样例1:样例2:样例3:提示:进阶:分析:在这里插入图片描述题解:rust:go:c++:python:java:76.最小覆盖子串:给你一个字符串s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串""。注意:对于t中重复字符,我们寻找的子

外包干了2个月,技术退步明显...

先说一下自己的情况,大专生,19年通过校招进入湖南某软件公司,干了接近4年的功能测试,今年8月份,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试,已经让我变得不思进取,谈了2年的女朋友也因为我的心态和工资和我分手了。于是,我决定要改变现状,冲击下大厂。刚开始准备

[C/C++]天天酷跑超详细教程-中篇

个人主页:北·海🎐CSDN新晋作者🎉欢迎👍点赞✍评论⭐收藏✨收录专栏:C/C++🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,大家一起学习交流!🤗天天酷跑,一款童年游戏,主要是进行跳跃操作,和躲避障碍物,中篇主要实现人物的下蹲,随机障碍物的生成以及优化main函数里面的sleep(30)一.

Python操作Excel教程(图文教程,超详细)Python xlwings模块详解,

「作者主页」:士别三日wyx「作者简介」:CSDNtop100、阿里云博客专家、华为云享专家、网络安全领域优质创作者「推荐专栏」:小白零基础《Python入门到精通》xlwings模块详解1、快速入门1、打开Excel2、创建工作簿2.1、使用工作簿2.2、操作工作簿3、创建工作表3.1、使用工作表3.2、操作工作表3

热文推荐