一个core的完整背景

简介:

前言

前段时间,百度某产品线的一个模块在重启的时候出了core,本文尝试将这个core,

以及core引发的技术上的思索,完整的勾勒出来。

充分挖掘core文件

core文件时第一“犯罪现场”,它会告诉你很多很多事情。一个优秀的侦探是不会放过core中的任何蛛丝马迹的。那么从core文件开始:

 

(gdb) bt

#0 0x000000302af70b9a in memcmp () from /lib64/tls/libc.so.6

#1 0x000000000042bc64 in product::KeyMutex::UnlockKey (this=0x7fbfffec98,

key=@0x41e001f8) at KeyMutex.h:171

#2 0x000000000042a457 in ~Locker (this=0x41e001f0) at KeyMutex.h:198

#3 0x0000000000426298 in product::Someauthority::ItemDel (this=0x7fbfff5c50, key=@0x41e00350) at Someauthority.cpp:789

#4 0x000000000043032b in product::Someprocessor::Someupd (this=0x7fbfff4a40, pReqPack=0x2a9b6b5f90, luCmStamp=1281691520)

at Someprocessor.cpp:462

#5 0x000000000042f38b in product::Someprocessor::Proc (this=0x7fbfff4a40) at Someprocessor.cpp:264

#6 0x000000000041d8a7 in product::Processor::Process (this=0x7fbfff4a40) at Processor.cpp:75

#7 0x000000000041dd21 in product::Processor::Somecallback () at Processor.cpp:111

#8 0x0000000000455004 in eppool_consume (pool=0xaf41f0, data=0xa5cea0) at eppool.cpp:455

#9 0x0000000000454e60 in _eppool_workers (param=0xa5cea0) at eppool.cpp:410

#10 0x000000302b80610a in start_thread () from /lib64/tls/libpthread.so.0

#11 0x000000302afc6003 in clone () from /lib64/tls/libc.so.6

#12 0x0000000000000000 in ?? ()

 

看来core在了memcmp这个函数中了。 可惜的是memcmp函数在libc中,由于libc没有-g编译,看不到足够的debug信息。

(gdb) f 0

#0 0x000000302af70b9a in memcmp () from /lib64/tls/libc.so.6

(gdb) inf f

Stack level 0, frame at 0x41dffc20:

rip = 0x302af70b9a in memcmp; saved rip 0x42bc64

called by frame at 0x41dffce0

Arglist at 0x41dffba8, args:

Locals at 0x41dffba8, Previous frame's sp is 0x41dffc20

Saved registers:

rbx at 0x41dffbe8, rbp at 0x41dffbf0, r12 at 0x41dffbf8, r13 at 0x41dffc00, r14 at 0x41dffc08, r15 at 0x41dffc10,

rip at 0x41dffc18

 

我关心的是Previous frame's sp is 0x41dffc20, 因为我想看下调用memcmp函数的压栈的信息。

(gdb) x/10x 0x41dffc20

0x41dffc20: 0x41dffc70 0x00000000 0x0046f67a 0x00000000

0x41dffc30: 0x00000000 0x00000002 0x00a65b10 0x00000000

0x41dffc40: 0x00a65bc8 0x00000000

 

看了下栈的数据中没有全是0的,为何会core呢?

 

看下memcmp的原型:

int memcmp(const void *s1, const void *s2, size_t n);

3个参数压栈,如果参数都没有问题,core在了memcmp中,难道是memcmp的bug?

一般情况下,x86_64的函数调用的参数传递规则是:

参数小于6个的时候,通过寄存器传递参数,顺序是rdi,rsi,rdx,rcx,r8,r9,

参数个数大于6个时候,才使用堆栈传递参数

 

看一下frame1的函数的反汇编:

(gdb) disassemble product::KeyMutex::UnlockKey

我这里截取需要内容:

0x000000000042bc44 : mov 0xffffffffffffffe8(%rbp),%rdx

0x000000000042bc48: mov 0xffffffffffffffc4(%rbp),%eax

0x000000000042bc4b: shl $0x4,%rax

0x000000000042bc4f : mov %rax,%rsi

0x000000000042bc52 : add 0x70(%rdx),%rsi

0x000000000042bc56 : mov 0xffffffffffffffe0(%rbp),%rdi

0x000000000042bc5a: mov $0x10,%edx

0x000000000042bc5f: callq 0x410c38

 

这个0x410c38就是memcmp的函数地址,可以通过objdump查到。

那么接着看下寄存器的内容:

(gdb) p /x $rdx

$1 = 0x2

