在Linux下的記憶體臭蟲該如何解決

  Petr將告訴程式設計者如何解決那些令人討厭的記憶體臭蟲。

作者‧Petr Sorfa
譯者‧Jean Ting

  所有的程式都需要記憶體,即便是這個程式沒作任何事情。記憶體錯誤會讓一個程式原本是好的部份也造成錯誤,例如結束程式或發生無法預期的反應。
  記憶體是一個處理資訊的裝置。程式記憶體通常與電腦的實體記憶體(physical memory)總容量有關,但當程式不用時也可儲存在次要儲存器裡,例如儲存在硬碟中。對使用者而言,記憶體被分成二部份:核心本身,以及程式實際的呼 叫,就是對記憶體函式的呼叫如malloc()。

核心記憶體
  作業系統核心所管理的是,特定程式的所有記憶體需求,或者是程式的實體(instance),因為作業系統可同時執行一個程式裡的數個實體 (譯註:由可執行檔案executable file所產生出的實體就是process實體)。當使用者執行某個程式時,核心就會分配一個記憶體區(memory area)給該程式。然後這個程式就會將這個記憶區分成下列各區域來管理:

  .text——只用來儲存程式的唯讀部份。這通常是程式的實際指令程式碼(instruction code)。同一個程式裡的實體(instance)群可共享這一區的記憶體。
  .Static Data——這區是分配給已知記憶體(preknown memory)。通常用來作為整體變數(global variables),以及靜態C++的類別成員。作業系統會將這個記憶體區的副本分配給每個程式的實體。
  .Memory Arena(也就是分散空間break space)——用來儲存動態程式執行的記憶體(dynamic runtime memory)。這一區是由堆積記憶體(heap memory)以及尚未使用的記憶體所構成的。堆積(Heap)就是所有使用者分配的記憶體位址所在。堆積(Heap)會自較低的記憶體位址(lower memory address)發展到較高的位址(higher memory address)。
  .stack——當一個程式作函式呼叫時,目前的函式狀態就需要先被儲存到堆疊(stack)裡。堆疊(stack)會自較高的記憶體位址 (higher memory address)發展到較低的位址(lower memory address)。這是一個獨特的記憶體區,堆疊(stack)的存在是因為程式的每個實體(instance)。

使用者記憶體
  使用者可分配的記憶體就位於記憶體區的堆積(heap)裡。記憶體區的管理則是靠常式malloc()、realloc()、free()以 及calloc()。它們是GNU的C函式庫裡的一部份。然而,這些函式可用其他實作替代,在某些特殊使用上這些實作的表現可能更好(請參考相關資源)。
  在Linux的系統下,程式可在預知的增加量裡(precalculated increments)擴充記憶體的容量,通常一個記憶體依容量分頁或排成限量的直線。一旦堆積(heap)需要更多的容量,需要比記憶體區所能提供還要 多的容量,記憶體常式就會呼叫brk()作系統呼叫,這需要核心額外的記憶體。而實際增加的容量則可藉由呼叫sbrk()來設定。
  從列表1裡,看一下每一個處理程序(process)其目前的堆疊(stack)及其記憶體區(memory areana),挑一個特定的處理程序,看看它放在目錄/proc//maps下的內容,其中pid就是指處理程序的process id(列表1)。

結構
  每次用malloc()來分配新的記憶體,就可獲得比所需還更多一點的記憶體。記憶體常式就利用這個多餘的記憶體來作維護。想要知道分配給使 用者運用的實際記憶體容量有多少,可利用函式呼叫malloc_usable_space()就可知道有多少啦。通常實際容量會大於8位元組。
  記憶體區塊(memory chunk)的結構包括區塊預等(chunk prepended)的容量,以及加到區塊末端的容量(圖2)。這個容量值也具有一個旗標位元,它可指出記憶體管理系統是否在使用目前的記憶體之前,就有立即維護記憶體區塊。
  在GNU C函式庫裡的記憶體常式利用二進位(bin)來儲存容量相近的記憶體區塊,以協助改善效能,並防止記憶區的不完整,在那一片不完整的記憶區裡,佈滿了未使 用的記憶體缺口(memory gaps)。這些記憶體常式也是安全緒(threadsafe)。雖然這些常式快又穩定,但是還是有許多可以改善的區域,例如記憶體速度以及涵蓋範圍 (memory coverage)。

