记一次内存踩踏事件

 简介

内存踩踏是一种现象比较随机,比较隐晦的bug,大多数时候都较难复现和排查。此次内存踩踏十分幸运发生在静态数据区且踩踏位置比较固定,因此才得以顺利的正向解决。

事件起因

在移植第三方gps解析库之后,原来的cpp程序出现了segment fault信号而异常退出,经过进一步挖掘,初步定性为内存踩踏。

1.生成coredump

程序运行在linux设备,要生成coredump文件,需要设置ulimit 参数和 coredump路径。

修改ulimit 参数

/etc/profile文件末尾添加:

ulimit -c unlimited

设置coredump文件为无限大,否则比较大的程序无法生成完整的core文件。

修改coredump存放路径

在/etc/sysctl.conf末尾添加:

kernel.core_pattern = /opt/xxx/log/%e-%t.core

设置core文件的路径和文件名模板。

场景复现

重启系统,重新运行问题代码,复现段错误:

Segmentation fault (core dumped)

解析core文件

可以使用gdb来解析core文件:

gdb <问题程序> <问题程序的core文件> 

root@xxx-2-15:/opt/xxx/log# /gdb /xxxapp xxxapp-1594990464.core 
#0  0xb6aa594c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars(char*, char*, char*)
    () from /usr/lib/libstdc++.so.6
#1  0x00055f90 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*> (
    this=0xa21feb78, __beg=0x12d5aa0 <error: Cannot access memory at address 0x12d5aa0>, 
    __end=0x12d5aa1 <error: Cannot access memory at address 0x12d5aa1>)
    at /home/dchen/petalinux/tools/linux-i386/gcc-arm-linux-gnueabi/arm-linux-gnueabihf/include/c++/6.2.1/bits/basic_string.tcc:225
#2  0xb6aa80b4 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /usr/lib/libstdc++.so.6
#3  0x0004b0b0 in std::operator+<char, std::char_traits<char>, std::allocator<char> > (__lhs=..., __rhs=0x239b0c "/")
    at /home/dchen/petalinux/tools/linux-i386/gcc-arm-linux-gnueabi/arm-linux-gnueabihf/include/c++/6.2.1/bits/basic_string.h:4969
#4  0x000e36f4 in SystemUpdate::getLastInfo (lastVersion=..., lastVersionId=@0x2f6930: 0)
    at /home/dchen/chendu_repo/apd-unifyrepo/packages/application/apdapp/src/function/SystemUpdate.cpp:65

定位段错误的发生地

从调用链中可以看到错误发生在SystemUpdate.cpp:65。经排查此处代码访问一个全局string类。通过gdb断点调试,发现在问题发生前,此string对象的_M_ptr指针已经被篡改,但内容仍在,走查代码发现该对象为常量,不存在非法写入,因此初步判定为内存踩踏。

定位踩踏事件的凶手

由于已经锁定了一个百分百被踩踏的受害者,只要watch这个受害对象即可追溯到凶手。

尝试:

a.重新启动gdb,watch xxxstring

b.run

程序卡住无法启动,经查询才知道,watch需要cpu硬件支持,但是能够watch的长度是有限的,而string对象的长度超出范围,因此无法watch。

改进:

取xxxstring的地址

p &xxxstring

得到地址:0xabcdef

watch *0xabcdef

此时默认将该地址作为4字节变量进行watch。

执行:run

Thread 2 "apdapp" hit Hardware watchpoint 2: SystemUpdate::filePath._M_string_length

Old value = 11
New value = 1
nmea_parser_gsa (tokenizer=0xb6873240, navdata=0x2d5700 <s_nav>)
    at /home/dchen/chendu_repo/apd-unifyrepo/packages/application/apdapp/src/function/BDSparse.cpp:374

 程序在每次监视到该地址写入动作后都会停止。最终确定,凶手为第三方库的数组越界操作。

总结

内存踩踏大多数时候是比较隐晦的,此次幸运的是凶手代码逻辑比较固定,受害数据的作用比较明显,故障现象十分突出,从而可以顺利定位。如果受害数据位置不固定的话,现象可能每次都不一样,定位问题难度会变得很大。

因此,需要从源头解决,加强代码审核,加入动静态扫描,可以有效避免有数组越界-缓冲区溢出带来的内存踩踏事件。

