th0x4c 備忘録

[GDB] Linux X86-64 の呼出規約(calling Convention)を Gdb で確認する

目的

Linux x86-64 の呼出規約(calling convention)を gdb で確認する。

環境

  • OS: CentOS 5.5
  • Kernel: 2.6.18-194.el5 x86_64
  • GCC: gcc 4.1.2 20080704
  • GDB: GNU gdb 7.0.1-23.el5

呼出規約(calling convention)

プログラムで関数を呼び出す際に、レジスタやスタックを使いどのように引数を渡すか、戻り値をどのように受け取るかは呼出規約(calling convention)で決められている。

Linux x86-64 では、以下のような呼出規約になっている。 (Wikepedia x86 calling conventions, 呼出規約 から抜粋)

The calling convention of the System V AMD64 ABI is followed on Solaris, GNU/Linux, FreeBSD, and other non-Microsoft operating systems. The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments. For system calls, R10 is used instead of RCX. As in the Microsoft x64 calling convention, additional arguments are passed on the stack and the return value is stored in RAX.

  • 整数・ポインタ引数: RDI, RSI, RDX, RCX, R8, R9
  • 浮動小数点引数: XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7
  • 戻り値: RAX
  • システムコールでは RCX の代わりに R10 を使用
  • レジスタだけでは引数の数が不足する場合はスタックを利用

より詳細には System V Application Binary Interface AMD64 Architecture Processor Supplement を参照。

gdb による確認

上記の呼出規約を実際のプログラムで確認してみる。

使用するサンプルプログラムは以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

int sum(int a1, int a2, int a3, int a4, int a5,
        int a6, int a7, int a8, int a9);
void func();

int sum(int a1, int a2, int a3, int a4, int a5,
        int a6, int a7, int a8, int a9)
{
  int s = -9999;

  s = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9;

  return s;
}

void func()
{
  int ret = -1;

  ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);

  printf("sum: %d\n", ret);
}

int main(int argc, char *argv[])
{
  func();
  return 0;
}

実行結果は以下。

$ gcc -g -o sample1 sample1.c
$ ./sample1
sum: 495

gdb から、関数 func から関数 sum を呼び出している個所を確認する。 disas/m というふうに修飾子 /m を指定するとソースと共に表示されて分かりやすくなる。 (ちなみに objdump -d -S ./sample1 としてもソースと関数の逆アセンブル結果が出力される)

$ gdb ./sample1
(gdb) set height 0
(gdb) disas/m func
Dump of assembler code for function func:
18      {
0x00000000004004da <func+0>:    push   %rbp
0x00000000004004db <func+1>:    mov    %rsp,%rbp
0x00000000004004de <func+4>:    sub    $0x30,%rsp

19        int ret = -1;
0x00000000004004e2 <func+8>:    movl   $0xffffffff,-0x4(%rbp)

20
21        ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);
0x00000000004004e9 <func+15>:   movl   $0x63,0x10(%rsp)  <== 第9引数(0x63 = 99) を スタック+0x10 の位置に格納
0x00000000004004f1 <func+23>:   movl   $0x58,0x8(%rsp)   <== 第8引数(0x58 = 88) を スタック+0x8  の位置に格納
0x00000000004004f9 <func+31>:   movl   $0x4d,(%rsp)      <== 第7引数(0x4d = 77) を スタック      の位置に格納
0x0000000000400500 <func+38>:   mov    $0x42,%r9d        <== 第6引数(0x42 = 66) を R9  に格納
0x0000000000400506 <func+44>:   mov    $0x37,%r8d        <== 第5引数(0x37 = 55) を R8  に格納
0x000000000040050c <func+50>:   mov    $0x2c,%ecx        <== 第4引数(0x2c = 44) を ECX に格納
0x0000000000400511 <func+55>:   mov    $0x21,%edx        <== 第3引数(0x21 = 33) を EDX に格納
0x0000000000400516 <func+60>:   mov    $0x16,%esi        <== 第2引数(0x16 = 22) を RSI に格納
0x000000000040051b <func+65>:   mov    $0xb,%edi         <== 第1引数( 0xb = 11) を RDI に格納
0x0000000000400520 <func+70>:   callq  0x400498 <sum>    <== 関数 sum を呼出
0x0000000000400525 <func+75>:   mov    %eax,-0x4(%rbp)   <== 戻り値を RAX から受け取る

22
23        printf("sum: %d\n", ret);
0x0000000000400528 <func+78>:   mov    -0x4(%rbp),%esi
0x000000000040052b <func+81>:   mov    $0x400658,%edi
0x0000000000400530 <func+86>:   mov    $0x0,%eax
0x0000000000400535 <func+91>:   callq  0x400398 <printf@plt>

24      }
0x000000000040053a <func+96>:   leaveq
0x000000000040053b <func+97>:   retq

End of assembler dump.

確かに呼出規約通り、第1引数~第6引数まで RDI, RSI, RDX, RCX, R8, R9 を順に使用して、 残りの第7引数~第9引数はスタックを利用している。戻り値は RAX に格納されている。

ちなみに呼び出される関数 sum の disas の結果は以下

(gdb) disas/m sum
Dump of assembler code for function sum:
9       {
0x0000000000400498 <sum+0>:     push   %rbp
0x0000000000400499 <sum+1>:     mov    %rsp,%rbp
0x000000000040049c <sum+4>:     mov    %edi,-0x14(%rbp)
0x000000000040049f <sum+7>:     mov    %esi,-0x18(%rbp)
0x00000000004004a2 <sum+10>:    mov    %edx,-0x1c(%rbp)
0x00000000004004a5 <sum+13>:    mov    %ecx,-0x20(%rbp)
0x00000000004004a8 <sum+16>:    mov    %r8d,-0x24(%rbp)
0x00000000004004ac <sum+20>:    mov    %r9d,-0x28(%rbp)

10        int s = -9999;
0x00000000004004b0 <sum+24>:    movl   $0xffffd8f1,-0x4(%rbp)

11
12        s = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9;
0x00000000004004b7 <sum+31>:    mov    -0x18(%rbp),%eax
0x00000000004004ba <sum+34>:    add    -0x14(%rbp),%eax
0x00000000004004bd <sum+37>:    add    -0x1c(%rbp),%eax
0x00000000004004c0 <sum+40>:    add    -0x20(%rbp),%eax
0x00000000004004c3 <sum+43>:    add    -0x24(%rbp),%eax
0x00000000004004c6 <sum+46>:    add    -0x28(%rbp),%eax
0x00000000004004c9 <sum+49>:    add    0x10(%rbp),%eax
0x00000000004004cc <sum+52>:    add    0x18(%rbp),%eax
0x00000000004004cf <sum+55>:    add    0x20(%rbp),%eax
0x00000000004004d2 <sum+58>:    mov    %eax,-0x4(%rbp)

13
14        return s;
0x00000000004004d5 <sum+61>:    mov    -0x4(%rbp),%eax

15      }
0x00000000004004d8 <sum+64>:    leaveq
0x00000000004004d9 <sum+65>:    retq

End of assembler dump.

関数 sum で break させて、レジスタの状態やスタックを確認すれば引数の値が分かる。

(gdb) b sum
Breakpoint 1 at 0x4004b0: file sample1.c, line 10.
(gdb) run
Starting program: /home/hashi/tmp/sample1

Breakpoint 1, sum (a1=11, a2=22, a3=33, a4=44, a5=55, a6=66, a7=77, a8=88, a9=99) at sample1.c:10
10        int s = -9999;
(gdb) info reg
rax            0x0      0
rbx            0x3e1c81bbc0     266766236608
rcx            0x2c     44                        <== 第4引数
rdx            0x21     33                        <== 第3引数
rsi            0x16     22                        <== 第2引数
rdi            0xb      11                        <== 第1引数
rbp            0x7fffffffe270   0x7fffffffe270
rsp            0x7fffffffe270   0x7fffffffe270
r8             0x37     55                        <== 第5引数
r9             0x42     66                        <== 第6引数
r10            0x0      0
r11            0x3e1ca1d8a0     266768341152
r12            0x0      0
r13            0x7fffffffe3b0   140737488348080
r14            0x0      0
r15            0x0      0
rip            0x4004b0 0x4004b0 <sum+24>         <== sum+20 までの処理が終わっていて次の処理は sum+24
eflags         0x202    [ IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
fctrl          0x37f    895
fstat          0x0      0
ftag           0xffff   65535
fiseg          0x0      0
fioff          0x0      0
foseg          0x0      0
fooff          0x0      0
fop            0x0      0
mxcsr          0x1f80   [ IM DM ZM OM UM PM ]
(gdb) x/6gx $rbp
0x7fffffffe270: 0x00007fffffffe2b0      0x0000000000400525
0x7fffffffe280: 0x000000000000004d      0x0000000000000058
                          ~~~~~~~~第7引数         ~~~~~~~~第8引数
0x7fffffffe290: 0x2cb4304900000063      0x00000000004005a7
                          ~~~~~~~~第9引数

スタック上の値を確認するのに x/6gx $rbp としているのは、関数で break したときは関数の最初の方の処理(サブルーチンプロローグ)まで 終わっているが、普通はここまでで引数をスタックに積んだ後、

  • call <function> : スタックに戻り先の命令アドレスを積んで関数を呼ぶ(rsp が -0x8 される)
  • push %rbp : ベースポインタ(rbp)の値が push され、スタックに保存される(rsp が -0x8 される)
  • mov %rsp,%rbp : スタックポインタ(rsp)の値をベースポインタ(rbp)に保存する
  • (sub $0xXX,%rsp : ローカル変数を保持するためにスタックを上げる)

という処理が一般的に行われるためである。結果としてスタックに積まれた引数は $rbp + 0x10 の位置から、第7引数が $rbp + 0x10, 第8引数が $rbp + 0x18, 第9引数が $rbp + 0x20,… というふうに存在することになる。

関数 sum から返った後に レジスタ RAX にて戻り値が分かる。

(gdb) finish
Run till exit from #0  sum (a1=11, a2=22, a3=33, a4=44, a5=55, a6=66, a7=77, a8=88, a9=99) at sample1.c:12
0x0000000000400525 in func () at sample1.c:21
21        ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);
Value returned is $1 = 495
(gdb) p $rax
$2 = 495

詳細確認

せっかくなので、関数 func から関数 sum を呼び出しているところを逆アセンブルの結果から詳しく追ってみる。

初期状態(関数 func が呼ばれる直前)

rbp            0x7fffffffe2d0   0x7fffffffe2d0
rsp            0x7fffffffe2c0   0x7fffffffe2c0

        0x7fffffffe2b8 +------------------+
                       |                  |
rsp --> 0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
rbp --> 0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

関数 main から関数 func を呼ぶ

0x0000000000400550 <main+20>:   callq  0x4004da <func>  <== 関数 func を呼出

この命令後のレジスタの状態