除錯
  記憶體通常會有臭蟲的存在,並造成一些不良反應。其中一種除錯方法是利用可使用記憶體的使用情況來偵測,也就是程式閒置時,記憶體區塊 (memory chunk)的使用情況。雖然這不一定可以馬上就看到問題,但是當一個新的記憶體分配到相同的記憶體區時就會出錯。因此,相同的記憶體區被用來作兩種情況 測試:第一個是會產生未預期的數值,若記憶體區包含有指標值或偏移量(pointer values or offsets)時,就可能會導致程式核心轉錄(core dump)。
  第二個問題是,記憶體區塊的「越區(trampling)」情形。若是程式溢寫越過防線跨過記憶體區塊,而記憶體區塊發生資料被「竄改(corrupted)」時,記憶體管理系統就會當掉,或運作不正常。
  有時候相鄰的記憶體區塊會發生「越區」的情形,而這有可能會造成資料被竄改。而使用者可能要等到程式執行時,發現怪怪的數值以及程式怪異的反應,才知道出錯了。
  同樣地,若是一個可使用的記憶體區塊,其管理資訊遭越區竄改、或被濫用而被破壞了,那麼它的記憶體管理系統很有可能也會發生錯誤。
  記憶體區未分配空間的使用可能也是影響因素之一。它可能使用堆積(heap)以外的記憶體,但它仍在記憶體區之內。這通常不會造成錯誤,直到這個空間被新分配的記憶體用到。這種錯誤很不容易被偵測到,因為一連串的記憶體動作會將它保持在一定的堆積(heap)空間內。
  當程式企圖使用超過可使用的記憶體區時,此時最容易發生錯誤,這種錯誤也最明顯。這會造成區段違規錯誤SIGSEGV(segmentation violation fault),而且程式會自動地轉錄核心(dump core)。
  最具破壞力且最難抓到的記憶體臭蟲就是,程式的堆疊(stack)被竄改。從前面的架構中,以及最重要的從堆疊回傳的位址裡,程式儲存了區域 變數、參數、以及紀錄器(registers)。所以若是堆疊的資料被竄改,一般傳統的除錯器(debugger)不太可能解決得了這種錯誤,而且被改的 堆疊架構一點用處也沒了。要解決這種記憶體的堆疊問題就只能利用少數有開放原始碼的程式 (例如libsafe) ,以及「專用的」記憶體除錯器,因為需要修改程式執行,或加強偵測記憶體堆疊違規的情形。
  還有數個方法可嘗試找到記憶體錯誤。不幸的是,有些會有副作用,例如程式的執行速度會減慢,而且會佔用到更多的記憶體;因此,可能並不適用在加強記憶體(memory-intensive)的程式裡。
  在列表2、3、4裡,有臭蟲的程式範例可用下列的記憶體除蟲劑來解決。
  藉由為環境變數MALLOC_CHECK_設定一個預設值,可令malloc作基本的除錯。為了要取得錯誤描述(error reporting),可將MALLOC_CHECK_設定為1,或者也可設定為2,令函式malloc發生錯誤時放棄程式。其輸出結果可被隱藏,因為在 這個除錯模式裡,描述問題區的方式是利用地址(addresses),而不是用可讀符號(readable symbols)。所以囉,手中有除錯器就不怕找不到這些程式出錯的地方啦!下列記憶體除錯程式的範例則是有設定預設值的情形:
