1. 程式人生 > 實用技巧 >/proc/<pid>/maps簡要分析【轉】

/proc/<pid>/maps簡要分析【轉】

轉自:https://www.cnblogs.com/arnoldlu/p/10272466.html

定位記憶體洩漏基本上是從巨集觀到微觀,進而定位到程式碼位置。

/proc/meminfo可以看到整個系統記憶體消耗情況,使用top可以看到每個程序的VIRT(虛擬記憶體)和RES(實際佔用記憶體),基本上就可以將洩漏記憶體定位到程序範圍。

之前也大概瞭解過/proc/self/maps,基於裡面資訊能大概判斷洩露的記憶體的屬性,是哪個區域在洩漏、對應哪個檔案。輔助工具procmem輸出更可讀的maps資訊。

下面分別從程序地址空間各段劃分、maps和段如何對應、各段異常如何定位三方面展開。

1.程序地址空間劃分

1.1 段及其作用

首先通過下圖簡單看一下,程序地址空間從低地址開始依次是程式碼段(Text)、資料段(Data)、BSS段、堆、記憶體對映段(mmap)、棧。

1.1.1 程式碼段(text)

程式碼段也稱正文段或文字段,通常用於存放程式執行程式碼(即CPU執行的機器指令)。一般C語言執行語句都編譯成機器程式碼儲存在程式碼段。通常程式碼段是可共享的,因此頻繁執行的程式只需要在記憶體中擁有一份拷貝即可。

程式碼段通常屬於只讀,以防止其他程式意外地修改其指令(對該段的寫操作將導致段錯誤)。某些架構也允許程式碼段為可寫,即允許修改程式。

程式碼段指令根據程式設計流程依次執行,對於順序指令,只會執行一次(每個程序);若有反覆,則需使用跳轉指令;若進行遞迴,則需要藉助棧來實現。

程式碼段指令中包括操作碼和操作物件(或物件地址引用)。若操作物件是立即數(具體數值),將直接包含在程式碼中;若是區域性資料,將在棧區分配空間,然後引用該資料地址;若位於BSS段和資料段,同樣引用該資料地址。

程式碼段最容易受優化措施影響。

1.1.2 資料段(Data)

資料段通常用於存放程式中已初始化且初值不為0的全域性變數和靜態區域性變數。資料段屬於靜態記憶體分配(靜態儲存區),可讀可寫。

資料段儲存在目標檔案中(在嵌入式系統裡一般固化在映象檔案中),其內容由程式初始化。例如,對於全域性變數int gVar = 10,必須在目標檔案資料段中儲存10這個資料,然後在程式載入時複製到相應的記憶體。

資料段與BSS段的區別如下:

1) BSS段不佔用物理檔案尺寸,但佔用記憶體空間;資料段佔用物理檔案,也佔用記憶體空間。

對於大型陣列如int ar0[10000] = {1, 2, 3, ...}和int ar1[10000],ar1放在BSS段,只記錄共有10000*4個位元組需要初始化為0,而不是像ar0那樣記錄每個資料1、2、3...,此時BSS為目標檔案所節省的磁碟空間相當可觀。

2) 當程式讀取資料段的資料時,系統會出發缺頁故障,從而分配相應的實體記憶體;當程式讀取BSS段的資料時,核心會將其轉到一個全零頁面,不會發生缺頁故障,也不會為其分配相應的實體記憶體。

執行時資料段和BSS段的整個區段通常稱為資料區。某些資料中“資料段”指代資料段 + BSS段 + 堆。

1.1.3 BSS段

BSS(Block Started by Symbol)段中通常存放程式中以下符號:

  • 未初始化的全域性變數和靜態區域性變數
  • 初始值為0的全域性變數和靜態區域性變數(依賴於編譯器實現)
  • 未定義且初值不為0的符號(該初值即common block的大小)

C語言中,未顯式初始化的靜態分配變數被初始化為0(算術型別)或空指標(指標型別)。由於程式載入時,BSS會被作業系統清零,所以未賦初值或初值為0的全域性變數都在BSS中。BSS段僅為未初始化的靜態分配變數預留位置,在目標檔案中並不佔據空間,這樣可減少目標檔案體積。但程式執行時需為變數分配記憶體空間,故目標檔案必須記錄所有未初始化的靜態分配變數大小總和(通過start_bss和end_bss地址寫入機器程式碼)。當載入器(loader)載入程式時,將為BSS段分配的記憶體初始化為0。在嵌入式軟體中,進入main()函式之前BSS段被C執行時系統對映到初始化為全零的記憶體(效率較高)。

