GDB: gdb plus で malloc したメモリアドレスを確認する


## gdb plus

[CORE ANALYZER](https://sourceforge.net/projects/core-analyzer/) に gdb にヒープ情報の出力を追加した gdb plus が含まれています。
この gdb plus を利用すると何ができるかというとタイトルの通り malloc() で確保されたメモリ領域の情報が確認できます。
わたしの経験的なものですが、プログラムの不具合などでメモリリークが発生している場合は、同じサイズのメモリエリアが極端に多く確保されているケースが多く、何バイトのサイズが何個プロセス中に存在しているかの特徴を見つける事で原因の究明に役立ちます。
## こんな事ができます
heap /v で、arena の数や範囲、サイズ毎の数やサイズがリストされます。
heap /c に続いて arena のアドレスを指定すると、さらに arena に含まれる malloc() で確保されたエリア毎の情報がレポートされます。

```
(gdb) heap /v
        Tuning params & stats:
                mmap_threshold=901120
                pagesize=4096
                n_mmaps=2
                mmapped_mem=385024
                sbrk_base=0x17c6c000
        Main arena (0x37297549e0) owns regions:
                [0x17c6c010 - 0x18e92000] Total 18MB in-use 14964(7MB) free 94(10MB)
        mmap-ed large memory blocks:
                [0xf5a73010 - 0xf5a93000] Total 127KB in-use 1(127KB) free 0(0)
                [0xf5aeb010 - 0xf5b0b000] Total 127KB in-use 1(127KB) free 0(0)
                [0xf5cea010 - 0xf6c5a000] Total 15MB in-use 1(15MB) free 0(0)
                [0x2b97a7e55010 - 0x2b97a7e7d000] Total 159KB in-use 1(159KB) free 0(0)
                [0x2b97a8023010 - 0x2b97a8059000] Total 215KB in-use 1(215KB) free 0(0)
                5 mmap-ed large memory blocks are found, however, 2 is recorded in mp_

        There are 2 arenas and 5 mmap-ed memory blocks Total 34MB
        Total 14969 blocks in-use of 23MB
        Total 94 blocks free of 10MB

        ========== In-use Memory Histogram ==========
        Size-Range     Count       Total-Bytes
        16 - 32        5557(37%)   130KB(0%)
        32 - 64        6015(40%)   264KB(1%)
        64 - 128       2125(14%)   196KB(0%)
        128 - 256      232(1%)     38KB(0%)
        256 - 512      207(1%)     71KB(0%)
        512 - 1024     74(0%)      60KB(0%)
        1024 - 2KB     69(0%)      91KB(0%)
        2KB - 4KB      31(0%)      87KB(0%)
        4KB - 8KB      12(0%)      71KB(0%)
        8KB - 16KB     624(4%)     5MB(24%)
        16KB - 32KB    16(0%)      413KB(1%)
        32KB - 64KB    2(0%)       67KB(0%)
        64KB - 128KB   2(0%)       255KB(1%)
        128KB - 256KB  2(0%)       375KB(1%)
        512KB -        1(0%)       15MB(66%)
        Total          14969       23MB
        ========== Free Memory Histogram ==========
        Size-Range     Count       Total-Bytes
        16 - 32        1(1%)       24(0%)
        32 - 64        1(1%)       40(0%)
        256 - 512      1(1%)       424(0%)
        512 - 1024     4(4%)       3KB(0%)
        8KB - 16KB     5(5%)       43KB(0%)
        16KB - 32KB    24(25%)     767KB(6%)
        32KB - 64KB    23(24%)     1MB(12%)
        64KB - 128KB   20(21%)     1MB(18%)
        128KB - 256KB  8(8%)       1MB(14%)
        256KB - 512KB  4(4%)       1MB(11%)
        512KB -        3(3%)       3MB(36%)
        Total          94          10MB
(gdb) 
(gdb) heap /c 0x17c6c010
        Main arena (0x37297549e0): [0x17c6c000 - 0x18e92000]
                        [0x17c6c010 - 0x17c6c058] 72 bytes inuse
                        [0x17c6c060 - 0x17c6c0a8] 72 bytes inuse
                        [0x17c6c0b0 - 0x17c6c0f8] 72 bytes inuse
                        [0x17c6c100 - 0x17c6c148] 72 bytes inuse
                        [0x17c6c150 - 0x17c6c1a8] 88 bytes inuse
                        [0x17c6c1b0 - 0x17c6c1d8] 40 bytes inuse
                        [0x17c6c1e0 - 0x17c6c238] 88 bytes inuse
...
                        [0x18e503e0 - 0x18e50408] 40 bytes inuse
                [0x18e50410 - 0x18e703c8] 131000 bytes free
                        [0x18e703d0 - 0x18e71118] 3400 bytes inuse
                        [0x18e71120 - 0x18e71138] 24 bytes inuse
                        [0x18e71140 - 0x18e71158] 24 bytes inuse
                        [0x18e71160 - 0x18e71178] 24 bytes inuse
                        [0x18e71180 - 0x18e71198] 24 bytes inuse
                        [0x18e711a0 - 0x18e711b8] 24 bytes inuse
                        [0x18e711c0 - 0x18e711d8] 24 bytes inuse
                        [0x18e711e0 - 0x18e711f8] 24 bytes inuse
                        [0x18e71200 - 0x18e71308] 264 bytes inuse
                        [0x18e71310 - 0x18e71328] 24 bytes inuse
                        [0x18e71330 - 0x18e71378] 72 bytes inuse
                        [0x18e71380 - 0x18e71468] 232 bytes inuse
                        [0x18e71470 - 0x18e71488] 24 bytes inuse
                        [0x18e71490 - 0x18e714a8] 24 bytes inuse
                        [0x18e714b0 - 0x18e71538] 136 bytes inuse
                        [0x18e71540 - 0x18e71558] 24 bytes inuse
                [0x18e71560 - 0x18e92000] 133792 bytes free

        Total inuse 14964 blocks 7448048 bytes
        Total free 94 blocks 11461496 bytes
(gdb) 

```

## RHEL 5.X core_analyzer 2.16 でOK

すこし古いのですが RedHat 5.11 の環境では、core_analayzer_2_16 で問題なく動作します。
※ バージョンの古い core_analyzer は https://sourceforge.net/projects/core-analyzer/files/Core%20Analyzer/ からダウンロードできます。

## RHEL 6.X では core_analyzer 2.16

core_analyzer はとても良いツールなのですが、GitHub をみると2015年 2月3日が最後にコミットされた日時になっており、しばらくメンテナンスされている様子がありません。
RHEL 6.X や 7.X で利用するには少し手を加える必要があります。

**動作する core_analyzer のバージョン確認**

core_analyzer_2.18 や2.17では、ライブラリが無いとのエラー内容

```core_analyzer_2.18
$ ./gdb /work/abc /work/core.1234
./gdb: /usr/lib64/liblzma.so.5: no version information available (required by ./gdb)
./gdb: /lib64/libncurses.so.5: no version information available (required by ./gdb)
./gdb: /lib64/libncurses.so.5: no version information available (required by ./gdb)
./gdb: /lib64/libtinfo.so.5: no version information available (required by ./gdb)
./gdb: /lib64/libc.so.6: version `GLIBC_2.15' not found (required by ./gdb)
./gdb: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./gdb)
$
```

core_analyzer_2.16 では、動作するが Main Arena の情報が正しく取得できていないエラー
※ http://core-analyzer.sourceforge.net/index_files/Page335.html の説明の赤色枠の Main Heap の情報です。

```diff:core_analyzer_2.16
(gdb) heap /v
        Tuning params & stats:
                mmap_threshold=131072
                pagesize=4096
                n_mmaps=0
                mmapped_mem=0
                sbrk_base=0x147c000
        Main arena (0x3d82d8e120) owns regions:
