嗨喽,我是春哥,今天主要介绍段错误以及调试方法,经常遇到段错误,对C语言的理解才会更深。

个人建议收藏此文,这应该是介绍调试方法比较全面的了。

先介绍一下什么是段错误,段错误就意味着你访问了错误的内存段,一种情况是你没有这个内存段的权限,另一种情况就是根本不存在对应的物理地址,比如0地址。

我们知道,系统运行程序时会给程序分配一段内存空间,通常这个值由gdtr来保存,

它是一个48位的寄存器,其中32位用于保存由它指向的gdt表;后13位用于保存相应于gdt的下标;最后3位包括了程序是否在内存中,以及程序在cpu中的运行级别。指向的gdt是一个以64位为单位的表,在这张表中保存着程序运行的代码段、数据段的起始地址,以及与此相应的段限制和页面交换还有程序运行的级别,还有内存粒度信息。如果一个程序发生了越界访问,CPU就会产生相应的异常保护,这时段错误就出现了。

在日常工作中,内存管理绝大部分工作是我们来完成的,只要涉及到内存管理,就不可能避免段错误,所以,介绍几个调试的方法。

方法一:利用gdb逐步查找段错误

这种方法应该是用的最多的,作为学生,可能写的程序简单用不上gdb,但是学习gdb调试真的很重要(本人表示在学校没用过,但是实习期间基本上天天用)学会gdb很重要,不止gdb,各种调试方法都要学会。

alex@alex-Yan:~/work$ gcc -g -rdynamic -o a a.calex@alex-Yan:~/work$ gdb aGNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from a...done.(gdb) runStarting program: /home/alex/work/a
Program received signal SIGSEGV, Segmentation fault.0x00005555555547dd in main () at a.c:66               *ptr = 0;

这是用gdb调试段错误的步骤,这里面很清晰的告诉我们,段错误的位置在a.c文件的第6行,连语句都告诉我们了。然后他还说,程序收到了SIGSEGV信号而终止,然后查阅文档(man 7 signal),发现SIGSEGV默认的handler动作是打印“段错误”的出错信息,并产生core文件,所以,除了gdb调试,我们还可以分析core文件。

方法二:分析core文件

这个我之前听说过,但是在我实习之前我都没发现掌握好调试技术多么重要,所以我实习期间基本上都是现学现用(当然那个时候不像现在没工作能静下来学习,有点浮躁)。

一般core路径和可执行文件路径一样,也可以指定core的生成路径。core生成之后,用“gdb ./可执行文件 core”命令查看core。

alex@alex-Yan:~/work$ ulimit -c0alex@alex-Yan:~/work$ ulimit -c 1000alex@alex-Yan:~/work$ ulimit -c1000alex@alex-Yan:~/work$ ./a段错误 (核心已转储)alex@alex-Yan:~/work$ lsa  a.c  core

我们的系统在默认情况下是将core文件限制为0的,也就上面指向ulimit -c出现的0,意思是不生成core文件,所以要生成core文件我们需要修改core的限制,当然上面的只能解决临时的问题,要想修改为开机就限制1000,需要修改配置文件。

接下来用gdb来调试一下core看看:​​​​​​​