注意,儘管均放置於BSS段,但初值為0的全域性變數是強符號,而未初始化的全域性變數是弱符號。若其他地方已定義同名的強符號(初值可能非0),則弱符號與之連結時不會引起重定義錯誤,但執行時的初值可能並非期望值(會被強符號覆蓋)。因此,定義全域性變數時,若只有本檔案使用,則儘量使用static關鍵字修飾;否則需要為全域性變數定義賦初值(哪怕0值),保證該變數為強符號,以便連結時發現變數名衝突,而不是被未知值覆蓋。

某些編譯器將未初始化的全域性變數儲存在common段,連結時再將其放入BSS段。在編譯階段可通過-fno-common選項來禁止將未初始化的全域性變數放入common段。

1.1.4 堆(heap)

堆用於存放程序執行時動態分配的記憶體段,可動態擴張或縮減。堆中內容是匿名的,不能按名字直接訪問,只能通過指標間接訪問。當程序呼叫malloc(C)/new(C++)等函式分配記憶體時,新分配的記憶體動態新增到堆上(擴張);當呼叫free(C)/delete(C++)等函式釋放記憶體時,被釋放的記憶體從堆中剔除(縮減)。

分配的堆記憶體是經過位元組對齊的空間,以適合原子操作。堆管理器通過連結串列管理每個申請的記憶體,由於堆申請和釋放是無序的,最終會產生記憶體碎片。堆記憶體一般由應用程式分配釋放,回收的記憶體可供重新使用。若程式設計師不釋放,程式結束時作業系統可能會自動回收。

堆的末端由break指標標識,當堆管理器需要更多記憶體時,可通過系統呼叫brk()和sbrk()來移動break指標以擴張堆,一般由系統自動呼叫。

使用堆時經常出現兩種問題:1) 釋放或改寫仍在使用的記憶體(“記憶體破壞”);2)未釋放不再使用的記憶體(“記憶體洩漏”)。當釋放次數少於申請次數時,可能已造成記憶體洩漏。洩漏的記憶體往往比忘記釋放的資料結構更大,因為所分配的記憶體通常會圓整為下個大於申請數量的2的冪次(如申請212B,會圓整為256B)。

1.1.5 記憶體對映段(mmap)

此處,核心將硬碟檔案的內容直接對映到記憶體, 任何應用程式都可通過Linux的mmap()系統呼叫請求這種對映。記憶體對映是一種方便高效的檔案I/O方式, 因而被用於裝載動態共享庫。使用者也可建立匿名記憶體對映,該對映沒有對應的檔案, 可用於存放程式資料。在 Linux中,若通過malloc()請求一大塊記憶體,C執行庫將建立一個匿名記憶體對映,而不使用堆記憶體。”大塊” 意味著比閾值 MMAP_THRESHOLD還大,預設為128KB,可通過mallopt()調整。

該區域用於對映可執行檔案用到的動態連結庫。在Linux 2.4版本中,若可執行檔案依賴共享庫,則系統會為這些動態庫在從0x40000000開始的地址分配相應空間,並在程式裝載時將其載入到該空間。在Linux 2.6核心中,共享庫的起始地址被往上移動至更靠近棧區的位置。

從程序地址空間的佈局可以看到,在有共享庫的情況下,留給堆的可用空間還有兩處:一處是從.bss段到0x40000000,約不到1GB的空間;另一處是從共享庫到棧之間的空間,約不到2GB。這兩塊空間大小取決於棧、共享庫的大小和數量。這樣來看,是否應用程式可申請的最大堆空間只有2GB?事實上,這與Linux核心版本有關。在上面給出的程序地址空間經典佈局圖中,共享庫的裝載地址為0x40000000,這實際上是Linux kernel 2.6版本之前的情況了,在2.6版本里,共享庫的裝載地址已經被挪到靠近棧的位置,即位於0xBFxxxxxx附近,因此,此時的堆範圍就不會被共享庫分割成2個“碎片”,故kernel 2.6的32位Linux系統中,malloc申請的最大記憶體理論值在2.9GB左右。

1.1.6 棧(stack)

