如何在Linux中从用户空间查找variables的物理地址?

我想find在用户空间进程中定义的variables的物理地址? 有没有办法使用root权限来做到这一点?

Solutions Collecting From Web of "如何在Linux中从用户空间查找variables的物理地址?"

首先,你为什么要这样做? 现代VM系统的目的是从物理内存布局的复杂性中移除应用程序员。 给他们每个自己的统一的地址空间,使他们的生活easyer。

如果你真的想这样做,你几乎肯定需要使用一个内核模块。 以正常方式获取变量的虚拟地址,用它来索引进程页表,并读取你找到的值(帧的物理地址)。 然后添加页面偏移量以获得完整的物理地址。 请注意,您将无法使用此地址,而分页启用。

(如果你幸运的话,你可以从/ proc文件系统中得到一个VM区域的帧地址,因此不需要编写一个内核模块。)

之前已经部分回答,正常的程序不需要担心物理地址,因为它们在虚拟地址空间中运行,并且具有所有的便利。 此外,不是每个虚拟地址都有一个物理地址,可能属于映射文件或交换页面。 然而,即使在用户区域,有时看到这个映射也许是有趣的。

为此,Linux内核通过/proc中的一组文件将其映射到userland。 文档可以在这里找到。 简短的摘要:

  1. /proc/$pid/maps提供了虚拟地址的映射列表以及附加信息,例如映射文件的相应文件。
  2. /proc/$pid/pagemap提供关于每个映射页面的更多信息,包括物理地址(如果存在)。

本网站提供了一个C程序,它使用这个接口转储所有正在运行的进程的映射,并说明它的功能。

 #include "stdio.h" #include "unistd.h" #include "inttypes.h" uintptr_t vtop(uintptr_t vaddr) { FILE *pagemap; intptr_t paddr = 0; int offset = (vaddr / sysconf(_SC_PAGESIZE)) * sizeof(uint64_t); uint64_t e; // https://www.kernel.org/doc/Documentation/vm/pagemap.txt if ((pagemap = fopen("/proc/self/pagemap", "r"))) { if (lseek(fileno(pagemap), offset, SEEK_SET) == offset) { if (fread(&e, sizeof(uint64_t), 1, pagemap)) { if (e & (1ULL << 63)) { // page present ? paddr = e & ((1ULL << 54) - 1); // pfn mask paddr = paddr * sysconf(_SC_PAGESIZE); // add offset within page paddr = paddr | (vaddr & (sysconf(_SC_PAGESIZE) - 1)); } } } fclose(pagemap); } return paddr; } 

(编辑:如果是“物理地址”,则表示“存储哪个RAM模块”的级别,则下面的回答是不合适的。)

你不需要root权限来执行此操作。 你需要的是一个调试器。 在这里我们去(在x86_64上使用Linux系统):

首先,我们需要一个小程序来玩。 这个访问一个全局变量并连续打印两次。 它有两个全局变量,我们在后面的内存中找到它们。

 #include <stdio.h>

 int a,b = 0;

 int main(void)
 {
     printf(“a:”);
     if(fscanf(“%d”,&a)<1)
        返回0;

     printf(“a =%d \ n”,myglobal);

     printf(“b:”);
    如果(fscanf(“%d”,&b)<1)
        返回0;

     printf(“a =%d,b =%d \ n”,a,b);

    返回0;
 }

第1步:编译程序并从中除去所有的调试信息,所以我们没有从调试器中得到任何暗示,我们不会在现实生活中遇到任何问题。

 $ gcc -s -W -Wall -Os -o ab ab.c

步骤2:运行程序并输入两个数字中的一个。

 $ ./ab
答:123
 a = 123
 b:_

第3步:找到过程。

 $ ps aux |  grep ab
 roland 21601 0.0 0.0 3648 456 pts / 11 S + 15:17 0:00 ./ab
 roland 21665 0.0 0.0 5132 672 pts / 12 S + 15:18 0:00 grep ab

步骤4:将调试器连接到进程(21601)。

 $ gdb
 ...
 (gdb)附加21601
 ...
 (gdb)在哪里
从/lib/libc.so.6读取()中的#0 0x00007fdecfdd2970
 #1 0x00007fdecfd80b40在_IO_file_underflow()从/lib/libc.so.6
 #2 0x00007fdecfd8230e在_IO_default_uflow()从/lib/libc.so.6
在/lib/libc.so.6的_IO_vfscanf()中#3 0x00007fdecfd66903
 #4 0x00007fdecfd7245c在scanf()从/lib/libc.so.6
 #5 0x0000000000400570在?  ()
来自/lib/libc.so.6的__libc_start_main()中的#6 0x00007fdecfd2f1a6
 #7 0x0000000000400459在?  ()
 #8 0x00007fffd827da48在?  ()
 #9 0x000000000000001c在?  ()
 #10 0x0000000000000001在?  ()
 #11 0x00007fffd827f9a2在?  ()
 #12 0x0000000000000000在?  ()

有趣的框架是数字5,因为它在一些调用main函数的代码和scanf函数之间,所以它必须是我们的main功能。 继续调试会话:

 (gdb)框架5
 ...
 (gdb)反汇编$ pc $ pc + 50
 ...
 0x0000000000400570:test%eax,%eax
 0x0000000000400572:jle 0x40058c <scanf @ plt + 372>
 0x0000000000400574:mov 0x2003fe(%rip),%edx#0x600978 <scanf @ plt + 2098528>
 0x000000000040057a:mov 0x2003fc(%rip),%esi#0x60097c <scanf @ plt + 2098532>
 0x0000000000400580:mov $ 0x40068f,%edi
 0x0000000000400585:xor%eax,%eax
 0x0000000000400587:callq 0x4003f8 <printf @ plt>
 ...

现在我们知道函数printf会得到三个参数,而且两个只有四个字节。 这是一个好兆头,这两个是我们的变量ab 。 所以a的地址是0x600978或0x60097c。 让我们试试看:

 (gdb)x / w 0x60097c        
 0x60097c <scanf @ plt + 2098532>:0x0000007b
 (gdb)x / w 0x600978
 0x600978 <scanf @ plt + 2098528>:0x00000000

因此,首先读取的变量是地址0x60097c(因为0x0000007b是我们输入的123的十六进制表示),而b是0x600978。

仍然在调试器中,我们现在可以修改变量a ,然后继续执行程序。

 (gdb)set *(int *)0x60097c = 1234567
 (gdb)继续

回到那个要求我们输入两个数字的程序中:

 $ ./ab
答:123
 a = 123
 b:5
 a = 1234567,b = 5
 $