rbp            0x7fffffffe2d0   0x7fffffffe2d0
rsp            0x7fffffffe2b8   0x7fffffffe2b8

rip            0x4004da 0x4004da <func>

rsp --> 0x7fffffffe2b8 +------------------+
                       |0x0000000000400555| <== 関数 main の戻り先命令アドレス
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
rbp --> 0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

関数 func が呼び出され、ここから関数 func に入る。

Dump of assembler code for function func:
18      {
0x00000000004004da <func+0>:    push   %rbp
0x00000000004004db <func+1>:    mov    %rsp,%rbp
0x00000000004004de <func+4>:    sub    $0x30,%rsp

この命令後のレジスタの状態

rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe280   0x7fffffffe280

rsp --> 0x7fffffffe280 +------------------+ <== ローカル変数を保持するためにスタックを上げる
                       |                  |
        0x7fffffffe288 +------------------+
                       |                  |
        0x7fffffffe290 +------------------+
                       |                  |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |                  |
rbp --> 0x7fffffffe2b0 +------------------+ <== 元のスタックの位置がベースポインタに保存される
                       |0x00007fffffffe2d0| <== ベースポインタの値がスタックに保存される
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

19        int ret = -1;
0x00000000004004e2 <func+8>:    movl   $0xffffffff,-0x4(%rbp)

この命令後のレジスタの状態

rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe280   0x7fffffffe280

rsp --> 0x7fffffffe280 +------------------+
                       |                  |
        0x7fffffffe288 +------------------+
                       |                  |
        0x7fffffffe290 +------------------+
                       |                  |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
rbp --> 0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

20
21        ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);
0x00000000004004e9 <func+15>:   movl   $0x63,0x10(%rsp)  <== 第9引数(0x63 = 99) を スタック+0x10 の位置に格納
0x00000000004004f1 <func+23>:   movl   $0x58,0x8(%rsp)   <== 第8引数(0x58 = 88) を スタック+0x8  の位置に格納
0x00000000004004f9 <func+31>:   movl   $0x4d,(%rsp)      <== 第7引数(0x4d = 77) を スタック      の位置に格納
0x0000000000400500 <func+38>:   mov    $0x42,%r9d        <== 第6引数(0x42 = 66) を R9  に格納
0x0000000000400506 <func+44>:   mov    $0x37,%r8d        <== 第5引数(0x37 = 55) を R8  に格納
0x000000000040050c <func+50>:   mov    $0x2c,%ecx        <== 第4引数(0x2c = 44) を ECX に格納
0x0000000000400511 <func+55>:   mov    $0x21,%edx        <== 第3引数(0x21 = 33) を EDX に格納
0x0000000000400516 <func+60>:   mov    $0x16,%esi        <== 第2引数(0x16 = 22) を RSI に格納
0x000000000040051b <func+65>:   mov    $0xb,%edi         <== 第1引数( 0xb = 11) を RDI に格納

この命令後のレジスタの状態

rcx            0x2c     44                  <== 第4引数
rdx            0x21     33                  <== 第3引数
rsi            0x16     22                  <== 第2引数
rdi            0xb      11                  <== 第1引数
rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe280   0x7fffffffe280
r8             0x37     55                  <== 第5引数
r9             0x42     66                  <== 第6引数

rsp --> 0x7fffffffe280 +------------------+
                       |0x0000004d        | <== 第7引数
        0x7fffffffe288 +------------------+
                       |0x00000058        | <== 第8引数
        0x7fffffffe290 +------------------+
                       |0x00000063        | <== 第9引数
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
rbp --> 0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

0x0000000000400520 <func+70>:   callq  0x400498 <sum>    <== 関数 sum を呼出

この命令後のレジスタの状態

rcx            0x2c     44
rdx            0x21     33
rsi            0x16     22
rdi            0xb      11
rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe278   0x7fffffffe278
r8             0x37     55
r9             0x42     66

rip            0x400498 0x400498 <sum>  <== 次は関数 sum の命令を呼ぶ

rsp --> 0x7fffffffe278 +------------------+
                       |0x0000000000400525| <== 関数 func の戻り先命令アドレス
        0x7fffffffe280 +------------------+
                       | 0x0000004d       |
        0x7fffffffe288 +------------------+
                       | 0x00000058       |
        0x7fffffffe290 +------------------+
                       | 0x00000063       |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |       0xffffffff |
rbp --> 0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

ここから関数 sum に入る

Dump of assembler code for function sum:
9       {
0x0000000000400498 <sum+0>:     push   %rbp
0x0000000000400499 <sum+1>:     mov    %rsp,%rbp
0x000000000040049c <sum+4>:     mov    %edi,-0x14(%rbp)
0x000000000040049f <sum+7>:     mov    %esi,-0x18(%rbp)
0x00000000004004a2 <sum+10>:    mov    %edx,-0x1c(%rbp)
0x00000000004004a5 <sum+13>:    mov    %ecx,-0x20(%rbp)
0x00000000004004a8 <sum+16>:    mov    %r8d,-0x24(%rbp)
0x00000000004004ac <sum+20>:    mov    %r9d,-0x28(%rbp)

この命令後のレジスタの状態

rcx            0x2c     44
rdx            0x21     33
rsi            0x16     22
rdi            0xb      11
rbp            0x7fffffffe270   0x7fffffffe270
rsp            0x7fffffffe270   0x7fffffffe270
r8             0x37     55
r9             0x42     66

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037| <== 66 55
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021| <== 44 33
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b| <== 22 11
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |                  |
rsp +-->0x7fffffffe270 +------------------+ <== 元のスタックの位置がベースポインタに保存される
rbp +                  |0x00007fffffffe2b0| <== ベースポインタの値がスタックに保存される
        0x7fffffffe278 +------------------+
                       |0x0000000000400525|
        0x7fffffffe280 +------------------+
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
        0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

10        int s = -9999;
0x00000000004004b0 <sum+24>:    movl   $0xffffd8f1,-0x4(%rbp)

この命令後のレジスタの状態

rcx            0x2c     44
rdx            0x21     33
rsi            0x16     22
rdi            0xb      11
rbp            0x7fffffffe270   0x7fffffffe270
rsp            0x7fffffffe270   0x7fffffffe270
r8             0x37     55
r9             0x42     66

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037|
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021|
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b|
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |        0xffffd8f1| <== -9999
rsp +-->0x7fffffffe270 +------------------+
rbp +                  |0x00007fffffffe2b0|
        0x7fffffffe278 +------------------+
                       |0x0000000000400525|
        0x7fffffffe280 +------------------+
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
        0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

11
12        s = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9;
0x00000000004004b7 <sum+31>:    mov    -0x18(%rbp),%eax
0x00000000004004ba <sum+34>:    add    -0x14(%rbp),%eax
0x00000000004004bd <sum+37>:    add    -0x1c(%rbp),%eax
0x00000000004004c0 <sum+40>:    add    -0x20(%rbp),%eax
0x00000000004004c3 <sum+43>:    add    -0x24(%rbp),%eax
0x00000000004004c6 <sum+46>:    add    -0x28(%rbp),%eax
0x00000000004004c9 <sum+49>:    add    0x10(%rbp),%eax
0x00000000004004cc <sum+52>:    add    0x18(%rbp),%eax
0x00000000004004cf <sum+55>:    add    0x20(%rbp),%eax
0x00000000004004d2 <sum+58>:    mov    %eax,-0x4(%rbp)

この命令後のレジスタの状態

rax            0x1ef    495

rcx            0x2c     44
rdx            0x21     33
rsi            0x16     22
rdi            0xb      11
rbp            0x7fffffffe270   0x7fffffffe270
rsp            0x7fffffffe270   0x7fffffffe270
r8             0x37     55
r9             0x42     66

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037|
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021|
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b|
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |        0x000001ef| <== 495
rsp +-->0x7fffffffe270 +------------------+
rbp +                  |0x00007fffffffe2b0|
        0x7fffffffe278 +------------------+
                       |0x0000000000400525|
        0x7fffffffe280 +------------------+
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
        0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

13
14        return s;
0x00000000004004d5 <sum+61>:    mov    -0x4(%rbp),%eax

この命令後のレジスタの状態

rax            0x1ef    495 <== 戻り値が RAX に入っている

rcx            0x2c     44
rdx            0x21     33
rsi            0x16     22
rdi            0xb      11
rbp            0x7fffffffe270   0x7fffffffe270
rsp            0x7fffffffe270   0x7fffffffe270
r8             0x37     55
r9             0x42     66

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037|
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021|
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b|
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |        0x000001ef|
rsp +-->0x7fffffffe270 +------------------+
rbp +                  |0x00007fffffffe2b0|
        0x7fffffffe278 +------------------+
                       |0x0000000000400525|
        0x7fffffffe280 +------------------+
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
        0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

15      }
0x00000000004004d8 <sum+64>:    leaveq

この命令後のレジスタの状態

rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe278   0x7fffffffe278

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037|
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021|
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b|
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |        0x000001ef|
        0x7fffffffe270 +------------------+
                       |0x00007fffffffe2b0|
rsp --->0x7fffffffe278 +------------------+
                       |0x0000000000400525|
        0x7fffffffe280 +------------------+
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
rbp --->0x7fffffffe2b0 +------------------+  <== ベースポインタが関数 sum 呼出直前に戻る
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

0x00000000004004d9 <sum+65>:    retq

この命令後のレジスタの状態

rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe280   0x7fffffffe280

rip            0x400525 0x400525 <func+75>      <== 関数 func の戻り先アドレス

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037|
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021|
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b|
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |        0x000001ef|
        0x7fffffffe270 +------------------+
                       |0x00007fffffffe2b0|
        0x7fffffffe278 +------------------+
                       |0x0000000000400525|
rsp --> 0x7fffffffe280 +------------------+  <== スタックポインタが関数 sum 呼出直前に戻る
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0xffffffff|
rbp --->0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

関数 func に戻ってきた

0x0000000000400525 <func+75>:   mov    %eax,-0x4(%rbp)   <== 戻り値を RAX から受け取る

この命令後のレジスタの状態

rax            0x1ef    495

rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe280   0x7fffffffe280

        0x7fffffffe248 +------------------+
                       |0x00000042 0x00000037|
        0x7fffffffe250 +------------------+
                       |0x0000002c 0x00000021|
        0x7fffffffe258 +------------------+
                       |0x00000016 0x0000000b|
        0x7fffffffe260 +------------------+
                       |                  |
        0x7fffffffe268 +------------------+
                       |        0x000001ef|
        0x7fffffffe270 +------------------+
                       |0x00007fffffffe2b0|
        0x7fffffffe278 +------------------+
                       |0x0000000000400525|
rsp --> 0x7fffffffe280 +------------------+
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0x000001ef|  <== 戻り値を RAX から受け取って格納された
rbp --->0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

