2010年12月17日 星期五

Linux 下程序的記憶體映射

Standard
Linux 下的執行檔為 ELF 格式,其啟動原理與各作業系統上的執行檔一樣,不外乎是載入檔案到記憶體上,並讀取需要的 Shared library link 清單,最後找到於 Filesystem 上對應的 symbol link 和 library,載入外部函式和執行程式。

這邊以 VIM 為例,我們透過 Linux 下的 ldd 指令可以得知執行檔需要的外部 Libraries:
$ ldd /usr/bin/vim
 linux-gate.so.1 =>  (0xb7837000)
 libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb77f2000)
 libncurses.so.5 => /lib/libncurses.so.5 (0xb77b8000)
 libselinux.so.1 => /lib/libselinux.so.1 (0xb779c000)
 libacl.so.1 => /lib/libacl.so.1 (0xb7795000)
 libgpm.so.2 => /usr/lib/libgpm.so.2 (0xb778f000)
 libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7649000)
 libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7645000)
 /lib/ld-linux.so.2 (0xb7838000)
 libattr.so.1 => /lib/libattr.so.1 (0xb763f000)

由以上結果可以看到 VIM 所需的 library,以及在 filesystem 上找到的對應 library ,然後還可以得到當 library 被載入到記憶體上時的起始位置。其中比較特別的是 linux-gate.so.1,實體並不存在於 filesystem 中,這代表 kernel system call 的記憶體映射和其位址。

Kernel 其實有 Interface 提供我們取得更多 Process 的記憶體映射資料,藉由讀取 /proc/[Process ID]/maps 這檔案,我們除了可以得到 Process 連結的 library,還可以得到映射的範圍和使用權限等訊息,。

