当前位置: 首页 > 科技观察

炫耀!漏洞排查已经暴露,那些涉及Linux内核的

时间:2023-03-21 13:11:52 科技观察

编程高手,不仅擅长写代码,调试代码也很容易发现bug。编写代码只是程序员的工作之一。调试代码甚至会比写代码花费更多的时间。系统、架构、编程等方面我已经给大家讲解了很多。本文将向您展示内核的方方面面。故障排除过程。发现问题后,某天公司服务器报了警。登录机器后发现进程已经“卡死”了。常规的GDB调试没有反应,找不到Log的线索。问题似乎无法解决。就在这时,岛国出现在了博主的脑海中。.是的,你猜错了,是岛国的一修哥和柯南哥,中国的包青天,国外的狄仁杰,夏洛克等等,瞬间如神助,必有办法!是的!问题分析首先,我们仔细分析一下。由于进程好像卡住了,如果卡在用户态,那么进程的CPU占用肯定很高(比如死循环);如果卡在内核态,进程应该是IO或者网络通信等,所以CPU占用率应该很低,现在可以查到进程ID。有了进程ID,运行top命令可以看到:注意CPU一栏,显示CPU使用率为0%。发现此时进程几乎不占用CPU,基本说明进程卡在了内核态。如果进程进入内核态,是因为调用了一个阻塞系统调用导致操作系统挂掉。那么如何知道进程调用了哪些系统调用呢?trace进程系统调用strace命令就是用来告诉你这个的,运行strace命令看看此时进程在调用什么系统调用:哎呀!strace命令也卡住了,无奈只能想其他办法。.可以使用pstack命令打印出进程运行时的堆栈信息。这个命令虽然不能跟踪内核,但是可以看到用户态最后调用了什么函数,从而推断出调用了什么系统调用,运行一下:和strace一样,pstack也卡住了。我们现在还能在哪里寻找线索?古老的ps命令永远不会过时。我们可以使用ps命令查看进程的运行状态和WCHAN(waitingchannel)。WCHAN是什么意思?在Linux的世界里,如果你有问题要问一个人(man),这就是万能的man命令,我们用man命令看看ps显示的是什么:$manps运行man命令,搜索“WCHAN”,啊哈!最后在“STANDARDFORMATSPECIFIERS”部分找到了WCHAN的意思,是这样写的:这里写的很清楚,WCHAN指的是当前进程阻塞在哪个内核函数上。OK,我们运行ps命令:这里值得注意的是,因为ps只是在运行ps命令的瞬间打印对应进程的状态,也就是说运行一次ps就相当于一次采样,所以应该多次运行ps,确保运行的结果没有改变,否则只运行一次并且时间足够聪明,可能会得到错误的线索。从ps打印的结果可以看出两个进程阻塞状态。进程的运行状态为D,D的运行状态是什么意思?我们再问man,查到这个信息:原来进程运行状态D表示uninterruptiblesleep,sleepthatcannotbeinterrupted,表示进程正在休眠,即使你拍打它也唤醒不了,即进程目前它不响应任何外部信号。这时,即使是kill命令也无法杀死进程(除非内核允许进程接收到kill信号)。直观的感觉就是进程“卡住了”。不可打扰的睡眠与可打扰的睡眠是相反的。从上图可以看出,状态为S,此时进程处于阻塞状态,等待一个事件(如网络数据到达等),处于该状态的进程可以接收到Signal,直观感受是该过程仍然有响应。通过ps命令,我们可以看到进程状态为D,进一步验证进程确实“卡死”了。那么进程卡在哪里呢?幸运的是,WCHAN专栏可以告诉您答案。进程阻塞在哪个内核函数上?上面ps命令WCHAN一栏显示rpc_wa,嗯。.rpc_wa是什么?好像被截断了,不过没关系,我们可以从源码中找到wchan的完整输出。其实ps等命令也是在这个源上搜索信息并显示的。这个源是proc文件系统,proc文件系统记录了内核和各个进程的运行时信息,我们可以使用最简单的cat命令,使用proc后跟进程ID和wchan:啊哈,终于找到哪里了此时进程卡住了!似乎该进程正在等待RPC调用。RPC实际上是一个进程与另一个进程通信。虽然我们知道进程卡在哪里了,但是我们仍然不知道为什么会卡在这里。此时线程似乎断了。..让我们再考虑一下。既然进程卡住了,那么此时进程一定不是用户态。如果不是用户态,那一定是内核态。进程如何进入内核态?显而易见的答案是系统调用被调用。那么我们怎么知道一个进程当前正在调用哪个系统调用呢?你是幸运狗,向/proc/***/syscall问好,我们也可以使用简单的cat命令在proc文件系统中搜索,只需使用/proc后跟进程ID+syscall。卧槽..这到底是什么!原来这串看似不可理解的东西就是系统调用。第一个数字代表系统调用ID,其余都是参数,我们不用关心。从上面的输出我们可以看到调用了262号系统调用。只有一个数字是没有意义的。这个数字代表哪个系统调用?根据内核源码查看系统调用。要知道这个数字的含义,我们需要参考内核代码。一般Linux系统中必要的内核头文件都位于/usr/include目录下。在博主的64位Linux机器上,找到了这个文件:Gotyou!!!我们可以看到调用了newfstatat系统调用。这个系统调用是做什么的?让我们再次询问这个人(man命令):$mannewfstatat得到这条消息:啊哈!原来是fstatat,它正在读取文件的元信息。现在我们知道调用什么系统调用了,但是又出现了一个新的问题,就是为什么我们调用了这个系统调用后,最后还是卡在等待rpc呢?显然我们需要调用栈信息来验证。跟踪内核运行时栈OOOOKey,是时候求一个重量级的工具了,这个就是/proc/PID/stack,通过简单查看这个文件,我们就可以知道内核中对应进程的调用栈!!!就问你Linux的设计是不是很厉害,是的!!!这个内核调用栈终于揭开了所有的秘密。事实很清楚。首先,让我们看一下调用堆栈的顶部。堆栈的顶部正是ps命令WCHAN打印的列。内核中的进程因为调用了这个函数而卡住了。接下来,我们查看调用栈的底部,我们找到了系统调用,这证实了是调用这个系统调用的进程导致了卡死。那么当这个系统调用被调用时会发生什么?让我们抬头注意这些行:最后!!!从调用栈中,我们看到了一系列与NFS相关的函数。NFS的全称是NetworkFileSystem,即网络文件系统。我们通常通过NFS挂载(mount)一个远程文件系统。它是用于网络通信的NFS。它导致等待rpc。从内核调用栈我们知道,在查询远程主机上的文件的元数据时,由于网络问题,进程卡住了。有了这个线索,我们终于锁定了有问题的代码。总结本文为您展示了一个完整的bug定位过程。可以看到Linux为我们提供了非常丰富的调试工具。当然,这离不开Linux系统本身优秀的设计思想,即进程和内核的运行时信息。通过文件系统提供,极大的方便了问题的排查和定位。希望本文能帮助大家了解Linux系统下调试的问题。