+               [0x3b2900f - 0x3b29000] Total 17179869184.0GBFailed to get the first chunk at 0x3b28fff

1 Errors encountered while walking the heap!
[Error] Failed to walk heap
(gdb)
```

× core_analyzer_2.18 (latest 2018/5/23現在) ライブラリが無いとエラーが出て動作しない。
× core_analyzer_2.17 同様にライブラリが無いとエラーが出て動作しない。
△ core_analyzer_2.16 動作するには動作しますが Main arena のヒープ情報が正しく取れていない。

上記の結果から、とりあえず動作はする core_analyzer_2.16 を元にして環境を作成します。

**core_analyzer 2.16 のインストール**

```
# mkdir -p /work/core_analyzer_2.16
# cd /work/core_analyzer_2.16
# wget https://jaist.dl.sourceforge.net/project/core-analyzer/Core%20Analyzer/core_analyzer_2_16.7z

... 7 zip 形式なので、7zip の rpm も適当なところから以下のように入手します。...

# wget https://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/6/x86_64/Packages/p/p7zip-16.02-10.el6.x86_64.rpm
# rpm -ivh p7zip-16.02-10.el6.x86_64.rpm 
# 7za x core_analyzer_2_16.7z
...
# chmod 755 ./gdbplus/gdb-7.7/gdb/gdb
Fullパスだと、この場合 /work/core_analyzer_2.16/gdbplus/gdb-7.7/gdb/gdb となります。
```

**Main Arena が確認できるようにパッチを当てる**

少々問題があっても、情報が得られれば良いという内容です。
また、ソースコードからコンパイルするのは面倒なので、手抜ですが以下のようにしてバイナリにパッチを当てます。

```
# cp /work/core_analyzer_2.16/gdbplus/gdb-7.7/gdb/gdb /work/core_analyzer_2.16/gdbplus/gdb-7.7/gdb/gdb.org
# /usr/bin/gdb -write -q /work/core_analyzer_2.16/gdbplus/gdb-7.7/gdb/gdb
...
(gdb) x/i 0x5bff29
   0x5bff29 <build_arena+1082>: jne    0x5bffe8 <build_arena+1273>