alex@alex-Yan:~/work$ gdb ./a coreGNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from ./a...done.[New LWP 11664]Core was generated by `./a'.Program terminated with signal SIGSEGV, Segmentation fault.#0  0x00005603d28647dd in main () at a.c:66               *ptr = 0;

一下就找到了位置,跟gdb调试一样的,这两种方法真是太好用了。

方法三:出现段错误时启动调试

我们先来看一段代码,别问我代码意思,我也是抄的代码,还没看懂,分享出来,大家有需要的自提。这段代码的作用就是出现段错误时启动gdb调试。​​​​​​​

void dump(int signo){    char buf[1024];    char cmd[1024];    FILE *fh;
    snprintf(buf,sizeof(buf),"/proc/%d/cmdline",getpid());    if(!(fh=fopen(buf,"r")))        exit(0);    if(!fgets(buf,sizeof(buf),fh))        exit(0);    fclose(fh);    if(buf[strlen(buf)-1]=='\n')        buf[strlen(buf)-1]='\0';    snprintf(cmd,sizeof(cmd),"gdb %s %d",buf,getpid());    system(cmd);    exit(0);}

然后在main函数开头调用signal(SIGSEGV,&dump);​​​​​​​

alex@alex-Yan:~/work$ gcc -g -o a a.calex@alex-Yan:~/work$ sudo ./aGNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from ./a...done.Attaching to program: /home/alex/work/a, process 12048Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.27.so...done.done.Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.27.so...done.done.0x00007f770e258457 in __GI___waitpid (pid=12049, stat_loc=stat_loc@entry=0x7ffd81d97638, options=options@entry=0) at ../sysdeps/unix/sysv/linux/waitpid.c:3030      ../sysdeps/unix/sysv/linux/waitpid.c: 没有那个文件或目录.(gdb) bt#0  0x00007f770e258457 in __GI___waitpid (pid=12049, stat_loc=stat_loc@entry=0x7ffd81d97638, options=options@entry=0) at ../sysdeps/unix/sysv/linux/waitpid.c:30#1  0x00007f770e1c3177 in do_system (line=<optimized out>) at ../sysdeps/posix/system.c:149#2  0x000055ad4af339ba in dump (signo=11) at a.c:25#3  <signal handler called>#4  0x000055ad4af339ec in main () at a.c:33

编译时记得和平时相比要加-g选项,然后后面的运行可执行文件a我也不知道为啥非要用root权限,我之前看的其他大佬的文章是不用提权的,有粉丝指定这个怎么解决可以私聊我。相互学习,共同进步。

写到这里,我们突然发现前三种方法都要用到gdb,下面再介绍一种不用


gdb的调试方法。

方法四:利用backtrace和objdump进行分析

将dump重写​​​​​​​

void dump(int signo){        void *array[10];        size_t size;        char **strings;        size_t i;
        size = backtrace (array, 10);        strings = backtrace_symbols (array, size);
        printf ("Obtained %zd stack frames.\n", size);
        for (i = 0; i < size; i++)                printf ("%s\n", strings[i]);
        free (strings);
        exit(0);}

编译运行​​​​​​​

alex@alex-Yan:~/work$ gcc -g -o a a.calex@alex-Yan:~/work$ ./aObtained 6 stack frames../a(+0x82b) [0x56338059082b]/lib/x86_64-linux-gnu/libc.so.6(+0x3f040) [0x7f67fda2a040]./a(+0x8c1) [0x5633805908c1]./a(+0x8e6) [0x5633805908e6]/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f67fda0cbf7]./a(+0x71a) [0x56338059071a]

这里没有很明显标示出来错误信息,我们用dbjdump反汇编程序,定位到0x5633805908c1的位置​​​​​​​

00000000000008ae <test_dump>: 8ae:   55                      push   %rbp 8af:   48 89 e5                mov    %rsp,%rbp 8b2:   48 8d 05 d7 00 00 00    lea    0xd7(%rip),%rax        # 990 <_IO_stdin_used+0x20> 8b9:   48 89 45 f8             mov    %rax,-0x8(%rbp) 8bd:   48 8b 45 f8             mov    -0x8(%rbp),%rax 8c1:   c6 00 00                movb   $0x0,(%rax) 8c4:   90                      nop 8c5:   5d                      pop    %rbp 8c6:   c3                      retq

这儿是表示方法的原因,前13位是一样的,所以只看后三位。

方法五:printf跟踪打印

这个是看似最简单但往往很多情况下十分有效的调试方式,也许可以说是程序员用的最多的调试方式。简单来说,就是在程序的重要代码附近加上像printf这类输出信息,这样可以跟踪并打印出段错误在代码中可能出现的位置。

为了方便使用这种方法,可以使用条件编译指令#ifdef DEBUG和#endif把printf函数包起来。这样在程序编译时,如果加上-DDEBUG参数就能查看调试信息;否则不加该参数就不会显示调试信息。

以上五种方法,不能说哪个重要哪个不重要,只是面对不同的场景有不同的方法,所以大家五种方法都要掌握。

C语言基础分享——内存管理3相关推荐

  1. python终结一个循环额_Python语言入门之内存管理方式和垃圾回收算法解析

    本文主要向大家介绍了Python语言入门之内存管理方式和垃圾回收算法解析,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 在列表,元组,实例,类,字典和函数中存在循环引用问题.有 ...

  2. C语言知识点 -- 动态内存管理、文件操作

    C语言知识点 – 动态内存管理.文件操作 文章目录 C语言知识点 -- 动态内存管理.文件操作 一.动态内存管理 1.malloc 2.free 3.calloc 4.realloc 二.文件操作 1 ...

  3. C语言之动态内存管理与动态内存函数

    文章目录 一.为什么存在动态内存分配? 二.动态内存函数的介绍 1.malloc和free 2.calloc函数 3.realloc函数 一.为什么存在动态内存分配? 学习动态内存的管理方法之前,我们 ...

  4. C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组

    动态内存管理目录: 动态内存函数的介绍 常见的动态内存函数的错误 柔性数组 为什么会有动态内存管理呢 我们在日常使用中,创建一个数组,一个变量时都会开辟空间 如: int a; //在栈上开辟一个四字 ...

  5. C语言与JAVA内存管理_C语言内存管理

    本章将介绍C语言动态内存管理. C语言编程语言提供了多种功能的内存分配和管理.这些函数可以在头文件中找到. S.N. 函数与说明 1 void *calloc(int num, int size); ...

  6. C语言进阶——动态内存管理

    目录 一.为什么存在内存分配 二.动态内存函数 1.malloc 2.free 3.calloc 4.realloc 三.常见的动态内存错误 1.对NULL指针的解引用操作 2.对动态开辟空间的越界访 ...

  7. 【C语言】动态内存管理

    文章目录 前言 一.动态内存函数介绍 1. malloc和free 2. calloc 3. realloc 二.常见的动态内存错误 1.对NULL指针进行解引用操作 2.对动态开辟空间的越界访问 3 ...

  8. C语言学习笔记 —— 内存管理

    一.内存模型 对于一个C语言程序而言,内存空间主要由五个部分组成 代码段(text).数据段(data).未初始化数据段(bss),堆(heap) 和 栈(stack) 组成,其中代码段,数据段和BS ...

  9. Go 语言中手动内存管理

    2019独角兽企业重金招聘Python工程师标准>>> Go 语言是自带GC的, 相对C语言等的手动内存管理省事很多, 弊端便是会消耗更多的内存, 以及在GC时导致整个程序的停顿. ...

最新文章

  1. Windows Server 2008 R2 配置笔记,密码设置为任意长度,远程桌面终端连接数的设置...
  2. 使用webpack打包后,vscode中vue代码变白色的解决办法
  3. 黄聪:基于Linq to edmx的实体数据模型(EDM)类名批量修改工具
  4. 第二章 第三节 创建第一个程序
  5. 中国全装修行业发展形势及投资决策建议报告2022版
  6. android 获取u盘名字_android 获取U盘路径
  7. 前端学习(680):switch注意事项
  8. leetCode 206. Reverse Linked List 反转链表
  9. iPhone XR再降价:64GB到手最低仅需4149元
  10. python的numpy是什么_python中numpy是什么
  11. 系统类配置(二)【深度学习装机详细教程-ubuntu16.04下安装cuda9.0+nvidia-384+cudnn7.1.4+tensorflow1.9。】
  12. 安装vs2013以后,链接数据库总是报内存损坏,无法写入的错误
  13. sap未分摊差异怎么处理_MM采购中形成的差异
  14. MyBatis配置使用
  15. Echarts 自定义、覆盖legend点击事件、禁用legend默认的点击行为的实现
  16. vue实现动态css,巧用 CSS 动画实现动态气泡背景__Vue.js__CSS__前端
  17. 第十章 国民收入的决定:收入-支出模型
  18. echars自定义y轴为图片
  19. 花仙里云课堂知识付费v1.2.25知识付费 教育 直播
  20. 《淘宝店铺经营管理一册通》一一1.1 宝贝标题优化

热门文章

  1. 操作系统_第三章处理器管理_批处理作业的调度算法
  2. ATECC508A芯片开发笔记(八):ECDH算法配置方法、执行过程及实现原理
  3. pstools的使用方法
  4. 常用单电源运放的偏置方法
  5. 杰理之MIC 省电 容方案 微信语音 或通话 时前面 几秒钟有 哒哒声【篇】
  6. 国家测绘局公布2007年十大测绘违法典型案件(转)
  7. 大数据行业部署实战2:环境大数据统计
  8. 浅谈未来的人工智能与奇点临近
  9. java 声明式编程_声明式编程 - SegmentFault 思否
  10. upload-labs通关详解