下面這例子是觀察正在執行的 VIM(PID=17605):
$ cat /proc/17605/maps 
08048000-081c0000 r-xp 00000000 08:05 7029211    /usr/bin/vim.basic
081c0000-081ce000 rw-p 00178000 08:05 7029211    /usr/bin/vim.basic
081ce000-081d8000 rw-p 00000000 00:00 0 
089df000-08b30000 rw-p 00000000 00:00 0          [heap]
b739a000-b73a4000 r-xp 00000000 08:05 4202534    /lib/i686/cmov/libnss_files-2.11.2.so
b73a4000-b73a5000 r--p 00009000 08:05 4202534    /lib/i686/cmov/libnss_files-2.11.2.so
b73a5000-b73a6000 rw-p 0000a000 08:05 4202534    /lib/i686/cmov/libnss_files-2.11.2.so
b73a6000-b73b9000 r-xp 00000000 08:05 4202536    /lib/i686/cmov/libnsl-2.11.2.so
b73b9000-b73ba000 r--p 00012000 08:05 4202536    /lib/i686/cmov/libnsl-2.11.2.so
b73ba000-b73bb000 rw-p 00013000 08:05 4202536    /lib/i686/cmov/libnsl-2.11.2.so
b73bb000-b73bd000 rw-p 00000000 00:00 0 
b73da000-b73f7000 r--p 00000000 08:05 7074661    /usr/share/vim/vim73/lang/zh_TW.UTF-8/LC_MESSAGES/vim.mo
b73f7000-b756d000 r--p 00000000 08:05 7045410    /usr/lib/locale/locale-archive
b756d000-b756e000 rw-p 00000000 00:00 0 
b756e000-b7572000 r-xp 00000000 08:05 4194576    /lib/libattr.so.1.1.0
b7572000-b7573000 rw-p 00003000 08:05 4194576    /lib/libattr.so.1.1.0
b7573000-b7574000 rw-p 00000000 00:00 0 
b7574000-b7576000 r-xp 00000000 08:05 4202526    /lib/i686/cmov/libdl-2.11.2.so
b7576000-b7577000 r--p 00001000 08:05 4202526    /lib/i686/cmov/libdl-2.11.2.so
b7577000-b7578000 rw-p 00002000 08:05 4202526    /lib/i686/cmov/libdl-2.11.2.so
b7578000-b76b8000 r-xp 00000000 08:05 4202537    /lib/i686/cmov/libc-2.11.2.so
b76b8000-b76ba000 r--p 0013f000 08:05 4202537    /lib/i686/cmov/libc-2.11.2.so
b76ba000-b76bb000 rw-p 00141000 08:05 4202537    /lib/i686/cmov/libc-2.11.2.so
b76bb000-b76be000 rw-p 00000000 00:00 0 
b76be000-b76c3000 r-xp 00000000 08:05 11665845   /usr/lib/libgpm.so.2.0.0
b76c3000-b76c4000 rw-p 00004000 08:05 11665845   /usr/lib/libgpm.so.2.0.0
b76c4000-b76ca000 r-xp 00000000 08:05 4194402    /lib/libacl.so.1.1.0
b76ca000-b76cb000 rw-p 00006000 08:05 4194402    /lib/libacl.so.1.1.0
b76cb000-b76e4000 r-xp 00000000 08:05 4194310    /lib/libselinux.so.1
b76e4000-b76e5000 r--p 00018000 08:05 4194310    /lib/libselinux.so.1
b76e5000-b76e6000 rw-p 00019000 08:05 4194310    /lib/libselinux.so.1
b76e6000-b76e7000 rw-p 00000000 00:00 0 
b76e7000-b771e000 r-xp 00000000 08:05 4194642    /lib/libncurses.so.5.7
b771e000-b7721000 rw-p 00036000 08:05 4194642    /lib/libncurses.so.5.7
b7721000-b7745000 r-xp 00000000 08:05 4202516    /lib/i686/cmov/libm-2.11.2.so
b7745000-b7746000 r--p 00023000 08:05 4202516    /lib/i686/cmov/libm-2.11.2.so
b7746000-b7747000 rw-p 00024000 08:05 4202516    /lib/i686/cmov/libm-2.11.2.so
b774a000-b7752000 r-xp 00000000 08:05 4202519    /lib/i686/cmov/libnss_nis-2.11.2.so
b7752000-b7753000 r--p 00008000 08:05 4202519    /lib/i686/cmov/libnss_nis-2.11.2.so
b7753000-b7754000 rw-p 00009000 08:05 4202519    /lib/i686/cmov/libnss_nis-2.11.2.so
b7754000-b775a000 r-xp 00000000 08:05 4202515    /lib/i686/cmov/libnss_compat-2.11.2.so
b775a000-b775b000 r--p 00006000 08:05 4202515    /lib/i686/cmov/libnss_compat-2.11.2.so
b775b000-b775c000 rw-p 00007000 08:05 4202515    /lib/i686/cmov/libnss_compat-2.11.2.so
b775c000-b7763000 r--s 00000000 08:05 1799068    /usr/lib/gconv/gconv-modules.cache
b7763000-b7764000 r--p 00175000 08:05 7045410    /usr/lib/locale/locale-archive
b7764000-b7766000 rw-p 00000000 00:00 0 
b7766000-b7767000 r-xp 00000000 00:00 0          [vdso]
b7767000-b7782000 r-xp 00000000 08:05 4194720    /lib/ld-2.11.2.so
b7782000-b7783000 r--p 0001a000 08:05 4194720    /lib/ld-2.11.2.so
b7783000-b7784000 rw-p 0001b000 08:05 4194720    /lib/ld-2.11.2.so
bff9b000-bffb0000 rw-p 00000000 00:00 0          [stack]

這些看似複雜且嚇人的訊息,每個欄位已清楚描述著此 process 的記憶體映射和對應的實體檔案,由於數量太多,我們暫且抽出 VIM 本身的映射訊息做說明:
08048000-081c0000 r-xp 00000000 08:05 7029211    /usr/bin/vim.basic
081c0000-081ce000 rw-p 00178000 08:05 7029211    /usr/bin/vim.basic

『08048000-081c0000』和『081c0000-081ce000』代表程式在記憶體上的起始點和終點,『r-xp』和『rw-p』是存取權限,『00000000』和『00178000』是映射記憶體位址在實體檔案中的偏移量,『08:05』分別代表的是 major 和 minor device number,而『7029211』是 device node number,最後一欄則為記憶體所對應的實體檔案。

此外,在範例中我們看到了幾乎每個程式和 library 都映射了兩個以上的區塊,這意味著第一段是 code segment,而第二,第三段映射是 data segment (data + bss + heap)。

後記

更多的驗證可以閱讀[The True Story of Hello World] 一文,作者透過簡單的 Hello World 程式驗證 segment 和 memory mapping 的觀念,值得參考。