22
23        printf("sum: %d\n", ret);
0x0000000000400528 <func+78>:   mov    -0x4(%rbp),%esi
0x000000000040052b <func+81>:   mov    $0x400658,%edi
0x0000000000400530 <func+86>:   mov    $0x0,%eax
0x0000000000400535 <func+91>:   callq  0x400398 <printf@plt>

この命令後のレジスタの状態(printf 関数から返った直後)

rax            0x9      9      <=== printf からの戻り値(出力バイト数なので9バイト, "sum: 495\n")

rsi            0x2aaaaaaac000   46912496123904  <== printf の中で使用されたので変わっている
rdi            0x1      1                       <== printf の中で使用されたので変わっている
rbp            0x7fffffffe2b0   0x7fffffffe2b0
rsp            0x7fffffffe280   0x7fffffffe280

rsp --> 0x7fffffffe280 +------------------+  <== スタックポインタより上位アドレス(この絵では↓)は printf 呼出前と同じ
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0x000001ef|
rbp --->0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
        0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
        0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

進める

24      }
0x000000000040053a <func+96>:   leaveq
0x000000000040053b <func+97>:   retq

この命令後のレジスタの状態

rbp            0x7fffffffe2d0   0x7fffffffe2d0  <== 関数 func 呼出直前に戻っている
rsp            0x7fffffffe2c0   0x7fffffffe2c0  <== 関数 func 呼出直前に戻っている

rip            0x400555 0x400555 <main+25>      <== 関数 main の戻り先アドレス

        0x7fffffffe280 +------------------+ 
                       |0x0000004d        |
        0x7fffffffe288 +------------------+
                       |0x00000058        |
        0x7fffffffe290 +------------------+
                       |0x00000063        |
        0x7fffffffe298 +------------------+
                       |                  |
        0x7fffffffe2a0 +------------------+
                       |                  |
        0x7fffffffe2a8 +------------------+
                       |        0x000001ef|
        0x7fffffffe2b0 +------------------+
                       |0x00007fffffffe2d0|
        0x7fffffffe2b8 +------------------+
                       |0x0000000000400555|
rsp --> 0x7fffffffe2c0 +------------------+
                       |                  |
        0x7fffffffe2c8 +------------------+
                       |                  |
rbp --> 0x7fffffffe2d0 +------------------+
                       |                  |
        0x7fffffffe2d8 +------------------+

参考

[OS] OS コマンドによるボトルネック調査

目的

OS コマンドによるボトルネック調査方法をまとめる。

  • CPU
  • メモリ
  • I/O
  • ネットワーク

環境

  • OS: CentOS 5.5
  • Kernel: 2.6.18-194.el5 x86_64

CPU

サーバ全体の CPU 使用率

CPU 使用率を確認する。使用率が 100% に近くなっている(= idle が 0% に近くなっている)とボトルネック。

top

top では複数の論理 CPU がある場合もサーバ全体として 1 つに集約されて出力される。

$ top
top - 07:23:36 up 45 days, 17:41,  2 users,  load average: 7.22, 9.43, 8.03
Tasks: 223 total,   1 running, 222 sleeping,   0 stopped,   0 zombie
Cpu(s):  7.1%us,  8.0%sy,  0.0%ni, 70.8%id,  7.1%wa,  2.7%hi,  4.4%si,  0.0%st
Mem:   4044532k total,  3735152k used,   309380k free,   180688k buffers
Swap:  8159224k total,   629880k used,  7529344k free,  1822664k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 1314 oracle    15   0 1800m  83m  63m S  1.3  2.1  16:38.42 oracle
32392 root      15   0  362m  39m  15m S  1.0  1.0  18:24.31 orarootagent.bi
  667 grid      15   0 1244m  41m  16m S  0.7  1.0  11:23.32 oraagent.bin
32450 grid      RT   0  334m 121m  53m S  0.7  3.1  28:04.95 ocssd.bin
  442 grid      15   0  729m  32m  19m S  0.3  0.8   5:04.54 oracle
  676 root      15   0 1681m  27m  13m S  0.3  0.7  22:21.63 orarootagent.bi
 1016 oracle    15   0  826m  36m  16m S  0.3  0.9   7:57.93 oraagent.bin
 1300 oracle    -2   0 1781m  16m  14m S  0.3  0.4   0:28.27 oracle
 1306 oracle    15   0 1787m  24m  17m S  0.3  0.6   0:39.76 oracle

Cpu(s): で始まる行が CPU 使用率

Cpu(s):  7.1%us,  8.0%sy,  0.0%ni, 70.8%id,  7.1%wa,  2.7%hi,  4.4%si,  0.0%st

(-b オプションによるバッチモードでなく)対話的に起動した場合は 1 を押すと個々の CPU 毎の CPU 使用率が出力される。

$ top # 起動後 1 を押下
top - 07:25:32 up 45 days, 17:43,  2 users,  load average: 5.02, 8.37, 7.84
Tasks: 223 total,   1 running, 222 sleeping,   0 stopped,   0 zombie
Cpu0  :  5.6%us, 16.7%sy,  0.0%ni, 33.3%id, 41.7%wa,  0.0%hi,  2.8%si,  0.0%st
Cpu1  :  8.1%us,  5.4%sy,  0.0%ni, 70.3%id, 13.5%wa,  0.0%hi,  2.7%si,  0.0%st
Mem:   4044532k total,  3735368k used,   309164k free,   180704k buffers
Swap:  8159224k total,   629880k used,  7529344k free,  1822916k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
32392 root      15   0  362m  39m  15m S  1.0  1.0  18:24.71 orarootagent.bi
32450 grid      RT   0  334m 121m  53m S  1.0  3.1  28:05.51 ocssd.bin
  676 root      15   0 1681m  27m  13m S  0.7  0.7  22:22.10 orarootagent.bi
 1323 oracle    -2   0 1795m 280m 265m S  0.7  7.1  10:14.08 oracle
32382 grid      15   0  172m  25m  11m S  0.7  0.6   1:30.89 gpnpd.bin
  442 grid      15   0  729m  32m  19m S  0.3  0.8   5:04.61 oracle
  477 grid      15   0  718m  21m  16m S  0.3  0.6   1:58.51 oracle
  667 grid      15   0 1244m  41m  16m S  0.3  1.0  11:23.55 oraagent.bin
 1016 oracle    15   0  826m  36m  16m S  0.3  0.9   7:58.08 oraagent.bin

本環境は CPU 数が2つのため、以下のようにそれぞれの CPU 使用率が出力されている。

Cpu0  :  5.6%us, 16.7%sy,  0.0%ni, 33.3%id, 41.7%wa,  0.0%hi,  2.8%si,  0.0%st
Cpu1  :  8.1%us,  5.4%sy,  0.0%ni, 70.3%id, 13.5%wa,  0.0%hi,  2.7%si,  0.0%st

mpstat

mpstat でも CPU 使用率が確認できる。デフォルトではすべての CPU が集約されて出力される。

$ mpstat 2 3  # 2秒毎に3回出力
Linux 2.6.18-194.el5 (sv1.local)     04/08/13

07:33:04     CPU   %user   %nice    %sys %iowait    %irq   %soft  %steal   %idle    intr/s
07:33:06     all    4.41    0.00    4.41   13.24    1.47    0.00    0.00   76.47   1409.09
07:33:08     all    5.06    0.00    3.80    6.33    0.00    2.53    0.00   82.28   1481.40
07:33:10     all   10.61    0.00   10.61   12.12    0.00    3.03    0.00   63.64   1468.75
Average:     all    6.57    0.00    6.10   10.33    0.47    1.88    0.00   74.65   1455.56

個々の CPU の使用率を確認したい場合は、-P ALL オプションを付与する。

$ mpstat -P ALL 2 3
Linux 2.6.18-194.el5 (sv1.local)     04/08/13

07:35:03     CPU   %user   %nice    %sys %iowait    %irq   %soft  %steal   %idle    intr/s
07:35:05     all    9.09    0.00    5.19    7.79    0.00    2.60    0.00   75.32   1353.85
07:35:05       0   10.26    0.00    5.13   15.38    2.56    2.56    0.00   64.10   1353.85
07:35:05       1    7.69    0.00    2.56    2.56    0.00    2.56    0.00   84.62      0.00

07:35:05     CPU   %user   %nice    %sys %iowait    %irq   %soft  %steal   %idle    intr/s
07:35:07     all    5.80    0.00    4.35   10.14    0.00    2.90    0.00   76.81   1412.12
07:35:07       0    3.03    0.00    6.06   12.12    0.00    3.03    0.00   75.76   1412.12
07:35:07       1    5.88    0.00    2.94    8.82    0.00    0.00    0.00   82.35      0.00

07:35:07     CPU   %user   %nice    %sys %iowait    %irq   %soft  %steal   %idle    intr/s
07:35:09     all   10.00    0.00   15.00   13.33    0.00    1.67    0.00   60.00   1393.55
07:35:09       0    6.45    0.00   19.35   25.81    0.00    3.23    0.00   45.16   1393.55
07:35:09       1   13.33    0.00   13.33    0.00    0.00    0.00    0.00   73.33      0.00

Average:     CPU   %user   %nice    %sys %iowait    %irq   %soft  %steal   %idle    intr/s
Average:     all    8.25    0.00    7.77   10.19    0.00    2.43    0.00   71.36   1384.47
Average:       0    6.80    0.00    9.71   17.48    0.97    2.91    0.00   62.14   1384.47
Average:       1    8.74    0.00    5.83    3.88    0.00    0.97    0.00   80.58      0.00

各項目の意味は以下の通り。

項目 説明
CPU CPU番号。ALLの場合は、全CPUの平均値であることを示す。
%user ユーザレベル(アプリケーション)のCPU使用率
%nice 優先度(ナイス値)によるユーザーレベルのCPU使用率
%sys システムレベル(kernel)のCPU使用率
%iowait ディスクi/o競合によるCPU待機時間割合
%irq CPUの割り込み実行時間割合
%soft CPUのソフトウェア割り込み実行時間割合
%idle CPUのアイドル時間割合(ディスクi/o待機時間はのぞく)
intr/s 1秒あたりの平均割り込み数

vmstat

vmstat からも CPU 使用率が確認できる。

$ vmstat 2 3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0 629504 306172 181788 1825200    0    1   105   236   14   12  4  5 85  6  0
10  1 629504 306048 181788 1825200    0    0    35   732  300 1425  7  5 77 11  0
 4  0 629504 306040 181788 1825204    0    0    27     2  236 1552  9 11 75  5  0
項目 説明
r CPUを割り当て中もしくは割り当て可能なプロセスの数。CPUの個数以下であることが望ましい。
b 割り込みを禁止しているプロセスの数。I/O待ちなどで割り込み不可能なときに発生。ゼロであることが望ましい。
us ユーザー時間の CPU 使用率(nice 時間を含む)
sy システム時間の CPU 使用率
id アイドル時間の割合
wa IO 待ち時間の割合