(gdb) p /x $rsi

$2 = 0x0

(gdb) p /x $rdi

$3 = 0x41e001f8

 

可以清楚的看到第2个参数的内容是0x0. 根据memcmp的原型,第二个参数传入的就是null指针了。

 

这个就基本定位问题了。 是因为memcmp第二个参数填写0导致的core。

 

在我们不了解一个模块具体结构的情况下,core信息基本足够我们分析到什么原因,什么地方导致的core。

我们要充分利用core信息。

逻辑上的原因

这里简单分析下逻辑上的原因:

 

core在了这个地方:

if( 0 == memcmp(&key, &this->_pSlots[i], sizeof(key)))

 

根据我们之前的分析,第2个参数为null导致的core。

整体的流程是这样的:main退出,导致Someauthority析构,导致KeyMutex析构,导致_pSlots 被写0.导致另一个线程在读的时候core了。

 

对于为何main退出了线程还没有结束,先简单说下,程序在运行时的入口函数一般不是main。举个例子:

我写了一个很简单的d.cpp(程序我在后面描述),我们看下d编译后的一些信息:

readelf -h d

ELF Header:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

Entry point address: 0x4003b0

 

这个Entry point时实际的入口地址。 这个地址的函数时:

00000000004003b0 <_start>:

 

因此main函数退出的时候,thread并没有被清理掉。

这方面的更详细的分析我后面再给出。

 

线程的终结

继续分析线程的终结的问题。 在程序正常退出的情况下,线程是如何退出的。

为了证实线程和main的关系问题,我写了个小程序:就是上面说到的d.cpp:

#include

#include "stdio.h"

#include "stdlib.h"

#include "unistd.h"

#include "pthread.h"

 

class MyC{

public:

MyC();

~MyC();

};

 

MyC::MyC(void){

return;

}

 

MyC::~MyC(void){

while(1) sleep(1);

return;

}

 

void *server_thread(void* nu){

while(1){

sleep(1);

fprintf(stderr,".");

}

}

 

int main(){

MyC myc;

pthread_t ServerThread;

int pid = pthread_create(&ServerThread, NULL, server_thread, NULL);

return 0;

}

 

程序是为了证实,main函数结束后,线程仍然会在运行。我们在析构函数中无限sleep,让程序在退出main后暂停。

如果线程继续运行,会不断有屏幕打印。 编译后运行,证实了这个说法。

./d

...........

 

我们后面的分析也基于这个函数,因为其足够简单。

仍然从头开始:

readelf -l d

 

Elf file type is EXEC (Executable file)

Entry point 0x4005b0

 

我们得到了程序的入口地址,这个地址是不会变的。然后我们gdb一下d,run起来后停下来。

(gdb) disassemble 0x4005b0

Dump of assembler code for function _start:

0x00000000004005b0 <_start+0>: xor %rbp,%rbp

0x00000000004005b3 <_start+3>: mov %rdx,%r9

0x00000000004005b6 <_start+6>: pop %rsi

0x00000000004005b7 <_start+7>: mov %rsp,%rdx

0x00000000004005ba <_start+10>: and $0xfffffffffffffff0,%rsp

0x00000000004005be <_start+14>: push %rax

0x00000000004005bf <_start+15>: push %rsp

0x00000000004005c0 <_start+16>: mov $0x400780,%r8

0x00000000004005c7 <_start+23>: mov $0x400720,%rcx

0x00000000004005ce <_start+30>: mov $0x4006da,%rdi

0x00000000004005d5 <_start+37>: callq 0x400560

0x00000000004005da <_start+42>: hlt

0x00000000004005db <_start+43>: nop

End of assembler dump.

 

可以看到,程序的入口是_start,这个是和编译器相关的。

在进行了一坨寄存器处理后,调用了一个函数。 继续看一下0x400560是啥函数:

 

(gdb) x/i 0x400560

0x400560: jmpq *1050242(%rip) # 0x500be8 <_GLOBAL_OFFSET_TABLE_+24>

 

函数的第一句话就是跳转到一个地址。 后面的注释也说明了是got表中的一项。got表中存放的一般都是地址信息。

那么这个地方存放的应该是需要跳转的下一个函数的地址。继续看一下地址0x500be8放的是什么内容:

 

(gdb) x/2x 0x500be8

0x500be8 <_GLOBAL_OFFSET_TABLE_+24>: 0x8a21c3e0 0x0000003b

 

地址一下子变的很大,这里使用的是64位系统。这个高的地址很可能是系统函数。看下这个地址是啥函数:

 