棧又稱堆疊,由編譯器自動分配釋放,行為類似資料結構中的棧(先進後出)。堆疊主要有三個用途:

  • 為函式內部宣告的非靜態區域性變數(C語言中稱“自動變數”)提供儲存空間。
  • 記錄函式呼叫過程相關的維護性資訊,稱為棧幀(Stack Frame)或過程活動記錄(Procedure Activation Record)。它包括函式返回地址,不適合裝入暫存器的函式引數及一些暫存器值的儲存。除遞迴呼叫外,堆疊並非必需。因為編譯時可獲知區域性變數,引數和返回地址所需空間,並將其分配於BSS段。
  • 臨時儲存區,用於暫存長算術表示式部分計算結果或alloca()函式分配的棧內記憶體。

持續地重用棧空間有助於使活躍的棧記憶體保持在CPU快取中,從而加速訪問。程序中的每個執行緒都有屬於自己的棧。向棧中不斷壓入資料時,若超出其容量就會耗盡棧對應的記憶體區域,從而觸發一個頁錯誤。此時若棧的大小低於堆疊最大值RLIMIT_STACK(通常是8M),則棧會動態增長,程式繼續執行。對映的棧區擴充套件到所需大小後,不再收縮。

Linux中ulimit -s命令可檢視和設定堆疊最大值,當程式使用的堆疊超過該值時, 發生棧溢位(Stack Overflow),程式收到一個段錯誤(Segmentation Fault)。注意,調高堆疊容量可能會增加記憶體開銷和啟動時間。

堆疊既可向下增長(向記憶體低地址)也可向上增長, 這依賴於具體的實現。本文所述堆疊向下增長。

棧的大小在執行時由核心動態調整。

1.1.7 棧和堆的區別

①管理方式:棧由編譯器自動管理;堆由程式設計師控制,使用方便,但易產生記憶體洩露。

②生長方向:棧向低地址擴充套件(即”向下生長”),是連續的記憶體區域;堆向高地址擴充套件(即”向上生長”),是不連續的記憶體區域。這是由於系統用連結串列來儲存空閒記憶體地址,自然不連續,而連結串列從低地址向高地址遍歷。

③空間大小:棧頂地址和棧的最大容量由系統預先規定(通常預設2M或10M);堆的大小則受限於計算機系統中有效的虛擬記憶體,32位Linux系統中堆記憶體可達2.9G空間。

④儲存內容:棧在函式呼叫時,首先壓入主調函式中下條指令(函式呼叫語句的下條可執行語句)的地址,然後是函式實參,然後是被調函式的區域性變數。本次呼叫結束後,區域性變數先出棧,然後是引數,最後棧頂指標指向最開始存的指令地址,程式由該點繼續執行下條可執行語句。堆通常在頭部用一個位元組存放其大小,堆用於儲存生存期與函式呼叫無關的資料,具體內容由程式設計師安排。

⑤分配方式:棧可靜態分配或動態分配。靜態分配由編譯器完成,如區域性變數的分配。動態分配由alloca函式在棧上申請空間,用完後自動釋放。堆只能動態分配且手工釋放。

⑥分配效率:棧由計算機底層提供支援:分配專門的暫存器存放棧地址,壓棧出棧由專門的指令執行,因此效率較高。堆由函式庫提供,機制複雜,效率比棧低得多。Windows系統中VirtualAlloc可直接在程序地址空間中分配一塊記憶體,快速且靈活。

⑦分配後系統響應:只要棧剩餘空間大於所申請空間,系統將為程式提供記憶體,否則報告異常提示棧溢位。

作業系統為堆維護一個記錄空閒記憶體地址的連結串列。當系統收到程式的記憶體分配申請時,會遍歷該連結串列尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點連結串列中刪除,並將該結點空間分配給程式。若無足夠大小的空間(可能由於記憶體碎片太多),有可能呼叫系統功能去增加程式資料段的記憶體空間,以便有機會分到足夠大小的記憶體,然後進行返回。,大多數系統會在該記憶體空間首地址處記錄本次分配的記憶體大小,供後續的釋放函式(如free/delete)正確釋放本記憶體空間。

此外,由於找到的堆結點大小不一定正好等於申請的大小,系統會自動將多餘的部分重新放入空閒連結串列中。

⑧碎片問題:棧不會存在碎片問題,因為棧是先進後出的佇列,記憶體塊彈出棧之前,在其上面的後進的棧內容已彈出。而頻繁申請釋放操作會造成堆記憶體空間的不連續,從而造成大量碎片,使程式效率降低。