$ MALLOC_CHECK_=1 ./mytest00
malloc: using debugging hooks
hello Linux users
free(): invalid pointer 0x80496d0
hello again
free(): invalid pointer 0x80496d0
realloc(): invalid pointer 0x80496d0
malloc: top chunk is corrupt
hello there

  從輸出結果指出了mytest00.c的問題,就在列表2的第8行,其中strcpy()函式被msg指標溢改(overflows and corrups)了記憶體區塊。因為這個記憶體被竄改了,所以後續一直對message作除錯的動作。
  此外還有一些很棒有開放原始碼的記憶體工具可供利用(請見相關資源)。每個工具解決記憶體臭蟲的種類範圍、輸出結果、以及互動情形都不同。
  電子柵欄(Electric Fence)是一個簡單又好用的工具。其函式會對記憶體檢查數次,當發現有問題時,立即停止程式執行。它通常會作核心轉錄(core dump),然後使用者就可利用除錯器來偵錯。在除錯器裡加上電子柵欄工具,使用威力就更強大了,例如搭配使用GNU除錯器(GNU Debugger;GDB)。當電子柵欄中斷程式時,GDB可在程式錯誤發生的位址上重新獲得控制(列表5)。
  在範例的輸出中顯示了,在GDB除錯器底下建立電子柵欄函式庫執行的測試結果。一開始在mytest00.c第8行裡的違規造成區段違規錯誤(SIGSEGV)。當GDB偵測到錯誤時,提供堆疊軌跡(stack track),使用者即可辨識問題的位址所在。
  libsafe裡只有一些少數的C函式(strcpy、strcat、getwd、gets、scanf、vscanf、fscanf、 realpath、sprintf、以及vsprintf),可用它們來檢查某些可能的堆疊架構邊界違規(stack frame boundary violations)。
  libsafe輸出的結果很簡潔。當堆疊一發生錯誤時,libsafe 就會馬上顯示錯誤訊息,並中斷程式。然而,libsafe還會發e-mail將實際的錯誤內容傳送給數個收信人。就算這樣繞一大圈得到了錯誤內容的描述, 然而使用libsafe的用意是要防止入侵緩衝區溢出的漏洞(exploit buffer overflow),而不僅僅是一份報告而已。利用一點點的編輯,開發人員就可加強libsafe的程式碼,獲得更有用的錯誤內容訊息。另一個方法就是在 GDB除錯器裡執行程式,並在_libsafe_die()函式裡設定一個中斷點(breakpoint),如此一來,當libsafe一偵測到有堆疊違 規的情況發生時,就立刻回應。在mytest01.c第8行裡的函式strcpy()所造成堆疊溢位的結果就是(列表3):
$ LD_PRELOAD=/lib/libsafe.so.1.3 ./mytest01
Detected an attempt to write across stack boundary.(偵測到有資料企圖寫跨過堆疊邊界)
Terminating mytest01.(終止mytest01)
Null message body; hope that's ok (無訊息內容;希望一切正常)

# Email is the sent with the following subject header(以下列2行為標頭傳送電子郵件傳送)

libsafe violation for /tmp/mytest01, pid=27265;
overflow caused by strcpy()

  debauch程式將它的輸出限制在含有地址(addresses)而不是符號(symbols),因此它必須利用除錯器的協助。 debauch具有使用者可指定主動利用GDB的特殊功能。這些功能可提供更好的記憶體配置追蹤結果。所以debauch對記憶體偵錯的種類更徹底也更 多,且錯誤復原的能力也較強(列表6)。
  memprof的主要特色就在GUI介面,它很輕易地就可以查出記憶體流失的發生處。它的功能相當強大,因為它利用函式控制,也就是GDB 透過二進位檔案描述子(binary file descriptor;BFD)函式庫來控制處理程序。圖3展示了memprof偵測到mytest02.c裡函式alloc_two()的流失 (leak)。
  除了有開放原始碼的記憶體工具以外,還有一些專門的工具可供利用,這些專用工具提供了圖形使用者介面,並且比有開放原始碼的工具檢查的還更徹底(見相關資源)。
  最後如果可以的話,關於記憶體除錯還有一招,就是自己寫一個記憶體偵錯函式囉,這也是一個可行方式。因為您自己特殊的需求,自己最了解了,所以要什麼樣的記憶體管理或要加強哪方面的效能,只有您最清楚。
  解決記憶體問題是很重要的!不只為了程式的穩定性,同時也顧慮到安全的問題。市面上有數個可用於Linux系統的記憶體除錯器,每一種都有其 特殊功能以及使用標準。最保險的方式,就是不只使用一種除錯器來測試程式(例如搭配著GDB一起使用),其威力就更強大,而且可偵測的問題範圍更廣。

