GDB & Virtual Address


GDB & Virtual Address


仮想メモリの概念をより明確に理解するために、GDBの実践を通じて、実際の仮想メモリにおけるグローバル変数と領域変数の位置を理解します.32ビットコンピュータに基づいています.
>> docker pull tomerbd/gcc-gdb-dockerfile
>> docker run -it -v $(pwd):/src \
   --security-opt seccomp=unconfined \
   tomerbd/gcc-gdb-dockerfile \
   /bin/bash
--security-opt seccomp=unconfinedが与えられた場合、gdb動作はアドレス空間ランダム化を生じない.
#include <stdlib.h> 

int main() {
    int a = 0xBEEF;
    int *b = malloc(sizeof(int));
    g = 0xDDDD;
    return 0; 
}
上記のコードを実行可能オブジェクトファイルにコンパイルし、gdbを使用します.
root@8c32719ecb7c:~# gcc main.c
root@8c32719ecb7c:~# ls
a.out  main.c
root@8c32719ecb7c:~# gdb a.out

(gdb) disas main
Dump of assembler code for function main:
   0x000000000040052d <+0>:	    push   %rbp
   0x000000000040052e <+1>:	    mov    %rsp,%rbp
   0x0000000000400531 <+4>:	    sub    $0x10,%rsp
   0x0000000000400535 <+8>:	    movl   $0xbeef,-0x10(%rbp)
   0x000000000040053c <+15>:	mov    $0x4,%edi
   0x0000000000400541 <+20>:	callq  0x400430 <malloc@plt>
   0x0000000000400546 <+25>:	mov    %rax,-0x8(%rbp)
   0x000000000040054a <+29>:	movl   $0xdddd,-0xc(%rbp)
   0x0000000000400551 <+36>:	mov    $0x0,%eax
   0x0000000000400556 <+41>:	leaveq
   0x0000000000400557 <+42>:	retq
End of assembler dump.
コンポーネントを確認できます.gdbコマンドdisasは、コードの周囲の構築を確認することができ、disas mainは、仮想メモリ内のプライマリ関数構築を確認することができる.まずInstruction第1部0x000000000040052d <+0>: push %rbpを見てみましょうここでは、仮想メモリ上のコード領域の開始位置です.

上の図では、Read-onlyセグメントは、例では0 x 40052 dのアドレスを持つコードの開始部分であり、理論的にはコード位置0 x 40054 s付近に位置する.これにより、ギジェコードが理論に従って仮想メモリに位置することを確保できます.
ここの0xBEEFのaの住所はどこですか?
(gdb) break *0x000000000040053c
Breakpoint 1 at 0x40053c
(gdb) run
Starting program: /root/a.out

Breakpoint 1, 0x000000000040053c in main ()
(gdb)
(gdb) disas
Dump of assembler code for function main:
   0x000000000040052d <+0>:	    push   %rbp
   0x000000000040052e <+1>:	    mov    %rsp,%rbp
   0x0000000000400531 <+4>:	    sub    $0x10,%rsp
   0x0000000000400535 <+8>:	    movl   $0xbeef,-0x10(%rbp)
=> 0x000000000040053c <+15>:	mov    $0x4,%edi
   0x0000000000400541 <+20>:	callq  0x400430 <malloc@plt>
   0x0000000000400546 <+25>:	mov    %rax,-0x8(%rbp)
   0x000000000040054a <+29>:	movl   $0xdddd,-0xc(%rbp)
   0x0000000000400551 <+36>:	mov    $0x0,%eax
   0x0000000000400556 <+41>:	leaveq
   0x0000000000400557 <+42>:	retq
End of assembler dump.
次のコンストラクションmovl、次のコンストラクションmovでは、プログラムを中断的に開始しました.movl src destの構成は、srcからdestにコピーされた4バイトを表す.$0xbeefはこの値を表し、movl $0xbeef,-0x10(%rbp)-0x10(%rbp)$0xbeefにコピーすることを表す.したがって、-0x10(%rbp)は変数aのアドレスである.
(gdb) x/2xb $rbp-0x10
0x7fffffffe700:	0xef	0xbe
xはgdbの「チェック」コマンドで、メモリの値を表示します.xコマンドの使用方法については、次のリンクを参照してください.
gdbのxを使用して仮想アドレスを表示する $rbp-0x10はrbpのrbp 레지스터에 들어있는 값 -0x10を表し、仮想メモリアドレスを表す.rbpレジスタに含まれる値がどれくらいなのかを見て、実際の状況を理解しましょう.
(gdb) p $rbp
$1 = (void *) 0x7fffffffe710
rbpはアドレス0x7fffffffe710を含む.これは、アドレスから0x10を減算したアドレスに0 xbeef値が含まれていることを意味しますよね?メモリアドレス値を入力して検証しましょう.
(gdb) x/2x 0x7fffffffe700
0x7fffffffe700:	0xef	0xbe
0 xefと0 xbeの値を確認できます.興味深いことに、表示される値は順番ではなく、バイト順に並べられています.
理由はlittle endianの概念に基づいている.アドレス0x7fffffffe700は0 xefバイト値を含み、アドレス0x7fffffffe701は0 xbeバイト値を含む.Little endianは、メモリにデータを格納するときにless signigantバイト値を最初に格納する方法です.この部分は別の文章でさらに議論される.
(gdb) x/xw 0x7fffffffe700
0x7fffffffe700:	0x0000beef
字単位で読むと0x0000beefが見えます.次に、malloc関数を呼び出し、HipMemoryアドレスを受信した整数ポインタ変数bの値を調べ、HipMemoryアドレスの位置を決定する.
(gdb) break *0x000000000040054a
Breakpoint 2 at 0x40054a
(gdb) cont
Continuing.

