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.c
alex@alex-Yan:~/work$ gdb a
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (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) run
Starting program: /home/alex/work/a
Program received signal SIGSEGV, Segmentation fault.
0x00005555555547dd in main () at a.c:6
6 *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 -c
0
alex@alex-Yan:~/work$ ulimit -c 1000
alex@alex-Yan:~/work$ ulimit -c
1000
alex@alex-Yan:~/work$ ./a
段错误 (核心已转储)
alex@alex-Yan:~/work$ ls
a a.c core
我们的系统在默认情况下是将core文件限制为0的,也就上面指向ulimit -c出现的0,意思是不生成core文件,所以要生成core文件我们需要修改core的限制,当然上面的只能解决临时的问题,要想修改为开机就限制1000,需要修改配置文件。
接下来用gdb来调试一下core看看:
alex@alex-Yan:~/work$ gdb ./a core
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (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:6
6 *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.c
alex@alex-Yan:~/work$ sudo ./a
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (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 12048
Reading 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:30
30 ../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.c
alex@alex-Yan:~/work$ ./a
Obtained 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相关推荐
- python终结一个循环额_Python语言入门之内存管理方式和垃圾回收算法解析
本文主要向大家介绍了Python语言入门之内存管理方式和垃圾回收算法解析,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 在列表,元组,实例,类,字典和函数中存在循环引用问题.有 ...
- C语言知识点 -- 动态内存管理、文件操作
C语言知识点 – 动态内存管理.文件操作 文章目录 C语言知识点 -- 动态内存管理.文件操作 一.动态内存管理 1.malloc 2.free 3.calloc 4.realloc 二.文件操作 1 ...
- C语言之动态内存管理与动态内存函数
文章目录 一.为什么存在动态内存分配? 二.动态内存函数的介绍 1.malloc和free 2.calloc函数 3.realloc函数 一.为什么存在动态内存分配? 学习动态内存的管理方法之前,我们 ...
- C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组
动态内存管理目录: 动态内存函数的介绍 常见的动态内存函数的错误 柔性数组 为什么会有动态内存管理呢 我们在日常使用中,创建一个数组,一个变量时都会开辟空间 如: int a; //在栈上开辟一个四字 ...
- C语言与JAVA内存管理_C语言内存管理
本章将介绍C语言动态内存管理. C语言编程语言提供了多种功能的内存分配和管理.这些函数可以在头文件中找到. S.N. 函数与说明 1 void *calloc(int num, int size); ...
- C语言进阶——动态内存管理
目录 一.为什么存在内存分配 二.动态内存函数 1.malloc 2.free 3.calloc 4.realloc 三.常见的动态内存错误 1.对NULL指针的解引用操作 2.对动态开辟空间的越界访 ...
- 【C语言】动态内存管理
文章目录 前言 一.动态内存函数介绍 1. malloc和free 2. calloc 3. realloc 二.常见的动态内存错误 1.对NULL指针进行解引用操作 2.对动态开辟空间的越界访问 3 ...
- C语言学习笔记 —— 内存管理
一.内存模型 对于一个C语言程序而言,内存空间主要由五个部分组成 代码段(text).数据段(data).未初始化数据段(bss),堆(heap) 和 栈(stack) 组成,其中代码段,数据段和BS ...
- Go 语言中手动内存管理
2019独角兽企业重金招聘Python工程师标准>>> Go 语言是自带GC的, 相对C语言等的手动内存管理省事很多, 弊端便是会消耗更多的内存, 以及在GC时导致整个程序的停顿. ...
最新文章
- Windows Server 2008 R2 配置笔记,密码设置为任意长度,远程桌面终端连接数的设置...
- 使用webpack打包后,vscode中vue代码变白色的解决办法
- 黄聪:基于Linq to edmx的实体数据模型(EDM)类名批量修改工具
- 第二章 第三节 创建第一个程序
- 中国全装修行业发展形势及投资决策建议报告2022版
- android 获取u盘名字_android 获取U盘路径
- 前端学习(680):switch注意事项
- leetCode 206. Reverse Linked List 反转链表
- iPhone XR再降价:64GB到手最低仅需4149元
- python的numpy是什么_python中numpy是什么
- 系统类配置(二)【深度学习装机详细教程-ubuntu16.04下安装cuda9.0+nvidia-384+cudnn7.1.4+tensorflow1.9。】
- 安装vs2013以后,链接数据库总是报内存损坏,无法写入的错误
- sap未分摊差异怎么处理_MM采购中形成的差异
- MyBatis配置使用
- Echarts 自定义、覆盖legend点击事件、禁用legend默认的点击行为的实现
- vue实现动态css,巧用 CSS 动画实现动态气泡背景__Vue.js__CSS__前端
- 第十章 国民收入的决定:收入-支出模型
- echars自定义y轴为图片
- 花仙里云课堂知识付费v1.2.25知识付费 教育 直播
- 《淘宝店铺经营管理一册通》一一1.1 宝贝标题优化