CPU の割り当て状況を示す r, b の値も重要。r が CPU の個数と同じ場合は、システムの CPU がフルで使われており、 r が CPU 数より多い場合は、CPU の割り当てを待っていて、ボトルネックとなっている状況。 また、b が 0 より大きい場合は、I/O 等 CPU 以外のボトルネックが発生している可能性がある。

プロセス単位の CPU 使用率

プロセス単位の CPU 使用率を確認し、CPU 使用率が 100% に近くなっているプロセスが 無いか確認する。

top

top によりプロセス単位の CPU 使用率が確認できる。

出力結果の下部にプロセス毎の情報があり、%CPU で CPU 使用率が確認できる。

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 1314 oracle    15   0 1800m  83m  63m S  1.3  2.1  16:38.42 oracle
32392 root      15   0  362m  39m  15m S  1.0  1.0  18:24.31 orarootagent.bi
  667 grid      15   0 1244m  41m  16m S  0.7  1.0  11:23.32 oraagent.bin
32450 grid      RT   0  334m 121m  53m S  0.7  3.1  28:04.95 ocssd.bin
  442 grid      15   0  729m  32m  19m S  0.3  0.8   5:04.54 oracle
  676 root      15   0 1681m  27m  13m S  0.3  0.7  22:21.63 orarootagent.bi
 1016 oracle    15   0  826m  36m  16m S  0.3  0.9   7:57.93 oraagent.bin
 1300 oracle    -2   0 1781m  16m  14m S  0.3  0.4   0:28.27 oracle
 1306 oracle    15   0 1787m  24m  17m S  0.3  0.6   0:39.76 oracle

top を対話的に起動すると画面サイズ分しかプロセスが出力されず、すべてのプロセス が確認できるわけではない。その場合は -b オプションでバッチモードで起動する。 例えば、バッチモードで 2 秒毎に 3 回出力する場合は、top -b -d 2 -n 3 とする。

ps aux

ps aux の %CPU の項目でプロセス単位の CPU 使用率が確認できる。こちらはすべてのプロセスが 確認できる。

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  10348   680 ?        Ss   Feb21   0:15 init [5]
root         2  0.0  0.0      0     0 ?        S<   Feb21   5:34 [migration/0]
root         3  0.0  0.0      0     0 ?        SN   Feb21   0:10 [ksoftirqd/0]
root         4  0.0  0.0      0     0 ?        S<   Feb21   3:58 [migration/1]
root         5  0.0  0.0      0     0 ?        SN   Feb21   0:18 [ksoftirqd/1]
root         6  0.0  0.0      0     0 ?        S<   Feb21  33:22 [events/0]
root         7  0.0  0.0      0     0 ?        S<   Feb21   0:06 [events/1]
...

プロセス単位で CPU を使用している原因の特定

CPU を消費しているプロセスを特定したら、プロファイラ(OProfile, Valgrind(Callgrind)など)や 動的トレーサ(strace, ltrace など)で、どの関数で CPU を消費しているか特定していく。 簡易的には pstack を定期的に採取して、どの関数を通っている割合が多そうか確認する。

メモリ

物理メモリ、スワップの確認

物理メモリのサイズを確認するには次を実行。

$ grep MemTotal /proc/meminfo
MemTotal:      4044532 kB

スワップのサイズを確認するには次を実行。

$ grep SwapTotal /proc/meminfo
SwapTotal:     8159224 kB

サーバ全体の メモリ 使用量

メモリ使用量を確認して、物理メモリ以上使用されてスワップが多発していないか確認する。

vmstat, free, cat /proc/meminfo

同じタイミングで取得した vmstat, free, cat /proc/meminfo の出力結果は以下

vmstat の出力。

$ vmstat 2 3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0 629504 306172 181788 1825200    0    1   105   236   14   12  4  5 85  6  0
10  1 629504 306048 181788 1825200    0    0    35   732  300 1425  7  5 77 11  0
 4  0 629504 306040 181788 1825204    0    0    27     2  236 1552  9 11 75  5  0
項目 説明
swapd 仮想メモリの量(KB)
free 空きメモリの量(KB)
buff バッファに用いられているメモリの量(KB)
cache キャッシュに用いられているメモリの量(KB)

free の出力

$ free
             total       used       free     shared    buffers     cached
Mem:       4044532    3738376     306156          0     181788    1825204
-/+ buffers/cache:    1731384    2313148
Swap:      8159224     629504    7529720

cat /proc/meminfo の出力

$ cat /proc/meminfo
MemTotal:      4044532 kB
MemFree:        306148 kB
Buffers:        181788 kB
Cached:        1825204 kB
SwapCached:     358460 kB
Active:        2598312 kB
Inactive:       867084 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:      4044532 kB
LowFree:        306148 kB
SwapTotal:     8159224 kB
SwapFree:      7529720 kB
Dirty:             588 kB
Writeback:           0 kB
AnonPages:     1099912 kB
Mapped:         687964 kB
Slab:           153028 kB
PageTables:      63084 kB
NFS_Unstable:        0 kB
Bounce:              0 kB
CommitLimit:  10181488 kB
Committed_AS:  6503324 kB
VmallocTotal: 34359738367 kB
VmallocUsed:    286400 kB
VmallocChunk: 34359451067 kB
HugePages_Total:     0
HugePages_Free:      0
HugePages_Rsvd:      0
Hugepagesize:     2048 kB
項目 説明
MemTotal システム全体で利用できる物理メモリの総容量。システム起動時に計算される。その後、この値が変化することはない。
MemFree システム全体で利用できる物理メモリの空き容量
Buffers ファイルなどのメタデータとして使用している物理メモリの総容量
Cached ファイルデータのキャッシュなどに使用している物理メモリの総容量。共有メモリは Cached に加算される。SwapCachedは含まない。
SwapCached 物理メモリ上にキャッシュされたスワップページの総容量
Active 最近アクセスした(とカーネルが思っている)物理メモリの容量
Inactive 最近アクセスしていない(とカーネルが思っている)、解放してよい物理メモリの容量
Slab スラブアロケータで使用されている物理メモリの総容量
VmallocUsed vmalloc()で確保された物理メモリ領域とMMCONFIGで確保しているメモリ領域の総容量
AnonPages 無名ページ(Anonymous Page)の領域。無名ページとは、ユーザープロセスがmalloc()などで確保したり、プログラム本体用に利用するメモリ領域。

出力を確認すると以下が分かる。

  • vmstat の free」 = 「free の Mem: の free」 = 「cat /proc/meminfo の MemFree」
  • vmstat の buff」 = 「free の Mem: の buffers」 = 「cat /proc/meminfo の Buffers」
  • vmstat の cache」 = 「free の Mem: の cached」 = 「cat /proc/meminfo の Cached」

Linux では、空いているメモリはファイル I/O を効率化させるためにページキャッシュ として利用する。 buffers と cached はページキャッシュ(の一部)であり、実際はストレージと同期がとれ ていれば再利用可能なメモリである。

したがって利用可能な物理メモリ量は実際は、free + buffers + cached (free の free+)となる。

|================= total =================|
|= free =|============= used =============|

+--------+----------+-----------+---------+
|        |          |           |         |
|        |          |           |         |
|        |          |           |         |
+--------+----------+-----------+---------+

         |= cached =|= buffers =|
|============ free+ ============|= used- =|

厳密には、使用可能な物理メモリは「ストレージと同期されていない」ページキャッシュを除かないといけない。 ストレージと同期されており、すぐに再利用可能なメモリは「cat /proc/meminfo の Inactive 」 (もしくは vmstat -a)にて確認ができる。したがって、実際に再利用可能なメモリは厳密には「cat /proc/meminfo の MemFree + Inactive」 となる。(概算としては、free + buffers + cached でよいと思う。)

ちなみに /proc/meminfo に関して原則として、以下のような計算式が成り立つ。

  • MemTotal = MemFree + Active + Inactive + Slab + VmallocUsed + PageTables
  • Active + Inactive = AnonPages + Cached + Buffers + SwapCached
  • 利用可能なメモリ = MemFree + Inactive, 解放できないメモリ = Active + Slab + VmallocUsed + PageTables

スワップ状況の確認

サーバ全体のスワップ状況は、vmstat の swap 欄の si(ディスクからページインされるメモリの量 KB/秒), so(ディスクにページアウトしているメモリ量 KB/秒)から確認する。 si, so が定常的に 0 より大きい場合は、スワップが発生しているのでメモリ不足に陥っている。

$ vmstat 2    # メモリを使用するプログラムを実行中

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
20  1 731484  36160 180152 1722248    0    1   105   238    7    9  4  5 84  6  0
16  1 731484  31292 180152 1722248    0    0    69    80  151  788 22 78  0  0  0
13  3 731484  26836 180152 1722248    0    0    19   168  149  807 23 69  8  0  0
 2  0 733040  26528 179984 1720100    0    0    34    55  134  850 22 53 13 13  0
...<中略>
19  2 797140  24992   4276 787872    0 2804    56  2859  254 1024 38 62  0  0  0
19  1 814564  26708   2404 761484    0 5008   131  5191  246  912 37 63  0  0  0
 6  1 816372  25500   2236 755376    0  626   113   882  156  599 35 65  0  0  0
28  4 824480  26208   1516 739372    0 3912   871  3934  265 1038 28 61  0 10  0
16  2 840912  24156   1232 725428    0 3166   963  3216  196  834 30 66  0  5  0
19  4 861804  25324   1268 718716    0 6790   323  7015  222  839 34 66  0  0  0
 7  2 890208  28728   1296 709336    0 8648   878  8650  282 1061 30 66  2  3  0
19  3 919964  34312   1376 707076    0 10578   399 10820  293 1106 29 66  0  5  0
24  6 987308  55048   1384 707584    0 24256   293 24326  425 1415 36 64  0  0  0
 2  9 987292  38232   1444 707816   32    0   207   158  189  738 48 50  0  2  0
 6  2 1009184  33604   1504 709208    0 10228   780 10291  342 1047 30 58  5  7  0
 5  2 1014212  26284   1536 709544    0 2078   259  2086  151  798 37 55  0  8  0
21  1 1030672  27936   1556 710044   16 7522   323  7758  225  944 37 58  2  3  0
10  1 1041180  26028   1604 710244   18 5318   239  5320  207 1013 38 62  0  0  0
24  5 1049788  28312   1632 710332    0 3618    31  3681  117  494 44 56  0  0  0
項目 説明
si ディスクからスワップインされているメモリの量 (KB/s)
so ディスクにスワップしているメモリの量 (KB/s)

また、スワップ時は kswapd0 というカーネルスレッドが動作するので、これが top などで確認して CPU 使用率の上位に出現しているとスワップが多発している状況と判断できる。

プロセス単位の メモリ 使用量

プロセス単位のメモリ使用量を確認して、物理メモリを多く消費しているプロセスが無いか確認する。

