Linux上coredump调试:call stack栈顶函数地址为0 分析实战

这几天测试中,又收到了coredump的报告,调用栈如下:

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x0000000000432bb4 in ChargingNode::canProcessed (this=0x7f87b40118e0, maxTimestamp=9000000000) at src/sl/ChargingFile.C:406
#2  0x0000000000445de4 in BucketFileAdapter::checkin (this=0x2192b98, startTime=<value optimized out>) at src/sl/BucketFileAdapter.C:118
#3  0x0000000000446114 in BucketFileAdapter::start (this=0x2192b98) at src/sl/BucketFileAdapter.C:87
#4  0x000000000043560e in file_reader_run (arg=0x2192b98) at src/sl/ChargingFileAdapter.C:234
#5  0x0000003657607851 in start_thread () from /lib64/libpthread.so.0
#6  0x0000003656ee890d in clone () from /lib64/libc.so.6

栈顶函数地址为0x0。

这是一个很有意思的现象,以前我没有遇到这种case。

结合代码看看:出错行的C++代码:

if(chargingFile  && (chargingFile ->canDecoded() == false))

用gdb看chargingFile的值,结果显示被优化了(使用了-O2):

(gdb) p chargingFile
$3 = <value optimized out>

那我们分析下frame 1 对应的C++代码,chargingFile即使为NULL也不可能导致core,所以只可能是chargingFIle不空,但调用canDecode()函数时出问题了。

对应反汇编代码:

(gdb) disas
Dump of assembler code for function ChargingNode::canProcessed(long):
   0x0000000000432b20 <+0>:     mov    %rbx,-0x18(%rsp)
   0x0000000000432b25 <+5>:     lea    0x78(%rdi),%rbx
   0x0000000000432b29 <+9>:     mov    %rbp,-0x10(%rsp)
   0x0000000000432b2e <+14>:    mov    %r12,-0x8(%rsp)
   0x0000000000432b33 <+19>:    mov    %rdi,%rbp
   0x0000000000432b36 <+22>:    sub    $0x18,%rsp
   0x0000000000432b3a <+26>:    mov    %rbx,%rdi
   0x0000000000432b3d <+29>:    mov    %rsi,%r12
   0x0000000000432b40 <+32>:    callq  0x407820 <[email protected]>
   0x0000000000432b45 <+37>:    mov    0x18(%rbp),%rcx
   0x0000000000432b49 <+41>:    mov    0x28(%rbp),%rdx
   0x0000000000432b4d <+45>:    mov    0x38(%rbp),%rax
   0x0000000000432b51 <+49>:    sub    0x40(%rbp),%rax
   0x0000000000432b55 <+53>:    sub    %rcx,%rdx
   0x0000000000432b58 <+56>:    sar    $0x3,%rdx
   0x0000000000432b5c <+60>:    sar    $0x3,%rax
   0x0000000000432b60 <+64>:    add    %rax,%rdx
   0x0000000000432b63 <+67>:    mov    0x50(%rbp),%rax
   0x0000000000432b67 <+71>:    sub    0x30(%rbp),%rax
   0x0000000000432b6b <+75>:    sar    $0x3,%rax
   0x0000000000432b6f <+79>:    shl    $0x6,%rax
   0x0000000000432b73 <+83>:    lea    -0x40(%rax,%rdx,1),%rax
   0x0000000000432b78 <+88>:    test   %rax,%rax
   0x0000000000432b7b <+91>:    je     0x432b83 <ChargingNode::canProcessed(long)+99>
   0x0000000000432b7d <+93>:    cmpb   $0x0,0x70(%rbp)
   0x0000000000432b81 <+97>:    je     0x432ba0 <ChargingNode::canProcessed(long)+128>
   0x0000000000432b83 <+99>:    mov    %rbx,%rdi
   0x0000000000432b86 <+102>:   callq  0x407250 <[email protected]>
   0x0000000000432b8b <+107>:   xor    %eax,%eax
   0x0000000000432b8d <+109>:   mov    (%rsp),%rbx
   0x0000000000432b91 <+113>:   mov    0x8(%rsp),%rbp
   0x0000000000432b96 <+118>:   mov    0x10(%rsp),%r12
   0x0000000000432b9b <+123>:   add    $0x18,%rsp
   0x0000000000432b9f <+127>:   retq
   0x0000000000432ba0 <+128>:   cmp    %r12,0x68(%rbp)
   0x0000000000432ba4 <+132>:   jg     0x432bd0 <ChargingNode::canProcessed(long)+176>
   0x0000000000432ba6 <+134>:   mov    (%rcx),%rdi
   0x0000000000432ba9 <+137>:   test   %rdi,%rdi
   0x0000000000432bac <+140>:   je     0x432bb8 <ChargingNode::canProcessed(long)+152>
   0x0000000000432bae <+142>:   mov    (%rdi),%rax
   0x0000000000432bb1 <+145>:   callq  *0x20(%rax)
