一篇知晓-内存竟被”无意“破坏,真相究竟如何?
创始人
2024-03-25 06:17:26
0

内存是C/C++程序员的好帮手,我们通常说C/C++程序性能更高其原因之一就在于可以自己来管理内存,然而计算机科学中没有任何一项技术可以包治百病,内存问题也给C/C++程序员带来无尽的烦恼。

野指针、数组越界、错误的内存分配或者释放、多线程读写导致内存被破坏等等,这些都会导致某段内存中的数据被”无意“的破坏掉,这类bug通常很难定位,因为当程序开始表现异常时通常已经距离真正出问题的地方很远了,常用的程序调试方法往往很难排查此类问题。

既然这类问题通常是由于内存的读写造成,那么如果要是某一段内存被修改或者读取时我们能观察到此事件就好了,幸运的是这类技术已经实现了。

一段示例

在GDB中你可以通过添加watchpoint来观察一段内存,这段内存被修改时程序将会停止,此时我们就能知道到底是哪行代码对该内存进行了修改,这功能是不是很强大。

接下来我们用示例来讲解一下,有这样一段代码:

#include 
#include 
using namespace std;// 线程修改变量值
void memory_write(int* value) {*value = 1;
}int main()
{int a = 10;// 获取局部变量a的地址int* c = &a;for (int i = 0; i < 100; i++) {a += i;}cout << a << endl;// 将变量a的地址传递到线程thread t(memory_write, c);t.join();return 0;
}

这段代码非常简单,创建局部变量a,然后获取变量a的地址并赋值给指针c,此后对变量a进行累加和,然后输出a的值,此时a的值为4960。

假设此后你发现变量a的值竟然变为了1,然而由于代码非常复杂你并不知道到底是哪段代码对变量a进行修改,在上述代码中我们利用线程a来模拟这个场景,线程获取变量a的地址后对其进行了修改,将其变为了1,接下来我们利用调试工具gdb来定位到底是谁修改了变量a。

   资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

开始捕捉“肇事者”

对上述代码进行编译,接下来利用gdb进行调试,假设源文件的名称是a.cc,编译后的可执行程序名字为a:

$ gdb a.out
(gdb) b a.cc:20
Breakpoint 1 at 0x400f23: file a.cc, line 20.
(gdb) r
Starting program: /bin/a
Breakpoint 1, main () at a.cc:20
20          cout << a << endl;

上述调试命令(b a.cc:20)表示我们在代码的第20行加断点,当程序运行到这里后暂停,调试命令r表示开始运行程序,可以看到运行到第20行后暂停,此时我们查看一下变量a的地址:

(gdb) p &a
$1 = (int *) 0x7fffffffe508

可以看到,变量a位于内存地址0x7fffffffe508,接下来重点来了,我们该怎样告诉gdb让它帮我们时刻监测0x7fffffffe508这个内存地址中的值有没有被修改呢?很简单:

(gdb) watch *(int*)0x7fffffffe508
Hardware watchpoint 2: *(int*)0x7fffffffe508

我们利用watch命令,让gdb帮我们时刻监测一段从0x7fffffffe508开始大小为4字节的内存区域(假设int占据4字节),这就是watch *(int*)0x7fffffffe508这行指令的含义:

除此之外上面gdb的输出中还有一段值得注意:

Hardware watchpoint 2: *(int*)0x7fffffffe508

注意看,什么是Hardware watchpoint呢?先卖个关子,我们稍后聊,接下来我们运行gdb中的c命令,意思是continue,让程序继续运行:

(gdb) c
Continuing.
4960

此时第20行执行完毕并打印出了变量a的值4960,我们接着往下看:

[New Thread 0x7ffff6f5c700 (LWP 531823)]
[Switching to Thread 0x7ffff6f5c700 (LWP 531823)]
Hardware watchpoint 2: *(int*)0x7fffffffe508Old value = 4960
New value = 1
memory_write (value=0x7fffffffe508) at a.cc:8
8       }
(gdb)