<think>我们正在讨论Valgrind工具,特别是它是否能够检测内存越界访问(也称为内存踩踏)问题。根据提供的引用,我们可以分析如下: 引用[1]提到:“内存泄露、内存越界、踩内存、野指针等都是我们项目开发过程中常见的问题”,并且指出Valgrind可用于此类问题的定位。这里明确将“内存越界”和“踩内存”列为Valgrind能够定位的问题。 引用[2]介绍了另一种工具ASan(AddressSanitizer),它也能检测内存问题,包括内存泄漏。但用户的问题是关于Valgrind的,所以我们重点看引用[1]和引用[3]。 引用[3]展示了使用Valgrind的命令:`valgrind -v --error-limit=no --leak-check=full --tool=memcheck ./xxxx`,其中`--tool=memcheck`表示使用内存检查工具。Memcheck是Valgrind默认的工具,它可以检测多种内存错误,包括内存越界访问。 因此,结合引用[1]和引用[3],我们可以确认Valgrind的Memcheck工具确实能够检测内存越界访问(踩内存)问题。 回答结构: 1. 明确回答用户问题:Valgrind支持检测内存越界访问(踩踏)问题。 2. 解释Valgrind如何检测:通过Memcheck工具。 3. 给出一个简单的使用示例命令(参考引用[3])。 4. 提醒用户注意事项(例如,程序运行速度会变慢,但检测准确)。 相关问题生成: 根据用户问题,我们可以生成一些与Valgrind使用相关的问题,帮助用户深入理解。 注意:引用标识要自然地添加在回答中引用的内容末尾。</think>### Valgrind 对内存越界访问的检测能力 Valgrind **完全支持检测内存越界访问(内存踩踏)问题**,这是其核心功能之一。通过默认工具`Memcheck`,Valgrind 能精确捕捉以下内存越界行为: 1. **堆内存越界** - 检测数组访问超出分配范围(如 `int* arr = malloc(10*sizeof(int)); arr[10] = 5;`) - 识别写操作覆盖相邻内存块头部的元数据(称为 "redzone" 污染) 2. **栈内存越界** - 发现局部变量数组越界访问(如函数内 `char buf[8]; buf[8] = 'x';`) 3. **全局数据区越界** - 检测全局数组或静态数组的非法访问 --- ### 检测原理与技术实现 Valgrind 通过动态二进制插桩实现内存监控: 1. **虚拟地址空间影子映射** 为每个进程内存字节维护状态标(合法/非法),通过影子内存实时跟踪访问权限[^1]。 2. **内存操作插桩** 在每条内存读写指令前插入检查代码,验证目标地址是否在合法范围内。 3. **自定义内存分配器** 替换 `malloc/free` 等函数,在分配块两侧添加保护区(redzones),触发越界时立即报错[^3]。 --- ### 典型错误报告示例 执行命令 `valgrind --tool=memcheck ./your_program` 可能输出: ``` ==12345== Invalid write of size 4 ==12345== at 0x40065B: main (test.c:10) ==12345== Address 0x520f048 is 0 bytes after a block of size 40 alloc'd ==12345== at 0x4C29BE3: malloc (vg_replace_malloc.c:299) ``` 报告明确指示: - 错误类型:非法写操作(Invalid write) - 越界位置:分配块末尾后 0 字节处 - 源码位置:`test.c` 第 10 行 --- ### 使用注意事项 1. **性能影响** Valgrind 会使程序运行速度降低 10-50 倍,仅适用于调试环境。 2. **编译要求** 建议编译时添加 `-g` 选项保留调试符号,以显示源码行号: ```bash gcc -g your_program.c -o your_program ``` 3. **完整检测命令** 推荐启用所有检查选项[^3]: ```bash valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./your_program ``` --- ### 与其他工具对比 | 工具 | 内存越界检测 | 实时性能影响 | 适用场景 | |---------------|-------------|-------------|------------------| | Valgrind | ★★★★ | 高 (10-50x) | 深度调试 | | AddressSanitizer (ASan) | ★★★★☆ | 中 (2-4x) | 开发/测试环境[^2] | | GDB | ★★ | 低 | 简单越界定位 | > 对于生产环境,建议使用 ASan 作为轻量级替代方案,其错误报告格式更友好[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值