---Type <return> to continue, or q <return> to quit---
=> 0x0000000000432bb4 <+148>:   test   %al,%al

......

看到红色的汇编代码了吗?callq是X86上的函数调用指令,后面跟的是一个间接地址,不是一个直接的函数地址。

这种形式的汇编代码,我见过的只有两种情况:1 是内核启动的时候为了防止编译器优化而这样做,2虚函数调用。

这么说,canDecode函数是虚函数?查了下源码,果然是:virtual bool canDecoded();

那这么说,%eax里放的就是class的vptr。

证明一下:

(gdb) i r
rax            0x7f87b40c4670   140220818015856
rbx            0x7f87b4011958   140220817283416
rcx            0x7f87b4054478   140220817556600
rdx            0x4c     76
rsi            0x0      0
rdi            0x7f87b40cd480   140220818052224

根据X86_64上的函数调用传参数的习惯,rdi里存的就是chargingFile的值。

(gdb) p *(ChargingFile*)0x7f87b40cd480
$7 = {_vptr.ChargingFile = 0x7f87b40c4670, nodeName = "", hostName = "bucket", fileName =
    "/incoming4cdrsch/reported/acr/bucket/bucket12/MAS2_-_0000001709.20130704_-_2126+0800.INC", fileType = CDR_FILE_TYPE_ACR, qid = {id =
    -1}, timestamp = 1372944386, bufferedChargingRec = false, chargingNode = 0x7f87b40118e0, static fileStatusDir =
    "/incoming4cdrsch//status", static processedRecNumLimit = 300000, static acrFilesTotalSize = 0, decodeFlag = false, localFileFlag =
    false, fpFile = 0x0, fpStatusFile = 0x0, stopFlag =false, statusFileName = KeyboardInterrupt: Quit
, offset = 4184212, recordNum = 5695, totalRecordNum = 5695, accumNum = 0, static batchSize = 1000}

然后看上面标红色的,发现:vptr的值和rax的值一样。前面的分析是正确的。

那我们根据  0x0000000000432bb1 <+145>:   callq  *0x20(%rax) 来看一下内存 0x20(%rax)里到底是什么内容:

%rax=0x7f87b40c4670

0x20(%rax) = 0x7f87b40c4690

看内存:

(gdb)  x/40x 0x7f87b40c4670
0x7f87b40c4670: 0x4100434e      0x2d5f3253      0x00000211      0x00000000
0x7f87b40c4680: 0xb4024f10      0x00007f87      0xb40cd470      0x00007f87
0x7f87b40c4690: 0x00000000      0x00000000      0x00000000      0x00000000

看上面标蓝色的,哇塞地址为0x0。说明了什么呢童鞋们,对象被破坏了,虚函数表被覆盖了。

结合代码,发现问题是因为多线程情况下,互斥锁使用范围不当,导致对象被过早释放出现问题。

总结:

1. 碰到call stack栈顶函数地址为0,考虑虚函数表被破坏,即对象呗破坏的情况。

2. 熟悉常用的X86_64函数调用习惯,rdi里放的是第一个函数参数。

3. 多线程中的锁使用一定要注意范围,锁的太小可能不够,太大了性能会有问题。

时间: 2024-10-19 18:37:33

Linux上coredump调试:call stack栈顶函数地址为0 分析实战的相关文章

Linux上从Java程序中调用C函数

原则上来说,"100%纯Java"的解决方法是最好的,但有些情况下必须使用本地方法.特别是在以下三种情况: 需要访问Java平台无法访问的系统特性和设备: 通过基准测试,发现Java代码比其他语言编写的等价代码慢得多: 其他语言编写的代码已经经过大量测试和调试,并且知道如何将其导出到所有的目标平台上. Java平台有一个用于和本地C.C++代码进行互操作的API,称为Java本地接口(JNI).下面将举例讨论Linux平台下的JNI编程. 1. 创建java类文件 创建一个native

Linux上安装Docker,并成功部署NET Core 2.0