Petr Sorfa(petrs@sco.com)是Santa Cruz Operation's Development Systems Group的一名成員,也是cscope及Sar3D open-source projects的維護員。拿有Cape Town大學的理學學位,同時也是Rhodes大學的榮譽理學士。他的興趣則包括了open-source計劃、電腦繪圖、開發系統、以及看連環漫畫。

其他的記憶體函式
Boehm Garbage Collector:
http://www.hpl.hp.com/personal/Hans_Boehm/gc/
CSRI:
ftp://ftp.cs.toronto.edu/pub/moraes/malloc.tar.gz
GNU Malloc:
ftp://ftp.cs.colorado.edu/pub/misc/malloc-implementations/
Hoard:
http://www.cs.utexas.edu/users/emery/hoard/
Ptmalloc(GNU C Library):
http://www.malloc.de/en/index.html
QuickFit Malloc:
ftp://ftp.cs.colorado.edu/pub/misc/qf.c
vmalloc (Part of ast):
http://www.research.att.com/sw/download/

有開放原始碼的記憶體檢測工具
ccmalloc:
http://www.inf.ethz.ch/personal/biere/projects/ccmalloc/
Checker:
http://www.gnu.org/software/checker/checker.html
dbmalloc:
http://dickey.his.com/dbmalloc/dbmalloc.html
DbMalloc:
http://www.cs.bris.ac.uk/~mm7323/DbMalloc/
debauch:
http://quorum.tamu.edu/jon/gnu/
dmalloc:
http://dmalloc.com/
Electric Fence:
http://www.perens.com/FreeSoftware/
fda:
http://packages.debian.org/unstable/devel/fda.html
leak:
http://sources.isc.org/devel/memleak/leak.txt
LeakTracer:
http://www.andreasen.org/LeakTracer/
libcw:
http://libcw.sourceforge.net/debugging/
libsafe:
http://www.bell-labs.com/org/11356/libsafe.html
MCheck:
http://www.cs.vu.nl/~rveldema/mcheck/mcheck.html
Memleak(Part of X11R6.4):
ftp://ftp.x.org/pub/R6.4/xc/util/memleak/
Memdebug:
http://www.bss.lu/Memdebug/Memdebug.html
MemProf:
http://people.redhat.com/otaylor/memprof/
Memwatch:
http://www.link-data.com/sourcecode.html
MM:
http://www.engelschall.com/sw/mm/
mpatrol:
http://www.cbmamiga.demon.co.uk/mpatrol/
mpr:
http://freshmeat.net/projects/mpr/
NJAMD:
http://fscked.org/proj/njamd.shtml
YaMa:
http://www.geocities.com/ipsgvm/libyama/index.html
YAMD:
http://www3.hmc.edu/~neldredge/yamd/

專門的記憶體檢測工具
Aprobe:
http://www.aprobe.com/
Insure++:
http://www.parasoft.com/products/insure/
Purify:
http://www.rational.com/products/purify_unix/


列表1 /proc//maps的輸出結果
$ cat /proc/$$/maps
08048000-08091000 r-xp 00000000 03:03 77807 /bin/bash
08091000-08097000 rw-p 00048000 03:03 77807 /bin/bash
08097000-08115000 rwxp 00000000 00:00 0
40000000-40016000 r-xp 00000000 03:03 33122 /lib/ld-2.2.so
40016000-40017000 rw-p 00015000 03:03 33122 /lib/ld-2.2.so
40017000-40018000 rwxp 00000000 00:00 0
40018000-4001a000 rw-p 00000000 00:00 0
40023000-40026000 r-xp 00000000 03:03 31161 /lib/libtermcap.so.2.0.8
40026000-40027000 rw-p 00002000 03:03 31161 /lib/libtermcap.so.2.0.8
40027000-40148000 r-xp 00000000 03:03 33125 /lib/libc-2.2.so
40148000-4014e000 rw-p 00120000 03:03 33125 /lib/libc-2.2.so
4014e000-40152000 rw-p 00000000 00:00 0
40152000-4015c000 r-xp 00000000 03:03 33137 /lib/libnss_files-2.2.so
4015c000-4015d000 rw-p 00009000 03:03 33137 /lib/libnss_files-2.2.so
4015d000-40167000 r-xp 00000000 03:03 33140 /lib/libnss_nisplus-2.2.so
40167000-40169000 rw-p 00009000 03:03 33140 /lib/libnss_nisplus-2.2.so
40169000-4017c000 r-xp 00000000 03:03 33130 /lib/libnsl-2.2.so
4017c000-4017d000 rw-p 00012000 03:03 33130 /lib/libnsl-2.2.so
4017d000-40180000 rw-p 00000000 00:00 0
40180000-4018a000 r-xp 00000000 03:03 33139 /lib/libnss_nis-2.2.so
4018a000-4018b000 rw-p 00009000 03:03 33139 /lib/libnss_nis-2.2.so
bfffc000-c0000000 rwxp ffffd000 00:00 0

