linux glob_filename 解析超长字符串致夯死问题

0.背景

大清早的同事过来找,说有个机器无法登录之后无法通过sudo su -切换到root,上去看了一下,发现确实如此,于是开始愉(dan)快(teng)的排障之旅。

1.思路

确定应该不是shell本身的原因导致,因为普通权限的用户可以登录,并且没有问题,但是切换到root的时候会卡住,于是怀疑bash的rc文件或者profile文件导致问题,重点排查这个环节

2.步骤

2.0

首先,现在切不到root,很多不方便的地方,虽然root bash不可以用,但是还有其他很多shell哦,没有装?装一个,sudo yum install csh,然后sudo csh,可以获得root权限,后续就方便很多了

2.1

开启另一个终端做调试,执行sudo su – ,夯住:

htop查看下负载,单核跑满:

确认是bash进程跑满单核,并且内存占用会逐步上升

2.2

查看了bashrc跟profile.d下面所有文件,并未发现异常于其他机器的,开始怀疑特殊的场景,诱发了bash,或者某些rc文件的bug,尝试用perf top -p pid 分析下,查看下函数cpu占用情况:

2.3

搜索资料发现,上面三个占用cpu最高的函数,是处理字符串的函数,怀疑某种特殊字符的字符串导致bash僵死,后来证明这个猜测是错误的,不过还好方向没走太偏。

要找到bash内部的问题,目前看来只能尝试gdb去跟踪了,先sudo gdb bash跟踪了一次,发现输出都是汇编,还是很头疼的,为了将汇编跟源代码对应起来,需要安装debuginfo的包,安装的debuginfo包方法在这里:

https://www.jianshu.com/p/5b4ef8112b97

在这个场景,我们需要安装的是bash,glibc的debuginfo包。

装好之后,gdb走起

2.4

sudo gdb bash 执行之后,run一下,hang住之后,等一段时间,大约10s,ctrl c掉

然后执行bt查看下栈信息,装了debuginfo终于可以看到源码跟参数信息了,泪目。上下翻下栈的详细信息

可以看到,栈深度已经到达49,正常不会有这么多的,事后复现发现,栈深度也会持续增加,从第1个栈帧到34个,基本都是雷同的内容,猜测有递归调用,而其中的内容,应该是应用层的内容,开始排查profile.d 下面的各个文件发现有一行:

这段逻辑是要把history中上一条执行记录提取出来,发送到日志中,但是在echo $y的时候,命令行中的参数会被解释!

2.5

把这个文件移走之后,发现可以正常切换了,断定问题根源在这个文件

3.复现

3.0

原理,linux在shell中遇到形如 /dir/* 的参数的时候,会解析展开其中的内容如:/dir/f1 /dir/f2 /dir/f3 ……

如果某个目录下文件太多,那会导致这个解析过程处理的字符串过大,由于解析逻辑中有递归的过程,也导致调用不断增加,内存不断增加,最后导致夯住,持续下去,进程也会耗光内存,导致OOM

3.1

如果希望复现,我们可以构建一个较长的目录名称,然后在这个目录下创建大量文件:

mkdir /tmp/123456789012345678901234567890123456789012345678901234567890
cd /tmp/123456789012345678901234567890123456789012345678901234567890
for i in `seq 1 10000`
do
touch $i
done

这样,我们在执行如下命令的时候:

echo /tmp/123456789012345678901234567890123456789012345678901234567890/* >/dev/null

参数就会被解析,最终会展开一个几十万byte的字符串

以上这个场景,bash会处理十几分钟,最后会报错太长参数退出。

大家可以尝试构建更多的文件,夯更久一些,直到OOM,玩的开心

4.总结

总体来说,排障的思路还是没有走入太多的歪路,gdb还是神器,后续可能还要继续了解使用下,后续类似这种问题,其实简单来说可以把profile.d跟bashrc移走先,基本可以快速定位问题文件。其中也使用strace以及bash -x 也可以发现一些端倪,如果思路清晰对位,最终一定能找到真正的问题所在。

此条目发表在debug, 好玩的linux分类目录。将固定链接加入收藏夹。