(gdb) x/i 0x0000003b8a21c3e0

0x3b8a21c3e0 <__libc_start_main>: push %r12

 

这个函数就是glibc中的著名的__libc_start_main, 这是一切程序的第一个main。

 

我们看下这个函数的内容:

(gdb) disassemble 0x0000003b8a21c3e0

我只截取关注的内容:

0x0000003b8a21c4aa <__libc_start_main+202>: mov 0x14(%rsp),%edi

0x0000003b8a21c4ae <__libc_start_main+206>: mov 0x8(%rsp),%rsi

0x0000003b8a21c4b3 <__libc_start_main+211>: mov 0x0(%rbp),%rdx

0x0000003b8a21c4b7 <__libc_start_main+215>: callq *0x18(%rsp)

0x0000003b8a21c4bb <__libc_start_main+219>: mov %eax,%edi

0x0000003b8a21c4bd <__libc_start_main+221>: callq 0x3b8a230c70

 

3个连续的mov显然是在准备参数,callq *0x18(%rsp) 这个地方才是真正的调用的main函数。

可以看到,在main函数返回后还远没有结束,

首先继续调用了exit来结束程序。 看下exit的内容:

(gdb) disassemble 0x3b8a230c70

Dump of assembler code for function exit:

 

0x0000003b8a230d26 : callq 0x3b8a28f070 <_exit>

 

在函数_exit中,看到了syscall,在这里交给了内核,内核负责完成进程结束的处理。所有的线程就在这里结束了。

 

后记

对于C和C++程序,在main结束后并不意味着程序运行的结束。

当然在main被运行前也并不意味着程序没有开始运行。

C++程序尤其要注意到这一点。

因此当你优雅的通过析构清理了所有内容的时候,很可能还有莫名的线程在等待着走向那非法的地址空间。

 

(全文完)

 









本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/743808 ,如需转载请自行联系原作者
相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
传感器 算法 Linux
查看 PCD 点云 windows
在Linux系统查看PCD 点云有许多方法,但发现在windows下的工具比较少,这里分享两个思路,一个是使用MATLAB工具编程,另一个是下载CloudCompare软件进行查看点云。
14133 0
查看 PCD 点云 windows
|
Java API Python
Burpsuite系列 -- SQLiPy插件使用
Burpsuite系列 -- SQLiPy插件使用
1034 0
Burpsuite系列 -- SQLiPy插件使用
|
监控 Serverless API
ModelScope一键部署模型:新手村实操FAQ篇
魔搭社区支持开源模型一键部署至阿里云函数计算,本文以小白视角进行操作实操与FAQ讲解。
11367 2
|
9天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
10天前
|
JavaScript 定位技术 API
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
CodeGraph 是一款爆火的本地代码智能工具,通过 tree-sitter 解析 AST 构建结构化知识图谱(存于 SQLite),为编程 Agent 提前生成“代码地图”。它显著降低 Agent 在中大型项目中的探索成本——实测工具调用减少71%、Token 降57%、速度提升46%,支持19+语言及主流框架路由识别,完全离线、无需 API Key。
770 11
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
|
10天前
|
人工智能 运维 JavaScript
阿里云Qoder CN(原通义灵码)全解析 产品形态、版本划分与技术适配说明
在AI辅助开发与智能办公工具持续普及的当下,阿里云旗下原通义灵码正式更名为Qoder CN,同时延伸出QoderWork CN、Qoder CN CLI、Qoder CN Mobile等多款配套产品,形成覆盖代码开发、日常办公、终端交互、移动端使用的完整工具矩阵。Qoder CN核心定位为AI智能编码助手,深度适配主流代码编辑器、集成开发环境以及终端场景;QoderWork CN则偏向桌面端综合办公辅助,二者面向不同使用场景,划分了多个版本档位,搭配差异化资源配额、功能权限与计费规则,同时兼容多款主流大模型。
799 7
|
10天前
|
存储 安全 Java
AgentScope Java 2.0:打造分布式、企业级智能体底座
AgentScope 2.0 面向分布式部署、稳定运行、权限安全等企业级需求全面升级,打造支持多租户隔离与长期稳定运行的企业级智能体底座。
|
10天前
|
JSON 缓存 安全
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型
CC Switch 通过本地路由(`127.0.0.1:15721`)实现协议转换:将 Codex 的 Responses API 请求自动映射为 DeepSeek 等厂商的 Chat Completions 接口,兼容流式响应与工具调用,无需修改 Codex 源码,安全隔离 API Key。(239字)
2106 4
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型

热门文章

最新文章