1.linux内核调试工具crash并不能直接显示函数参数,而这个对调试又非常重要
下面是工作中一个实际的问题,我们的进程hang在如下一个内核栈中了,通过栈回溯可知是打开了一个nfs3的网盘文件或者目录,已知客户机器的NAS盘不可访问了,只要访问就会hang住,但我们的进程理论上是不会访问该NAS盘的,那么如何知道open打开的是什么文件呢,这时候就迫切的需要知道do_sys_open的filename参数了(此时多么希望VS, gdb能够出手相救)。
但在x64位linux系统中,前6个参数使用的是rdi, rsi, rdx, rcx, r8, r9寄存器来传递,超出的才会用栈来传递,filename是第2个参数,会用rsi来传递,这下就GG了,函数经过这么多层调用,到当前栈帧的时候rsi早就是n手货了,而且rsi又不是保留寄存器,下层函数不会给他提供VIP待遇做保存,绝望,绝望,就是这么的绝望。

 

 
 
2.希望之光:通过函数参数被局部变量缓存获取

1> 上帝关上了一扇门,必然会为你打开另一扇窗

虽然寄存器不会被缓存,但局部变量会啊,局部变量是用栈保存的,函数调用栈未返回之前,栈都会一直给你当宝一样存着,so换个思路,假设,我们假设这个函数参数filename传递给了某个路人局部变量,那么我们找到这个路人,不就相当于找到了filename么。
 

2> 思路打开,上帝就立马给你送来了这个路人。

我们看do_sys_open,开头就把filename丢给了tmp这个路人,因此我们只要去栈上把tmp逮捕归案就能收工下班。

 

3> 逮捕方案1:"-FF"

crash的bt命令提供了"-FF"参数,宣称可以打印局部变量。但是吗,往往宣称是一回事,实际又是另一回事。"bt -FF"一敲,甭说放大镜,用电子显微镜也找不到tmp在哪。

4> 逮捕方案2:速请汇编大仙

既然tmp是个局部变量,又会作为第二个参数传递给do_filp_open,那么我们找到汇编大仙call do_filp_open的地方,就能找到tmp了。
来吧,dis照妖镜,do_sys_open原形毕露如下,tmp是调用getname返回的,返回值rax立马给了r14(汇编里返回值都是用rax传递),不妙不妙,还是不给留栈上啊,难怪"bt -FF"瞎眼了。虽然但是呢,上帝又给留了一扇窗(PS: 这上帝给的有点多),r14是保留寄存器,享受终生大保健VIP待遇,下层函数调用一定会给留个位的,走,去do_filp_open栈上逛逛。
 
dis给do_filp_open一照,嘿,上帝对咱是true love了,第2个push就把r14留do_filp_open栈上了,下班收工指日可待了这不。
调转枪头再来看一眼前面让我们绝望的"bt -FF"的栈,根据汇编基础知识,linux x64调用函数先将返回地址压栈,调用do_filp_open后再将rbp压栈,最后就是我们朝思暮想的r14了,所以从do_filp_open栈底数第3个就是tmp了,抓!!!
原来是用的第三方库openssl里面会打开一个编译机上的openssl.cnf文件,刚好在编译机的/mnt目录下,而/mnt目录是NAS等网盘默认挂载路径,当NAS出现异常时(如在有文件被占用情况下卸载NAS),此时访问NAS文件目录都会hang住,导致进程hang。

 
3.后记
上面的例子上帝开的窗有点多,通过保存了函数参数的局部变量,咱们很快就真相大白了,但是如果万一万一非常点背,我们要找的函数参数就是没有在局部变量中保存,那怎么办呢。这种情况一般是不会有的,因为重要的参数只要有需要,就会传递到某一层函数局部变量中,耐心点找找就会有的,如果确实没有,可能就需要更耐心的分析整个调用栈,看看哪里会有些勾勾搭搭的局部变量,寄存器没有被破坏,也终究是可以抓出来的。

