首页 » PHP教程 » phpcoredump技巧_避雷心经 Coredump排查入门

phpcoredump技巧_避雷心经 Coredump排查入门

访客 2024-11-29 0

扫一扫用手机浏览

文章目录 [+]

| 导语 想必每一位 C++ 选手在事情中都难免会踩中 Coredump 地雷,而我作为 C++ 新手也与 Coredump 有过激烈的战斗,下文正是我在排查 Coredump 过程中总结的一些心得履历。

1. 观点

Coredump(核心存储)是进程非常终止或崩溃时的内存快照,操作系统会在程序发生非常而非常在进程内部又没有被捕获的情形下,会把进程此刻内存、寄存器状态、堆栈指针、内存管理信息以及函数调用堆栈等信息转储保存在一个文件里(Corefile)。
Coredump 对付开拓者诊断和调试程序是非常有帮助的,由于对付有些程序缺点很难重现,例如偶发的指针越界,而 Corefile 可以再现程序出错时的情景。

phpcoredump技巧_避雷心经  Coredump排查入门

2. 工具2.1 Coredump 开启

有时进程 Core 了却没有找到 Corefile,有可能是由于没有开启 Coredump,可通过 ulimit 命令开启:

phpcoredump技巧_避雷心经  Coredump排查入门
(图片来自网络侵删)

# 查看当前 Corefile 大小,为0则表示禁止产生 Corefileulimit -c# 设置 Corefile 大小无限大ulimit -c unlimited# 设置 Corefile 大小1024kbulimit -c 1024

2.2 GDB 工具2.2.1 观点

GDB(GNU Debugger)是 GNU 软件系统中的标准调试器,具备各种调试功能,包括但不限于打断点、单步实行、打印变量、查看寄存器、查看函数调用堆栈等,能够有效地针对函数的运行进行追踪,也常用于 Coredump 剖析。

2.2.2 GDB 剖析 Corefile

1.运行 gdb [executable file] [corefile] 开始调试,实行 corefile 须要对应的可实行文件:

2.键入 bt (backtrace)打印函数调用栈,第一行即为发生 Core 的末了调用处。

3.键入 f {num} 可切换至指定的一帧,从而打印该阶段的干系信息。

4.其他常用命令

命令

浸染

print {variable}

打印当前函数的指定变量值

info args

打印出当前函数的参数名及其值

info locals

打印出当前函数中所有局部变量及其值

disas

查看当前栈帧的汇编代码

2.2.3 GDB单步调试网络程序gdb -p {进程pid}:使GDB监听某个进程。

b {Class::func}:给某个可疑函数打断点。

客户端要求网络做事。
c (continue):运行程序,会停在断点处

n (next):一行一走运行代码,如果碰着core会报错,此时便可通过bt、print去排查core。
(其它命令:until:跳出循环、finish:实行完当前函数)

更多GDB利用:GDB调试

2.3 AddressSanitizer 工具2.3.1 观点

很多时候通过 GDB 调试 Corefile 看出的只是 Core 的表象缘故原由,例如通过 GDB 看出 Core 的直接缘故原由是 NULL 指针引用,但真正的问题是这些不应该为 NULL 的指针为何被赋值成 NULL。
在如今异步和多线程编程的场景下,很难只从函数调用栈等分析出缘故原由。
而 AddressSanitizer 工具正是针对这类问题的一把利器,用来探测内存访问的 bug,包括了缓冲区溢出、重复利用已被开释的堆内存等。

2.3.2 利用

1.安装依赖 sudo yum install libasan

2.在项目根目录添加“.bazelrc”文件

3.在“.bazelrc”文件中配置以下内容

build --copt=-gbuild --copt=-Og // 禁用gcc优化,方便排查问题build --copt=-ggdbbuild --strip=neverbuild --copt=-fno-omit-frame-pointerbuild --copt=-fsanitize-recover=address // 检测到缺点后,不退出,连续实行(须要合营环境变量配置)build --copt=-fsanitize=address // 打开AddressSanitizer模块(LeakSanitizer、MemorySanitizer实际上也都整合到了这里)build --linkopt=-fsanitize=address

4.编译程序,并将程序上传到测试容器,测试容器安装依赖 sudo yum install libasan

