2010-03-13 56 views
12

我想找到用戶空間進程中定義的變量的物理地址?有沒有辦法使用root權限來完成它?如何在Linux中從用戶空間找到變量的物理地址?

+0

大概用/ dev/mem? – user2284570 2014-07-19 08:59:10

+1

一些相關的信息在http://stackoverflow.com/questions/5748492/is-there-any-api-for-determining-the-physical-address-from-virtual-address-in-li – 2015-11-04 02:12:59

回答

2

首先,你爲什麼要這樣做?現代VM系統的目的是將應用程序員從物理內存佈局的複雜性中移除。給他們分配自己的統一地址空間,讓他們的生活變得輕鬆自在。

如果您確實想這樣做,您幾乎肯定需要使用內核模塊。以正常方式獲取變量的虛擬地址,使用它來索引進程頁表並讀取您找到的值(幀的物理地址)。然後添加頁面偏移量以獲取完整的物理地址。請注意,在啓用分頁功能時,您將無法使用此地址。

(如果你運氣好,你可能能夠得到從/ proc文件系統中的VM區域的幀地址,因而難道不要求寫一個內核模塊。)

+4

...除非您將頁面鎖定到內存中,該物理地址可能隨時更改。 – caf 2010-03-14 09:22:03

+1

您不需要編寫內核模塊:正如其他示例所解釋的,這已通過'/ proc/$ pid/pagemap'公開。 – poolie 2017-02-11 02:16:08

+0

NUMA體系結構中可能有趣的是知道變量的物理地址 – horro 2017-07-26 10:35:48

-5

(編輯:如果通過「物理地址「,你的意思是」RAM模塊存儲在哪個級別「,那麼下面的答案是不合適的。)

你不需要root權限就可以做到這一點。你需要的是一個調試器。在這裏,我們走(在x86_64上使用Linux系統):

首先我們需要一個小程序來玩。這個訪問一個全局變量並連續打印兩次。它有兩個全局變量,我們稍後在內存中找到它們。

 
#include <stdio.h> 

int a, b = 0; 

int main(void) 
{ 
    printf("a: "); 
    if (fscanf("%d", &a) < 1) 
     return 0; 

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

    printf("b: "); 
    if (fscanf("%d", &b) < 1) 
     return 0; 

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

    return 0; 
} 

第1步:編譯程序,並從中去除所有調試信息,所以我們沒有得到我們不會在真實的生活狀況得到了調試任何提示。

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

步驟2:運行程序並輸入兩個數字中的一個。

 
$ ./ab 
a: 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) attach 21601 
... 
(gdb) where 
#0 0x00007fdecfdd2970 in read() from /lib/libc.so.6 
#1 0x00007fdecfd80b40 in _IO_file_underflow() from /lib/libc.so.6 
#2 0x00007fdecfd8230e in _IO_default_uflow() from /lib/libc.so.6 
#3 0x00007fdecfd66903 in _IO_vfscanf() from /lib/libc.so.6 
#4 0x00007fdecfd7245c in scanf() from /lib/libc.so.6 
#5 0x0000000000400570 in ??() 
#6 0x00007fdecfd2f1a6 in __libc_start_main() from /lib/libc.so.6 
#7 0x0000000000400459 in ??() 
#8 0x00007fffd827da48 in ??() 
#9 0x000000000000001c in ??() 
#10 0x0000000000000001 in ??() 
#11 0x00007fffd827f9a2 in ??() 
#12 0x0000000000000000 in ??() 

有趣的幀編號爲5,因爲它是一些代碼調用main功能和scanf功能的,所以它必須是我們main功能。繼續調試會話:

 
(gdb) frame 5 
... 
(gdb) disassemble $pc $pc+50 
... 
0x0000000000400570 :  test %eax,%eax 
0x0000000000400572 :  jle 0x40058c <[email protected]+372> 
0x0000000000400574 :  mov 0x2003fe(%rip),%edx  # 0x600978 <[email protected]+2098528> 
0x000000000040057a :  mov 0x2003fc(%rip),%esi  # 0x60097c <[email protected]+2098532> 
0x0000000000400580 :  mov $0x40068f,%edi 
0x0000000000400585 :  xor %eax,%eax 
0x0000000000400587 :  callq 0x4003f8 <[email protected]> 
... 

現在我們知道該函數printf將得到三個參數,和兩個有相互只有四個字節的路程。這是一個好兆頭,這兩個是我們的變量ab。所以a的地址是0x600978或0x60097c。讓我們來看看通過嘗試:

 
(gdb) x/w 0x60097c   
0x60097c <[email protected]+2098532>: 0x0000007b 
(gdb) x/w 0x600978 
0x600978 <[email protected]+2098528>: 0x00000000 

所以a,即在第一次閱讀的變量,在地址0x60097c(因爲0X0000007B是123的十六進制表示,這是我們進入),並b是0x600978。

仍然在調試器中,我們現在可以修改變量a,然後繼續執行程序。

 
(gdb) set *(int *)0x60097c = 1234567 
(gdb) continue 

返回在要求我們的程序輸入兩個數字:

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

這爲您提供虛擬地址,而不是物理地址。 – 2010-05-29 11:58:21

16

之前由於部分答案,正常的程序應該不需要擔心物理地址,因此,在虛擬地址空間中運行所有的便利。此外,並非每個虛擬地址都有物理地址,可能屬於映射文件或交換頁面。但是,有時甚至在用戶區看到這種映射可能會很有趣。

爲此,Linux內核通過/proc中的一組文件公開其映射到userland的映射。該文檔可以找到here。簡短的摘要:

  1. /proc/$pid/maps提供的附加信息一起的虛擬地址,諸如用於映射文件對應的文件的映射的列表。
  2. /proc/$pid/pagemap提供了有關每個映射頁面的更多信息,包括物理地址(如果存在)。

This website提供了一個C程序,該程序使用此接口轉儲所有正在運行的進程的映射並解釋它的作用。

+0

測試的最小示例:https://stackoverflow.com/questions/17021214/how-to-decode-proc-pid-pagemap-entries-in-linux/45126141#45126141 – 2017-07-16 13:03:57

14
#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; 
} 
相關問題