C语言基础分享——内存管理3

C语言基础分享——内存管理3,第1张

C语言基础分享——内存管理3

  嗨喽,我是春哥,今天主要介绍段错误以及调试方法,经常遇到段错误,对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 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:.Find the GDB manual and other documentation resources online at:.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 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:.Find the GDB manual and other documentation resources online at:.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]='';    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 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:.Find the GDB manual and other documentation resources online at:.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=) at ../sysdeps/posix/system.c:149#2  0x000055ad4af339ba in dump (signo=11) at a.c:25#3  #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 ("%sn", 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 : 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参数就能查看调试信息;否则不加该参数就不会显示调试信息。

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4950039.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-13
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存