概述 容器,顾名思义是用来存放并容纳东西的器皿: 而容器技术伴着Docker的兴起也渐渐的映入大家的眼帘,它是一个抽象的概念,同时也是默默存在世上多年的技术,不仅能使应用程序间完全的隔离,而且还能在共享底层系统资源的同时发挥它最大的优势.相比于虚拟机来说,同一服务器它可以创建出两倍的实例,这样一来,不仅节省了系统开销,而且利用率和性能也得到了提升,何乐而不为.最重要的一点是还帮助开发人员实现了"一次构建,到处运行"的理想! 那么Docker又是什么呢? Docker是基于Go语言开发并

pycharm远程linux开发和调试代码

pycharm是一个非常强大的python开发工具,现在很多代码最终在线上跑的环境都是linux,而开发环境可能还是windows下开发,这就需要经常在linux上进行调试,或者在linux对代码进行编写,而pycharm提供了非常便捷的方式.具体实现在windows上远程linux开发和调试的代码步骤如下: 配置远程linux主机信息 选择Tools--Deployment--Configuration 这里选择SFTP就可以 下面这个是因为第一次连接,所以会有这个提示 这里默认根路径就可以

C#在Linux上的开发指南

本人才疏学浅,在此记录自己用C#在Linux上开发的一点经验,写下这篇指南.(给想要在Linux上开发C#程序的朋友提供建议) 目前在Linux上跑的网站:http://douxiubar.com | http://douxiubar.com/AdminLogin/Index(MVC4+Dapper+Autofac)的一个作品 在Linux上开发建议上http://www.linuxdot.net/和http://jexus.org/讨论学习,我刚接触那会才知道自己才疏学浅,受益良多(入门题;I

linux下coredump学习

参照 https://www.cnblogs.com/alantu2018/p/8468879.html 1.查看linux下coredump是否开启 在linux上coredump默认是关闭的,可以通过ulimit -c查看,如果输出为0,则代表coredump没有开启. 可以使用 ulimit -c unlimited开启,再用ulimit -c查看,结果为unlimited: 但这种操作只能对当前终端有效,想让coredump持久开启,需修改vim /etc/security/limits

linux下coredump的产生及调试方法

什么是coredump 通常情况下coredmp包括了程序执行时的内存,寄存器状态,堆栈指针,内存管理信息等.能够理解为把程序工作的当前状态存储成一个文件.很多程序和操作系统出错时会自己主动生成一个core文件. 怎样使用coredump coredump能够用在非常多场合,使用Linux,或者solaris的人可能都有过这样的经历,系统在跑一些压力測试或者系统负载一大的话,系统就hang住了或者干脆system panic.这时唯一能帮助你分析和解决这个问题的就是coredump了. 如今非常

0xC0000005;Access Violation(栈区空间很宝贵, linux上栈区空间默认为8M,vc6下默认栈空间大小为1M)

写C/C++程序最怕出现这样的提示了,还好是在调试环境下显示出来的,在非调试状态就直接崩溃退出. 从上述汇编代码发现在取内存地址 eax+38h 的值时出错, 那说明这个地址非法呗, 不能访问, 一般是访问了空指针引起的. 直接调用QList::append()方法也会出错了, 此时汇编也指向的是在读取内存 ebp-8 时出错. 这段代码运行背景是在栈上申请了很多缓冲区,然后缓冲区在不停添加内容, 直到某一阶段程序崩溃. 由于栈区空间很宝贵, linux上栈区空间默认为8M,vc6下默认栈空间大

VS2017 Linux 上.NET Core调试

调试Linux 上.NET Core Visual Studio 2017 通过SSH 调试Linux 上.NET Core 应用程序. 本文环境 开发环境:Win10 x64 Visual Studio 2017 部署环境:Ubuntu 14.04 x64 .NET Core SDK 1.0.1 Ubuntu上安装.NET Core SDK Ubuntu 14.04 x64 sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficman

Windows远程调试Linux上的C++程序:Eclipse+MingW+Samba+GDBserver

转自:http://www.heimizhou.com/windows-remote-debug-linux-c-plus-plus.html 最近有一个需求,就是需要在Windows上远程调试Linux上的C++程序,然后我就从网上搜集各种方法,但是发现很多方法中是先在Windows上编译程序,然后再从Linux上编译程序,最后进行远程调试,这种方法使我的调试不能进入源代码,后来经过尝试发现只需要在Linux上编译程序即可.下面从三个方面:需要安装的软件.安装与配置.配置远程调试,来介绍一下我