gdb plus
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では、ライブラリが無いとのエラー内容
$ ./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 の情報です。
(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 行以降が実行されるべきのように思いますが、そうなっていないため変更しています。
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 を出力して、内容を確認します。
# 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 のヒープ情報が重複してレポートされていますが、とりあえず情報取得できていれば良しとしています。
下記の +で始まる色付きの箇所に2回同じ情報が表示されていますし、malloc() を、50 回実行しているのに Total のカウント部分は Count 100 とレポートされている部分などです。
$ 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 などで集計します。
(gdb) set height 0 # 1画面毎に ..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 などで確認する事もできます。
以下は、もっともカウントの多いサイズ順に5つを表示。
$ 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
以上、何かのお役に立ちましたら幸いです。