5.由于通过123无法正常启动会一贯重启,建议停滞容器,避免滋扰

6.测试容器中启动程序,不雅观察 std out

export LD_PRELOAD=/lib64/libasan.so.4export ASAN_OPTIONS=halt_on_error=0 // 检测到缺点后,不退出,连续实行(框架或者某些库可能会运行到我们的关注点之前就报错)3. 实践3.1 WebQO 下线老紧密度算子引起的 Coredump3.1.1 背景

WebQO 是搜狗搜索引擎依赖的 Query 理解系统模块,其紧张供应切词、词权、紧密度、意图识别的功能。
由于项目过于悠久,WebQO 上冗余了两个紧密度算子,据剖析,老紧密度算子结果处于无人利用废弃状态,于是我便打算下线老紧密度算子,欲降落系统繁芜度与提升性能。

3.1.2. Coredump 情景回顾

由于主流程仅调用了老紧密度算子对外供应 run() 接口,以是我下意识地认为注释掉这行调用代码即完成了算子的下线。

但事情每每没那么大略,在我提交测试后,创造每次对做事要求都会 Coredump,无法获取相应结果。

3.1.3. 缘故原由剖析

1.利用 GDB 对 Corefile 进行剖析,键入 bt 显示 Coredump 时的堆栈。

2.堆栈的第一行即为发生 Coredump 时的末了调用处,咱们定位到其对应的代码,这个函数的浸染是用新紧密度算子的结果更换掉老紧密度算子的结果。
从代码可以看出这里很有可能是数组越界访问的问题,访问了造孽地址。
我们会产生一个疑问:ngram_nodes这个数组会不会压根不存在 或者 长度与 seginfo.termsCount 不一致?

3.带着上面的疑问,我们可以先通过 GDB 打印 ngram_nodes 的值,可以看到数组的长度为0,验证了我们的猜想。

4.再结合代码剖析,我们直接搜索 ngram_nodes 被赋值的地方,果不其然,ngram_nodes 数组是在老紧密度算子里被赋值的。
当我把老紧密度算子调用注释掉后,该数组无元素,而上图函数的 for 循环条件是依据 term 的个数,而非 ngram_nodes 的数组长度,从而导致的数组访问越界。

3.1.4. 办理思路3.1.4.1. 做好防御

在原来代码中,for 循环对两个数组(seginfo.terms & ngram_nodes)都进行了遍历,由于在原逻辑中默认两个数组长度是同等的,以是原作者设置的循环条件只是 seginfo.terms 的长度。
但是这种做法不足安全,由于当 ngram_nodes 的长度小于 seginfo.terms 时会发生数组越界。
因此这里的循环条件该当是 i < min(seginfo.terms长度, ngram_nodes数组长度)。

但是该方案并不敷以知足业务需求,由于如此修正后会进入不了 for 循环,也就无法对 ngram_nodes.adjoin 进行赋值。

3.1.4.2. 预分配内存

由于老紧密度结果的 adjoin 字段还在被下贱做事利用,这里须要进入 for 循环将新紧密度值赋给 adjoin 字段。
以是更合理的思路是对 ngram_nodes 数组预分配内存,使其与 seginfo.termsCount 保持同等长度。

3.1.5. 小结

这类 Coredump 算是较大略的必现型 Coredump,其 Coredump 缘故原由在于业务功能冗余的情形下又没做好细节防御,从而在 ngram_nodes 数组长度变动时导致了数组访问越界。

3.2. 修正 QU_Platform 业务配置引起的 Coredump3.2.1. 背景

QU_Platform 是搜索中台下的 Query 理解系统平台,其下席卷了浩瀚 QU 算子做事,如切词、同义词、意图等。

3.2.2. Coredump 情景回顾

鹰眼日志上报在配置中是默认开启的,当我在线上修正了做事的业务配置后,便产生了 Coredump。
这个 Coredump 是偶发性的,集群中并非所有节点都 Coredump。

3.2.3. 缘故原由剖析

1.利用 GDB 对 Corefile 进行剖析,键入 f 0 显示发生 Coredump 时的堆栈首行。

