在 Visual Studio 调试模式下显示奇怪的内存内容
Strange memory content display in Visual Studio debug mode
我正在编写一些多线程 C 程序。我试图修改函数体开头的几条指令,以将执行重定向到其他地方。
但我注意到在 Visual Studio 2015 内调试时,某些内存位置似乎无法更改,如 Memory
window.
中所示
例如:
在下图中,函数 ApSignalMceToOs()
从 0x7FFBBEE51360
开始。 0x7FFBBEE51360
到0x7FFBBEE5136E
的内存范围我已经取消保护修改了
305~312行修改地址范围0x7FFBBEE51360
~0x7FFBBEE5136E
。
在 0x7FFBBEE51369
之前一切都很好。在第 311 行,(uint32_t(((uintptr_t)dst) >> 32
是 0x00007ffb
。
执行完第311行后,本以为0x7FFBBEE51369
~0x7FFBBEE5136C
的内存范围会被填充为fb 7f 00 00
。但是如下图,Visual Studio表示是48 7f 00 00
,其中48
是旧值。
然后我去查看函数ApSignalMceToOs()
的反汇编代码。毫不奇怪,00007FFBBF171365
处的指令是 mov dword ptr [rsp+4], 7F48h
, 应该 是 7FFB
。如下图红色框内
所以直到现在,Visual Studio 2015 告诉我我的修改 会 失败。
但是如上图黄色箭头所示,mov dword ptr [rsp+4], 7F48h
执行完后,我查看了栈区的内容。 令人惊讶的是它确实7f fb
被移到了堆栈上(显示在上方[=136]中的绿色框中=] 图片).
而ret
指令执行后,RIP
寄存器确实变成了00007FFBBEEAD940
,这并不奇怪。见下文:
并且在另一个函数中,正在读取相同的位置。如下图:
code[len]
或 byte ptr [rax]
是保存 48
或 fb
的内存位置。但它显示为 0xcc
,既不是 0x48
也不是 0xfb
。
Visual Studio反汇编代码是从内存内容中解码出来的。所以内存内容还是VS2015如何read/refresh呢才是重点。
基于以上观察,我对 VS 2015 调试模式得出了 2 个结论:
- 某些内存内容未正确显示(或在 GUI 中刷新)。
- 某些内存读取操作无法正常工作。
但是程序在不调试的时候运行很流畅
有人知道为什么会这样吗?
添加 1 - 5:08 下午 10/14/2019
感谢@MichaelBurr。我想我现在可以解释了。
根本原因是我在 0x00007FFB...369
在反汇编代码级别添加了一个断点,而不是 C 源代码级别。
当我这样做时,VS 调试器 确实 在位置 0x00007FFB...369
添加了一条 0xCC
指令。但似乎 Visual Studio 2015 年竭尽全力掩盖这一事实。下面是断点在0x00007FFB...369
的内存内容显示,我们可以看到0x00007FFB...369
仍然保持旧值0x48
。
但是在我手动将内存从0x00007FFB...360
复制到0x00007FFB...36e
到其他地方之后。偏移 0x9
处的 0xCC
指令是 公开的 。见下文:
当我修改0x00007FFB...369
处的内容时,Visual Studio似乎被提醒,它只是将内容恢复到旧的保留内容,即0x48
。不是我新写的
但我认为这种修复没有任何意义。此时不应以任何方式触发保留字节内容的恢复。更合理的做法是稍微更新断点的位置,将 0xCC 指令插入到新位置。因为新修改的代码可能会改变"instruction boundary"。这样,可以最好地保留自修改代码的调试体验。但这将需要 Visual Studio 在附近反汇编新代码。并且如果程序员出错,新的指令内容可能无效。
我认为您实际上是在与调试器的 breakpoint/single 步骤处理作斗争。断点通常使用编码为 0xCC
的 int 3
指令实现。当调试器为断点设置 0xCC
时,它必须保存原始值,然后在调试器停止程序执行时替换它。
在正常情况下(未自行修改的代码),这会使检查代码内存区域时出现您预期的情况。但是,如果您的程序修改了由调试器管理的内存,您可能会得到令人困惑的结果,因为调试器将恢复它在设置断点时保存的值(覆盖您的修改)。
我正在编写一些多线程 C 程序。我试图修改函数体开头的几条指令,以将执行重定向到其他地方。
但我注意到在 Visual Studio 2015 内调试时,某些内存位置似乎无法更改,如 Memory
window.
例如:
在下图中,函数 ApSignalMceToOs()
从 0x7FFBBEE51360
开始。 0x7FFBBEE51360
到0x7FFBBEE5136E
的内存范围我已经取消保护修改了
305~312行修改地址范围0x7FFBBEE51360
~0x7FFBBEE5136E
。
在 0x7FFBBEE51369
之前一切都很好。在第 311 行,(uint32_t(((uintptr_t)dst) >> 32
是 0x00007ffb
。
执行完第311行后,本以为0x7FFBBEE51369
~0x7FFBBEE5136C
的内存范围会被填充为fb 7f 00 00
。但是如下图,Visual Studio表示是48 7f 00 00
,其中48
是旧值。
然后我去查看函数ApSignalMceToOs()
的反汇编代码。毫不奇怪,00007FFBBF171365
处的指令是 mov dword ptr [rsp+4], 7F48h
, 应该 是 7FFB
。如下图红色框内
所以直到现在,Visual Studio 2015 告诉我我的修改 会 失败。
但是如上图黄色箭头所示,mov dword ptr [rsp+4], 7F48h
执行完后,我查看了栈区的内容。 令人惊讶的是它确实7f fb
被移到了堆栈上(显示在上方[=136]中的绿色框中=] 图片).
而ret
指令执行后,RIP
寄存器确实变成了00007FFBBEEAD940
,这并不奇怪。见下文:
并且在另一个函数中,正在读取相同的位置。如下图:
code[len]
或 byte ptr [rax]
是保存 48
或 fb
的内存位置。但它显示为 0xcc
,既不是 0x48
也不是 0xfb
。
Visual Studio反汇编代码是从内存内容中解码出来的。所以内存内容还是VS2015如何read/refresh呢才是重点。
基于以上观察,我对 VS 2015 调试模式得出了 2 个结论:
- 某些内存内容未正确显示(或在 GUI 中刷新)。
- 某些内存读取操作无法正常工作。
但是程序在不调试的时候运行很流畅
有人知道为什么会这样吗?
添加 1 - 5:08 下午 10/14/2019
感谢@MichaelBurr。我想我现在可以解释了。
根本原因是我在 0x00007FFB...369
在反汇编代码级别添加了一个断点,而不是 C 源代码级别。
当我这样做时,VS 调试器 确实 在位置 0x00007FFB...369
添加了一条 0xCC
指令。但似乎 Visual Studio 2015 年竭尽全力掩盖这一事实。下面是断点在0x00007FFB...369
的内存内容显示,我们可以看到0x00007FFB...369
仍然保持旧值0x48
。
但是在我手动将内存从0x00007FFB...360
复制到0x00007FFB...36e
到其他地方之后。偏移 0x9
处的 0xCC
指令是 公开的 。见下文:
当我修改0x00007FFB...369
处的内容时,Visual Studio似乎被提醒,它只是将内容恢复到旧的保留内容,即0x48
。不是我新写的
但我认为这种修复没有任何意义。此时不应以任何方式触发保留字节内容的恢复。更合理的做法是稍微更新断点的位置,将 0xCC 指令插入到新位置。因为新修改的代码可能会改变"instruction boundary"。这样,可以最好地保留自修改代码的调试体验。但这将需要 Visual Studio 在附近反汇编新代码。并且如果程序员出错,新的指令内容可能无效。
我认为您实际上是在与调试器的 breakpoint/single 步骤处理作斗争。断点通常使用编码为 0xCC
的 int 3
指令实现。当调试器为断点设置 0xCC
时,它必须保存原始值,然后在调试器停止程序执行时替换它。
在正常情况下(未自行修改的代码),这会使检查代码内存区域时出现您预期的情况。但是,如果您的程序修改了由调试器管理的内存,您可能会得到令人困惑的结果,因为调试器将恢复它在设置断点时保存的值(覆盖您的修改)。