美文网首页Linux我用 LinuxLinux学习之路
扣丁学堂Linux开发解析如何得到一个段错误的核心转储

扣丁学堂Linux开发解析如何得到一个段错误的核心转储

作者: 994d14631d16 | 来源:发表于2018-07-19 11:16 被阅读0次

      在现如今,大多数程序开发都会涉及到程序调试一个段错误,比如在Linux开发培训中,如何得到一个段错误的核心转储呢?下面我们一起来看一下吧。

    ​  什么是段错误?

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

      试图解引用空指针(你不被允许访问内存地址0);

      试图解引用其他一些不在你内存(LCTT译注:指不在合法的内存地址区间内)中的指针;

      一个已被破坏并且指向错误的地方的C++虚表指针C++vtablepointer,这导致程序尝试执行没有执行权限的内存中的指令;

      其他一些我不明白的事情,比如我认为访问未对齐的内存地址也可能会导致段错误(LCTT译注:在要求自然边界对齐的体系结构,如MIPS、ARM中更容易因非对齐访问产生段错误)。

      这个“C++虚表指针”是我的程序发生段错误的情况。我可能会在未来的博客中解释这个,因为我最初并不知道任何关于C++的知识,并且这种虚表查找导致程序段错误的情况也是我所不了解的。

      但是!这篇博客后不是关于C++问题的。让我们谈论的基本的东西,比如,我们如何得到一个核心转储?

      步骤1:运行valgrind

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

      valgrind-vyour-program

      这给了我一个故障时的堆栈调用序列。简洁!

      但我想也希望做一个更深入调查,并找出些valgrind没告诉我的信息!所以我想获得一个核心转储并探索它。

      如何获得一个核心转储

      核心转储coredump是您的程序内存的一个副本,并且当您试图调试您的有问题的程序哪里出错的时候它非常有用。

      当您的程序出现段错误,Linux的内核有时会把一个核心转储写到磁盘。当我最初试图获得一个核心转储时,我很长一段时间非常沮丧,因为-Linux没有生成核心转储!我的核心转储在哪里?

      这就是我最终做的事情:

      在启动我的程序之前运行ulimit-cunlimited

      运行sudosysctl-wkernel.core_pattern=/tmp/core-%e.%p.%h.%t

      ulimit:设置核心转储的最大尺寸

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

      例如这些是我的系统上一个随便一个Firefox进程的资源限制:

      $cat/proc/6309/limits

      LimitSoftLimitHardLimitUnits

      Maxcputimeunlimitedunlimitedseconds

      Maxfilesizeunlimitedunlimitedbytes

      Maxdatasizeunlimitedunlimitedbytes

      Maxstacksize8388608unlimitedbytes

      Maxcorefilesize0unlimitedbytes

      Maxresidentsetunlimitedunlimitedbytes

      Maxprocesses3057130571processes

      Maxopenfiles10241048576files

      Maxlockedmemory6553665536bytes

      Maxaddressspaceunlimitedunlimitedbytes

      Maxfilelocksunlimitedunlimitedlocks

      Maxpendingsignals3057130571signals

      Maxmsgqueuesize819200819200bytes

      Maxnicepriority00

      Maxrealtimepriority00

      Maxrealtimetimeoutunlimitedunlimitedus

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

      kernel.core_pattern:核心转储保存在哪里

      kernel.core_pattern是一个内核参数,或者叫“sysctl设置”,它控制Linux内核将核心转储文件写到磁盘的哪里。

      内核参数是一种设定您的系统全局设置的方法。您可以通过运行sysctl-a得到一个包含每个内核参数的列表,或使用sysctlkernel.core_pattern来专门查看kernel.core_pattern设置。

      所以sysctl-wkernel.core_pattern=/tmp/core-%e.%p.%h.%t将核心转储保存到目录/tmp下,并以core加上一系列能够标识(出故障的)进程的参数构成的后缀为文件名。

      如果你想知道这些形如%e、%p的参数都表示什么,请参考mancore。

      有一点很重要,kernel.core_pattern是一个全局设置——修改它的时候最好小心一点,因为有可能其它系统功能依赖于把它被设置为一个特定的方式(才能正常工作)。

      kernel.core_pattern和Ubuntu

      默认情况下在ubuntu系统中,kernel.core_pattern被设置为下面的值:

      $sysctlkernel.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-wkernel.core_pattern=/tmp/core-%e.%p.%h.%t,因为我在一台开发机上,我不在乎apport是否工作,我也不想尝试让apport把我的核心转储留在磁盘上。

      现在你有了核心转储,接下来干什么?

      好的,现在我们了解了ulimit和kernel.core_pattern,并且实际上在磁盘的/tmp目录中有了一个核心转储文件。太好了!接下来干什么?我们仍然不知道该程序为什么会出现段错误!

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

      从gdb中得到堆栈调用序列

      你可以像这样用gdb打开一个核心转储文件:

      $gdb-cmy_core_file

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

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

      symbol-file/path/to/my/binary

      sharedlibrary

      这从二进制文件及其引用的任何共享库中加载符号。一旦我这样做了,当我执行bt时,gdb给了我一个带有行号的漂亮的堆栈跟踪!

      如果你想它能工作,二进制文件应该以带有调试符号信息的方式被编译。在试图找出程序崩溃的原因时,堆栈跟踪中的行号非常有帮助。:)

      查看每个线程的堆栈

      通过以下方式在gdb中获取每个线程的调用栈!

      threadapplyallbtfull

      gdb+核心转储=惊喜

      如果你有一个带调试符号的核心转储以及gdb,那太棒了!您可以上下查看调用堆栈(LCTT译注:指跳进调用序列不同的函数中以便于查看局部变量),打印变量,并查看内存来得知发生了什么。这是最好的。

      如果您仍然正在基于gdb向导来工作上,只打印出栈跟踪与bt也可以。:)

      ASAN

      另一种搞清楚您的段错误的方法是使用AddressSanitizer选项编译程序(“ASAN”,即$CC-fsanitize=address)然后运行它。本文中我不准备讨论那个,因为本文已经相当长了,并且在我的例子中打开ASAN后段错误消失了,可能是因为ASAN使用了一个不同的内存分配器(系统内存分配器,而不是tcmalloc)。

      在未来如果我能让ASAN工作,我可能会多写点有关它的东西。(LCTT译注:这里指使用ASAN也能复现段错误)

      从一个核心转储得到一个堆栈跟踪真的很亲切!

      这个博客听起来很多,当我做这些的时候很困惑,但说真的,从一个段错误的程序中获得一个堆栈调用序列不需要那么多步骤:

      试试用valgrind

      如果那没用,或者你想要拿到一个核心转储来调查:

      确保二进制文件编译时带有调试符号信息;

      正确的设置ulimit和kernel.core_pattern;

      运行程序;

      一旦你用gdb调试核心转储了,加载符号并运行bt;

      尝试找出发生了什么!

      我可以使用gdb弄清楚有个C++的虚表条目指向一些被破坏的内存,这有点帮助,并且使我感觉好像更懂了C++一点。也许有一天我们会更多地讨论如何使用gdb来查找问题!

    相关文章

      网友评论

        本文标题:扣丁学堂Linux开发解析如何得到一个段错误的核心转储

        本文链接:https://www.haomeiwen.com/subject/gaozpftx.html