2.从堆栈信息可以看出,程序在调 atta_api 时发生 Coredump。
结合我修正配置的操作,以及并非所有做事都 Coredump 的特点来看,这里有可能是我修正配置后触发某些逻辑,而与此同时做事恰好在上报鹰眼日志,从而导致了多线程冲突问题。

3.结合代码进行剖析,在我修正了鹰眼日志上报的配置后,触发了 AttaReporter::ReInit() 函数,此函数会对 atta_client_ 做 release() 操作。

而与此同时,一个检索要求触发了 AttaReporter::Report() 调用 atta_client_→send_string(),而由于 atta_client_ 在此时已经被被析构了,这便产生了 Coredump。

3.2.4. 办理思路3.2.4.1. 删除 ReInit 功能

当前考虑到 atta_conf 的配置险些不用变动,以是采纳直接删除 ReInit 功能的做法,这样其他业务配置修正时就不会影响到 AttaReport 上报日志。
此做法下,如果改动了 atta_conf 配置,须要重启做事才能生效。

3.2.4.2. 加互斥锁

另一种方法便是给 atta_client_ 加互斥锁,在 ReInit() 和 Report() 时都去申请该互斥锁,避免冲突。
但这导致:纵然不是 atta_conf 的配置更新,也会判断该申请锁逻辑,带来一定的性能损耗。

3.2.5. 小结

这类 Coredump 是稍繁芜的偶现型 Coredump,其 Coredump 缘故原由在于没有对存在多线程竞争的资源做保护,可通过对竞争资源加锁保护办理,或者放弃写功能,使该资源只可读。

4. 总结

在经历了一些 Coredump 的折磨后,我也从中学习积累了一些常见的 Coredump 场景以及应对办法,往后再碰着 Coredump 可以参照历史履历并按照下述步骤进行排查。

4.1. 常见 Coredump 场景

1.内存访问越界。

由于利用缺点的下标,导致数组访问越界搜索字符串时,依赖字符串结束符来判断字符串是否结束,但是字符串没有正常的利用结束符利用 strcpy, strcat, sprintf 等字符串操作函数,将目标字符串读/写爆。
该当利用 strncpy, strncat, snprintf 等函数防止读写越界。

2.多线程问题

多线程读写的数据未加锁保护多线程程序利用了线程不屈安的函数智能指针多重析构

3.造孽指针

空指针访问随意利用指针转换。
一个指向一段内存的指针,除非确定这段内存原来就分配为某种构造或类型,或者这种构造或类型的数组,否则不要将它转换为这种构造或类型的指针,而该当将这段内存拷贝到一个这种构造或类型中,再访问这个构造或类型。
这是由于如果这段内存的开始地址不是按照这种构造或类型对齐的,那么访问它时就很随意马虎由于 bus error 而 Coredump。

4.堆栈溢出

不要利用大的局部变量(由于局部变量都分配在栈上),这样随意马虎造成堆栈溢出,毁坏系统的栈和堆构造,导致涌现莫名其妙的缺点。

5.格式化输出时数据类型缺点

例如输出日志时,将整型数据用字符串输出4.2. Coredump 排查步骤先深呼吸一口,调度下心态,催眠自己的内心:“真好,又得到了一次学习的机会”。
做事应急方法。
如果 Coredump 影响了线上做事,做事该扩容扩容,该重启重启。
保存 Coredump 现场。
千万不要头脑一热就把原来 Coredump 的节点直接删掉,可停滞 Coredump 的节点,便于事后剖析修复。
梳理记录发生 Coredump 前的行为,任何举动都可能成为有代价的排查信息。
利用 GDB 对 Corefile 进行剖析,定位 Coredump 的代码位置,缩小排查范围。
并结合代码等信息进行剖析。
如果仅通过 Corefile 无法确认 Coredump 的机遇,也可参照 3.2.3 单步调试网络程序。
若 GDB 无法办理当前问题,考试测验利用 AddressSanitizer 工具深入排查。

以上便是我作为 C++ 新手在踩了 Coredump 地雷后收成的履历,希望能给读者带来一些帮助,避免重复踩坑。

相关文章

mysql类php技巧_PHPAJAX 与 MySQL

AJAX 数据库实例下面的实例将演示网页如何通过 AJAX 从数据库读取信息:本教程利用到的 Websites 表 SQL 文件:...

PHP教程 2024-12-07 阅读0 评论0