top

VIRT, RES, SHR, %MEM から確認する。RES が物理メモリ使用量。

$ top
top - 11:41:10 up 46 days, 21:59,  3 users,  load average: 9.69, 8.95, 7.96
Tasks: 226 total,   9 running, 217 sleeping,   0 stopped,   0 zombie
Cpu(s):  3.8%us,  4.4%sy,  0.1%ni, 84.5%id,  6.1%wa,  0.3%hi,  0.8%si,  0.0%st
Mem:   4044532k total,  2077900k used,  1966632k free,     7208k buffers
Swap:  8159224k total,   938488k used,  7220736k free,   745852k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
  477 grid      15   0  718m  21m  16m S  1.0  0.6   2:35.70 oracle
  667 grid      15   0 1247m  44m  16m S  1.0  1.1  15:30.20 oraagent.bin
 1310 oracle    15   0 1781m  17m  15m S  1.0  0.4   1:34.40 oracle
 1314 oracle    15   0 1800m  84m  64m S  1.0  2.1  21:53.70 oracle
32241 root      15   0  319m  51m  21m S  1.0  1.3   7:56.18 ohasd.bin
32360 grid      15   0  310m  35m  15m S  1.0  0.9   7:53.41 oraagent.bin
32582 root      16   0  240m  21m  10m S  1.0  0.6   8:48.50 octssd.bin
    1 root      15   0 10348  672  568 S  0.0  0.0   0:15.40 init
項目 説明
VIRT 使用している仮想メモリの総量
RES 使用しているスワップされていない物理メモリの総量
SHR 利用している共有メモリの総量。他のプロセスと共有される可能性がある。
%MEM 現在使用している利用可能な物理メモリの占有率

ps aux

%MEM, VSZ, RSS から確認する。RSS が物理メモリ使用量。

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  10348   672 ?        Ss   Feb21   0:15 init [5]
root         2  0.0  0.0      0     0 ?        S<   Feb21   5:40 [migration/0]
...<中略>
grid       477  0.0  0.5 735924 22424 ?        Ss   Apr04   2:35 asm_gmon_+ASM1
...<中略>
grid       667  0.2  1.1 1277500 45740 ?       Ssl  Apr04  15:30 /u01/app/11.2.0.3/grid/bin/oraagent.bin
...<中略>
oracle    1310  0.0  0.4 1823952 17752 ?       Ss   Apr04   1:34 ora_ping_rac1
oracle    1312  0.0  0.4 1823952 17188 ?       Ss   Apr04   0:04 ora_acms_rac1
oracle    1314  0.3  2.1 1843460 86036 ?       Ss   Apr04  21:53 ora_dia0_rac1
...
項目 説明
%MEM 現在使用している利用可能な物理メモリの占有率
VSZ 使用している仮想メモリの総量(KB)
RSS 使用しているスワップされていない物理メモリの総量(KB)

I/O

デバイス毎の I/O 状況

デバイス毎の I/O 状況を確認する。ビジー率が 100% に近かったり、IOPS, スループット(MB/s) が カタログ・スペックと比較して限界性能に近かったり、定常的に I/O キューが溜まっていると ボトルネックとなっている。

iostat -x

iostat -x によりデバイス毎の I/O 状況が確認できる。 最初の1回目はシステムがブートしてからその時点までの統計情報であるので注意。

$ iostat -x 2 3
Linux 2.6.18-194.el5 (sv1.local)       04/09/2013

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           1.63    0.01    1.58    1.91    0.00   94.88

Device:         rrqm/s   wrqm/s   r/s   w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.22    17.31  0.43  8.47    19.48   206.31    25.34     0.32   36.46   3.53   3.14
sda1              0.00     0.00  0.00  0.00     0.05     0.00    33.95     0.00    4.87   3.33   0.00
sda2              0.21    17.31  0.42  8.47    19.41   206.31    25.38     0.32   36.53   3.53   3.14
sdb               0.01     1.34  0.10  0.20     4.00    12.34    54.49     0.01   24.80   4.21   0.13
sdb1              0.01     1.34  0.10  0.20     4.00    12.34    54.60     0.01   24.85   4.22   0.13
sdc               0.07    13.21  0.52  4.53    49.09   141.94    37.83     0.16   31.58   8.10   4.09
sdc1              0.07    13.21  0.52  4.53    49.09   141.94    37.83     0.16   31.58   8.10   4.09
sdd               0.05     3.10  0.13  0.09    29.64    25.52   244.66     0.01   35.69   5.43   0.12
sdd1              0.05     3.10  0.13  0.09    29.63    25.52   245.32     0.01   35.79   5.45   0.12
dm-0              0.00     0.00  1.01 43.09    66.84   344.69     9.33     1.30   29.41   1.42   6.28
dm-1              0.00     0.00  0.21  0.45     1.65     3.57     8.00     0.03   40.23   0.35   0.02
dm-2              0.00     0.00  0.28  4.73    33.61    37.87    14.26     0.69  137.64   0.42   0.21

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           1.43    0.00    0.00    2.86    0.00   95.71

Device:         rrqm/s   wrqm/s   r/s   w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sda1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sda2              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdb               0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdb1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdc               0.00     7.14  0.00  4.29     0.00    91.43    21.33     0.49  114.33 107.00  45.86
sdc1              0.00     7.14  0.00  4.29     0.00    91.43    21.33     0.49  114.33 107.00  45.86
sdd               0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdd1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
dm-0              0.00     0.00  0.00 11.43     0.00    91.43     8.00     1.83  159.88  40.12  45.86
dm-1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
dm-2              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           1.19    0.00    0.00    1.19    0.00   97.62

Device:         rrqm/s   wrqm/s   r/s   w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sda1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sda2              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdb               0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdb1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdc               0.00     5.95  0.00  2.38     0.00    47.62    20.00     0.03    6.50   8.00   1.90
sdc1              0.00     5.95  0.00  2.38     0.00    47.62    20.00     0.03    6.50   8.00   1.90
sdd               0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
sdd1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
dm-0              0.00     0.00  0.00  9.52     0.00    76.19     8.00     0.07    4.62   2.00   1.90
dm-1              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
dm-2              0.00     0.00  0.00  0.00     0.00     0.00     0.00     0.00    0.00   0.00   0.00
項目 説明
r/s 読み込みリクエスト数(回/秒)
w/s 書き込みリクエスト数(回/秒)
rsec/s 読み込みセクタ数(個/秒)。1 セクタ = 512bytes なので、512 を掛ければ読み込み byte 量が分かる。
wsec/s 書き込みセクタ数(個/秒)。1 セクタ = 512bytes なので、512 を掛ければ書き込み byte 量が分かる。
avgqu-sz IOリクエストのキュー(待ち行列)の平均サイズ
await IOリクエストの平均待ち時間(ミリ秒)。キューにいる時間+処理時間。
svctm IOリクエストの平均処理時間(ミリ秒)
%util IOリクエスト実行中の CPU 時間の割合。この値が 100% に近いとビジーであり、ボトルネックとなる。

IOPS は r/s + w/s で算出できる。 スループットについては、rsec/s, wsec/s に 512 を掛ければ byte 単位のスループット(bytes/s)が算出できる。 iostat -x -k というように -k オプションを付与すると書込み量が kB/s で出力されるので見やすくなる。

ちなみに dm-* で表されているデバイスは、デバイス・マッパーと呼ばれるもので Logical Volume に対応している。 lvdisplay で出力される Block device の項目の右の数字や、ls -l /dev/mapper で LV との対応が分かる。

$ sudo /usr/sbin/lvdisplay
  --- Logical volume ---
  LV Name                /dev/VolGroup01/LogVol00
  VG Name                VolGroup01
  LV UUID                hDNPM9-jBMV-7xto-mChi-ubR9-iN3i-styC8L
  LV Write Access        read/write
  LV Status              available
  # open                 1
  LV Size                39.99 GB
  Current LE             10237
  Segments               2
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:2

  --- Logical volume ---
  LV Name                /dev/VolGroup00/LogVol00
  VG Name                VolGroup00
  LV UUID                2V6IUo-8Eui-DHq1-Nnzl-g0Pf-Z8HN-FIKz2E
  LV Write Access        read/write
  LV Status              available
  # open                 1
  LV Size                35.94 GB
  Current LE             1150
  Segments               2
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:0

  --- Logical volume ---
  LV Name                /dev/VolGroup00/LogVol01
  VG Name                VolGroup00
  LV UUID                sotwax-PxcG-hwzh-dCG7-tkO1-Vy2m-W5qk5M
  LV Write Access        read/write
  LV Status              available
  # open                 1
  LV Size                3.91 GB
  Current LE             125
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:1

$ ls -l /dev/mapper
total 0
crw------- 1 root root  10, 63 Apr  9 16:50 control
brw-rw---- 1 root disk 253,  0 Apr  9 16:50 VolGroup00-LogVol00
brw-rw---- 1 root disk 253,  1 Apr  9 16:50 VolGroup00-LogVol01
brw-rw---- 1 root disk 253,  2 Apr  9 16:55 VolGroup01-LogVol00

ネットワーク

ネットワークインターフェイス毎の ネットワーク使用状況

ネットワークインターフェイス毎の ネットワーク使用状況を確認する。 ネットワーク帯域 bps (bit per second), 処理パケット数 pps (packet per second) といったスループットが カタログ・スペックと比較して限界性能に近いとボトルネックとなっている。 bps は (byte でなく) bit 単位であることに注意。

また、処理するパケット数が多い(chatty な処理)と、CPU のソフトウェア割り込みが多くなるので、CPU 使用率で ソフトウェア割り込みが多くなっていないかも確認する。(mpstat%soft の項目から確認できる。)

netstat -e -a -i -n

netstat -e -a -i -n によりネットワークインターフェイス毎の ネットワーク使用状況が確認できる。 インターフェイス起動後からの累積値で表されるため、定期的に採取して差分を採る必要がある。

$ netstat -e -a -i -n
Kernel Interface table
eth0      Link encap:Ethernet  HWaddr 00:0C:29:1C:4C:61
          inet addr:192.168.238.138  Bcast:192.168.238.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe1c:4c61/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8836237 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9148333 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:1491856405 (1.3 GiB)  TX bytes:6627509379 (6.1 GiB)

eth0:1    Link encap:Ethernet  HWaddr 00:0C:29:1C:4C:61
          inet addr:192.168.238.141  Bcast:192.168.238.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

eth0:3    Link encap:Ethernet  HWaddr 00:0C:29:1C:4C:61
          inet addr:192.168.238.140  Bcast:192.168.238.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

eth0:5    Link encap:Ethernet  HWaddr 00:0C:29:1C:4C:61
          inet addr:192.168.238.143  Bcast:192.168.238.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

