当程序具有命令行参数时,如何使用 GDB 分析程序的核心转储文件?

2024-10-09 09:11:00
admin
原创
80
摘要:问题描述:我的程序运行如下:exe -p param1 -i param2 -o param3 它崩溃并生成了核心转储文件core.pid。我想通过以下方式分析核心转储文件gdb ./exe -p param1 -i param2 -o param3 core.pid 但是GDB将EXE文件的参数识别为GDB...

问题描述:

我的程序运行如下:

exe -p param1 -i param2 -o param3

它崩溃并生成了核心转储文件core.pid

我想通过以下方式分析核心转储文件

gdb ./exe -p param1 -i param2 -o param3 core.pid

但是GDB将EXE文件的参数识别为GDB的输入。

在这种情况下,我该如何分析核心转储文件?


解决方案 1:

您可以通过多种方式将核心文件与 GDB 结合使用,但将要传递给可执行文件的参数传递给 GDB 并不是使用核心文件的方式。这也可能是出现该错误的原因。您可以通过以下方式使用核心文件:

gdb <executable> <core-file>或者gdb <executable> -c <core-file>或者

gdb <executable>
...
(gdb) core <core-file>

使用核心文件时,您不必传递参数。崩溃场景显示在 GDB 中(在 Ubuntu 上使用 GDB 版本 7.1 检查)。

例如:

