malloc 中断_malloc内存初始为0

malloc 中断_malloc内存初始为0C语言出现分段错误怎么办?求大神救救题主大概是C语言初学者,这段代码的主要问题是指针的使用问题,其实没必要那么复杂,这么改就可以了:下面介绍几种常用调试方法。引言:什么是段错误每个在Linux环境下工作的程序员,都遇到过段错误(segmentation fault)。所谓段错误,本质

C语言出现分段错误怎么办?   求大神救救
malloc 中断_malloc内存初始为0   题主大概是C语言初学者,这段代码的主要问题是指针的使用问题,其实没必要那么复杂,这么改就可以了:
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   下面介绍几种常用调试方法。   引言:什么是段错误   每个在Linux环境下工作的程序员,都遇到过段错误(segmentation fault)。所谓段错误,本质上是程序访问了非法内存地址而引起的一种错误类型。   导致程序访问非法地址的原因有很多,如野指针、内存被踩、栈溢出、访问没有权限的内存等。   之前更新调试专题文章时,有朋友问到段错误的调试方法,我承诺会更新文章专门介绍,本文就是来填这个坑的。   本文将介绍9种非常实用的段错误调试方法。   1. 日志   日志是一种非常实用的调试手段,我们可以从系统日志中获得很多非常有用的信息,从而反推问题出现的前后系统中究竟发生了什么异常状况。   printf可能是最简单的日志记录方法,大家都懂的,不再赘述。   2. GDB   GDB的强大无需多言,对于段错误,利用GDB很容易就能定位到触发问题的那一行代码。如下图示例代码:   编译时加上-g选项:   在GDB中运行程序:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   段错误触发时,GDB会直接告诉我们问题出现在哪一行代码,并且可以利用backtrace命令查看完整调用栈信息。此外,还可以利用其他常规调试命令来查看参数、变量、内存等数据。   这种方式虽然非常有效,但很多时候,问题并不是100%必现的,我们不可能一直把程序运行在GDB中,这对程序的执行性能等会有很大的影响。   这时,我们可以让程序在异常终止时生成core dump文件,然后用调试工具对它进行离线调试。   3. Core Dump + GDB   Core dump是Linux提供的一种非常实用的程序调试手段,在程序异常终止时,Linux会把程序的上下文信息记录在一个core文件中,然后可以利用GDB等调试工具对core文件进行离线调试。   很多系统中,根据默认配置,程序异常退出时不会产生core dump文件。可以通过下面这条命令查看:   如果值是0,则默认不会产生core dump文件。可以用下面命令设置生成core dump文件的大小:   上面命令把core dump文件大小设置为10MB。如果存储空间不受限的话,可以直接取消大小限制:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   然后重新运行示例程序,段错误触发后,默认会在当前目录下生产一个core文件:
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   然后用GDB加载调试core文件。调试时,除了core dump文件外,GDB还需要从可执行文件中加载调试信息。   结果如下图:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   与直接在GDB运行程序类似,core dump文件加载起来之后,GDB会直接显示触发问题的那一行代码,也可以使用backtrace、print等常规命令从core dump文件中信息。   在大多数系统中,这种core dump + GDB的手段非常有效,而且应该优先考虑使用。   但是有时候,由于某种原因,系统可能无法生存core dump文件。比如出于安全考虑,core dump功能可能是被彻底禁止的,或者在一些存储空间受限的嵌入式系统中,也无法生成core dump文件。   此时,我们就不得不考虑其它的调试手段了。   4. signal capture + backtrace   4.1 段错误在Linux系统上的处理过程   在Linux系统中,程序访问非法地址时,会被CPU捕获后触发硬件异常处理机制,并通知Linux kernel程序运行出现异常,kernel会对各种异常进行区分,然后向应用程序发送不同的signal,由应用程序自己进行故障恢复处理。   对于访问非法地址引起的段错误,Linux kernel会向应用程序发送11号signal,也就是SIGSEGV信号,该信号的默认处理是终止程序运行。   我们可以注册一个信号处理函数,当接受到Linux kernel发送过来的SIGSEGV信号后,在信号处理函数中把当前程序的上下文信息记录下来,方面后续问题定位。   4.2 两个有用的函数   backtrace程序的调用栈地址信息,并存储在buffer指定的一个数组中,数组大小为size。   backtrace_symbols_fd根据backtrace得到的调用栈地址数据,地址对应的符号信息,并把结果写到fd指定的文件中。   4.3 示例   对上面的示例做下修改,增加一个信号处理函数,如下:   在信号处理函数signal_handler中,先把寄存器信息打印出来,然后用backtrace和backtrace_symbols_fd调用栈信息,并写入stdout。   然后,在main函数中注册SIGSEGV的信号处理函数,如下:   编译一下:   看下运行结果:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   为了方便演示,示例中的信号处理函数只记录了寄存器和调用栈信息,实际项目中根据需求,可以同时记录其它重要信息,如stack dump、全局变量、数据段dump等。   有两点需要注意:示例信号处理函数中打印寄存器的部分是针对x64 CPU的,其它CPU请参考sys/ucontext.h文件中对mcontext_t的定义。编译时需要加上-rdynamic选项,否则backtrace_symbols_fd无法正确符号信息。   5. signal capture + GDB   有些问题很难重现,直接在GDB里运行调试的话,可能要浪费很多时间去不停的尝试重现它。   那有没有一种方式,可以让问题重现时自动启动GDB呢?当然有!   与上面的一种方法类似,我们仍然利用signal capture的方式。只不过,在信号处理函数中,我们不再使用backtrace调用栈信息,而是直接启动GDB。   对信号处理函数作一些修改,如下:   原理很简单,就是段错误发生时,在SIGSEGV信号处理函数中执行命令:   启动GDB,并attach到当前进程,然后执行backtrace命令打印调用栈信息。-q选项只是让GDB启动时不要打印版本信息,避免视觉干扰。   编译一下,需要加上-g选项:   运行,结果如下图:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   注意:这种方法只能在测试环境中使用,且要确保GDB可以正常使用。生产环境中不要使用!   6. libSegFault.so   除了上面提到的几种方式外,其实glibc也已经很贴心地提供了一种问题定位的方案:libSegFault.so   libSegFault.so是glibc提供的一个动态链接库,用于捕捉程序运行异常并记录调用栈等调试信息。   它的实现原理和上面提到的第4种方法是一样的,即通过signal capture的方式,程序发生异常时,在信号处理函数中记录调试信息。   使用时,先确定系统中是否存在这个动态链接库。在我的系统中,有这么几个:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   根据自己的实际情况,选择一个使用。比如我的测试环境是x64的,我选择使用:   然后利用环境变量LD_PRELOAD,在测试程序运行前,把libSegFault.so链接进来。   仍以本文第一个测试程序为例:   编译:   运行:   测试程序触发段错误后,libSegFault.so中的信号处理函数会把寄存器、调用栈、内存映射全部dump出来。结果如下图(信息太多,分成了两张图片):   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   libSegFault.so默认只捕捉SIGSEGV,可以通过设置环境变量SEGFAULT_SIGNALS指定要捕捉的信号,如:   环境变量SEGFAULT_USE_ALTSTACK可以指定是否让信号处理函数使用独立的栈,这在程序发送栈溢出时会很有用。   libSegFault.so默认把调试信息输出到stderr,可以通过设置环境变量SEGFAULT_OUTPUT_NAME,指定调试信息记录到一个文件中。比如:   此外,为了方便用户使用,很多系统中还提供了一个名为catchsegv的脚本:   其效果与通过LD_PRELOAD加载libSegFault.so是相同的:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   7. Valgrind   Valgrind是一个很强大的工具集,它可以检测内存泄露、栈溢出、非法内存访问等多种内存相关的错误,还可以对程序进行性能剖析、生成函数调用关系图、统计Cache命中率、监测多线程竞争等,是程序调试的利器。   Valgrind功能非常强大,但文章篇幅有限,不对其展开讨论,后续会更新文章专门讲解它的各种功能,感兴趣的朋友可以右上角一下。   下面演示用Valgrind检测示例程序的内存访问错误。   编译时加上-g选项:   然后用Valgrind启动示例程序:   显示数据较多,仅截取感兴趣的部分信息,如下图所示:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   Valgrind成功检测出地址0x既不是栈地址,也不是malloc分配的动态内存。并且它也会把调用栈信息dump出来。   Valgrind虽然在检测内存相关的错误时非常强大,但是它有一个致命的缺点,就是慢。据统计,通过Valgrind运行程序时,速度会降低10倍。这在调试大型项目时,尤其是对实时性非常敏感的程序,是无法接受的。   不过,我们还有一个更好的选择 — AddressSanitizer。   8. AddressSanitizer   AddressSanitizer最初是Google开发的一个检测多种内存相关问题的工具,AddressSanitizer现在已经集成到GCC和LLVM中。它最大的特点是:功能强大。它可以检测内存泄露、访问越界、栈溢出、多次释放等各种内存问题。快。使用AddressSanitizer检测内存问题时,原始程序运行速度只会降低2倍左右,相比Vagrind来说,运行效率有了很大的提升。   本文只简单演示用AddressSanitizer检测示例程序中的内存访问错误,后续会专门更新文章详细讲解它的各种功能,感兴趣的朋友可以一下。   AddressSanitizer的使用方法也非常简单,只需要在编译时加上相应的编译选项,然后正常运行程序即可。   这里,我只使用最简单的一个编译选项-fsanitize=address开启AddressSanitizer功能。   然后正常运行即可,如下图:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   9. dmesg + objdump   有时,可能由于各种原因,以上几种方法都不适用,比如程序中无法添加调试信息、程序无法重新编译、没有GDB和Valgrind等调试工具等。   这种情况下,调试起来,会相对比较困难一些,但也并不是完全不可能。   大多数情况下,程序发生segmentation fault而异常退出时,会在系统日志中记录一些信息,可以用dmesg查看:   
malloc 中断_malloc内存初始为0
malloc 中断_malloc内存初始为0   可以从中得到触发异常的指令地址和被访问的内存地址,然后利用系统中现有的一些工具进行调试,如利用objdump对可执行文件进行反汇编,然后从汇编代码入手进行分析,限于篇幅,不再展开讨论。   Linux下有很多非常有用的工具,如binutils工具集(objdump、nm、readelf等)、strace等,熟悉并善用这些工具,会事半功倍。   —————-   欢迎知乎/:原点技术,分享真正有用的东西!   技术探讨,欢迎添加作者:CreCoding

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/33161.html

(0)
上一篇 2024年 9月 10日
下一篇 2024年 9月 10日

相关推荐

关注微信