(gdb) set {unsigned char}0x5bff2a = 0x84
(gdb) x/i 0x5bff29
   0x5bff29 <build_arena+1082>: je     0x5bffe8 <build_arena+1273>
(gdb) quit
```

**変更箇所の説明**

どこにパッチをあてているかと言うと、heap_htmalloc.c の 916 行部分です。
jne を je に変える事で、if (contiguous(&arena->mpState)) これの条件を "!" を加えたように条件を逆にしています。

詳細まで調べていませんが、...get the sbrk-ed heap にあるように Main Arena の sbrk ベースのヒープだから 925 行以降が実行されるべきのように思いますが、そうなっていないため変更しています。

```c:/work/core_analyzer_2.16/gdbplus/gdb-7.7/gdb/heap_ptmalloc.c

    913         heap = (struct ca_heap*) malloc(sizeof(struct ca_heap));
    914         memset(heap, 0, sizeof(struct ca_heap));
    915         // main_arena
    916         if (contiguous(&arena->mpState)) 
    917         {
    918                 heap->mEndAddr = (address_t)top_addr + ca_chunksize(ptr_bit, &top_chunk);
    919                 heap->mStartAddr = heap->mEndAddr - arena->mpState.system_mem + size_t_sz;
    920                 heap->mSegment = segment;
    921                 add_ca_heap (arena, heap);
    922         }
    923         else
    924         {
    925                 // non-contiguous main_arena, get the sbrk-ed heap
```

## REHL 7.X では core_analyzer_2.18 

△ core_analyzer_2.18 動作しますが Main arena のヒープ情報が正しく取れていない。
△ core_analyzer_2.17 動作しますが Main arena のヒープ情報が正しく取れていない。
× core_analyzer_2.16 'The memory manager of glibc 2.17 is not supported in this release' で heap コマンドは動作しない。

上記から core_analyzer_2.18 で環境を作成します。

```
# mkdir -p /work
# cd /work
# wget https://codeload.github.com/yanqi27/core_analyzer/zip/master
# unzip master
# unzip -l master
Archive:  master
c62519f29910d149785c659b8f786cc73f0c11d4
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  01-31-2018 00:46   core_analyzer-master/
      378  01-31-2018 00:46   core_analyzer-master/.gitattributes
      956  01-31-2018 00:46   core_analyzer-master/.gitignore
     2311  01-31-2018 00:46   core_analyzer-master/ChangeLog
     1504  01-31-2018 00:46   core_analyzer-master/README.txt
...
```

core_analyzer 2.16 と同様にパッチを当てます。

```
# cd /work/ 
# cp ./core_analyzer-master/bin/Linux/ptmalloc/gdb-7.11.1/gdb ./core_analyzer-master/bin/Linux/ptmalloc/gdb-7.11.1/gdb.org
#/usr/bin/gdb -write -q ./core_analyzer-master/bin/Linux/ptmalloc/gdb-7.11.1/gdb
...
(gdb) x/i  0x59e73d
   0x59e73d <build_arena+1469>: jne    0x59ecaf <build_arena+2863>
(gdb) set {unsigned char}0x59e73e = 0x84
(gdb)  x/i  0x59e73d
   0x59e73d <build_arena+1469>: je     0x59ecaf <build_arena+2863>
(gdb) 
```


##変更後の動作確認

簡単なプログラムで core を出力して、内容を確認します。

```c:malloc1.c
#include <stdio.h>
#include <stdlib.h>

#define SIZE (size_t)(32*1024)

main(int argc, char *argv[])
{
    int i;
    int *ptr;

    for ( i = 0 ; i < 50; i++) {
        if ( NULL == (ptr = malloc(SIZE)))
            perror("malloc"), exit(1);
        printf("0x%.16x\n", ptr );
    }
    abort();
}

```

main arena のヒープ情報が重複してレポートされていますが、とりあえず情報取得できていれば良しとしています。
下記の　＋で始まる色付きの箇所に２回同じ情報が表示されていますし、malloc() を、50 回実行しているのに Total のカウント部分は Count 100 とレポートされている部分などです。

```diff:

$ make malloc1
cc     malloc1.c   -o malloc1
$ ulimit -c unlimited 
$ ./malloc1 
0x0000000000d5d010
0x0000000000d65020
0x0000000000d6d030
0x0000000000d75040
0x0000000000d7d050
0x0000000000d85060
0x0000000000d8d070
...
中止 (コアダンプ)

$ /work/core_analyzer-master/bin/Linux/ptmalloc/gdb-7.11.1/gdb ./malloc1 ./core.
...
(gdb) heap /v
Internal error: two ca_heaps are of the same start address.
Python Exception <type 'exceptions.NameError'> Installation error: gdb.execute_unwinders function is missing: 
        Tuning params & stats:
                mmap_threshold=131072
                pagesize=4096
                n_mmaps=0
                n_mmaps_max=65536
                total mmap regions created=0
                mmapped_mem=0
                sbrk_base=0xd5d000
        Main arena (0x7f49ab30e760) owns regions:
+               [0xd5d010 - 0xeee000] Total 1MB in-use 50(1MB) free 1(3KB)
+               [0xd5d010 - 0xeee000] Total 1MB in-use 50(1MB) free 1(3KB)

        There are 1 arenas Total 3MB
        Total 100 blocks in-use of 3MB
        Total 2 blocks free of 6KB

        ========== In-use Memory Histogram ==========
        Size-Range     Count       Total-Bytes
        32KB - 64KB    100(100%)   3MB(100%)
        Total          100         3MB
        ========== Free Memory Histogram ==========
        Size-Range     Count       Total-Bytes
        2KB - 4KB      2(100%)     6KB(100%)
        Total          2           6KB
(gdb) heap /c 0xd5d010
        Main arena (0x7f49ab30e760): [0xd5d000 - 0xeee000]
                        [0xd5d010 - 0xd65018] 32776 bytes inuse
                        [0xd65020 - 0xd6d028] 32776 bytes inuse
                        [0xd6d030 - 0xd75038] 32776 bytes inuse
                        [0xd75040 - 0xd7d048] 32776 bytes inuse
                        [0xd7d050 - 0xd85058] 32776 bytes inuse
...
```

## gdb の出力をファイルに

複数の Arena を持つプロセスなどや出力量に応じて、gdb の出力を一旦ファイルに出力し、のちに sort や uniq などで集計します。

```shell:
(gdb) set height 0             # １画面毎に ..return to continue .. の入力待ちにしないため。
(gdb) set logging file /tmp/gdb.out  # 出力ファイル名を指定
(gdb) set logging on                 # 出力を開始
Copying output to /tmp/gdb.out.

heap /c 0xd5d010 など、 heap /v でレポートされる Main Arena やDynamic Arina の先頭アドレスを指定する。

(gdb) set logging off
Done logging to /tmp/gdb.out.
(gdb) 

あとは、取得した /tmp/gdb.out から、ひたすら awk, sort, uniq などで確認する事もできます。
以下は、もっともカウントの多いサイズ順に５つを表示。

$ grep "bytes inuse" /tmp/gdb.out |  awk '{print $(NF-2), $(NF-1), $NF}' | sort -n -k1,1 | uniq -c |sort -nr -k1,1 | head -n 5
  15594 24 bytes inuse
  11851 120 bytes inuse
  10266 88 bytes inuse
   4379 40 bytes inuse
   4306 32776 bytes inuse


```

以上、何かのお役に立ちましたら幸いです。