linux内核调试痛点之函数参数抓捕记的更多相关文章

  1. linux 内核调试之关键函数名记要

    gdbserver + gdb 调试内核 记到函数名,其它就能用gdb看了 start_kernel 内核启动 run_init_process    init进程启动 主要是根据shell脚本初始化 ...

  2. Linux内核调试方法总结【转】

    转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...

  3. linux内核调试指南

    linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 ...

  4. 【转】Linux内核调试方法总结

    目录[-] 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2  dump_sta ...

  5. Linux内核调试方法总结

    Linux内核调试方法总结 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2   ...

  6. Linux内核调试方法【转】

    转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...

  7. Linux Kernel - Debug Guide (Linux内核调试指南 )

    http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级 ...

  8. Linux内核调试的方式以及工具集锦【转】

    转自:https://blog.csdn.net/gatieme/article/details/68948080 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...

  9. Linux内核调试的方式以及工具集锦

    原文:https://blog.csdn.net/gatieme/article/details/68948080 CSDN GitHubLinux内核调试的方式以及工具集锦 LDD-LinuxDev ...

  10. Linux内核调试方法总结之栈帧

    栈帧 栈帧和指针可以说是C语言的精髓.栈帧是一种特殊的数据结构,在C语言函数调用时,栈帧用来保存当前函数的父一级函数的栈底指针,当前函数的局部变量以及被调用函数返回后下一条汇编指令的地址.如下图所示: ...

随机推荐

  1. 嵌入式开发SQLite 快速掌握

    SQLite是什么 SQLite又称(RDBMS)它 是本地数据库,可以用在手机,嵌入式设备的精简数据库和大名的mysql 一样的数据库存,只是可以理解为它是精简版,事务处理.表连接.索引.触发器等都 ...

  2. docker 将镜像发布到网络

    1.发布自己的镜像 hub.docker.com 创建账号 docker login -u supermao -p xxxx docker tag ls supermaofox/ls:1.0 先打标签 ...

  3. jpa+querydsl的平替国产easy-query最好用的orm

    jpa+querydsl的平替国产easy-query最好用的orm 一款国产最强java orm,完美支持可控强类型dsl,外加完美支持对象模型筛选拉取的orm,拥有非常智能的include(s)一 ...

  4. 【Project】原生JavaWeb工程 02 登陆业务的流程(第一阶段样例)

    1.对用户信息的描述 首先用户有一些基本信息: 最简单的: 用户名称 + 用户密码 然后是用户状态,例如封号,注销,停用,等等 用户名称 + 用户密码 + 账号状态 接着为了防止脚本攻击,又产生了图形 ...

  5. “refer to”和“refer to as”在英语中的用法有所不同

    "refer to"和"refer to as"在英语中的用法有所不同,具体区别如下: Refer to "Refer to"意为" ...

  6. 强化学习算法真的适合于你的应用吗 —— 强化学习研究方向(研究领域)现有的不足(短板、无法落地性) —— Why You (Probably) Shouldn’t Use Reinforcement Learning

    外文原文: Why You (Probably) Shouldn't Use Reinforcement Learning 地址: https://towardsdatascience.com/why ...

  7. 关于“内网穿透”的一些知识(续3)—— NAT类型判断

    前文: 关于"内网穿透"的一些知识(续2)-- 端口预测 ------------------------------------------- 本文是对前面几篇文章的补充.这里要 ...

  8. java多线程之-线程池状态

    1.背景 这一节我们来学习一下线程池状态..... 2.线程池状态 状态名称 高3位 是否接受新任务 是否处理队列中的任务 说明 RUNNING 111 是 是 线程池正常运行状态 SHUTDOWN ...

  9. 【模板】树的直径(dfs & dp)

    树的直径 给定n个点 n-1条边 和每条边的val 输出直径的大小和 直径上的点的序号 input: 8 1 2 2 1 3 1 1 5 10 2 4 3 4 6 4 3 7 5 7 8 2 outp ...

  10. iOS开发基础149-由UUIDString引发的思考

    问题1:[[UIDevice currentDevice] identifierForVendor].UUIDString什么情况下值会变化? [[UIDevice currentDevice] id ...