eth1      Link encap:Ethernet  HWaddr 00:0C:29:1C:4C:6B
          inet addr:192.168.13.14  Bcast:192.168.13.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe1c:4c6b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:160311137 errors:0 dropped:0 overruns:0 frame:0
          TX packets:139265311 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:118804988857 (110.6 GiB)  TX bytes:84439638737 (78.6 GiB)

eth1:1    Link encap:Ethernet  HWaddr 00:0C:29:1C:4C:6B
          inet addr:169.254.94.158  Bcast:169.254.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:23503874 errors:0 dropped:0 overruns:0 frame:0
          TX packets:23503874 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:20946402718 (19.5 GiB)  TX bytes:20946402718 (19.5 GiB)

sit0      Link encap:IPv6-in-IPv4
          NOARP  MTU:1480  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
項目 説明
MTU MTU 値
RX packets 受信パケット数
TX packets 送信パケット数
RX bytes 受信バイト数
TX bytes 送信バイト数

まとめ

まとめると、以下のような点を確認してボトルネックとなっていないか特定する。

  • CPU: CPU 使用率が 100% に近くないか。
  • メモリ: 物理メモリに空きがあるか(利用可能な物理メモリ量は十分か)、スワップが発生していないか。
  • I/O: デバイスがビジーでないか、IOPS, スループットがスペック限界に達していないか。
  • ネットワーク: ネットワークの帯域限界、パケット数の限界に達していないか。

ボトルネック特定のフロー

4.メモリ使用率(第5章 パフォーマンス管理~上級:基本管理コースII) のフローが参考になる。

システム全体の調査

+---------+
| vmstat  |
+----+----+
     |
     V
+---------+  Yes   +------------------+
| id < 10 +------->| CPU 使用率の評価 |
+----+----+        +------------------+
     |
     | No
     V
+---------+  No    +------------------+
| so >  0 +------->| Disk 使用率の評価|
+----+----+        +------------------+
     |
     | Yes
     V
+----------+
|メモリ不足|
+----------+

CPU 使用率の調査

+---------+
| vmstat  |
+----+----+
     |
     V
+---------+  Yes   
| sy > 30 +-------------+
+----+----+             |
     |                  V
     | No          +----------+  No    +------------------+
     |             | in > 200 +------->| Disk 使用率の評価|
     |             +----+-----+        +------------------+
     |                  |
     |                  | Yes
     |                  V
     |             +------------------+
     |             |ハードウェアの問題|
     V             +------------------+
+----------+  Yes
|  r > 0   +-------------+
+----+-----+             |
     |                   |
     | No                |
     V                   V
+------------+     +------------+
|CPU のアップ|     | CPU の追加 |
|グレード    |     +------------+
+------------+

Disk 使用率の調査

+---------+
|iostat -x|
+----+----+
     |
     V
+---------+  Yes   +------------------+
|%util >80+------->|デバイスの負荷分散|
+----+----+        +------------------+
     |
     | No
     V
+---------+  Yes   +--------------------------+
|w/s > r/s+------->|ディスク・キャッシュの使用|
+----+----+        +--------------------------+
     |
     | No
     V
+------------------+
|ネットワークの調査|
+------------------+

参考

[OS] メモリリークの調査方法

目的

メモリリークの調査方法をまとめる。

環境

  • OS: CentOS 5.5
  • Kernel: 2.6.18-194.el5 x86_64
  • GCC: gcc 4.1.2 20080704
  • GDB: GNU gdb 7.0.1-23.el5
  • Valgrind: valgrind-3.5.0

サンプルプログラム

メモリリークが起きるサンプルとして以下を利用する。 leak_func() が実行される度に 2048 bytes メモリリークする。 合計で 101 回 leak_func() が実行されるので 206848bytes(= 2048 * 101 bytes) リークする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>

#define STR_BYTES 2048

void *my_alloc(size_t size)
{
  void *ret = malloc(size);

  if (ret == NULL)
  {
    fprintf(stdout, "Cannot malloc struct\n");
    exit(1);
  }

  return ret;
}

void my_free(void *ptr)
{
  free(ptr);
}

void leak_func()
{
  char *str = NULL;
  char *leak_str = NULL;

  str = (char *)my_alloc(STR_BYTES);
  leak_str = (char *)my_alloc(STR_BYTES);

  snprintf(str, STR_BYTES, "freed memory");
  snprintf(leak_str, STR_BYTES, "leaked memory");

  printf("%s: 0x%016lx, ", str, str);
  printf("%s: 0x%016lx\n", leak_str, leak_str);

  my_free((void *)str);
  leak_str = NULL; /* leak_str が free() されていないのでリークする */
}

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

  leak_func();
  printf("Press enter key:");
  getchar();

  for (i = 0; i < 100; ++i)
    leak_func();

  printf("Press enter key:");
  getchar();
  return 0;
}

実行結果は以下。

$ gcc -o memory_leak_sample memory_leak_sample.c
$ ./memory_leak_sample
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a19820
Press enter key:
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a1a030
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a1a840
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a1b050
<中略>
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a4b650
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a4be60
Press enter key:

valgrind による調査

Valgrind は、メモリリークの検出等を行うツール。 プロファイリング等メモリリーク検出以外の機能もあり、メモリリー検出で使用する場合は --tool=memcheck を指定する。

Valgrind を使用して上記サンプルを動作させた例が以下。なお、Valgrind を通してプログラムを実行するとすごく遅くなるので注意。

$ valgrind --tool=memcheck --leak-check=yes --leak-resolution=high --num-callers=40 --undef-value-errors=no --run-libc-freeres=no -v ./memory_leak_sample
==16207== Memcheck, a memory error detector
==16207== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==16207== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==16207== Command: ./memory_leak_sample
==16207==
--16207-- Valgrind options:
--16207--    --tool=memcheck
--16207--    --leak-check=yes
--16207--    --leak-resolution=high
--16207--    --num-callers=40
--16207--    --undef-value-errors=no
--16207--    --run-libc-freeres=no
--16207--    -v
--16207-- Contents of /proc/version:
--16207--   Linux version 2.6.18-194.el5 (mockbuild@builder10.centos.org) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-48)) #1 SMP Fri Apr 2
 14:58:14 EDT 2010
--16207-- Arch and hwcaps: AMD64, amd64-sse3-cx16
--16207-- Page sizes: currently 4096, max supported 4096
--16207-- Valgrind library directory: /usr/lib64/valgrind
--16207-- Reading syms from /home/hashi/tmp/memory_leak_sample (0x400000)
--16207-- Reading syms from /usr/lib64/valgrind/memcheck-amd64-linux (0x38000000)
--16207--    object doesn't have a dynamic symbol table
--16207-- Reading syms from /lib64/ld-2.5.so (0x3e1c600000)
--16207-- Reading suppressions file: /usr/lib64/valgrind/default.supp
--16207-- REDIR: 0x3e1c614620 (strlen) redirected to 0x3803e767 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--16207-- Reading syms from /usr/lib64/valgrind/vgpreload_core-amd64-linux.so (0x4802000)
--16207-- Reading syms from /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so (0x4a03000)
==16207== WARNING: new redirection conflicts with existing -- ignoring it
--16207--     new: 0x3e1c614620 (strlen              ) R-> 0x04a06dc0 strlen
--16207-- REDIR: 0x3e1c614440 (index) redirected to 0x4a06c30 (index)
--16207-- REDIR: 0x3e1c6145f0 (strcmp) redirected to 0x4a06e90 (strcmp)
--16207-- Reading syms from /lib64/libc-2.5.so (0x3e1ca00000)
--16207-- REDIR: 0x3e1ca79ba0 (rindex) redirected to 0x4a06ae0 (rindex)
--16207-- REDIR: 0x3e1ca74c70 (malloc) redirected to 0x4a05d9a (malloc)
--16207-- REDIR: 0x3e1ca797b0 (strlen) redirected to 0x4a06d80 (strlen)
freed memory: 0x0000000004c24040, leaked memory: 0x0000000004c24880
--16207-- REDIR: 0x3e1ca72720 (free) redirected to 0x4a059aa (free)
Press enter key:
freed memory: 0x0000000004c250c0, leaked memory: 0x0000000004c25900
freed memory: 0x0000000004c26140, leaked memory: 0x0000000004c26980
<中略>
freed memory: 0x0000000004c89140, leaked memory: 0x0000000004c89980
freed memory: 0x0000000004c8a1c0, leaked memory: 0x0000000004c8aa00
freed memory: 0x0000000004c8b240, leaked memory: 0x0000000004c8ba80
Press enter key:
==16207==
==16207== HEAP SUMMARY:
==16207==     in use at exit: 206,848 bytes in 101 blocks
==16207==   total heap usage: 202 allocs, 101 frees, 413,696 bytes allocated
==16207==
==16207== Searching for pointers to 101 not-freed blocks
==16207== Checked 73,400 bytes
==16207==
==16207== 2,048 bytes in 1 blocks are definitely lost in loss record 1 of 2
==16207==    at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207==    by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x4007AE: main (in /home/hashi/tmp/memory_leak_sample)
==16207==
==16207== 204,800 bytes in 100 blocks are definitely lost in loss record 2 of 2
==16207==    at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207==    by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x4007D5: main (in /home/hashi/tmp/memory_leak_sample)
==16207==
==16207== LEAK SUMMARY:
==16207==    definitely lost: 206,848 bytes in 101 blocks
==16207==    indirectly lost: 0 bytes in 0 blocks
==16207==      possibly lost: 0 bytes in 0 blocks
==16207==    still reachable: 0 bytes in 0 blocks
==16207==         suppressed: 0 bytes in 0 blocks
==16207==
==16207== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==16207== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

いろいろ出力があるが、以下の出力からどの Call Stack で確保されたメモリがどれだけリークしているか分かる。

==16207== 2,048 bytes in 1 blocks are definitely lost in loss record 1 of 2
==16207==    at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207==    by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x4007AE: main (in /home/hashi/tmp/memory_leak_sample)
==16207==
==16207== 204,800 bytes in 100 blocks are definitely lost in loss record 2 of 2
==16207==    at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207==    by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207==    by 0x4007D5: main (in /home/hashi/tmp/memory_leak_sample)

上述のやり方だと、シェルから valgrind と共に直接実行できるプログラムでないと調査できない。 デーモンなど直接実行できないプログラムの場合は、ラップするシェルスクリプトを用意するとよい。

例えば、最終的に some_daemon.bin というバイナリが実行されるデーモンがあるとして、以下の ようにラップするシェルスクリプトを同一名で用意して、元のデーモンと同じように起動・停止すればよい。

$ cp -p /path/to/some_daemon.bin /path/to/some_daemon.bin.backup
$ mv /path/to/some_daemon.bin /path/to/some_daemon.bin.orig
$ vi some_daemon.bin # 以下の内容で作成
--------
#!/bin/sh