可見,堆容易造成記憶體碎片;由於沒有專門的系統支援,效率很低;由於可能引發使用者態和核心態切換,記憶體申請的代價更為昂貴。所以棧在程式中應用最廣泛,函式呼叫也利用棧來完成,呼叫過程中的引數、返回地址、棧基指標和區域性變數等都採用棧的方式存放。所以,建議儘量使用棧,僅在分配大量或大塊記憶體空間時使用堆。

使用棧和堆時應避免越界發生,否則可能程式崩潰或破壞程式堆、棧結構,產生意想不到的後果。

1.2 段和mm_struct關係

struct mm_struct是程序記憶體結構體,裡面的引數和各段地址對應關係如下圖。

struct mm_struct {
    struct vm_area_struct *mmap;        /* list of VMAs */
...
    unsigned long mmap_base;        /* base of mmap area */
    unsigned long mmap_legacy_base;         /* base of mmap area in bottom-up allocations */...
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack;
    unsigned long arg_start, arg_end, env_start, env_end;
...
    struct mm_rss_stat rss_stat;
...
};

mm_strutc資料結構和段對應關係如下:

2. /proc/<pid>/maps

在瞭解了段及其作用之後,再來看看maps中各個vma對應哪個段?

static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma, int is_pid)
{
    struct mm_struct *mm = vma->vm_mm;
    struct file *file = vma->vm_file;
    struct proc_maps_private *priv = m->private;
    vm_flags_t flags = vma->vm_flags;
    unsigned long ino = 0;
    unsigned long long pgoff = 0;
    unsigned long start, end;
    dev_t dev = 0;
    const char *name = NULL;

    if (file) {
        struct inode *inode = file_inode(vma->vm_file);
        dev = inode->i_sb->s_dev;
        ino = inode->i_ino;
        pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;------------------------------此vma第一頁在地址空間中是第幾頁。
    }

    /* We don't show the stack guard page in /proc/maps */
    start = vma->vm_start;
    end = vma->vm_end;

    seq_setwidth(m, 25 + sizeof(void *) * 6 - 1);
    seq_printf(m, "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu ",
            start,
            end,
            flags & VM_READ ? 'r' : '-',
            flags & VM_WRITE ? 'w' : '-',
            flags & VM_EXEC ? 'x' : '-',
            flags & VM_MAYSHARE ? 's' : 'p',
            pgoff,
            MAJOR(dev), MINOR(dev), ino);-------------------------------------------首先列印maps裡面前5項資料,起訖地址、屬性、偏移地址、主從裝置號、inode編號。

    /*
     * Print the dentry name for named mappings, and a
     * special [heap] marker for the heap:
     */
    if (file) {---------------------------------------------------------------------如果是個檔案,那麼列印檔案完整路徑。
        seq_pad(m, ' ');
        seq_file_path(m, file, "\n");
        goto done;
    }

    if (vma->vm_ops && vma->vm_ops->name) {
        name = vma->vm_ops->name(vma);
        if (name)
            goto done;
    }

    name = arch_vma_name(vma);
    if (!name) {
        if (!mm) {
            name = "[vdso]";---------------------------------------------------------vDSO是系統呼叫相關,詳細資訊見vDSOgoto done;
        }

        if (vma->vm_start <= mm->brk &&
            vma->vm_end >= mm->start_brk) {------------------------------------------滿足start_brk <= vma <= brk,則其vma是[heap]。
            name = "[heap]";
            goto done;
        }

        if (is_stack(priv, vma))-----------------------------------------------------滿足vma包含所在地址空間的start_stack地址,則vma是[stack]。
            name = "[stack]";
    }

done:
    if (name) {
        seq_pad(m, ' ');
        seq_puts(m, name);
    }
    seq_putc(m, '\n');
}

static int is_stack(struct proc_maps_private *priv,
            struct vm_area_struct *vma)
{
    return vma->vm_start <= vma->vm_mm->start_stack &&------------------------------判斷一個vma是否屬於stack,只需要判斷start_stack是否在其區域內。
        vma->vm_end >= vma->vm_mm->start_stack;
}

3. maps例項即如何異常定位

本例項中的使用者空間地址從0x00000000到0x80000000,從地址空間劃分可知,從低到高依次是:

  • 可執行檔案的程式碼段、資料段、BSS段。
  • 堆heap。
  • 檔案對映和匿名對映,包括vdso、庫的對映、mmap對映的記憶體等等。
  • 棧stack。

通過top或者procrank之類工具發現某個程序存在記憶體洩漏的風險,然後檢視程序的maps資訊,進而可以縮小洩漏點範圍。

一般情況下洩漏點常在堆和檔案/匿名對映區域。

