段错误 在 Linux 上如何得到一个段错误的核心转储

球探体育

中文:朱莉娅·埃文斯,由Linux中国/斯蒂芬翻译

linux.cn/article-9834-1.html

在这一周的工作中,我花了整整一周的时间试图调试一个节错误。我以前从来没有这样做过,花了很长时间才搞清楚其中涉及到的一些基本的东西(获取核心转储,找到导致段错误的行号)。所以我有这个博客来解释如何做那些事情!

看完这个博客,你应该知道如何从“哦,我的程序有一个段落错误,但我不知道发生了什么”到“我知道它有段落错误时的堆栈和行号!”。

什么是错误?

“分段错误”是指程序试图访问不允许访问的内存地址的情况。这可能是由于:

试图解引用空指针(你不被允许访问内存地址 0);试图解引用其他一些不在你内存(LCTT 译注:指不在合法的内存地址区间内)中的指针;一个已被破坏并且指向错误的地方的 C++ 虚表指针C++ vtable pointer,这导致程序尝试执行没有执行权限的内存中的指令;其他一些我不明白的事情,比如我认为访问未对齐的内存地址也可能会导致段错误(LCTT 译注:在要求自然边界对齐的体系结构,如 MIPS、ARM 中更容易因非对齐访问产生段错误)。

这个“C++虚拟表指针”是我的程序出现段错误的情况。这个我可能会在以后的博客里解释,因为我一开始对C++一无所知,也不知道这种虚表查找导致程序段出错的情况。

但是!这个博客不是讲C++的。先说基础的东西,比如我们如何得到一个核心转储?

运行valgrind

我发现找出为什么我的程序有段落错误的最简单的方法是使用valgrind:我运行

valgrind-vyour-program

这给了我一个失败时的堆栈调用序列。简单!

但是我想做更深入的调查,找出一些valgrind没有告诉我的信息!所以想弄个核心转储,探索一下。

如何获得核心转储

核心转储是你的程序内存的一个副本,当你试图调试你有问题的程序时,它非常有用。

当你的程序出现段错误时,Linux内核有时会将一个内核转储到磁盘。当我第一次试图获得核心转储时,我很长一段时间都很沮丧,因为——Linux没有生成核心转储!我的核心垃圾场在哪?

这就是我最终要做的:

在启动我的程序之前运行 ulimit -c unlimited运行 sudo sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t

Ulimit:设置核心转储的最大大小

Ulimit -c设置核心转储的最大大小。它经常被设置为0,这意味着内核根本不写内核转储。它以千字节为单位。Ulimit是为每个进程设置的——通过运行cat /proc/PID/limit可以看到一个进程的各种资源限制。

例如,这些是我的系统上任何Firefox进程的资源限制:

内核在决定写多大的核心转储文件时使用软限制软限制(在这种情况下,最大核心文件大小= 0)。您可以使用shell内置命令ulimit(ulimit -c unlimited)将软限制增加到硬限制。

内核. core_pattern:内核转储保存在哪里

Kernel.core_pattern是一个内核参数,或“sysctl设置”,它控制Linux内核将内核转储文件写入磁盘的位置。

内核参数是设置系统全局设置的一种方式。您可以通过运行sysctl -a获得包含每个内核参数的列表,或者使用sysctl kernel.core_pattern检查kernel.core_pattern设置。

因此,sysctl-w kernel . core _ pattern =/tmp/core-% e . % p . % h . % t将核心转储保存到目录/tmp,并以由core和一系列可以识别(失败)进程的参数组成的后缀作为文件名。

如果你想知道%e和%p这样的参数是什么意思,请参考man core。

很重要的一点是,kernel.core_pattern是一个全局设置——修改时最好小心,因为其他系统功能可能依赖于将其设置为特定模式。

Kernel.core_pattern和Ubuntu

在ubuntu系统中,kernel.core_pattern默认设置为以下值:

$sysctl kernel.core_pattern

kernel . core _ pattern = |/usr/share/apport/apport % P % s % c % d % P

这让我很困惑(这个apport做了什么,它对我的核心转储做了什么?)。以下是我对此的了解:

Ubuntu 使用一种叫做 apport 的系统来报告 apt 包有关的崩溃信息。 设定 kernel.core_pattern=|/usr/share/apport/apport %p %s %c %d %P 意味着核心转储将被通过管道送给 apport 程序。 apport 的日志保存在文件 /var/log/apport.log 中。 apport 默认会忽略来自不属于 Ubuntu 软件包一部分的二进制文件的崩溃信息

我刚刚跳过apport,把kernel.core_pattern重置为sysctl-w内核。core _ pattern =/tmp/core-% e . % p . % h . % t,因为我在开发机器上,所以不关心apport是否工作,也不想尝试让apport把我的core转储到磁盘上。

现在你有一个核心转储,接下来呢?

好了,现在我们知道了ulimit和kernel.core_pattern,我们实际上在磁盘的/tmp目录中有一个核心转储文件。太棒了!接下来呢?我们还是不知道这个程序为什么会有错误!

下一步是使用gdb打开核心转储文件并获取堆栈调用序列。

从gdb获取堆栈调用序列

您可以用gdb打开一个核心转储文件,如下所示:

$gdb-cmy_core_file

接下来,我们想知道程序崩溃时堆栈是什么样子的。在gdb提示符下运行bt会给你一个调用序列回溯。在我的例子中,gdb不加载二进制文件的符号信息,所以这些函数名像“??????"。幸运的是,它是通过加载符号来修复的。

下面是如何加载调试符号。

符号文件/路径/to/my/二进制

共享图书馆

这将从二进制文件及其引用的任何共享库中加载符号。有一次我这么做了,执行bt的时候,gdb给了我一个漂亮的带行号的栈迹!

如果您想让它工作,二进制文件应该用调试符号信息进行编译。当试图找出程序崩溃的原因时,堆栈跟踪中的行号非常有帮助。:)

查看每个线程的堆栈

用下面的方法获取gdb中每个线程的调用栈!

线程应用所有bt全

gdb + 核心转储 = 惊喜

如果你有一个包含调试符号和gdb的核心转储,那太好了!您可以上下查看调用堆栈(LCTT:跳转到具有不同调用序列的函数来查看局部变量),打印变量,并检查内存以了解发生了什么。这是最好的。

如果你还在基于gdb向导工作,只需打印出堆栈跟踪和bt。:)

牙山市

另一种找出你的段错误的方法是使用address消毒剂选项编译程序(“ASAN”,也就是$ $ CC-fsanizize = address),然后运行它。我不打算在本文中讨论这个问题,因为这篇文章很长,在我的例子中,打开ASAN后面部分的错误消失了,可能是因为ASAN使用了不同的内存分配器(系统内存分配器而不是tcmalloc)。

如果我将来能让ASAN工作,我可能会写更多关于它的东西。(LCTT翻译:在这里,ASAN也可以重现段落错误)

从核心转储获得堆栈跟踪真的很好!

这个博客听起来很多,我这样做的时候很困惑,但是说实话,从一个错误的程序得到一个堆栈调用序列并不需要那么多步骤:

试试用 valgrind

如果这不起作用,或者如果您想获得一个核心转储进行调查:

确保二进制文件编译时带有调试符号信息; 正确的设置 ulimit 和 kernel.core_pattern; 运行程序; 一旦你用 gdb 调试核心转储了,加载符号并运行 bt; 尝试找出发生了什么!

我可以用gdb发现C++中有一个虚拟表条目指向一些损坏的内存,这很有帮助,让我感觉自己更懂C++。也许有一天我们会更多的讨论如何用gdb发现问题!

[关于提交]

如果有原创的好文章投稿,请直接发消息到官方号。

标签: 段错误