ORG_BIN=/path/to/some_daemon.bin.orig
LOG_LOC_AND_PREFIX=/tmp/valgrind_instance.%p.log
VALG_PATH=/usr/bin/valgrind
VALGRIND_OPTS="--log-file=$LOG_LOC_AND_PREFIX --leak-check=yes --leak-resolution=high --num-callers=40 --undef-value-errors=no --run-libc-freeres=no --error-limit=no -v"
export VALGRIND_OPTS

exec $VALG_PATH --tool=memcheck $ORG_BIN "$*"
--------
$ chown root:root some_daemon.bin # 元のバイナリと同じオーナーにする
$ chmod 755 some_daemon.bin       # 元のバイナリと同じパーミッションにする

これで上記LOG_LOC_AND_PREFIXに指定したファイルにログが出力される。

pmap と gdb による調査

プロセスのメモリマップを表示する pmap を採取してリークしている領域を特定し、その内容を gdb から確認する。

まず、pmap を採取して増加している領域を特定する。合わせメモリダンプを確認するために gcore により core を採取しておく。

プログラムを実行

$ ./memory_leak_sample
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8e820
Press enter key:

この時の pmap の結果は以下

$ ps -ef | grep memory_leak_sample
hashi    16639 29479  0 10:47 pts/10   00:00:00 ./memory_leak_sample
$ pmap -x 16639
16639:   ./memory_leak_sample
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  memory_leak_sample
0000000000600000       4       4       4 rw---  memory_leak_sample
0000000007b8e000     132       8       8 rw---    [ anon ]
0000003e1c600000     112      96       0 r-x--  ld-2.5.so
0000003e1c81b000       4       4       4 r----  ld-2.5.so
0000003e1c81c000       4       4       4 rw---  ld-2.5.so
0000003e1ca00000    1336     260       0 r-x--  libc-2.5.so
0000003e1cb4e000    2044       0       0 -----  libc-2.5.so
0000003e1cd4d000      16      12       8 r----  libc-2.5.so
0000003e1cd51000       4       4       4 rw---  libc-2.5.so
0000003e1cd52000      20      16      16 rw---    [ anon ]
00002b2fac886000      12       8       8 rw---    [ anon ]
00002b2fac89f000       8       8       8 rw---    [ anon ]
00007fff1b3a6000      84      12      12 rw---    [ stack ]
ffffffffff600000    8192       0       0 -----    [ anon ]
----------------  ------  ------  ------
total kB           11976     440      76

合わせて core を採取しておく(上書きされないようにリネームしておく)

$ gcore 16639
0x0000003e1cac5ff0 in __read_nocancel () from /lib64/libc.so.6
Saved corefile core.16639

$ mv core.16639 core.16639.before

プログラムを進める

$ ./memory_leak_sample
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8e820
Press enter key:  <=== エンターキーを押下
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8f030
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8f840
<中略>
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bae410
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007baec20
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007baf430
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bafc40
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bb0450
<中略>
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bc0650
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bc0e60
Press enter key:

この時の pmap と core を採取しておく

$ pmap -x 16639
16639:   ./memory_leak_sample
Address           Kbytes     RSS   Dirty Mode   Mapping
0000000000400000       4       4       0 r-x--  memory_leak_sample
0000000000600000       4       4       4 rw---  memory_leak_sample
0000000007b8e000     264     208     208 rw---    [ anon ]
0000003e1c600000     112      96       0 r-x--  ld-2.5.so
0000003e1c81b000       4       4       4 r----  ld-2.5.so
0000003e1c81c000       4       4       4 rw---  ld-2.5.so
0000003e1ca00000    1336     260       0 r-x--  libc-2.5.so
0000003e1cb4e000    2044       0       0 -----  libc-2.5.so
0000003e1cd4d000      16      12       8 r----  libc-2.5.so
0000003e1cd51000       4       4       4 rw---  libc-2.5.so
0000003e1cd52000      20      16      16 rw---    [ anon ]
00002b2fac886000      12      12      12 rw---    [ anon ]
00002b2fac89f000       8       8       8 rw---    [ anon ]
00007fff1b3a6000      84      12      12 rw---    [ stack ]
ffffffffff600000    8192       0       0 -----    [ anon ]
----------------  ------  ------  ------
total kB           12108     644     280

$ gcore 16639
0x0000003e1cac5ff0 in __read_nocancel () from /lib64/libc.so.6
Saved corefile core.16639

$ mv core.16639 core.16639.after

1回目と2回目の pmap の結果を比較すると、以下の個所で仮想メモリ量が増加している(リークしている)ことが分かる。

5c5
< 0000000007b8e000     132       8       8 rw---    [ anon ]
---
> 0000000007b8e000     264     208     208 rw---    [ anon ]

仮想メモリ量が 132Kbytes –> 264Kbytes (+132Kbytes)に増加している。 増加したアドレスのメモリダンプを確認して、どのように利用されているか確認してみる。

具体的にはメモリ増加後の core で 0x0000000007b8e000 + 132Kbytes のアドレス(0x7baf000)から 増加した 132Kbytes 分のメモリダンプを確認する。 ダンプを採るときに x/16896xg 0x7baf000 としているのは、アドレス 0x7baf000 から、 8バイト(ジャイアント・ワード)単位で(g)、16896個分を、16進数で(x)出力するということ。 つまり、16896 * 8 = 135168 = 132Kbytes 分が出力される。

$ gdb
(gdb) set height 0
(gdb) file ./memory_leak_sample
(gdb) core-file core.16639.after
(gdb) set logging file core.16639.after.gdb.log
(gdb) set logging on
(gdb) x/16896xg 0x7baf000
0x7baf000:      0x0000000000000000      0x0000000000000000
0x7baf010:      0x0000000000000000      0x0000000000000000
<中略>
0x7baf410:      0x0000000000000000      0x0000000000000000
0x7baf420:      0x0000000000000000      0x0000000000000811
0x7baf430:      0x6d2064656b61656c      0x00000079726f6d65
0x7baf440:      0x0000000000000000      0x0000000000000000
<中略>
0x7bcffe0:      0x0000000000000000      0x0000000000000000
0x7bcfff0:      0x0000000000000000      0x0000000000000000
(gdb) quit

このメモリダンプが pmap 上増加した分。内容を確認すると以下の文字列が見える。

$ grep -v "0x0000000000000000" core.16639.after.gdb.log
0x7baf430:      0x6d2064656b61656c      0x00000079726f6d65
0x7bafc40:      0x6d2064656b61656c      0x00000079726f6d65
0x7bb0450:      0x6d2064656b61656c      0x00000079726f6d65
0x7bb0c60:      0x6d2064656b61656c      0x00000079726f6d65
<中略>
0x7bbf630:      0x6d2064656b61656c      0x00000079726f6d65
0x7bbfe40:      0x6d2064656b61656c      0x00000079726f6d65
0x7bc0650:      0x6d2064656b61656c      0x00000079726f6d65
0x7bc0e60:      0x6d2064656b61656c      0x00000079726f6d65

0x6d2064656b61656c 0x00000079726f6d65 は ASCII で直すと “leaked memory”

$ ruby -e 's="6d2064656b61656c"; s.unpack("a2" * (s.size / 2)) {|c| print c.hex.chr}; puts'
m dekael
$ ruby -e 's="00000079726f6d65"; s.unpack("a2" * (s.size / 2)) {|c| print c.hex.chr}; puts'
yrome

よってプログラム中で “leaked memory” を入れている領域がリークしていると分かる。 こんなに明らかに分かるケースは少なく、実際のプログラムではポインタが見えていたり して、さらに core を追わないとリーク原因箇所が追えないケースが多いと思うがメモリ リーク原因究明のとっかかりにはなる。

追記

gdb によるメモリダンプについて

gdb による core のメモリダンプは dump binary memory によって行うことができる。 (こちらのほうが x でダンプするより速い。)

(gdb) help dump binary memory
Write contents of memory to a raw binary file.
Arguments are FILE START STOP.  Writes the contents of memory
within the range [START .. STOP) to the specifed FILE in binary format.

例えば上述したもの同じように core で 0x7baf000 から 132Kbytes 分(0x7bd0000 まで)メモリダンプする場合は 以下のように行う。

$ gdb
(gdb) core-file core.16639.after
(gdb) dump binary memory core.16639.after.gdb.dump.log 0x7baf000 0x7bd0000
(gdb) quit

これでファイル core.16639.after.gdb.dump.log にダンプされている。 中身はバイナリなので odhexdump で内容を確認する。

$ od -v -t x8z -A x ./core.16639.after.gdb.dump.log
000000 0000000000000000 0000000000000000  >................<
000010 0000000000000000 0000000000000000  >................<
<中略>
000410 0000000000000000 0000000000000000  >................<
000420 0000000000000000 0000000000000811  >................<
000430 6d2064656b61656c 00000079726f6d65  >leaked memory...<
000440 0000000000000000 0000000000000000  >................<
<中略>
020fe0 0000000000000000 0000000000000000  >................<
020ff0 0000000000000000 0000000000000000  >................<
021000

同じ内容が連続する場合に省略する場合は -v を付けなければよい。

$ od -t x8z -A x ./core.16639.after.gdb.dump.log
000000 0000000000000000 0000000000000000  >................<
*
000420 0000000000000000 0000000000000811  >................<
000430 6d2064656b61656c 00000079726f6d65  >leaked memory...<
000440 0000000000000000 0000000000000000  >................<
*
000c30 0000000000000000 0000000000000811  >................<
000c40 6d2064656b61656c 00000079726f6d65  >leaked memory...<
000c50 0000000000000000 0000000000000000  >................<
*
<中略>
011e50 0000000000000000 0000000000000811  >................<
011e60 6d2064656b61656c 00000079726f6d65  >leaked memory...<
011e70 0000000000000000 0000000000000000  >................<
*
012660 0000000000000000 000000000000e9a1  >................<
012670 0000000000000000 0000000000000000  >................<
*
021000

core から pmap と同じような情報を得る方法

gdb の info target とか info filespmap と同じような情報を確認できる。 (pmap を採っていなくても core から同じような情報が何とか見れる。)