對於堆,需要了解哪些函式申請的記憶體在堆中,然後加以監控相關係統呼叫。

對於檔案對映,定位較簡單,可以通過檔名找到對應程式碼。

對於匿名對映,則需要根據大小或者地址範圍猜測用途。當然也可以通過strace 跟蹤和maps對應找到對應的洩漏點。

00008000-00590000 r-xp 00000000 b3:01 1441836    /root/xxx----------------------------可執行檔案的程式碼段,下面分別是隻讀和可讀寫的段。00590000-005b2000 r--p 00587000 b3:01 1441836    /root/xxx
005b2000-005c4000 rw-p 005a9000 b3:01 1441836    /root/xxx
005c4000-0280c000 rwxp 00000000 00:00 0          [heap]-------------------------------如果堆在業務穩定後,還繼續單向增加,則可能存在洩漏。
2aaa8000-2aac5000 r-xp 00000000 b3:01 786621     /lib/ld-2.28.9000.so-----------------下面是最複雜的部分,存在各種各種樣的記憶體使用情況,大體上有庫對映、匿名記憶體對映、檔案記憶體對映等。
2aac5000-2aac6000 r--p 0001c000 b3:01 786621     /lib/ld-2.28.9000.so
2aac6000-2aac7000 rw-p 0001d000 b3:01 786621     /lib/ld-2.28.9000.so
2aac7000-2aac8000 r-xp 00000000 00:00 0          [vdso]
2aac8000-2aaca000 rw-p 00000000 00:00 0...
2d9aa000-2d9c8000 r-xp 00000000 b3:01 656126     /usr/lib/libv4lconvert.so.0.0.0
2d9c8000-2d9c9000 ---p 0001e000 b3:01 656126     /usr/lib/libv4lconvert.so.0.0.0
2d9c9000-2d9ca000 r--p 0001e000 b3:01 656126     /usr/lib/libv4lconvert.so.0.0.0
2d9ca000-2d9cb000 rw-p 0001f000 b3:01 656126     /usr/lib/libv4lconvert.so.0.0.0
2d9cb000-2da23000 rw-p 00000000 00:00 0...
3e8aa000-3e90c000 rw-s 00000000 00:06 5243       /dev/mem_cma
3ea00000-3ea42000 rw-p 00000000 00:00 0 
3ea42000-3eb00000 ---p 00000000 00:00 07fa4a000-7fa6b000 rwxp 00000000 00:00 0          [stack]--------------------------------棧的大小是可變的,但是不能超過RLIMIT_STACK規定的大小。

3.1 堆記憶體

堆記憶體主要由malloc()/calloc()/realloc()/fre()申請釋放,所以如果發生了堆洩漏就需要重點看著幾個函式呼叫情況。

malloc()對應的系統呼叫是brk(),但是當申請超過128KB記憶體時就會呼叫mmap()。

關於堆記憶體管理參考:《Linux堆記憶體管理深入分析(上)》、《Linux堆記憶體管理深入分析(下)》、《對堆疊中分析的比較好的文章進行的總結》、《Linux記憶體分配小結--malloc、brk、mmap》、《Linux C 堆記憶體管理函式malloc()、calloc()、realloc()、free()詳解》。

3.2 棧記憶體

棧的地址方向是從高到低,範圍由RLIMIT_STACK規定。

可以通過ulimit -s檢視,一般是8MB。

棧相關問題多是溢位問題。

3.3 mmap對映區

重點關注mmap相關呼叫《Linux記憶體管理 (9)mmap》、《Linux記憶體管理 (9)mmap(補充)》。

4. 讓maps可讀性更強

通過/proc/<pid>/maps獲取的資料可讀性不強,通過指令碼可以使其更容易理解。更容易找出記憶體消耗在哪裡。

在jupyter-notebook中輸入如下指令碼:

import re
import pandas as pd
import matplotlib.pyplot as plt
import sys
reload(sys)
sys.setdefaultencoding('utf8')

maps_filename="maps_aie_thd_165.txt"
#maps_filename="maps_init.txt"
maps_list=[]
maps_file = open(maps_filename, 'rb')
maps_columns = ["start", "end", "size(KB)", "filename", 'permission']
maps_process_end='80000000'

