问题现象
在搞一个VPN的连接代码测试,发现每次客户端断开连接都会导致VPN服务器崩掉,debug跟进服务端的代码里,发现是Free连接环境时发生了异常,从debug的代码看到的现象很奇怪,一会是在执行Free函数时报错,一会又跑到了打印日志时报错。
详细跟进代码发现,无论上层的函数是在哪里报错,下层都是跑到了glibc中的_int_free函数中,那结论就比较清晰了,是free时内存出错了,多半是内存溢出了。
报错的内容就是这个free(): invalid pointer,在VSCode的debug过程中看到的是“Fetal Error: Aborted”,由SIGABRT信号触发。
解决过程
在网上查了一圈free(): invalid pointer的解决方法,有两种可能的原因,一种是多次free,第二次就会失败,另一种是这部分内存被别的代码给改动了。
路线1
按照这个思路,我先是花2天时间把项目代码翻来覆去的看,找哪里做了多次free,最后实在是看不下去了,整个代码中的malloc和free搜索了个遍,都是没有问题的,感觉不一定是这个多次free的问题。
路线2
于是换个思路,看看是不是被别的代码改动了内存。在经常报错的那些地方一一加了断点,然后在对应的参数生成的时候也加上断点,一个一个对比生成时的位置和抛出异常时的位置内存是否有变化,然而依然没有进展。
路线3
此路不通,继续想其他的问题。
这里跟代码时跟不到glibc中的代码,因此根据需要从网上找glibc的源码看看。
首先确定自己使用的glibc是什么版本的,使用
ldd ./vpnserver
指令查看当前程序依赖的动态库,找到glibc对应的那个动态库对应的文件路径,将这个路径下的文件当可执行文件直接跑一下,得到如下的结果:
看来是使用的2.31版本的glibc,从网上搜索对应的代码,gdb中显示信息的最后一段数字就是对应代码中的行号,找到这里一看发现新的思路!glibc中申请内存时,除了原本想申请的内存,还会额外多申请一些内存,附在返回的数据指针对应的内存前面,然后在释放内存时,也会向前搜索一段距离,并对这段额外的数据做判断,如果判断失败,就会抛出异常。
这样就发现了问题,不是我申请的那部分内存被改动了,而是前面看不见的头数据被改动了,于是再次借助强大的VS,使用数据断点的方式对被改动的内存位置加断点。
VS的数据断点是在内存中某个内存地址添加监控,当被监控的内存发生改动时,进入断点,程序暂停。使用方式如下:
- 首先在某个地方打断点,让程序暂停下来。
- 接着点击“调试”->“新建断点”->“数据断点”,在弹出的界面输入想监控的内存地址。
接下来就等待这个断点被触发就可以了,用这个方法最后成功找到了改动内存的罪魁祸首:gmssl库!好家伙,我说怎么看了多少代码都没用呢,原来不在VPN的代码里,在引用的库代码里,其实早就应该想到的。
总结
总结一下,C语言程序中的问题都不太好定位,逻辑问题可以靠debug解决,但是内存问题靠debug有时候真的完全没有头绪,还是抓紧拥抱RUST吧。
这次解决问题用到的技术栈和工具:
gdb调试
coredump文件
VSCode
Visual Studio 2022
glibc源码 [https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c]
今天先记到这里吧,希望这个文章能帮助到其他被Aborted支配的人。