$ ./crash -p param1 -o param2
Segmentation fault (core dumped)
$ gdb ./crash core
GNU gdb (GDB) 7.1-ubuntu
...
Core was generated by `./crash -p param1 -o param2'. <<<<< See this line shows crash scenario
Program terminated with signal 11, Segmentation fault.
#0  __strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:99
99    ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.
    in ../sysdeps/i386/i686/multiarch/../../i586/strlen.S
(gdb)

如果要将参数传递给要在 GDB 中调试的可执行文件,请使用--args

例如:

$ gdb --args ./crash -p param1 -o param2
GNU gdb (GDB) 7.1-ubuntu
...
(gdb) r
Starting program: /home/@@@@/crash -p param1 -o param2

Program received signal SIGSEGV, Segmentation fault.
__strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:99
99    ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.
    in ../sysdeps/i386/i686/multiarch/../../i586/strlen.S
(gdb)

手册页将有助于查看其他 GDB 选项。

最有用的命令是:

  • bt(回溯)

  • info locals(显示局部变量的值)

  • info registers(显示 CPU 寄存器的值)

  • frame X(更改为堆栈框架X

  • updown(在堆栈框架(调用链)中导航)

解决方案 2:

GDB的简单使用,调试coredump文件:

gdb <executable_path> <coredump_file_path>

“进程”的核心转储文件被创建为“core.pid”文件。

进入 GDB 提示符后(执行上述命令),输入:

...
(gdb) where

这将为您提供堆栈信息,您可以据此分析崩溃/故障的原因。
其他命令,出于相同目的,是:

...
(gdb) bt full

这与上面的相同。按照惯例,它列出了整个堆栈信息(最终指向崩溃位置)。

解决方案 3:

GDB 最小可运行示例

简单.c

int myfunc(int i) {
    *(int*)(0) = i;
    return i - 1;
}

int main(int argc, char **argv) {
    (void)argv;
    int i = argc * 2;
    int ret = myfunc(i);
    return ret;
}

编译:

gcc -ggdb3 -std=c99 -Wall -Wextra -pedantic -o simple.out simple.c

要生成核心文件,我们首先必须在当前终端中运行:

ulimit -c unlimited

意思是“无大小限制地转储核心文件”。这是因为核心文件包含崩溃进程的整个内存,因此它们可能非常大。

经 Ubuntu 16.04 测试,您必须删除一个预先存在的核心文件(TODO 是强制性的吗?我忘了):

rm -f core

从 Ubuntu 22.04 开始测试,你需要对抗 apport 来获取你的核心文件:https://askubuntu.com/questions/1349047/where-do-i-find-core-dump-files-and-how-do-i-view-and-analyze-the-backtrace-st/1442665#1442665例如:

echo 'core' | sudo tee /proc/sys/kernel/core_pattern

然后我们运行程序:

./simple.out

并且终端包含:

Segmentation fault (core dumped)

核心文件已生成。在 Ubuntu 16.04 上,该文件的名称为:

core

在 Ubuntu 22.04 上echo 'core' | sudo tee /proc/sys/kernel/core_pattern该文件命名为:

core.<pid>

其中 PID 是进程 ID,一个数字,例如:

core.162152

我认为这是因为 Linux 内核更新开始添加.pid后缀。请确认。

我们现在可以将核心文件用作

gdb simple.out core
gdb simple.out core.162152

现在我们进入 GDB 会话,它与程序崩溃时的情况完全一样,当然,我们不能“继续运行”,因为程序即将结束:

#0  0x0000557097e0813c in myfunc (i=2) at simple.c:2
2           *(int*)(0) = i; /* line 7 */
(gdb) bt
#0  0x0000557097e0813c in myfunc (i=2) at simple.c:2
#1  0x0000557097e0816b in main (argc=1, argv=0x7ffcffc4ba18) at simple.c:9
(gdb) up
#1  0x0000557097e0816b in main (argc=1, argv=0x7ffcffc4ba18) at simple.c:9
9           int ret = myfunc(i);
(gdb) p argc
$1 = 1

因此运行之后bt,我们立即知道代码崩溃时的位置,这有时足以解决错误。

从示例中可以看出,您现在可以检查崩溃时的程序内存以尝试确定故障原因,进程虚拟内存完全包含在核心文件中。

在 Ubuntu 16.04 和 22.04 amd64 中测试。

你也可以直接通过 GDB 运行程序

如果问题很容易重现(即快速且确定性地崩溃),并且您可以轻松控制命令行(即不是由您不想要/不能修改的另一个程序调用的程序)那么最好的方法就是通过 GDB 运行该程序:

gdb -ex run simple.out

当接收到信号时,GDB 默认会在信号原因处中断,我们将会处于与使用核心文件时完全相同的情况。

直接 Binutils 分析

让我们尝试不使用 GDB 来观察核心文件的内容,以便更好地理解它。因为我们可以。

让我们创建一个程序,打印它自己的一些内存地址,以便我们可以关联事物:

主程序

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int myfunc(int i) {
    *(int*)(NULL) = i; /* line 7 */
    return i - 1;
}

int main(int argc, char **argv) {
    /* Setup some memory. */
    char data_ptr[] = "string in data segment";
    char *mmap_ptr;
    char *text_ptr = "string in text segment";
    (void)argv;
    mmap_ptr = (char *)malloc(sizeof(data_ptr) + 1);
    strcpy(mmap_ptr, data_ptr);
    mmap_ptr[10] = 'm';
    mmap_ptr[11] = 'm';
    mmap_ptr[12] = 'a';
    mmap_ptr[13] = 'p';
    printf("text addr: %p
", text_ptr);
    printf("data addr: %p
", data_ptr);
    printf("mmap addr: %p
", mmap_ptr);

    /* Call a function to prepare a stack trace. */
    return myfunc(argc);
}

程序输出:

text addr: 0x4007d4
data addr: 0x7ffec6739220
mmap addr: 0x1612010
Segmentation fault (core dumped)

第一的:

file core

告诉我们该core文件实际上是一个 ELF 文件:

core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './main.out'

这就是为什么我们能够使用常用的 binutils 工具更直接地检查它。

快速浏览一下ELF 标准,可以发现实际上有一个专用于它的 ELF 类型:

Elf32_Ehd.e_type == ET_CORE

更多格式信息可在此处找到:

man 5 core

然后:

readelf -Wa core

给出了一些有关文件结构的提示。内存似乎包含在常规程序头中:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  NOTE           0x000468 0x0000000000000000 0x0000000000000000 0x000b9c 0x000000     0
  LOAD           0x002000 0x0000000000400000 0x0000000000000000 0x001000 0x001000 R E 0x1000
  LOAD           0x003000 0x0000000000600000 0x0000000000000000 0x001000 0x001000 R   0x1000
  LOAD           0x004000 0x0000000000601000 0x0000000000000000 0x001000 0x001000 RW  0x1000

注释区域中还存在一些元数据,特别prstatus是包含 PC:

Displaying notes found at file offset 0x00000468 with length 0x00000b9c:
  Owner                 Data size       Description
  CORE                 0x00000150       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000130       NT_AUXV (auxiliary vector)
  CORE                 0x00000246       NT_FILE (mapped files)
    Page size: 4096
                 Start                 End         Page Offset
    0x0000000000400000  0x0000000000401000  0x0000000000000000
        /home/ciro/test/main.out
    0x0000000000600000  0x0000000000601000  0x0000000000000000
        /home/ciro/test/main.out
    0x0000000000601000  0x0000000000602000  0x0000000000000001
        /home/ciro/test/main.out
    0x00007f8d939ee000  0x00007f8d93bae000  0x0000000000000000
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93bae000  0x00007f8d93dae000  0x00000000000001c0
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93dae000  0x00007f8d93db2000  0x00000000000001c0
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93db2000  0x00007f8d93db4000  0x00000000000001c4
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93db8000  0x00007f8d93dde000  0x0000000000000000
        /lib/x86_64-linux-gnu/ld-2.23.so
    0x00007f8d93fdd000  0x00007f8d93fde000  0x0000000000000025
        /lib/x86_64-linux-gnu/ld-2.23.so
    0x00007f8d93fde000  0x00007f8d93fdf000  0x0000000000000026
        /lib/x86_64-linux-gnu/ld-2.23.so
  CORE                 0x00000200       NT_FPREGSET (floating point registers)
  LINUX                0x00000340       NT_X86_XSTATE (x86 XSAVE extended state)

objdump可以轻松转储所有内存:

objdump -s core

其中包含:

Contents of section load1:

 4007d0 01000200 73747269 6e672069 6e207465  ....string in te
 4007e0 78742073 65676d65 6e740074 65787420  xt segment.text 

Contents of section load15:

 7ffec6739220 73747269 6e672069 6e206461 74612073  string in data s
 7ffec6739230 65676d65 6e740000 00a8677b 9c6778cd  egment....g{.gx.

Contents of section load4:

 1612010 73747269 6e672069 6e206d6d 61702073  string in mmap s
 1612020 65676d65 6e740000 11040000 00000000  egment..........

这与我们运行中的 stdout 值完全匹配。

在 Ubuntu 16.04 amd64、GCC 6.4.0、binutils 2.26.1 中测试。

Mozillarr逆向调试之终极“核心文件”

核心文件允许您检查中断时的堆栈。

但一般来说,您真正需要做的是回顾过去,进一步确定故障的根本原因。

令人惊奇的 Mozilla rr 允许您这样做,但代价是更大的跟踪文件和轻微的性能损失。

例如:反向调试如何工作?

参见

解决方案 4:

跳过参数。GDB 不需要它们:

gdb ./exe core.pid

解决方案 5:

来自RMS 的 GDB 调试器教程:

prompt > myprogram
Segmentation fault (core dumped)
prompt > gdb myprogram
...
(gdb) core core.pid
...

确保您的文件确实是core图像--使用 检查它file

解决方案 6:

稍微不同的方法将允许您完全跳过 GDB。如果您想要的只是回溯,Linux 特定的实用程序“catchsegv”将捕获 SIGSEGV 并显示回溯。

解决方案 7:

可执行文件是否带有参数并不重要。要对任何带有生成的核心文件的二进制文件运行 GDB,语法如下。

Syntax:
gdb <binary name> <generated core file>
Eg:
gdb l3_entity 6290-corefile

让我举下面的例子来帮助您更好地理解。

bash-4.1$ **gdb l3_entity 6290-corefile**

**Core was generated** by `/dir1/dir2/dir3/l3_entity **Program terminated with signal SIGABRT, Aborted.**
#0
#1
#2
#3
#4
#5
#6
#7
#8
#9
#10
(gdb)

从上面的输出,你可以猜测一些有关核心的事情,无论是NULL访问,SIGABORT等。

这些数字 #0 到 #10 是 GDB 的堆栈框架。这些堆栈框架不是你的二进制文件。在上述 0 - 10 帧中,如果你怀疑有任何错误,请选择该帧

(gdb) frame 8

现在查看有关它的更多详细信息:

(gdb) list +

为了进一步调查该问题,您可以在此处打印此时可疑的变量值。

(gdb) print thread_name

解决方案 8:

我只是使用coredumpctl debug(在 Fedora 32 上),它给我一个 GDB 控制台来调试我最近的核心转储。

解决方案 9:

只需输入命令:

$ gdb <Binary> <codeDump>

或者

$ gdb <binary>

$ gdb) core <coreDump>

无需提供任何命令行参数。代码转储是早期练习生成的。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用