pre_end=0
for line in maps_file:
    #00008000-0000b000 r-xp 00000000 b3:01 1023       /root/pidmax
    #0000b000-0000c000 r--p 00002000 b3:01 1023       /root/pidmax
    #0000c000-0000d000 rw-p 00003000 b3:01 1023       /root/pidmax
    maps_line_fmt = '(?P<start>.{8})-(?P<end>.{8}) (?P<permission>.{4}) (?P<size>.{8}) (?P<major>.{2}):(?P<minor>.{2}) (?P<handle>[0-9]*) *(?P<filename>.*)'
    m = re.match(maps_line_fmt, line)
    if(not m):
        continue
    start = m.group('start')
    end = m.group('end')
    permission = m.group('permission')
    #size = m.group('size')
    #major = m.group('major')
    #minor = m.group('minor')
    #handle = m.group('handle')
    filename = m.group('filename')
    
    start_int = int(start, 16)
    end_int = int(end, 16)

    if(pre_end != start_int):
        maps_list.append([ "{:0>8x}".format(pre_end), start, (start_int - pre_end)/1024, 'NOT USED', 'unknown'])
    #print start+','+end+','+permission+','+filename
    #---p r--p rw-p r-xp rwxp rw-s
    if permission == '---p':
        permission = 'guard/thread stack'
    elif (permission == 'r--p'):
        permission = 'readonly var'
    elif (permission == 'rw-p'):
        permission = 'read/write var'
    elif (permission == 'r-xp'):
        permission = 'code'
    elif (permission == 'rwxp'):
        permission = 'heap/stack'
    elif (permission == 'rw-s'):
        permission = 'sharememory'
    else:
        permission = 'unkown'
    maps_list.append([start, end, (end_int - start_int)/1024, filename, permission])
    pre_end = end_int
maps_file.close()
maps_list.append([end, maps_process_end, (int(maps_process_end, 16) - end_int)/1024, 'NOT USED', 'unknown'])

maps_pd = pd.DataFrame(columns=maps_columns, data=maps_list)
maps_pd.to_csv("maps.csv", encoding='utf-8')
print 'Total memory =', maps_pd['size(KB)'].sum()/1024,'(MB)'

rectangle_width = 800
maps_height_base = 40
maps_height_diff = 160
maps_size_min = maps_pd['size(KB)'].min()
maps_size_max = 16384
rectangle_x = 50
rectangle_y = 50

fig = plt.figure()

ax = fig.add_subplot(111)
for index, maps in maps_pd.iterrows():
    rectangle_height = (float)(min(maps_size_max, maps['size(KB)']) - maps_size_min)*maps_height_diff/maps_size_max + maps_height_base
    if maps['filename'] == 'NOT USED':
        color = 'red'
        text_color = 'white'
    else:
        color_value = (int)((float)(min(maps_size_max, maps['size(KB)']) - maps_size_min)/maps_size_max*0xffffff)
        color = '#%06x'%(color_value)
        text_color = '#%06x'%(0xffffff - color_value)
    if maps['size(KB)'] >= 1024:
        maps_label = "(%.2fMB)%s(%s)"%((float)(maps["size(KB)"])/1024, maps["filename"], maps['permission'])
    else:
        maps_label = "(%dKB)%s(%s)"%(maps["size(KB)"], maps["filename"], maps['permission'])
    #print rectangle_x, rectangle_y, rectangle_width, rectangle_height, maps['size(KB)'], color
    plt.bar(rectangle_x, rectangle_height, width=rectangle_width, bottom=rectangle_y, align='edge',facecolor=color, linewidth=1, edgecolor='red')
    plt.text(rectangle_x+10, rectangle_y+rectangle_height/2, maps_label, horizontalalignment='left', verticalalignment='center', color=text_color, fontsize=12)

    rectangle_y += rectangle_height

plt.xlim(0, rectangle_width+100)
plt.ylim(0, rectangle_y+100)
plt.axis('off')
    
#plt.show()
fig.set_size_inches(rectangle_width/100, rectangle_y/100)
fig.savefig('maps.svg', dpi=120, bbox_inches='tight', format='svg')

輸出兩份文件:maps.csv和maps.svg,一個是對每個vma進行文字統計,一個是將maps圖形化。

1. 檢視未使用區域,從大到小排列。

可以看出程序虛擬地址空間的空閒大小,以及分佈在哪裡。

2. 檢視已使用區域,從大到小排列。

可以看出程序虛擬記憶體都被誰使用了,是否有異常。

3. maps圖形化

未使用部分用紅色高亮顯示,高度根據大小響應變化。

其他模組顏色和大小都根據模組變化。

參考資料:《Linux虛擬地址空間佈局以及程序棧和執行緒棧總結