如何排查大型C程序中的内存越界写越界导致的coredump

&p&内存被别人写坏了,十有八九就是野指针了。&/p&&br&&p&野指针怎么造成的?&/p&&p&内存释放了,指针没有置成nullptr,然后这个指针somehow又被用了,于是写到了已经分配给别人的内存。&/p&&br&&p&最简单的办法当然是看代码,找释放内存但是没有情空指针的地方,但是这个,有的时候不好使,你可能要找大量的代码。&/p&&br&&p&除了常见的内存排错工具,比如ASAN,valgind&/p&&p&另外一个值得介绍的就是mprotect大法。。&/p&&br&&p&中心思想就是,当别人写到不该它写的内容的时候,我们让操作系统通知我们。&/p&&p&linux 里提供了一个函数&/p&&p&int mprotect(const void *addr, size_t len, int prot);&/p&&br&&p&其中 addr 得是按页对齐的地址,len 得是页面的整数倍,&/p&&br&&p&最后一个参数是你要给的权限&/p&&p&可以是 PROT_READ (只读) &/p&&p&也可以是 PROT_WRITE (只写)&/p&&p&还可以是 PROT_WRITE | PROT_READ (可读可写)&/p&&br&&p&一般来说,我们通过 获取的内存呢都是可读可写的,mprotect可以让指定的n个页面变成只读,这个时候是一旦有人写了,就会发出一个SIGSEGV 信号,&/p&&br&&p&也就是分配内存的时候上保护,需要写的时候去保护 写完以后再上保护。&/p&&br&&p&比如&/p&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&void&/span&&span class=&o&&*&/span& &span class=&n&&start_addess&/span& &span class=&o&&=&/span& &span class=&n&&mmap&/span&&span class=&p&&(&/span&&span class=&nb&&NULL&/span&&span class=&p&&,&/span&&span class=&n&&PROT_READ&/span& &span class=&o&&|&/span& &span class=&n&&PROT_WRITE&/span&&span class=&p&&,&/span&&span class=&n&&getpagesize&/span&&span class=&p&&(),&/span&&span class=&mi&&0&/span&&span class=&p&&,&/span&&span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&c1&&//老板,来一个页!&/span&
&span class=&c1&&//...&/span&
&span class=&c1&&//... 对这个页做一些羞羞的事&/span&
&span class=&c1&&//...&/span&
&span class=&c1&&//告诉你们这个页只能老子写进去!然而老子已经写进去了!谁都不许动!&/span&
&span class=&n&&mprotect&/span&&span class=&p&&(&/span&&span class=&n&&start_address&/span&&span class=&p&&,&/span& &span class=&n&&getpagesize&/span&&span class=&p&&(),&/span& &span class=&n&&PROT_READ&/span&&span class=&p&&);&/span&
&span class=&c1&&//这样当别的进程一碰这个页,程序立刻SIGSEGV,世界在这一天毁灭了。&/span&
&/code&&/pre&&/div&&br&&p&好了,下面简单说一下怎么处理这个SIGSEGV,&/p&&p&简单来说就是用libsigsegv&/p&&p&&a href=&///?target=https%3A//www.gnu.org/software/libsigsegv/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&libsigsegv - GNU Project&i class=&icon-external&&&/i&&/a&&/p&&br&&p&libsigsegv 提供了 sigsegv_install_handler 函数,简单来说就是让你注册一个callback 当发生SIGSEGV的时候,控制流就会进入你的callback,然后你在这个callback里backtrace,自然就知道谁是那个干坏事儿的人了。瞬间真相大白。&/p&&br&&br&&p&是啊,mprotect简直是坠吼的,但是吧,也有些问题,你看你必须得是mmap这样获取的按页对齐的内存地址,但是一般来说我们都会用malloc,然而malloc给出的地址往往不是按页对齐的,这个时候怎么办呢。 简单,你可以自己写个malloc,里面用mmap直接拿到页,一拿就是整页个,结构体不管多大一律按页对齐,但是这样会造成内存浪费,所以我们加个宏,需要debug的时候呢,用我们这个malloc,以及那些mprotect逻辑,relase的时候切换回去。&/p&&br&&p&这样一旦发生野指针就切换成我们的malloc,找到问题干掉,切换回去也不会有问题了。&/p&&br&&br&&p&---其实我就是个臭写网页的。&/p&
内存被别人写坏了,十有八九就是野指针了。 野指针怎么造成的?内存释放了,指针没有置成nullptr,然后这个指针somehow又被用了,于是写到了已经分配给别人的内存。 最简单的办法当然是看代码,找释放内存但是没有情空指针的地方,但是这个,有的时候不好使…
已有帐号?
无法登录?
社交帐号登录
59430 人关注
563 条内容
17823 人关注
1396 条内容
348 人关注
2040 人关注
149 条内容
2677 人关注
1085 条内容如何排查大型C程序中的内存写越界导致的coredump? - 知乎406被浏览14923分享邀请回答311 条评论分享收藏感谢收起如何排查大型C程序中的内存写越界导致的coredump? - 知乎406被浏览14923分享邀请回答void* start_addess = mmap(NULL,PROT_READ | PROT_WRITE,getpagesize(),0,0);
//老板,来一个页!
//... 对这个页做一些羞羞的事
//告诉你们这个页只能老子写进去!然而老子已经写进去了!谁都不许动!
mprotect(start_address, getpagesize(), PROT_READ);
//这样当别的进程一碰这个页,程序立刻SIGSEGV,世界在这一天毁灭了。
好了,下面简单说一下怎么处理这个SIGSEGV,简单来说就是用libsigsegvlibsigsegv 提供了 sigsegv_install_handler 函数,简单来说就是让你注册一个callback 当发生SIGSEGV的时候,控制流就会进入你的callback,然后你在这个callback里backtrace,自然就知道谁是那个干坏事儿的人了。瞬间真相大白。是啊,mprotect简直是坠吼的,但是吧,也有些问题,你看你必须得是mmap这样获取的按页对齐的内存地址,但是一般来说我们都会用malloc,然而malloc给出的地址往往不是按页对齐的,这个时候怎么办呢。 简单,你可以自己写个malloc,里面用mmap直接拿到页,一拿就是整页个,结构体不管多大一律按页对齐,但是这样会造成内存浪费,所以我们加个宏,需要debug的时候呢,用我们这个malloc,以及那些mprotect逻辑,relase的时候切换回去。这样一旦发生野指针就切换成我们的malloc,找到问题干掉,切换回去也不会有问题了。---其实我就是个臭写网页的。288 条评论分享收藏感谢收起用户名:a_liujin
文章数:67
评论数:144
访问量:16572
注册日期:
阅读量:1297
阅读量:3317
阅读量:446556
阅读量:1131847
51CTO推荐博文
1.gcc -g filename.c -o filename 需要生成带调试信息的文件2.调试& 方式一:gdb filename 调试file可执行文件& 方式二:&&gdb& & & & & && &&file filename$gdb -tui&&&& 启动gdb,并且分屏显示源代码3.打断点的方式& break line_num &在main.c中line_num打断点& break filename.c:line_num 在filename.c中line_num打断点& break funcname 在funcname函数入口上打断点& break line_num if 条件 & 条件成立时在某行上打断点< break 查看所有设置的断点号,并列出断点序号5.delete break_num &删除断点6.list funname 查看指定的函数代码&& list filename:N 查看指定文件第N行附近的代码7.run 开始全速运行程序,直到断点8.next 单步运行,不进入子程序; &step 单步运行,进入子程序9.continue 继续全速运行,直到断点10.print +表达式& 查看指定变量名 &;watch &varriblename &对指定变量进行监控display& 表达式 & 与print的区别是它会在程序停住时自动显示变量的值examine& 地址1)显示动态内存的值:&&&&int *array = (int *) malloc (len * sizeof (int)); &&& 于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值: &&&p
&&& @的左边是数组的首地址的值,也就是变量array所指向的内容,右边则是数据的长度,其保存在变量len中,其输出结果,大约是下面这个样子的: (gdb) p
$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40} 2)显示动态内存的值&&&&& 如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。3)print 显示数据的格式x 按十六进制格式显示变量。 d 按十进制格式显示变量。 u 按十六进制格式显示无符号整型。 o 按八进制格式显示变量。 t 按二进制格式显示变量。 a 按十六进制格式显示变量。 c 按字符格式显示变量。 f 按浮点数格式显示变量。(gdb) print/a&& i$22 = 0x654)print i=10&& 修改变量的值为105)examine/3uh 0x54320 以16进制显示3个双字节6)打印变量的地址(print &var)&&& 打印地址的数据值(print *address) 7)强制调用某个函数(gdb) call &expr&&这里,&expr&可以是一个函数,这样就会返回函数的返回值,如果函数的返回类型是void那么就不会打印函数的返回值,但是实践发现,函数运行过程中的打印语句还是没有被打印出来。 (gdb) print &expr&&这里,print和call的功能类似,不同的是,如果函数的返回值是void那么call不会打印返回值,但是print还是会打印出函数的返回值并且存放到历史记录中。11.finish& 运行程序,直到当前函数结束12.quit 退出gdb13.backtrace& 查看当前的程序栈14.break& make_ &按TAB键&&& 补齐函数内核转储是让系统在信号中断造成的应用程序错误时产生core文件, 保存应用程序当前状态。内核转储文件的作用:操作系统用来保存某应用程序崩溃时的程序执行状态,gdb可以用该转储文件来还原到程序崩溃时的状态。一、程序运行崩溃的原因Linux下c/c++开发程序崩溃(Segment fault)通常都是指针错误引起的.比如:(1)访问了不存在的内存地址(2)访问了只读的内存地址(3)访问了系统保护的内存地址int *p=0;*p=100;(4)栈溢出,无限递归(5)内存溢出二、内核转储文件作用发生Segment fault时,内核转储文件(core dump)作用(1) 内核转储的最大好处是能够保存问题发生时的状态。(2) 只要有可执行文件和内核转储,就可以知道进程当时的状态。(3) 只要获取内核转储,那么即使没有复现环境,也能调试。三、配置操作系统的内核转储功能可以参考《高并发服务器开发与配置》中,用户能打开的最大文件数的设置方法。启动系统的内核转储功能,需要做如下配置:(1)查看当前转储文件大小&& ulimit -c0&& 为0,表示当前转储文件大小为0,没有启动内核转储&&ulimit -c unlimited&&&&&& #设置coredump 大小为无限大这些需要有root权限, 在ubuntu下每次重新打开中断都需要重新输入上面的第一条命令, 来设置core大小为无限.四、gdb使用内核转储文件再现崩溃时的状态&&./test&& -&运行test崩溃,在当前目录下将产生一个core文件&&gdb -c ./corefile& ./test&& 使用gdb再现崩溃状态在进入gdb后, 用bt命令查看backtrace以检查发生程序运行到哪里, 来定位core dump的文件-&行.五、System Dump和Core Dump的区别1) 系统Dump(System Dump)所有开放式操作系统,都存在系统DUMP问题。产生原因:由于系统关键/核心进程,产生严重的无法恢复的错误,为了避免系统相关资源受到更大损害,操作系统都会强行停止运行,并将当前内存中的各种结构,核心进程出错位置及其代码状态,保存下来,以便以后分析。最常见的原因是指令走飞,或者缓冲区溢出,或者内存访问越界。走飞就是说代码流有问题,导致执行到某一步指令混乱,跳转到一些不属于它的指令位置去执行一些莫名其妙的东西(没人知道那些地方本来是代码还是数据,而且是不是正确的代码开始位置),或者调用到不属于此进程的内存空间。写过C程序及汇编程序的人士,对这些现象应当是很清楚的。系统DUMP生成过程的特点:在生成DUMP过程中,为了避免过多的操作结构,导致问题所在位置正好也在生成DUMP过程所涉及的资源中,造成DUMP不能正常生成,操作系统都用尽量简单的代码来完成,所以避开了一切复杂的管理结构,如文件系统)LVM等等,所以这就是为什么几乎所有开放系统,都要求DUMP设备空间是物理连续的――不用定位一个个数据块,从DUMP设备开头一直写直到完成,这个过程可以只用BIOS级别的操作就可以。这也是为什么在企业级UNIX普遍使用LVM的现状下,DUMP设备只可能是裸设备而不可能是文件系统文件,而且[b]只[/b]用作DUMP的设备,做 LVM镜像是无用的――系统此时根本没有LVM操作,它不会管什么镜像不镜像,就用第一份连续写下去。所以UNIX系统也不例外,它会将DUMP写到一个裸设或磁带设备。在重启的时候,如果设置的DUMP转存目录(文件系统中的目录)有足够空间,它将会转存成一个文件系统文件,缺省情况下,[b]对于AIX来说是/var/adm/ras/下的vmcore*这样的文件,对于HPUX来说是 /var/adm/crash下的目录及文件。[/b]当然,也可以选择将其转存到磁带设备。会造成系统DUMP的原因主要是:系统补丁级别不一致或缺少)系统内核扩展有BUG(例如Oracle就会安装系统内核扩展))驱动程序有 BUG(因为设备驱动程序一般是工作在内核级别的),等等。所以一旦经常发生类似的系统DUMP,可以考虑将系统补丁包打到最新并一致化)升级微码)升级设备驱动程序(包括FC多路冗余软件))升级安装了内核扩展的软件的补丁包等等。2) 进程Core Dump进程Core Dump产生的技术原因,基本等同于系统DUMP,就是说从程序原理上来说是基本一致的。但进程是运行在低一级的优先级上(此优先级不同于系统中对进程定义的优先级,而是指CPU代码指令的优先级),被操作系统所控制,所以操作系统可以在一个进程出问题时,不影响其他进程的情况下,中止此进程的运行,并将相关环境保存下来,这就是core dump文件,可供分析。如果进程是用高级语言编写并编译的,且用户有源程序,那么可以通过在编译时带上诊断用符号表(所有高级语言编译程序都有这种功能),通过系统提供的分析工具,加上core文件,能够分析到哪一个源程序语句造成的问题,进而比较容易地修正问题,当然,要做到这样,除非一开始就带上了符号表进行编译,否则只能重新编译程序,并重新运行程序,重现错误,才能显示出源程序出错位置。如果用户没有源程序,那么只能分析到汇编指令的级别,难于查找问题所在并作出修正,所以这种情况下就不必多费心了,找到出问题的地方也没有办法。进程Core Dump的时候,操作系统会将进程异常终止掉并释放其占用的资源,不可能对系统本身的运行造成危害。这是与系统DUMP根本区别的一点,系统DUMP产生时,一定伴随着系统崩溃和停机,进程Core Dump时,只会造成相应的进程被终止,系统本身不可能崩溃。当然如果此进程与其他进程有关联,其他进程也会受到影响,至于后果是什么,就看相关进程对这种异常情况(与自己相关的进程突然终止)的处理机制是什么了,没有一概的定论。六、内核转储文件(core dump)永久生效的办法在终端中输入以下命令,查看内核转储是否有效。 #ulimit -c 0 -c 表示内核转储文件的大小限制,现在显示为0,表示不能用。永久生效的办法是:#vi /etc/profile 然后,在profile中添加:ulimit -c &&&&& --1G大小(但是,若将产生的转储文件大小大于该数字时,将不会产生转储文件)或者ulimit -c unlimited这样重启机器后生效了。 或者, 使用source命令使之马上生效。#source /etc/profile七、指定内核转储的文件名和目录缺省情况下,内核在coredump时所产生的core文件放在与该程序相同的目录中,并且文件名固定为core。很显然,如果有多个程序产生core文件,或者同一个程序多次崩溃,就会重复覆盖同一个core文件。可以通过修改kernel的参数,指定内核转储所生成的core文件的路径和文件名。可以通过在/etc/sysctl.conf文件中,对sysctl变量kernel.core_pattern的设置。&&vi /etc/sysctl.conf 然后,在sysctl.conf文件中添加下面两句话:kernel.core_pattern = /var/core/core_%e_%pkernel.core_uses_pid = 0需要说明的是, /proc/sys/kernel/core_uses_pid。如果这个文件的内容被配置成1,即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID。这里%e, %p分别表示:%c 转储文件的大小上限%e 所dump的文件名%g 所dump的进程的实际组ID%h 主机名%p 所dump的进程PID%s 导致本次coredump的信号%t 转储时刻(由日起计的秒数)%u 所dump进程的实际用户ID可以使用以下命令,使修改结果马上生效。&&sysctl Cp /etc/sysctl.conf请在/var目录下先建立core文件夹,然后执行a.out程序,就会在/var/core/下产生以指定格式命名的内核转储文件。查看转储文件的情况:#ls /var/corecore_a.out_2834八、例子Linux下c/c++开发之程序崩溃(Segment fault)时内核转储文件(coredump)生成举例说明例子的源代码:#include &stdio.h&int main(void){int *a = NULL;*a = 0x1;return 0;}把以上源代码,写成一个a.c文件后,编译a.c文件产生一个a.out的可执行文件:#gcc -g a.c -o a.out修改a.out文件的权限后,执行它:#./a.out就会显示:Segmentation fault(core dump)这表示在当前目录下, 已经生成了a.out对应的内核转储文件。注意:后面带有(core dump), 才说明转储文件成功生成了。#file core*core:ELF 64-bit LSB core file x86-64, version 1(SYSV), SVR4-style, from './a.out'coreDump: UTF-8 Unicode C program text要用GDB调试内核转储文件,应该使用以下方式启动GDB:#gdb -c ./core ./a.outGNU gdb (GDB) 7.1-Ubuntu...Core was generated by './a.out'.Program terminated with signal 11, Segmentation fault.#0 0x04dc in main() at a.c:66 *a =0x1;a.c的第6行收到了11号信号。用GDB的list命令可以查看附近的源代码。(gdb) l 51&&&&&&&&&&& #include &stdio.h&2&&&&& 3&&&&&&&&&&& int main(void)4&&&&&&&&&&& {5&&&&&&&&&&&&&&&&&& int *a = NULL;6&&&&&&&&&&&&&&&&&& *a = 0x1;7&&&&&&&&&&&&&&&&&& return 0;8&&&&&&&&&&& }这里默认都是当前目录,也可以给core 和a.out 指定路径。&本文出自 “” 博客,谢绝转载!
了这篇文章
类别:┆阅读(0)┆评论(0)
10:14:00 11:14:45 12:27:20 22:08:07}

我要回帖

更多关于 内存越界 英文 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信