GDB 中的 x86 标签和 LEA

xst*_*xst 3 memory x86 assembly gdb nasm

我正在学习在 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
Run Code Online (Sandbox Code Playgroud)

我已经组装并链接到:

nasm -f elf -g -F stabs mem.s
ld -m elf_i386 -o mem mem.o
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

检查“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
Run Code Online (Sandbox Code Playgroud)

一次单步执行一条指令:

(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
Run Code Online (Sandbox Code Playgroud)

该数组按预期填充了值 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
Run Code Online (Sandbox Code Playgroud)

我不明白为什么在 gdb 中打印标签会导致这两个值。另外,如何打印未标记的字符串。提前致谢

Chr*_*odd 5

它有点令人困惑,因为 gdb 不理解标签的概念,实际上 - 它旨在调试用高级语言(通常为 C 或 C++)编写并由编译器编译的程序。因此,它尝试将它在二进制文件中看到的内容映射到高级语言概念——变量和类型——基于它对正在发生的事情的最佳猜测(在没有来自编译器的调试信息告诉它什么是继续)。

nasm 是做什么的

对于汇编程序,标签是尚未设置的值——它实际上是在链接器运行时获得其最终值。通常,标签用于引用内存部分中的地址——实际地址将在链接器布置最终可执行映像时定义。汇编器生成重定位记录,以便链接器可以正确设置标签的使用。

所以当汇编器看到

mov eax, msg
Run Code Online (Sandbox Code Playgroud)

它知道这msg是一个与数据段中的地址相对应的标签,因此它生成一条指令将该地址加载到 eax 中。当它看到

mov eax, [msg]
Run Code Online (Sandbox Code Playgroud)

它生成一条指令,从地址为 的内存中加载 32 位(寄存器 eax 的大小)msg。在这两种情况下,都会产生一个重定位,以便链接器可以插入最终地址msg

(除此之外——我不知道&nasm是什么意思——它没有出现在我能看到的文档中的任何地方,所以我很惊讶它没有给出错误。但它看起来像是把它当作别名对于[])

现在 LEA 是一条有趣的指令——它与从内存中移动的格式基本相同,但它不是读取内存,而是将它会读取的地址存储到目标寄存器中。所以

lea eax, msg
Run Code Online (Sandbox Code Playgroud)

没有意义——源是标签 (address) msg,它是一个(链接时间)常量,不在内存中的任何地方。

lea eax, [msg]
Run Code Online (Sandbox Code Playgroud)

工作,因为源在内存中,所以它将源的地址粘贴到 eax 中。这与 效果相同mov eax, msg。最常见的是,您只看到lea用于更复杂的寻址模式,以便您可以利用 x86 AGU 做有用的工作,而不仅仅是计算地址。例如:

lea eax, [ebx+4*ecx+32]
Run Code Online (Sandbox Code Playgroud)

它在 AGU 中进行了一次移位和两次相加,并将结果放入 eax 而不是从该地址加载。

gdb 做什么

在 gdb 中,当您键入时,p <expression>它会尝试评估<expression>其对 C/C++ 编译器对该表达式意味着什么的最佳理解。所以当你说

(gdb) p msg
Run Code Online (Sandbox Code Playgroud)

它查看msg并说“这看起来像一个变量,所以让我们获取该变量的当前值并打印它”。现在它知道编译器喜欢将全局变量放入 .data 段,并且他们为那些与变量同名的变量创建符号。由于它将msg符号表中的符号视为.data段中的符号,因此它假设正在发生的事情,并在该符号处获取内存并打印它。现在它不知道该变量是什么类型(没有调试信息),所以它猜测它是一个 32 位 int 并将其打印为。

所以输出

$1 = 1700946284
Run Code Online (Sandbox Code Playgroud)

是 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>
Run Code Online (Sandbox Code Playgroud)

请注意,在后一种情况下,字符串上没有 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
Run Code Online (Sandbox Code Playgroud)