哈哈,gdb成功的捕捉到了是哪一行代码修改了0x7fffffffe508这块内存,而且详细的告诉我们所有信息,可以看到gdb打印出了这块内存之前保存的数据是数字4960,修改后的值为1,并且是在a.cc:8这里被修改的,而这里正是我们创建的线程对变量a进行修改的地方,gdb成功的捕捉到了”肇事者“,原来是这个线程”无意“修改了变量a的值。

是不是很神奇,那么这一切都是怎样实现的呢?

watchpoint是怎样实现的?

原来这一切都是CPU的功劳。

现代处理器中具有特殊的debug寄存器,x86处理器中是DR0到DR7寄存器,利用这些寄存器硬件可以持续检测处理器发出的用于读写内存的地址,更强大的是,不但硬件watchpoint可以检查内存地址,而且还是可以监测到底是在读内存还是在写内存。

利用gdb中的rwatch命令你可以来监测是否有代码读取了某段内存;利用gdb中的awatch命令你可以来检查是否有代码修改了某段内存;利用gdb中的watch命令你可以检查对某段内存是否有读或者写这两种情况。

一旦硬件监测到相应事件,就会暂停程序的运行并把控制权交给debugger,也就是这里的gdb,此时我们就可以对程序的状态进行详细的查看了,这种硬件本身支持的调试能力就是刚才提到的Hardware watchpoint

有hardware watchpoint就会有software watchpoint,当硬件不支持hardware watchpoint时gdb会自动切换到software watchpoint,此时你的程序每被执行一条机器指令gdb就会查看相应的事件是否发生,因此software watchpoint要远比hardware watchpoint慢,你可以利用gdb中的”set can-use-hw-watchpoints“命令来控制gdb该使用哪类watchpoint。

值得注意的是,在多线程程序中software watchpoint作用有限,因为如果被检测的一段内存被其它线程修改(就像本文中的示例)那么gdb可能捕捉不到该事件。

 

相关内容

热门资讯

用心做好每一块电池的欣旺达,因... 这两天国内动力电池生产厂商欣旺达遇到麻烦事了,因其所生产的电芯存在质量问题被威睿电动汽车技术(宁波)...
以案为鉴筑防线 以审促廉扬清风... 为充分发挥以案释法、以案说纪的警示教育作用,进一步加强党风廉政建设,提高党员干部的法纪意识和廉洁意识...
新加坡国立大学东亚研究所高级研... 由三亚市人民政府主办,《财经》杂志、财经网、《财经智库》、三亚中央商务区管理局、三亚经济研究院承办的...
原创 全... 在国家有关调查力量进驻南京之后,一个并不显眼、却耐人寻味的现象悄然出现了。 短时间内,全国多地博物馆...
跨境金融研究院院长王志毅:离岸... 由三亚市人民政府主办,《财经》杂志、财经网、《财经智库》、三亚中央商务区管理局、三亚经济研究院承办的...
原告向法官出示证据,右下角赫然... 近日,湖北孝感大悟法院民二庭在审理一起房屋租赁合同纠纷案时,精准识破原告方利用AI技术伪造证据的行为...
美国纽约州出台法律约束“成瘾性... 美国纽约州州长凯茜·霍楚尔26日宣布,根据该州新出台的一项法律,具备无限刷新、自动播放和算法推送功能...
富安娜理财纠纷一审落槌,中信证... 乐居财经 李兰经历近三年后,富安娜(002327.SZ)理财纠纷有了新进展。 12月25日,富安娜发...
从合作伙伴到对簿公堂:威睿起诉... 12月26日,欣旺达发布公告,其全资子公司欣旺达动力科技股份有限公司(下称“欣旺达动力”)因买卖合同...
突发!俄称已控制库皮扬斯克;泽... 俄乌,突传大消息! 俄国防部称已控制库皮扬斯克 俄罗斯国防部12月27日在每日例行通报中说,库皮扬斯...