打印分段错误原因[重复]

Posted

技术标签:

【中文标题】打印分段错误原因[重复]【英文标题】:print the segmentation fault reason [duplicate] 【发布时间】:2013-04-07 13:32:36 【问题描述】:

假设我有一个导致分段错误的代码。

char * ptr = NULL;
*ptr = "hello"; /* this will cause a segmentation fault */

如何在runtime上打印,发生分段错误的内存地址,以及分段错误的原因(访问禁止的内存区域,或其他)。

我阅读了有关核心转储文件的信息,但我不确定它是否是正确的解决方案。

我该怎么做?

PS,我知道我可以通过使用 gdb 或其他调试器来实现这一点,但目的是通过使用代码来做到这一点,而且只有代码。 p>

【问题讨论】:

您可以使用backtrace 函数。但我真的建议你在调试器中运行你的程序,它不仅可以让你看到回溯,还可以向上调用堆栈并检查变量。 “阅读核心转储文件” - 我强烈推荐它们。它们将所有内容转储到内存中,然后您可以使用gdb 和正确的可执行文件打开它们。这将使您有机会查看到底发生了什么(除非内存没有搞砸,但这是非常罕见的情况) - 查看任何变量的值、回溯、线程等(当然,最好有最大调试级别,并且没有针对此类调查进行优化) hmm.. *ptr 的类型是 char,但 "hello" 的类型是 char*。您可能应该指定一个字符 (*ptr = 'h';) 或使用 memmove() 或类似的字符,以使示例正确。实际上,它获取字符串常量的地址,将其转换为整数,将其缩减为 1 个字节,然后将其分配给*ptr C/C++ 不像 Java,所以你必须使用 gdb 并分析转储的核心。 看在上帝的份上,请不要。您期望什么附加价值?与核心转储和调试器相比,您所做的任何事情都会受到更多限制。此外,尝试“处理”此类错误(如果仅用于诊断输出)是让您的流程陷入更深层次问题的好方法,甚至可能隐藏真正的错误(在生产应用程序中见过几次)。跨度> 【参考方案1】:

如果您想知道原因,您可以注册一个信号处理程序,例如:

void handler(int signum, siginfo_t *info, void *context)

  struct sigaction action = 
    .sa_handler = SIG_DFL,
    .sa_sigaction = NULL,
    .sa_mask = 0,
    .sa_flags = 0,
    .sa_restorer = NULL
  ;

  fprintf(stderr, "Fault address: %p\n", info->si_addr);
  switch (info->si_code) 
  case SEGV_MAPERR:
    fprintf(stderr, "Address not mapped.\n");
    break;

  case SEGV_ACCERR:
    fprintf(stderr, "Access to this address is not allowed.\n");
    break;

  default:
    fprintf(stderr, "Unknown reason.\n");
    break;
  

  /* unregister and let the default action occur */
  sigaction(SIGSEGV, &action, NULL);

然后你需要在某个地方注册它:

  struct sigaction action = 
    .sa_handler = NULL,
    .sa_sigaction = handler,
    .sa_mask = 0,
    .sa_flags = SA_SIGINFO,
    .sa_restorer = NULL
  ;


  if (sigaction(SIGSEGV, &action, NULL) < 0) 
    perror("sigaction");
  

基本上,您注册一个在 SIGSEGV 交付时触发的信号,然后您会获得一些附加信息,以引用手册页:

   The following values can be placed in si_code for a SIGSEGV signal:

       SEGV_MAPERR    address not mapped to object

       SEGV_ACCERR    invalid permissions for mapped object

这些映射到出现 seg 错误的两个基本原因 - 您访问的页面根本没有映射,或者您不被允许对该页面执行您尝试的任何操作。

在信号处理程序触发后,它会自行注销并替换默认操作。这会导致无法再次执行的操作,因此可以被正常路由捕获。这是页面错误的正常行为(发生 seg 错误的前兆),因此请求分页之类的事情可以正常工作。

【讨论】:

【参考方案2】:

这里已经回答了:How to generate a stacktrace when my gcc C++ app crashes

您可以(至少在 GCC 和 Linux/BSD 的情况下)相当容易地做到这一点:

示例代码:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>


void handler(int sig) 
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, 2);
  exit(1);


int main(int argc, char **argv) 
  signal(SIGSEGV, handler);   // install our handler

  char * ptr = NULL;
  *ptr = "hello"; /* this will cause a segmentation fault */

示例输出:

# gcc -g -rdynamic -o test test.c
# ./test
Error: signal 11:
0   test                                0x000000010e99dcfa handler + 42
1   libsystem_c.dylib                   0x00007fff95c1194a _sigtramp + 26
2   ???                                 0x0000000000000000 0x0 + 0
3   libdyld.dylib                       0x00007fff8fa177e1 start + 0
4   ???                                 0x0000000000000001 0x0 + 1

【讨论】:

这将不再产生核心转储,至少不会产生错误的原始原因。很好,你有某种堆栈跟踪,但价值要小得多。没有冒犯,我们只是在回答 OPs 的问题,但警告仍然适用;-)。另外,考虑使用sprintfwrite(2, buf, ...) 的组合来避免fprintf 在信号处理程序中可能很棘手(至少对于异步信号)。另外,我不会打电话给exit(),而只会打电话给_exit()abort()。但是YMMV。 我同意你的观点,Christian,可以说如果你真的不想发送核心转储,你可以为某些构建启用此功能。但就我个人而言,我只会将可执行文件包装在一个脚本中,让 gdb(如果可用)显示来自生成的核心转储的堆栈跟踪......似乎是一个更明智的解决方案。

以上是关于打印分段错误原因[重复]的主要内容,如果未能解决你的问题,请参考以下文章

分配时出现分段错误[重复]

C函数调用分段错误[重复]

返回指针时出现分段错误[重复]

在 C 中创建大型数组时出现分段错误

为啥释放内存会导致分段错误?

总线错误与分段错误