当每个进程都有自己的私有内存空间,没有外部进程可以访问时,调试器如何访问进程的内存空间?
例如,我可以使用gdb -p <pid>
将gdb连接到正在运行的进程,然后通过gdb访问该进程的所有内存。
gdb是如何做到的?
我在SO上阅读了相关的问题,但没有帖子回答了这一点。
由于这个问题被标记为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_PEEKTEXT
和PTRACE_PEEKDATA
是相同的功能,因此您可以为PTRACE_PEEKDATA
提供代码地址,例如在堆栈上为PTRACE_PEEKTEXT
提供地址,它将完美地为您复制回来。这种区别适用于将内存“分割”为数据内存和代码内存的操作系统/处理器组合。大多数现代操作系统和处理器不会做出这种区别。当然同样适用于PTRACE_POKEDATA
和PTRACE_POKETEXT
。
因此,假设“调试器进程”使用:
long data = ptrace(PTRACE_PEEKDATA, pid, 0x12340128, NULL);
当操作系统使用PTRACE_PEEKDATA
查询0x12340128地址时,它将“查看”该地址所对应的内存映射,如果存在,则将其映射到内核中,然后将数据从0x12340128地址复制到本地内存中,并将内存取消映射,将复制的数据作为返回值传回。
手册中指出了使用的启动方式:
父进程可以通过调用fork(2)并使生成的子进程执行PTRACE_TRACEME(通常跟随着exec(3))来启动跟踪。或者,父进程可以使用PTRACE_ATTACH来开始跟踪现有进程。
有关更多信息,请参阅man ptrace
。
ptrace
可以控制系统上的另一个进程。确实,您需要权限才能这样做,通常情况下,如果您是该进程的所有者并且没有手动删除权限,则会给予这些权限。ptrace
本身可以访问内存、程序计数器、寄存器和几乎所有其他相关事物以进行读写操作。man ptrace
。gdb-x.x.x/gdb/linux-nat.c
文件。在那里,您可以找到访问其他进程进行调试的核心内容。
gdb
启动进程时)。我没有投票支持关闭,但您可以通过这种方式缩小范围,或者命名您想了解更多信息的方法(例如远程调试)。 - Mario