$ gdb
(gdb) file ./memory_leak_sample
(gdb) core-file core.16639.after
(gdb) info target
Symbols from "/home/hashi/tmp/memory_leak_sample".
Local core dump file:
        `/home/hashi/tmp/core.16639.after', file type elf64-x86-64.
        0x0000000000400000 - 0x0000000000400000 is load1
        0x0000000000600000 - 0x0000000000601000 is load2
        0x0000000007b8e000 - 0x0000000007bd0000 is load3
        0x0000003e1c600000 - 0x0000003e1c600000 is load4
        0x0000003e1c81b000 - 0x0000003e1c81b000 is load5
        0x0000003e1c81c000 - 0x0000003e1c81d000 is load6
        0x0000003e1ca00000 - 0x0000003e1ca00000 is load7
        0x0000003e1cd4d000 - 0x0000003e1cd4d000 is load8
        0x0000003e1cd51000 - 0x0000003e1cd52000 is load9
        0x0000003e1cd52000 - 0x0000003e1cd57000 is load10
        0x00002b2fac886000 - 0x00002b2fac889000 is load11
        0x00002b2fac89f000 - 0x00002b2fac8a1000 is load12
        0x00007fff1b3a6000 - 0x00007fff1b3bb000 is load13
Local exec file:
        `/home/hashi/tmp/memory_leak_sample', file type elf64-x86-64.
        Entry point: 0x4005b0
        0x0000000000400200 - 0x000000000040021c is .interp
        0x000000000040021c - 0x000000000040023c is .note.ABI-tag
        0x0000000000400240 - 0x0000000000400264 is .gnu.hash
        0x0000000000400268 - 0x0000000000400370 is .dynsym
        0x0000000000400370 - 0x00000000004003d8 is .dynstr
        0x00000000004003d8 - 0x00000000004003ee is .gnu.version
        0x00000000004003f0 - 0x0000000000400410 is .gnu.version_r
        0x0000000000400410 - 0x0000000000400440 is .rela.dyn
        0x0000000000400440 - 0x0000000000400500 is .rela.plt
        0x0000000000400500 - 0x0000000000400518 is .init
        0x0000000000400518 - 0x00000000004005a8 is .plt
        0x00000000004005b0 - 0x00000000004008d8 is .text
        0x00000000004008d8 - 0x00000000004008e6 is .fini
        0x00000000004008e8 - 0x0000000000400957 is .rodata
        0x0000000000400958 - 0x0000000000400994 is .eh_frame_hdr
        0x0000000000400998 - 0x0000000000400a8c is .eh_frame
        0x0000000000600a90 - 0x0000000000600aa0 is .ctors
        0x0000000000600aa0 - 0x0000000000600ab0 is .dtors
        0x0000000000600ab0 - 0x0000000000600ab8 is .jcr
        0x0000000000600ab8 - 0x0000000000600c48 is .dynamic
        0x0000000000600c48 - 0x0000000000600c50 is .got
        0x0000000000600c50 - 0x0000000000600ca8 is .got.plt
        0x0000000000600ca8 - 0x0000000000600cac is .data
        0x0000000000600cb0 - 0x0000000000600cc8 is .bss

アドレスぐらいしか分からないが Local core dump file: の項が pmap の出力と対応している。 (アドレスを引けばサイズが分かる。)

Local core dump file:
        `/home/hashi/tmp/core.16639.after', file type elf64-x86-64.
        0x0000000000400000 - 0x0000000000400000 is load1  <=== TEXT 領域など
        0x0000000000600000 - 0x0000000000601000 is load2  <=== BSS 領域など
        0x0000000007b8e000 - 0x0000000007bd0000 is load3  <=== size: 264Kbytes (= 0x0000000007bd0000 - 0x0000000007b8e000)
        0x0000003e1c600000 - 0x0000003e1c600000 is load4  <=== size:   0?
        0x0000003e1c81b000 - 0x0000003e1c81b000 is load5  <=== size:   0?
        0x0000003e1c81c000 - 0x0000003e1c81d000 is load6  <=== size:   4Kbytes
        0x0000003e1ca00000 - 0x0000003e1ca00000 is load7  <=== size:   0?
        0x0000003e1cd4d000 - 0x0000003e1cd4d000 is load8  <=== size:   0?
        0x0000003e1cd51000 - 0x0000003e1cd52000 is load9  <=== size:   4Kbytes
        0x0000003e1cd52000 - 0x0000003e1cd57000 is load10 <=== size:  20Kbytes
        0x00002b2fac886000 - 0x00002b2fac889000 is load11 <=== size:  12Kbytes
        0x00002b2fac89f000 - 0x00002b2fac8a1000 is load12 <=== size:   8Kbytes
        0x00007fff1b3a6000 - 0x00007fff1b3bb000 is load13 <=== size:  84Kbytes

ちなみに maintenace info sections でも同じような情報が採れる。

(gdb) maintenance info sections
Exec file:
    `/home/hashi/tmp/memory_leak_sample', file type elf64-x86-64.
    0x00400200->0x0040021c at 0x00000200: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x0040021c->0x0040023c at 0x0000021c: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400240->0x00400264 at 0x00000240: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400268->0x00400370 at 0x00000268: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400370->0x004003d8 at 0x00000370: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x004003d8->0x004003ee at 0x000003d8: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x004003f0->0x00400410 at 0x000003f0: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400410->0x00400440 at 0x00000410: .rela.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400440->0x00400500 at 0x00000440: .rela.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400500->0x00400518 at 0x00000500: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x00400518->0x004005a8 at 0x00000518: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x004005b0->0x004008d8 at 0x000005b0: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x004008d8->0x004008e6 at 0x000008d8: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x004008e8->0x00400957 at 0x000008e8: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400958->0x00400994 at 0x00000958: .eh_frame_hdr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00400998->0x00400a8c at 0x00000998: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x00600a90->0x00600aa0 at 0x00000a90: .ctors ALLOC LOAD DATA HAS_CONTENTS
    0x00600aa0->0x00600ab0 at 0x00000aa0: .dtors ALLOC LOAD DATA HAS_CONTENTS
    0x00600ab0->0x00600ab8 at 0x00000ab0: .jcr ALLOC LOAD DATA HAS_CONTENTS
    0x00600ab8->0x00600c48 at 0x00000ab8: .dynamic ALLOC LOAD DATA HAS_CONTENTS
    0x00600c48->0x00600c50 at 0x00000c48: .got ALLOC LOAD DATA HAS_CONTENTS
    0x00600c50->0x00600ca8 at 0x00000c50: .got.plt ALLOC LOAD DATA HAS_CONTENTS
    0x00600ca8->0x00600cac at 0x00000ca8: .data ALLOC LOAD DATA HAS_CONTENTS
    0x00600cb0->0x00600cc8 at 0x00000cac: .bss ALLOC
    0x00000000->0x00000114 at 0x00000cac: .comment READONLY HAS_CONTENTS
Core file:
    `/home/hashi/tmp/core.16639.after', file type elf64-x86-64.
    0x00000000->0x00000528 at 0x00000350: note0 READONLY HAS_CONTENTS
    0x00000000->0x000000d8 at 0x00000470: .reg/16639 HAS_CONTENTS
    0x00000000->0x000000d8 at 0x00000470: .reg HAS_CONTENTS
    0x00000000->0x00000200 at 0x00000564: .reg2/16639 HAS_CONTENTS
    0x00000000->0x00000200 at 0x00000564: .reg2 HAS_CONTENTS
    0x00000000->0x00000100 at 0x00000778: .auxv HAS_CONTENTS
    0x00400000->0x00400000 at 0x00000878: load1 ALLOC READONLY CODE
    0x00600000->0x00601000 at 0x00000878: load2 ALLOC LOAD HAS_CONTENTS
    0x07b8e000->0x07bd0000 at 0x00001878: load3 ALLOC LOAD HAS_CONTENTS
    0x3e1c600000->0x3e1c600000 at 0x00043878: load4 ALLOC READONLY CODE
    0x3e1c81b000->0x3e1c81b000 at 0x00043878: load5 ALLOC READONLY
    0x3e1c81c000->0x3e1c81d000 at 0x00043878: load6 ALLOC LOAD HAS_CONTENTS
    0x3e1ca00000->0x3e1ca00000 at 0x00044878: load7 ALLOC READONLY CODE
    0x3e1cd4d000->0x3e1cd4d000 at 0x00044878: load8 ALLOC READONLY
    0x3e1cd51000->0x3e1cd52000 at 0x00044878: load9 ALLOC LOAD HAS_CONTENTS
    0x3e1cd52000->0x3e1cd57000 at 0x00045878: load10 ALLOC LOAD HAS_CONTENTS
    0x2b2fac886000->0x2b2fac889000 at 0x0004a878: load11 ALLOC LOAD HAS_CONTENTS
    0x2b2fac89f000->0x2b2fac8a1000 at 0x0004d878: load12 ALLOC LOAD HAS_CONTENTS
    0x7fff1b3a6000->0x7fff1b3bb000 at 0x0004f878: load13 ALLOC LOAD HAS_CONTENTS

なお、info proc mappings というコマンドもあるが、これはプロセス起動中に gdb でアタッチしたときに 使用して /proc から情報を採るものなので、core からの調査には使えないようだ。 (core に対して実行すると PID:1 の /sbin/init/proc/1/maps 情報が出てきてしまい役に立たない。)

core から文字列等を検索する

リークしている領域の特徴に当たりがついていれば gdb の find で見つけるという方法もある。

(gdb) help find
Search memory for a sequence of bytes.
Usage:
find [/size-char] [/max-count] start-address, end-address, expr1 [, expr2 ...]
find [/size-char] [/max-count] start-address, +length, expr1 [, expr2 ...]
size-char is one of b,h,w,g for 8,16,32,64 bit values respectively,
and if not specified the size is taken from the type of the expression
in the current language.
Note that this means for example that in the case of C-like languages
a search for an untyped 0x42 will search for "(int) 0x42"
which is typically four bytes.

The address of the last match is stored as the value of "$_".
Convenience variable "$numfound" is set to the number of matches.

例えば上述の例で 0x7baf000 から 132Kbytes 分の間に “leaked memory” という文字列を見つける。

$ gdb
(gdb) core-file core.16639.after
(gdb) find 0x7baf000, +(132 * 1024), "leaked memory"
0x7baf430
0x7bafc40
0x7bb0450
0x7bb0c60
0x7bb1470
0x7bb1c80
0x7bb2490
0x7bb2ca0
0x7bb34b0
0x7bb3cc0
0x7bb44d0
0x7bb4ce0
0x7bb54f0
0x7bb5d00
0x7bb6510
0x7bb6d20
0x7bb7530
0x7bb7d40
0x7bb8550
0x7bb8d60
0x7bb9570
0x7bb9d80
0x7bba590
0x7bbada0
0x7bbb5b0
0x7bbbdc0
0x7bbc5d0
0x7bbcde0
0x7bbd5f0
0x7bbde00
0x7bbe610
0x7bbee20
0x7bbf630
0x7bbfe40
0x7bc0650
0x7bc0e60
36 patterns found.

36 個所で見つかった。 なお、find の引数で文字列を渡して探すときは文字列は NULL 終端を含むことに注意。 例えば、上記の例で部分文字列で探そうと “leaked” としても引っかからない。

(gdb)  find 0x7baf000, +(132 * 1024), "leaked"
Pattern not found.

部分文字列で探す場合は文字列を16進数にするしかなさそう。 (リトルエンディアンで “leaked” は 0x64656b61656c になる。)

(gdb)  find /b  0x7baf000, +(132 * 1024), 0x64656b61656c
0x7baf430
0x7bafc40
<中略>
0x7bc0650
0x7bc0e60
36 patterns found.