3

私はx86アセンブリ(現時点では32ビット)でコーディングすることを学んでおり、メモリモデルを完全に理解するのに苦労しています。特に混乱するのは、ラベルのセマンティクス、LEA命令、および実行可能ファイルのレイアウトです。このサンプルプログラムを作成したので、gdbで実行されていることを確認できます。

; mem.s
SECTION .data
    msg: db "labeled string\n"
    db "unlabeled-string\n"
    nls: db 10,10,10,10,10,10,10,10
SECTION .text
global  _start
_start:
    ; inspect msg label, LEA instruction
    mov eax, msg
    mov eax, &msg
    mov eax, [msg]
    ; lea eax, msg (invalid instruction)
    lea eax, &msg
    lea eax, [msg]

    ; populate array in BSS section
    mov [arr], DWORD 1
    mov [arr+4], DWORD 2
    mov [arr+8], DWORD 3
    mov [arr+12], DWORD 4

    ; trying to print the unlabeled string
    mov eax, 4
    mov ebx, 1
    lea ecx, [msg+15]
    int 80H

    mov eax, 1      ; exit syscall
    mov ebx, 0      ; return value
    int 80H
SECTION .bss
    arr: resw 16

私は組み立ててリンクしました:

nasm -f elf -g -F stabs mem.s
ld -m elf_i386 -o mem mem.o

GDBセッション:

(gdb) disas *_start
Dump of assembler code for function _start:
   0x08048080 <+0>: mov    $0x80490e4,%eax
   0x08048085 <+5>: mov    0x80490e4,%eax
   0x0804808a <+10>:    mov    0x80490e4,%eax
   0x0804808f <+15>:    lea    0x80490e4,%eax
   0x08048095 <+21>:    lea    0x80490e4,%eax
   0x0804809b <+27>:    movl   $0x1,0x8049110
   0x080480a5 <+37>:    movl   $0x2,0x8049114
   0x080480af <+47>:    movl   $0x3,0x8049118
   0x080480b9 <+57>:    movl   $0x4,0x804911c
   0x080480c3 <+67>:    mov    $0x4,%eax
   0x080480c8 <+72>:    mov    $0x1,%ebx
   0x080480cd <+77>:    lea    0x80490f3,%ecx
   0x080480d3 <+83>:    int    $0x80
   0x080480d5 <+85>:    mov    $0x1,%eax
   0x080480da <+90>:    mov    $0x0,%ebx
   0x080480df <+95>:    int    $0x80

「msg」値の検査:

(gdb) b _start
Breakpoint 1 at 0x8048080
(gdb) run
Starting program: /home/jh/workspace/x86/mem_addr/mem
(gdb) p msg
# what does this value represent?
$1 = 1700946284
(gdb) p &msg
$2 = (<data variable, no debug info> *) 0x80490e4
# this is the address where "labeled string" is stored
(gdb) p *0x80490e4
# same value as above (eg: p msg)
$3 = 1700946284
(gdb) p *msg
Cannot access memory at address 0x6562616c
# NOTE: 0x6562616c is ASCII values of 'e','b','a','l'
# the first 4 bytes from msg: db "labeled string"... little-endian
(gdb) x msg
0x6562616c: Cannot access memory at address 0x6562616c
(gdb) x &msg
0x80490e4 <msg>:    0x6562616c
(gdb) x *msg
Cannot access memory at address 0x6562616c

一度に1つの命令をステップスルーします。

(gdb) p $eax
$4 = 0
(gdb) stepi
0x08048085 in _start ()
(gdb) p $eax
$5 = 134516964
0x0804808a in _start ()
(gdb) p $eax
$6 = 1700946284
(gdb) stepi
0x0804808f in _start ()
(gdb) p $eax
$7 = 1700946284
(gdb) stepi
0x08048095 in _start ()
(gdb) p $eax
$8 = 134516964

配列には、期待どおりに値1、2、3、4が入力されました。

# before program execution:
(gdb) x/16w &arr
0x8049104 <arr>:    0   0   0   0
0x8049114:  0   0   0   0
0x8049124:  0   0   0   0
0x8049134:  0   0   0   0
# after program execution
(gdb) x/16w &arr
0x8049104 <arr>:    1   2   3   4
0x8049114:  0   0   0   0
0x8049124:  0   0   0   0
0x8049134:  0   0   0   0

gdbでラベルを印刷すると、これら2つの値が得られる理由がわかりません。また、ラベルのない文字列を印刷するにはどうすればよいですか。前もって感謝します

4

2 に答える 2

5

gdbはラベルの概念を理解していないため、多少混乱します。実際には、高級言語(CまたはC ++)で記述され、コンパイラーによってコンパイルされたプログラムをデバッグするように設計されています。そのため、バイナリで見たものを、何が起こっているかについての最良の推測に基づいて、高級言語の概念(変数と型)にマップしようとします(コンパイラからのデバッグ情報がない場合は、進行中)。

nasmは何をしますか