列表2 mytest00.c程式範例
#include
#include

int
main (int argc, char **argv)
{
char *msg = malloc (4);
// Allocate 4 bytes

strcpy (msg, "hello Linux users");
// Overflow the allocated memory
printf ("%s\n", msg);
free (msg);
// Free the allocated memory
strcpy (msg, "hello again");
// Write to freed memory
printf ("%s\n", msg);
free (msg);
// Free the freed memory
realloc (msg, 2);
// Reallocate freed memory
strcpy (msg, "hello there");
// Writing to erroneous memory
printf ("%s\n", msg);

return 0;
}

列表3 mytest01.c程式範例
#include
#include

int
main (int argc, char **argv)
{
char msg[4];
// Allocate 4 bytes on the stack

strcpy (msg, "hello Linux users 1234");
// Overflow the stack frame
printf ("%s\n", msg);

return 0;
}

列表4 mytest02.c範例程式
#include

char *alloc_two ()
{
char *tmp = malloc(5005);
// Create a memory leak

return malloc(3300);
}

char *alloc_one ()
{
return malloc (1100);
}

int
main ()
{
char *mem1 = alloc_two ();
char *mem2 = alloc_one ();

free (mem1);

return 0;
}

列表5 在GDB下使用電子柵欄(Electric Fence)作記憶體除錯
$ gdb ./mytest00
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General
Public License, and you are welcome to change it
and/or distribute copies of it under certain
conditions. Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show
warranty" for details.
This GDB was configured as "i386-linux-linux"...
(gdb) r
Starting program: ./mytest00

Electric Fence 2.0.5 Copyright (C) 1987?998 Bruce
Perens.

Program received signal SIGSEGV, Segmentation fault.
strcpy (dest=0x4014fffc "hell", src=0x8049948 "hello
Linux users")
at ../sysdeps/generic/strcpy.c:40
40 ../sysdeps/generic/strcpy.c: No such file or
directory.
(gdb) bt
#0 strcpy (dest=0x4014fffc "hell", src=0x8049948
"hello Linux users")

at ../sysdeps/generic/strcpy.c:40
#1 0x804882b in main (argc=1, argv=0xbffff854) at
mytest00.c:8
(gdb)

列表6 利用debauch作記憶體除錯
$ ./mytest00
hello Linux users
Freeing corrupted data 0x0804c22c, size 4 (from 0x0) Saved return stack: 0x8049aa2 0x804897d 0x4003ee51 0x80488e1
Freeing corrupted data 0x0804c22c; now at stack address: Current return stack: 0x8048d3a 0x8049c64 0x80489b0 0x4003ee51 0x80488e1

hello again
Freeing something twice 0x0804c22c, size 4 (from 0x0) Saved return stack: 0x8049c81 0x80489b0 0x4003ee51 0x80488e1
Freeing something twice 0x0804c22c; now at stack address: Current return stack: 0x8048d3a 0x8049bd2 0x80489de 0x4003ee51 0x80488e1

Reallocing from freed data 0x0804c22c, size 4 (from 0x0) Saved return stack: 0x8049c81 0x80489b0 0x4003ee51 0x80488e1
Reallocing from freed data 0x0804c22c; now at stack address: Current return stack: 0x8048d3a 0x8049db2 0x80489ec 0x4003ee51 0x80488e1

hello there

FinalMemoryCheck

CheckMemory
2 bytes active memory in 1 allocations
4 bytes freed memory held from 1 allocations



本文摘自 -- (凌客誌)
arrow
arrow
    全站熱搜

    Bluelove1968 發表在 痞客邦 留言(0) 人氣()