天天看點

Linux X86_64位虛拟位址空間布局與試驗

Linux虛拟位址布局

x64 layout

在x86_64下面,其實虛拟位址隻使用了48位。是以C程式裡,列印的位址都是隻有12位16進制。48位位址長度也就是對應了256TB的位址空間。

而在Linux下有效的位址區間是從

0x00000000 00000000 ~ 0x00007FFF FFFFFFFF

還有

0xFFFF8000 00000000 ~ 0xFFFFFFFF FFFFFFFF

兩個位址區間。而每個位址區間都有128TB的位址空間可以使用,是以總共是256TB的可用空間。位址空間的劃分就如下所示

ffffffff`ffffffff     _____________   
                          |            |   
                          |   核心空間  |   
    ffff8000`     |____________|   
                          |            |   
                          |   未使用    |   
                          |   的空間    |   
                          |            |   
    fff`ffffffff     |____________|   
                          |            |   
                          |   使用者空間  |   
    `     |____________|
           

下面分析使用者空間的位址布局。一個程式的記憶體布局,可以通過

cat /proc/<pid>/maps
pmap <pid> -X
           
Linux X86_64位虛拟位址空間布局與試驗

這是一個簡單的helloworld程式的示例布局

  1. 前三行分别是

    Text Segment

    Data Segment

    BSS Segment

    Text Segment

    其實就是存放二進制可執行代碼的位置,是以它的權限是讀與可執行,

    Data Segment

    存放的是靜态常量,是以該位址段權限是隻讀,

    BSS Segment

    存放未初始化的靜态變量,是以也就是可以随意讀寫。
  2. 接下來是

    Heap

    位址段,heap位址是往高位址增長的,是用來動态配置設定記憶體的區域。它跟棧相反,是往高位址增長的,對應的記憶體申請系統調用是brk()。
  3. 接下來的區域是

    Memory Mapping Segment

    。這塊位址也是用來配置設定記憶體區域的,一般是用來把檔案映射進記憶體用的,但是你也可以直接在這裡申請記憶體空間來使用,對應的記憶體申請系統調用是mmap()。
  4. 再下面就是

    Stack

    位址段了。這個棧已經用了136KB了。棧的最大範圍,我們可以通過

    prlimit

    指令看到,預設的情況下是8MB,和Linux-x86一樣。
  5. 再下面就是

    vvar

    vdso

    vsyscall

    了。這三個東西都為了加速通路核心資料,比如讀取時間

    gettimeofday

    ,肯定不能頻繁地進行系統調用陷入核心,是以就映射到使用者空間了。所有程式都有這3個映射位址段。
關于vvar,vdso和vsyscall。先說vsyscall,這東西出現最早,比如讀取時間

gettimeofday

,核心會把時間資料和

gettimeofday

的實作映射到這塊區域,使用者空間可以直接調用。但是vsyscall區域太小了,而且映射區域固定,有安全問題。後來又造出了vdso,之是以保留是為了相容使用者空間程式。vdso相當于加載一個linux-vd.so庫檔案一樣(名字也由此而來),也就是把一些函數實作映射到這個區域,而vvar也就是存放資料的地方了,那麼使用者可以通過調用vdso裡的函數,使用vvar裡的資料,來獲得自己想要的資訊。而且位址是随機的,更安全。

具體的x64的記憶體布局如下圖所示:

Linux X86_64位虛拟位址空間布局與試驗

可以發現裡面有不少Random xx offset,這是Linux裡的ASLR政策。ASLR的話就是Address space layout randomization,是一種安全機制,主要防止緩沖區溢出攻擊。

brk嘗試

brk系統調用可以通過調整heap區域的brk指針,進而調整heap對的虛拟記憶體空間大小。實驗代碼如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    void *curr_brk, *tmp_brk = NULL;
    printf("Welcome to sbrk example:%d\n", getpid());
    tmp_brk = curr_brk = sbrk();   //獲得當然brk的位址
    printf("Program Break Location1:%p\n", curr_brk);
    getchar();
    brk(curr_brk+); //增加Heap段4KB空間
    curr_brk = sbrk();
    printf("Program break Location2:%p\n", curr_brk);
    int* a = (int *)tmp_brk+; //嘗試使用剛剛擴充的記憶體空間
    *a = ;
    printf("%d\n", *a); //沒有問題
    getchar();
    brk(tmp_brk);   //恢複到原來的brk大小
    curr_brk = sbrk();
    printf("Program Break Location3:%p\n", curr_brk);
    getchar();
    printf("%d\n", *a); //産生SIGSEGV,Segmentation Fault
    return ;
}
           

初始狀态

Linux X86_64位虛拟位址空間布局與試驗

按回車,調用brk(curr_brk+4096)之後

Linux X86_64位虛拟位址空間布局與試驗

看到heap大小從132KB變成了136KB,而且位址空間可以正常使用。再次回車,調用brk(tmp_brk)

Linux X86_64位虛拟位址空間布局與試驗

發現heap堆大小變回去了。再回車發現會産生段錯誤,因為記憶體已經被回收,程序無法使用該記憶體位址。

mmap嘗試

mmap系統調用是把一個檔案映射到一段記憶體位址空間,如上面截圖裡的/lib/libc.so所做的一樣。也可以匿名直接申請一段記憶體空間使用。實驗代碼如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <errno.h>

int main()
{
    void * addr;
    printf("Welcome to sbrk example:%d\n", getpid());
    getchar();
    //申請4096*2=8KB的空間,位址空間可讀寫,私有且匿名。
    addr = mmap(NULL,  * , PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, , );
    if(addr < ) {
        perror("mmap");
    }
    printf("Program Allocate :%p\n", addr);
    getchar();
    if(munmap(addr, ) != ){
        perror("munmap");
    }
    return ;
}
           

運作結果:在mmap之前,有這麼一條位址空間,大小是12KB

Linux X86_64位虛拟位址空間布局與試驗

在mmap之後,發現大小變成了20KB,剛好是我們申請了8KB

Linux X86_64位虛拟位址空間布局與試驗
注意mmap申請記憶體的時候,如果申請位址長度小于一個PAGE_SIZE=2KB=4096位元組=0x1000位元組,那麼會直接申請2KB。而PAGE_SIZE的話可以通過

getconf PAGE_SIZE

指令來檢視。

其實mmap不一定要在

Memory Mapping Segment

進行記憶體申請,你可以指定任意的記憶體位址,當然隻要不跟已有的沖突就好。這個位址也一定要是000結尾,才使得能頁對齊。比如你可以申請0x100000的位址段,他的位址比

Text Segment

的位址更低。這沒有任何問題。而mmap可申請的最低的位址由一個mmap_min_addr核心參數來控制,可以通過下面的指令讀取。

sysctl vm.mmap_min_addr
           

在debian下,它的預設值是4096。也就是你申請的首位址必須比0x1000大。

繼續閱讀