Breakpoint 2, 0x000000000040054a in main ()
(gdb)
(gdb) disas
Dump of assembler code for function main:
   0x000000000040052d <+0>:	push   %rbp
   0x000000000040052e <+1>:	mov    %rsp,%rbp
   0x0000000000400531 <+4>:	sub    $0x10,%rsp
   0x0000000000400535 <+8>:	movl   $0xbeef,-0x10(%rbp)
   0x000000000040053c <+15>:	mov    $0x4,%edi
   0x0000000000400541 <+20>:	callq  0x400430 <malloc@plt>
   0x0000000000400546 <+25>:	mov    %rax,-0x8(%rbp)
=> 0x000000000040054a <+29>:	movl   $0xdddd,-0xc(%rbp)
   0x0000000000400551 <+36>:	mov    $0x0,%eax
   0x0000000000400556 <+41>:	leaveq
   0x0000000000400557 <+42>:	retq
End of assembler dump.
%raxレジスタは、malloc関数を呼び出す例を示す戻り値を格納するレジスタであるため、%raxレジスタにhipメモリアドレス値が含まれることが予想される.
(gdb) p/x $rax
$4 = 0x602010
%raxレジスタの値は16進数で検証され、0x602010と決定される.この値はHip Memoryの開始アドレスです.では、ポインタ変数bを格納する位置はどこですか?0x0000000000400546 <+25>: mov %rax,-0x8(%rbp)では、%raxのレジスタ値がアドレス-0x8(%rbp)に格納されていることがわかります.もしそうであれば、アドレスはbの位置であり、私たちが前に見た0x602010が格納されている可能性があります.確認してみましょう.
(gdb) x/xw $rbp-0x8
0x7fffffffe708:	0x00602010
1つずつ読みます
(gdb) x/4xb $rbp-0x8
0x7fffffffe708:	0x10	0x20	0x60	0x00
値が含まれていることを確認します.Heapメモリも仮想メモリの低い位置にあります.また、stackとしての領域変数bのアドレス値は非常に大きい(0x7fffffffe708).仮想メモリマップに示すように、code、heap、stackの位置を決定できます.
今回は、グローバル変数がdataに存在するかどうかを決定するために、コードをいくつか変更します.
#include <stdlib.h> 

int g = 0xDEAD;

int main() {
    int a = 0xBEEF;
    g = 0xDDDD;
    return 0; 
}
以上のコードをコンパイルしgdbを実行します.
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004ed <+0>:	push   %rbp
   0x00000000004004ee <+1>:	mov    %rsp,%rbp
   0x00000000004004f1 <+4>:	movl   $0xbeef,-0x4(%rbp)
   0x00000000004004f8 <+11>:	movl   $0xdddd,0x200b36(%rip)        # 0x601038 <g>
   0x0000000000400502 <+21>:	mov    $0x0,%eax
   0x0000000000400507 <+26>:	pop    %rbp
   0x0000000000400508 <+27>:	retq
End of assembler dump.
(gdb) break *0x0000000000400502
Breakpoint 1 at 0x400502
(gdb) run
Starting program: /root/a.out

Breakpoint 1, 0x0000000000400502 in main ()
(gdb)
(gdb) disas
Dump of assembler code for function main:
   0x00000000004004ed <+0>:	push   %rbp
   0x00000000004004ee <+1>:	mov    %rsp,%rbp
   0x00000000004004f1 <+4>:	movl   $0xbeef,-0x4(%rbp)
   0x00000000004004f8 <+11>:	movl   $0xdddd,0x200b36(%rip)        # 0x601038 <g>
=> 0x0000000000400502 <+21>:	mov    $0x0,%eax
   0x0000000000400507 <+26>:	pop    %rbp
   0x0000000000400508 <+27>:	retq
End of assembler dump.
上記のコンポーネントコードでは、$0 xdddを特定のアドレスにコピーしています.その位置はグローバル変数0x200b36(%rip)に等しい
(gdb) x/2xb $rip+0x200b36
0x601038 <g>:	0xdd	0xdd
対応するアドレス値を読み込むと、0 xddと0 xddを確認できます.今度は単語単位で読みましょう
(gdb) x/xw $rip+0x200b36
0x601038 <g>:	0x0000dddd
文字単位で読み取っても正常に読み取れます.上のコンポーネントコードをよく見てください.g 0x00000000004004f8 <+11>: movl $0xdddd,0x200b36(%rip) # 0x601038 <g>が表示されます.gというグローバル変数の位置は、コンポーネントコードにおいて0 x 601038と決定され得る.上の# 0x601038 <g>も同じアドレスを指しています.さらに、$rip+0x200b36は、コード開始点0x601038の後に位置し、これは、仮想メモリ上のデータ部分がコード部分の後よりもアドレス上にあることを示す.仮想メモリにおけるコード,データ,スタック,スタック部分の理論的位置を検証した.