一个调试器如何查看另一个进程的内存?

12

当每个进程都有自己的私有内存空间,没有外部进程可以访问时,调试器如何访问进程的内存空间?

例如,我可以使用gdb -p <pid>将gdb连接到正在运行的进程,然后通过gdb访问该进程的所有内存。

gdb是如何做到的?

我在SO上阅读了相关的问题,但没有帖子回答了这一点。


这实际上取决于调试器的参与方式以及它连接到目标进程的方式。您可能希望缩小范围(例如,在使用 gdb 启动进程时)。我没有投票支持关闭,但您可以通过这种方式缩小范围,或者命名您想了解更多信息的方法(例如远程调试)。 - Mario
3个回答

16

由于这个问题被标记为Linux和Unix,我将扩展David Scwartz所说的内容,简而言之是“操作系统中有一个API”。Windows也适用同样的基本原则,但实际实现不同,尽管我怀疑操作系统内部的实现进行了相同的操作,但我们无法检查Windows的源代码(但是可以通过理解操作系统和处理器的工作方式,有点推测出必须发生的事情!)

Linux有一个名为ptrace的函数,允许一个进程(在某些特权检查后)以多种方式检查另一个进程。它只需要一次调用,但第一个参数是“你想做什么”。以下是一些最基本的例子-还有几十个其他的更少见的操作:

  • PTRACE_ATTACH - 连接到进程。
  • PTRACE_PEEKTEXT - 查看已连接进程的代码内存(例如反汇编代码)
  • PTRACE_PEEKDATA - 查看已连接进程的数据内存(以显示变量)
  • PTRACE_POKETEXT - 写入进程的代码内存
  • PTRACE_POKEDATA - 写入进程的数据内存。
  • PTRACE_GETREGS - 复制当前的寄存器值。
  • PTRACE_SETREGS - 更改当前的寄存器值(例如,在调试命令set variable x = 7中,如果变量x恰好在一个寄存器中)

在Linux中,由于内存“都是一样的”,实际上PTRACE_PEEKTEXTPTRACE_PEEKDATA是相同的功能,因此您可以为PTRACE_PEEKDATA提供代码地址,例如在堆栈上为PTRACE_PEEKTEXT提供地址,它将完美地为您复制回来。这种区别适用于将内存“分割”为数据内存和代码内存的操作系统/处理器组合。大多数现代操作系统和处理器不会做出这种区别。当然同样适用于PTRACE_POKEDATAPTRACE_POKETEXT

因此,假设“调试器进程”使用:

long data = ptrace(PTRACE_PEEKDATA, pid, 0x12340128, NULL); 

当操作系统使用PTRACE_PEEKDATA查询0x12340128地址时,它将“查看”该地址所对应的内存映射,如果存在,则将其映射到内核中,然后将数据从0x12340128地址复制到本地内存中,并将内存取消映射,将复制的数据作为返回值传回。

手册中指出了使用的启动方式:

父进程可以通过调用fork(2)并使生成的子进程执行PTRACE_TRACEME(通常跟随着exec(3))来启动跟踪。或者,父进程可以使用PTRACE_ATTACH来开始跟踪现有进程。

有关更多信息,请参阅man ptrace


3
对于Linux调试,有一个系统调用ptrace可以控制系统上的另一个进程。确实,您需要权限才能这样做,通常情况下,如果您是该进程的所有者并且没有手动删除权限,则会给予这些权限。
系统调用ptrace本身可以访问内存、程序计数器、寄存器和几乎所有其他相关事物以进行读写操作。
有关详细信息,请参见man ptrace
如果您对调试器中它是如何工作感兴趣,请查看gdb-x.x.x/gdb/linux-nat.c文件。在那里,您可以找到访问其他进程进行调试的核心内容。

2
当每个进程都有自己的私有内存空间且没有外部进程可以访问时,这个说法是错误的。对于具有正确权限和使用正确API的外部进程来说,可以访问其他进程的内存。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接