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 | 留下评论

MTU导致通信不正常问题

其实这个问题一看到题目就知道了,但是分析起来还是有点绕弯弯,虽然是一个貌似很常见的问题,但是职业生涯还是第一次遇到,记录一下。

下午业务跟网络的同学反馈,某云主机访问自建IDC个别服务不正常,拿过一份抓包文件看了下,看到有重传,但是并没有特别的明显标示,于是建议两端转包,然后拿到了如下两份抓包结果:

服务端:

客户端:

从图上看出,服务器收到客户端的get请求之后,回应了两个个2962+140的包,当然,因为打开了网卡拆分tcp选项,所以2962的包到网络必然会被拆成两个包,但是从对方来看,并没有收到2962的包内容,然后客户端回了一个dup ack,表示中间丢包了,服务器端连续发了7个包,客户端都未收到,然后客户端在1分钟后发送keep alive信息,服务端发送rst,会话终止,注意其中服务端发送的多个包,客户端未收到,总结下来,看起来较大的包,客户端都没收到,怀疑是MTU问题,拿到云上环境其他主机看了下,确实MTU是1400,比我方的1500小,但是注意截图中红框的部分,客户端TCP通告的MTU是1460,那么说明对方服务器配置的MTU可能仍然是1500,因为MSS通常是 网卡MTU-ip包头20-tcp包头20算出来的,登录上对方的服务器检查,发现MTU确实跟其他机器不一致,是1500。

那么,对方正常云主机MTU配置都是1400,应当是有依据的,因为linux默认是1500,应该是对方基础网络设备上的限制,必须要1400,大一些的可能通不过部分网络设备,所以,即使对方服务器配置1500,通告到我方之后,我方按较大的报文发送,到了对方网络环境当中某些节点,可能还是会被丢弃,这就是问题的原因。跟对方运维负责人沟通后,也验证了这一问题。

发表在 好玩的linux, 网络 | 留下评论

linux intel KPTi 补丁对性能影响

2018年最火的是什么,当然是intel的漏洞大礼包了,几十年来cpu全部中招,rhel也马上推出了补丁,补丁对性能的影响,大家也是看法不一,有的说2%,有的说20%到30%之间,那么具体的影响有多少,为什么性能影响上有很大分歧呢,这里简要的介绍一下。

根据漏洞描述,此补丁影响的是linux syscall跟进程切换的开销,如果是纯计算型的应用,基本不受影响,正好sysbench有一个线程创建的测试,我们通过如下命令:

sysbench --test=threads --num-threads=32 --thread-yields=100000 --thread-locks=80 run

同样的命令,在打过补丁跟没打过补丁的场景来看,打过补丁的机器需要80s跑完,而没打过补丁的场景,只需要30s即可跑完,那么从这个场景来看,性能的损失,大约有60%,大概算是影响较大的一个场景了,虽然正常的应用,不应该有很频繁的切换跟调用,但是,补丁对于这个切换跟调用下的性能影响,绝对不容小觑。

 

发表在 好玩的linux | 留下评论

systemtap查看udp rcvbuf满导致丢包

折腾了老半天,被atomic_t坑惨啦,匿名结构体必须要用cast转换类型,然后再访问成员,用来排查udp buffer满,导致丢失日志的场景

#!/usr/bin/env stap

probe kernel.statement("udp_queue_rcv_skb@net/ipv4/udp.c")
{
    printf("rmem_alloc:%d rcv_buf:%d drops:%d\n", @cast(&$sk->sk_backlog->rmem_alloc, "atomic_t", "kernel<linux/types.h>")->counter,  $sk->sk_rcvbuf, @cast(&$sk->sk_drops, "atomic_t", "kernel<linux/types.h>")->counter)
}

 

发表在 debug, 好玩的linux | 留下评论

systemtap查看socket 信息

通过systemtap查看socket信息的脚本,跟踪在sendmsg跟recvmsg,示例中只有sndbuf跟rcvbuf,实际上可以拓展到任意的结构体成员,比较方便

#! /usr/bin/env stap
%{
#include <linux/version.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <linux/tcp.h>
#include <net/inet_sock.h>
#include <net/ip.h>
#include <linux/skbuff.h>
%}

probe kernel.statement ("sock_sendmsg"),
      kernel.statement ("sock_recvmsg")
{
  printf ("%s:%d<->%s:%d\n", ip_ntop($sock->sk->__sk_common->skc_daddr), ntohs(@cast($sock->sk, "struct inet_sock")->inet_sport), ip_ntop($sock->sk->__sk_common->skc_rcv_saddr), ntohs($sock->sk->__sk_common->skc_dport) )
  printf ("%s(%d) %s : sock_flags:%d sndbuf:%d rcvbuf:%d\n",
    execname(), pid(), probefunc(), $sock->flags, $sock->sk->sk_sndbuf, $sock->sk->sk_rcvbuf)
}

 

 

发表在 好玩的linux | 留下评论