らんだむな記憶

blogというものを体験してみようか!的なー

みかん日記 (7)

みかん日記 (6) - らんだむな記憶 の補足として、lldb を使って中身を追いかけてみる。
まず、

sudo apt install lldb-7

lldb をインストールしておく。

lldb-7 foo

みたいな感じでプロセスにアタッチした状態で lldb を起動する。
今回はソースコードの 5 行目に関数 foo の処理の先頭が来るようにしているのでここにブレークポイントを設定する。

(lldb) b foo.cpp:5
Breakpoint 1: where = foo`foo() + 4 at foo.cpp:5, address = 0x00000000004004a4
(lldb) r
Process 13891 stopped
* thread #1, name = 'foo', stop reason = step in
    frame #0: 0x00000000004004a4 foo`foo() at foo.cpp:5
   2       
   3       void foo()
   4       {
-> 5           int i = 42;
   6           int* p = &i;
   7           int r1 = *p;
   8           *p = 1;

の時点で

(lldb) p/x $rsp
(unsigned long) $0 = 0x00007fffffffe0d0
(lldb) p/x $rbp
(unsigned long) $1 = 0x00007fffffffe0d0

なので、レジスタ rsprbp を見ると、確かに一致している。

(lldb) s
Process 14189 stopped
* thread #1, name = 'foo', stop reason = step in
    frame #0: 0x00000000004004ab foo`foo() at foo.cpp:6
   3       void foo()
   4       {
   5           int i = 42;
-> 6           int* p = &i;
   7           int r1 = *p;
   8           *p = 1;
   9           int r2 = i;

に移動すると、int i = 42; が実行されたはずである。これを確認すると、

(lldb) p *(int*)($rbp - 4)
(int) $2 = 42

となり、確かにそうなっている。さらに進めて、

(lldb) s
Process 14189 stopped
* thread #1, name = 'foo', stop reason = step in
    frame #0: 0x00000000004004b3 foo`foo() at foo.cpp:7
   4       {
   5           int i = 42;
   6           int* p = &i;
-> 7           int r1 = *p;
   8           *p = 1;
   9           int r2 = i;
   10          uintptr_t addr = reinterpret_cast<uintptr_t>(p);

まで来る。int* p = &i; が実行されたはずである。これを確認すると、

(lldb) p &i
(int *) $3 = 0x00007fffffffe0cc

より、変数 i のアドレスは 0x00007fffffffe0cc である。

(lldb) p/x $rax
(unsigned long) $4 = 0x00007fffffffe0cc

により、このアドレスが確かにレジスタ rax に入っていることが分かる。

(lldb) p/x *(uint64_t*)($rbp - 16)
(uint64_t) $5 = 0x00007fffffffe0cc

により、レジスタ rbp の手前 16 の位置に変数 i のアドレスが書き込まれていることが分かる。
さらに進めて、

(lldb) s
Process 14189 stopped
* thread #1, name = 'foo', stop reason = step in
    frame #0: 0x00000000004004bc foo`foo() at foo.cpp:8
   5           int i = 42;
   6           int* p = &i;
   7           int r1 = *p;
-> 8           *p = 1;
   9           int r2 = i;
   10          uintptr_t addr = reinterpret_cast<uintptr_t>(p);
   11          int* q = reinterpret_cast<int*>(addr);

まで来る。int r1 = *p; が実行されたはずである。これを確認すると、

(lldb) p p
(int *) $6 = 0x00007fffffffe0cc
(lldb) p/x $rax
(unsigned long) $7 = 0x00007fffffffe0cc

なので、レジスタ rax にはポインタ p の指しているアドレスが入っていることが分かる。

(lldb) p $ecx
(unsigned int) $8 = 42

より、レジスタ ecx には、ポインタ p が指しているアドレスの変数 i の値 42 が入っていることが分かる。

(lldb) p *(int*)($rbp - 20)
(int) $9 = 42

より、レジスタ rbp の手前 20 の位置に変数 i の値 42 が書き込まれていることが分かる。
後は繰り返しなので割愛するとして、レジスタ rbp が指し示すアドレス周辺のメモリの内容を見たい。

(lldb) $rbp - 20
(unsigned long) $10 = 0x00007fffffffe0bc

なので

(lldb) x/5 0x00007fffffffe0bc
0x7fffffffe0bc: 0x0000002a 0xffffe0cc 0x00007fff 0x00000000
0x7fffffffe0cc: 0x0000002a

くらいで幾らか書き出すと、$rbp - 20 の位置に 4 バイト分のデータ 42(変数 i 由来の値)があり、$rbp - 16 の位置に 8 バイト分のデータとして 変数 i のアドレスが見えていることが分かる。「(int) $2 = 42」で見たように $rbp - 4 の位置に 4 バイト分のデータ 42 があることも見て取れる。

lldb のコマンドについては lldb で使えるコマンド一覧 を参考にした。