アセンブラにとって、ラベルはまだ設定されていない値です。実際には、リンカの実行時に最終的な値を取得します。一般に、ラベルはメモリのセクション内のアドレスを参照するために使用されます。実際のアドレスは、リンカが最終的な実行可能イメージをレイアウトするときに定義されます。アセンブラは、リンカがラベルの使用を適切に設定できるように、再配置レコードを生成します。

したがって、アセンブラが見ると

mov eax, msg

これはデータセグメント内のアドレスに対応するラベルであることがわかっているmsgため、そのアドレスをeaxにロードするための命令を生成します。それが見たとき

mov eax, [msg]

のアドレスにあるメモリから32ビット(レジスタeaxのサイズ)をロードする命令を生成しますmsg。どちらの場合も、リンカが最終的なアドレスをプラグインできるように、再配置が生成さmsgれます。

(余談ですが&、nasmの意味がわかりませんが、ドキュメントのどこにも表示されていないので、エラーが発生しないことに驚いています。しかし、エイリアスとして扱われているようです。[])_

LEAはおかしな命令です。基本的にはメモリからの移動と同じ形式ですが、メモリを読み取る代わりに、読み取ったはずのアドレスをデスティネーションレジスタに格納します。それで

lea eax, msg

意味がありません-ソースはラベル(アドレス)msgです。これは(リンク時間)定数であり、メモリにはどこにもありません。

lea eax, [msg]

ソースがメモリ内にあるため、動作します。そのため、ソースのアドレスをeaxに固定します。これはと同じ効果mov eax, msgです。最も一般的には、leaより複雑なアドレッシングモードでのみ使用されるため、x86 AGUを利用して、アドレスの計算以外の便利な作業を行うことができます。例えば:

lea eax, [ebx+4*ecx+32]

これはシフトを実行し、AGUに2つの加算を行い、そのアドレスからロードするのではなく、結果をeaxに入れます。

gdbの機能

gdbでは、入力すると、C /C++コンパイラがその式に対して何を意味するかを最大限に理解p <expression>しようとします。<expression>だからあなたが言うとき

(gdb) p msg

「それmsgは変数のように見えるので、その変数の現在の値を取得してそれを出力しましょう」と表示されます。これで、コンパイラがグローバル変数を.dataセグメントに配置することを好み、変数と同じ名前でそれらの変数のシンボルを作成することがわかりました。msgシンボルテーブルではセグメント内のシンボルと見なされるため.data、それが進行中であると想定し、そのシンボルでメモリをフェッチして出力します。これで、その変数がどのTYPEであるか(デバッグ情報なし)がわからないため、32ビットintであると推測し、そのように出力します。

したがって、出力

$1 = 1700946284

msgの最初の4バイトであり、整数として扱われます。

p &msg変数のアドレスを取得することを理解しているためmsg、シンボルから直接アドレスを取得します。アドレスを出力するとき、gdbはそれらのアドレスに関するタイプ情報を出力します。したがって、「データ変数、デバッグ情報なし」が出力されます。

必要に応じて、キャストを使用してgdbに何かのタイプを指定できます。これにより、推測されたタイプの代わりにそのタイプが使用されます。

(gdb) p (char)msg
$6 = 108 'l'
(gdb) p (char [10])msg
$7 = "labeled st"
(gdb) p (char *)&msg
$8 = 0x80490e4 "labeled string\\nunlabeled-string\\n\n\n\n\n\n\n\n\n" <Address 0x804910e out of bounds>

後者の場合、文字列にはNULターミネータがないため、データセグメント全体が出力されることに注意してください...


ラベルのない文字列をsys_writeで印刷するには、文字列のアドレスと長さを把握する必要があります。これはほとんどの場合です。完全を期すために、戻り値も確認する必要があります。

    mov ebx, 1           ; fd 1 (stdout)
    lea ecx, [msg+15]    ; address
    mov edx, 17          ; length
write_more:
    mov eax, 4           ; sys_write
    int 80H              ; write(1, &msg[15], 17)
    test eax, eax        ; check for error
    js error             ; error, eax = -ERRNO
    add ecx, eax
    sub edx, eax
    jg write_more        ; only part of the string was written
于 2012-07-12T22:29:13.823 に答える
4

クリス・ドッドsez..。


(余談ですが、nasmの意味はわかりませんが、ドキュメントのどこにも表示されていないので、エラーが発生しないことに驚いています。しかし、それは[])のエイリアス

ああ、ああ!あなたは秘密の構文を発見しました!「&」は、かなり前に、ユーザー要求ごとにNasmに(「[]」のエイリアスとして)追加されました。文書化されたことはありません。削除することもありません。私は「[]」に固執します。「文書化されていない」ので、それはただ消えるかもしれません。意味は、gdbの意味とほぼ「反対」であることに注意してください。

「-Fスタブ」の代わりに「-Fドワーフ」を試してみてください。これは、gdbで使用される「ネイティブ」デバッグ情報形式であると想定されています。(私自身、大きな違いに気づいたことはありません)

一番、

フランク

http://www.nasm.us

于 2012-07-13T08:05:45.180 に答える