はじめに
普段ほとんどメモリを意識しておらず、ふとメモリがどうなってるのか気になったので、最近知ったgdbを使いつつ見ていきます。
前準備
source code
コメントアウトしてある部分は、そのデータがメモリ上のどのセグメントに入るのかを表しています。
#include <stdio.h>
#include <stdlib.h>
char a[] = "global variable"; //.data
const char b[] = "global constant"; // .rodata
int c; // .bss
int main() {
int d = 123456789; // stack
char e[] = "local variable"; // stack
static char f[] = "static variable"; // .data
int *g; // heap
g = (int*) malloc(5 * sizeof(int));
for(int i = 0; i < 5; ++i) {
g[i] = i + 1;
}
free(g);
printf("string literal\n"); // .rodata
return 0;
}
コンパイル
-
-O0
: 最適化をしないようにします。(多分optimizeを0の意) -
-g
: デバッグが可能になります。
% gcc -O0 -g -o sample sample.c
実行
% gdb ./sample
ブレイクポイントの設定
heap領域がいい感じで変化していくのが見たいので、16行目にブレイクポイントを入れます。
ブレイクポイントを入れて、そのままrunをすると16行目で止まります。
(gdb) break 17
Breakpoint 1 at 0x1201: file sample.c, line 17.
(gdb) run
Breakpoint 1, main () at sample.c:17
17 g[i] = i + 1;
メモリマッピングの確認
-
info proc mappings
: 現在のプロセスのメモリマッピングを見ることができます。
(gdb) info proc mappings
process 173
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x55c60bd4f000 0x55c60bd50000 0x1000 0x0 r--p /qiita/sample
0x55c60bd50000 0x55c60bd51000 0x1000 0x1000 r-xp /qiita/sample
0x55c60bd51000 0x55c60bd52000 0x1000 0x2000 r--p /qiita/sample
0x55c60bd52000 0x55c60bd53000 0x1000 0x2000 r--p /qiita/sample
0x55c60bd53000 0x55c60bd54000 0x1000 0x3000 rw-p /qiita/sample
0x55c60c8ca000 0x55c60c8eb000 0x21000 0x0 rw-p [heap]
0x7f9d1417c000 0x7f9d1417f000 0x3000 0x0 rw-p
0x7f9d1417f000 0x7f9d141a7000 0x28000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f9d141a7000 0x7f9d1433c000 0x195000 0x28000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f9d1433c000 0x7f9d14394000 0x58000 0x1bd000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f9d14394000 0x7f9d14398000 0x4000 0x214000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f9d14398000 0x7f9d1439a000 0x2000 0x218000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f9d1439a000 0x7f9d143a7000 0xd000 0x0 rw-p
0x7f9d143aa000 0x7f9d143ac000 0x2000 0x0 rw-p
0x7f9d143ac000 0x7f9d143ae000 0x2000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f9d143ae000 0x7f9d143d8000 0x2a000 0x2000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f9d143d8000 0x7f9d143e3000 0xb000 0x2c000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f9d143e4000 0x7f9d143e6000 0x2000 0x37000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f9d143e6000 0x7f9d143e8000 0x2000 0x39000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7fff6a31e000 0x7fff6a33f000 0x21000 0x0 rw-p [stack]
0x7fff6a39e000 0x7fff6a3a2000 0x4000 0x0 r--p [vvar]
0x7fff6a3a2000 0x7fff6a3a4000 0x2000 0x0 r-xp [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 r-xp [vsyscall]
-
info files
: 各アドレスがどのセグメントに対応しているかを確認することができます。
(gdb) info files
Symbols from "/qiita/sample".
Native process:
Using the running image of child Thread 0x7f9d1417c740 (LWP 173).
While running this, GDB does not access memory from...
Local exec file:
`/qiita/sample', file type elf64-x86-64.
Entry point: 0x55c60bd500c0
0x000055c60bd4f318 - 0x000055c60bd4f334 is .interp
0x000055c60bd4f338 - 0x000055c60bd4f368 is .note.gnu.property
0x000055c60bd4f368 - 0x000055c60bd4f38c is .note.gnu.build-id
0x000055c60bd4f38c - 0x000055c60bd4f3ac is .note.ABI-tag
0x000055c60bd4f3b0 - 0x000055c60bd4f3d4 is .gnu.hash
0x000055c60bd4f3d8 - 0x000055c60bd4f4c8 is .dynsym
0x000055c60bd4f4c8 - 0x000055c60bd4f57c is .dynstr
0x000055c60bd4f57c - 0x000055c60bd4f590 is .gnu.version
0x000055c60bd4f590 - 0x000055c60bd4f5d0 is .gnu.version_r
0x000055c60bd4f5d0 - 0x000055c60bd4f690 is .rela.dyn
0x000055c60bd4f690 - 0x000055c60bd4f6f0 is .rela.plt
0x000055c60bd50000 - 0x000055c60bd5001b is .init
0x000055c60bd50020 - 0x000055c60bd50070 is .plt
0x000055c60bd50070 - 0x000055c60bd50080 is .plt.got
0x000055c60bd50080 - 0x000055c60bd500c0 is .plt.sec
0x000055c60bd500c0 - 0x000055c60bd5025d is .text
0x000055c60bd50260 - 0x000055c60bd5026d is .fini
0x000055c60bd51000 - 0x000055c60bd5102f is .rodata
0x000055c60bd51030 - 0x000055c60bd51064 is .eh_frame_hdr
0x000055c60bd51068 - 0x000055c60bd51114 is .eh_frame
0x000055c60bd52da0 - 0x000055c60bd52da8 is .init_array
0x000055c60bd52da8 - 0x000055c60bd52db0 is .fini_array
0x000055c60bd52db0 - 0x000055c60bd52fa0 is .dynamic
0x000055c60bd52fa0 - 0x000055c60bd53000 is .got
0x000055c60bd53000 - 0x000055c60bd53030 is .data
0x000055c60bd53030 - 0x000055c60bd53038 is .bss
...
メモリレイアウト
※ 画像のライセンス(https://en.wikipedia.org/wiki/File:Program_memory_layout.pdf)
text
textセグメントには、機械語命令が含まれます。
0x000055c60bd500c0 - 0x000055c60bd5025d is .text
textセグメントは、0x000055c60bd500c0 ~ 0x000055c60bd5025d
にマッピングされていることがわかります。
これをxコマンドを使ってのぞいてみると、実際に機械語の命令が格納されていることがわかります。
(gdb) x/110x 0x000055c60bd500c0
0x55c60bd500c0 <_start>: 0xfa1e0ff3 0x8949ed31 0x89485ed1 0xe48348e2
0x55c60bd500d0 <_start+16>: 0x455450f0 0xc931c031 0xca3d8d48 0xff000000
0x55c60bd500e0 <_start+32>: 0x002ef315 0x2e66f400 0x00841f0f 0x00000000
0x55c60bd500f0 <deregister_tm_clones>: 0x393d8d48 0x4800002f 0x2f32058d 0x39480000
0x55c60bd50100 <deregister_tm_clones+16>: 0x481574f8 0x2ed6058b 0x85480000 0xff0974c0
0x55c60bd50110 <deregister_tm_clones+32>: 0x801f0fe0 0x00000000 0x801f0fc3 0x00000000
0x55c60bd50120 <register_tm_clones>: 0x093d8d48 0x4800002f 0x2f02358d 0x29480000
0x55c60bd50130 <register_tm_clones+16>: 0xf08948fe 0x3feec148 0x03f8c148 0x48c60148
0x55c60bd50140 <register_tm_clones+32>: 0x1474fed1 0xa5058b48 0x4800002e 0x0874c085
0x55c60bd50150 <register_tm_clones+48>: 0x0f66e0ff 0x0000441f 0x801f0fc3 0x00000000
0x55c60bd50160 <__do_global_dtors_aux>: 0xfa1e0ff3 0x2ec53d80 0x75000000 0x8348552b
0x55c60bd50170 <__do_global_dtors_aux+16>: 0x002e823d 0x89480000 0x480c74e5 0x2e863d8b
0x55c60bd50180 <__do_global_dtors_aux+32>: 0xe9e80000 0xe8fffffe 0xffffff64 0x2e9d05c6
0x55c60bd50190 <__do_global_dtors_aux+48>: 0x5d010000 0x001f0fc3 0x801f0fc3 0x00000000
0x55c60bd501a0 <frame_dummy>: 0xfa1e0ff3 0xffff77e9 0x1e0ff3ff 0x894855fa
0x55c60bd501b0 <main+7>: 0xec8348e5 0x8b486430 0x00282504 0x89480000
0x55c60bd501c0 <main+23>: 0xc031f845 0x15dc45c7 0x48075bcd 0x636f6cb8
0x55c60bd501d0 <main+39>: 0x76206c61 0x45894861 0xf145c7e9 0x62616972
0x55c60bd501e0 <main+55>: 0xf545c766 0x45c6656c 0x14bf00f7 0xe8000000
0x55c60bd501f0 <main+71>: 0xfffffebc 0xe0458948 0x00d845c7 0xeb000000
0x55c60bd50200 <main+87>: 0xd8458b20 0x8d489848 0x00008514 0x8b480000
0x55c60bd50210 <main+103>: 0x0148e045 0xd8558bd0 0x8901c283 0xd8458310
0x55c60bd50220 <main+119>: 0xd87d8301 0x48da7e04 0x48e0458b 0x4de8c789
0x55c60bd50230 <main+135>: 0x48fffffe 0x0de6058d 0x89480000 0xfe4ee8c7
0x55c60bd50240 <main+151>: 0x00b8ffff 0x48000000 0x64f8558b 0x25142b48
0x55c60bd50250 <main+167>: 0x00000028 0x45e80574 0xc9fffffe 0x000000c3
0x55c60bd50260 <_fini>: 0xfa1e0ff3 0x08ec8348 0x08c48348 0x000000c3
0x55c60bd50270: 0x00000000 0x00000000
0x55c60bd501a0 <frame_dummy>: 0xfa1e0ff3 0xffff77e9 0x1e0ff3ff 0x894855fa
0x55c60bd501b0 <main+7>: 0xec8348e5 0x8b486430 0x00282504 0x89480000
↑ 0x894855fa, 0xec8348e5, 0x8b486430 こんな感じでプロローグの部分が含まれていることもわかります。
55 push %rbp
48 89 e5 mov %rsp,%rbp
48 83 ec 30 sub $0x30,%rsp
data
dataセグメントは、初期化されたデータが格納されるセグメントで、さらにread-write
用のセグメント(.data
)とread-only
用のセグメント(.rodata
)に分けることができます。
グローバル変数やstaticローカル変数は、.data
に格納され、グローバル定数や文字列リテラルは、.rodata
に格納されます。
.data
0x000055c60bd53000 - 0x000055c60bd53030 is .data
0x000055c60bd53000
からのメモリをのぞいてみると、グローバル変数として定義したchar a[] = "global variable";
とstaticローカル変数として定義したstatic char f[] = "static variable";
のデータが格納されています。
(gdb) x/15s 0x000055c60bd53000
0x55c60bd53000: ""
0x55c60bd53001: ""
0x55c60bd53002: ""
0x55c60bd53003: ""
0x55c60bd53004: ""
0x55c60bd53005: ""
0x55c60bd53006: ""
0x55c60bd53007: ""
0x55c60bd53008: "\b0\325\v\306U"
0x55c60bd5300f: ""
0x55c60bd53010 <a>: "global variable"
0x55c60bd53020 <f.0>: "static variable"
0x55c60bd53030 <completed.0>: ""
0x55c60bd53031: ""
0x55c60bd53032: ""
.rodata
0x000055c60bd51000 - 0x000055c60bd5102f is .rodata
グローバル定数(const char b[] = "global constant";
)とprintf
の引数として渡した文字列リテラル(printf("string literal\n");
)が格納されています。
(gdb) x/20s 0x000055c60bd51000
0x55c60bd51000 <_IO_stdin_used>: "\001"
0x55c60bd51002 <_IO_stdin_used+2>: "\002"
0x55c60bd51004: ""
0x55c60bd51005: ""
0x55c60bd51006: ""
0x55c60bd51007: ""
0x55c60bd51008: ""
0x55c60bd51009: ""
0x55c60bd5100a: ""
0x55c60bd5100b: ""
0x55c60bd5100c: ""
0x55c60bd5100d: ""
0x55c60bd5100e: ""
0x55c60bd5100f: ""
0x55c60bd51010 <b>: "global constant"
0x55c60bd51020: "string literal"
0x55c60bd5102f: ""
0x55c60bd51030: "\001\033\003;4"
0x55c60bd51036: ""
0x55c60bd51037: ""
bss
bssセグメントには初期化されていない、グローバル変数やstatic変数が入ります。
このセグメントの実行時に0初期化されます。
0x000055c60bd53030 - 0x000055c60bd53038 is .bss
(gdb) x/10x 0x000055c60bd53030
0x55c60bd53030 <completed.0>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x55c60bd53038: 0x00 0x00
stack
0x7fff6a31e000 0x7fff6a33f000 0x21000 0x0 rw-p [stack]
この例だと、0x7fff6a31e000 ~ 0x7fff6a33f000
がstack領域になります。
ローカル変数はstack領域に置かれます。
といってもローカル変数は、rsp
とrbp
の間にあるので、そこを確認します。
(gdb) print $rsp
$2 = (void *) 0x7fff6a33e110
(gdb) print $rbp
$3 = (void *) 0x7fff6a33e140
int d = 123456789;
(gdb) x/16d 0x7fff6a33e110
0x7fff6a33e110: 0 0 0 123456789
0x7fff6a33e120: 210543264 21958 1668246528 1981836385
0x7fff6a33e130: 1634300513 6646882 1157728000 751852084
0x7fff6a33e140: 1 0 337284496 32669
char e[] = "local variable"
(gdb) x/20s 0x7fff6a33e110
0x7fff6a33e110: ""
0x7fff6a33e111: ""
0x7fff6a33e112: ""
0x7fff6a33e113: ""
0x7fff6a33e114: ""
0x7fff6a33e115: ""
0x7fff6a33e116: ""
0x7fff6a33e117: ""
0x7fff6a33e118: ""
0x7fff6a33e119: ""
0x7fff6a33e11a: ""
0x7fff6a33e11b: ""
0x7fff6a33e11c: "\025\315[\a\240\242\214\f\306U"
0x7fff6a33e127: ""
0x7fff6a33e128: ""
0x7fff6a33e129: "local variable"
0x7fff6a33e138: ""
0x7fff6a33e139: "\207\001E4Z\320,\001"
0x7fff6a33e142: ""
0x7fff6a33e143: ""
heap
0x55c60c8ca000 0x55c60c8eb000 0x21000 0x0 rw-p [heap]
heap領域は実行時に動的なメモリ確保に使われる領域です。
mallocやcallocを使って領域を確保することができます。
//...
int *g;
g = (int*) malloc(5 * sizeof(int));
for(int i = 0; i < 5; ++i) {
g[i] = i + 1; // <- ここが17行目
}
//...
現在は17行目にブレイクポイントを入れていて、止まっているので領域が確保されただけで特に値は入っていない状態です。
(gdb) print g
$4 = (int *) 0x55c60c8ca2a0
(gdb) x/4x 0x55c60c8ca2a0
0x55c60c8ca2a0: 0x00000000 0x00000000 0x00000000 0x00000000
next
コマンドで進めるとg[i] = i + 1;
が実行され、値が入ります。
(gdb) next 2
(gdb) x/4x 0x55c60c8ca2a0
0x55c60c8ca2a0: 0x00000001 0x00000000 0x00000000 0x00000000
(gdb) next 2
(gdb) x/4x 0x55c60c8ca2a0
0x55c60c8ca2a0: 0x00000001 0x00000002 0x00000000 0x00000000
(gdb) next 2
(gdb) x/4x 0x55c60c8ca2a0
0x55c60c8ca2a0: 0x00000001 0x00000002 0x00000003 0x00000000
(gdb) next 2
(gdb) x/4x 0x55c60c8ca2a0
0x55c60c8ca2a0: 0x00000001 0x00000002 0x00000000 0x00000004
参考