assembly 寻址模式不一致?为什么要在另一个文件中定义的.equ值上使用$foo?

nr7wwzry  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(98)

我正在阅读Jonathan Bartlett的《Learn to Program with Assembly》一书。我在第7章中介绍了数据记录。作者介绍了汇编中的结构和记录。他在文件personsdata.s中创建了一个人的简单记录,其中有一个由6个人及其特征组成的数组:体重发色身高和年龄

persondata.s
.section .data
.globl people, numpeople
numpeople:
    # Calculate the number of people in array
    .quad (endpeople - people)/PERSON_RECORD_SIZE
people:
    # Array of people
    .quad 200, 2, 74, 20
    .quad 280, 2, 72, 44 # me!
    .quad 150, 1, 68, 30
    .quad 250, 3, 75, 24
    .quad 250, 2, 70, 11
    .quad 180, 5, 69, 65
endpeople: # Marks the end of the array for calculation purposes
# Describe the components of the struct
.globl WEIGHT_OFFSET, HAIR_OFFSET, HEIGHT_OFFSET, AGE_OFFSET
.equ WEIGHT_OFFSET, 0
.equ HAIR_OFFSET, 8
.equ HEIGHT_OFFSET, 16
.equ AGE_OFFSET, 24

# Total size of the struct
.globl PERSON_RECORD_SIZE
.equ PERSON_RECORD_SIZE, 32

这个文件只是关于这个数据记录。在这个文件的末尾有一个常量PERSON_RECORD_SIZE,它描述了一个人数组的字节大小。它稍后被循环使用,以转到下一个人,特别是它的高度。他创建了一个返回最大身高值的程序。

tallest.s
.globl _start
.section .text
_start:
    ### Initialize Registers ###
    # Pointer to first record
    leaq people, %rbx
    # Record count
    movq numpeople, %rcx
    # Tallest value found
    movq $0, %rdi
    ### Check Preconditions ###
80Chapter 7
Data Records
    # If there are no records, finish
    cmpq $0, %rcx
    je finish
    ### Main Loop ###
mainloop:
    # %rbx is the pointer to the whole struct
    # This instruction grabs the height field
    # and stores it in %rax
    movq HEIGHT_OFFSET(%rbx), %rax
    # If it is less than or equal to our current
    # tallest, go to the next one.
    cmpq %rdi, %rax
    jbe endloop
    # Copy this value as the tallest value
    movq %rax, %rdi
endloop:
    # Move %rbx to point to the next record
    addq $PERSON_RECORD_SIZE, %rbx
    # Decrement %rcx and do it again
    loopq mainloop
    ### Finish it off ###
finish:
    movq $60, %rax
    syscall

我明白这份记录里的一切,除了一件事。我们使用movq HEIGHT_OFFSET(%rbx),%rax访问人的身高,我理解这一点,但当涉及到移动到下一个人,特别是人的身高时,他使用addq $PERSON_RECORD_SIZE,%rbx。我的问题是。如果这一行只是关于将32加到rbx中,以移动到内存中的下一个人和他们的身高值,为什么它在常量名称之前使用$符号。我觉得直接记忆模式在这里比较合适。我们在开始时使用它movq numpeople,%rcx将人数移动到rcx模块中。
我把$PERSON_RECORD_SIZE换成了$32,效果很好。但是当我输入PERSON_RECORD_SIZE时,会出现分段错误。我不明白似乎有些不一致。是不是我不知道的其他寻址模式?(请记住,我已经学了几个星期的汇编,我不是一个软件工程师每天的基础上)。我敢肯定,这是一些细节,我错过了。

m1m5dgzv

m1m5dgzv1#

.equ foo, 32定义了一个字节时间常数,在输出的当前部分中不组装任何字节。
实际上,它定义了一个像foo:标签一样的符号,但是“地址”是32,这就是为什么在GAS中,你可以用.globl foo导出它,这样它就可以在.o文件的符号表中对 * 链接器 * 可见。
add $foo, %rbx加上32,即符号的“值”(也称为地址)。
add foo, %rbx将从绝对地址32加载,使用符号作为存储器操作数的地址。
在这两种情况下,符号“地址”成为正在汇编的指令的机器代码的一部分,区别仅在于操作码是将其用作立即数还是用作内存操作数。如果您使用.quad foo发出8个具有该值(地址)的字节,也是如此。无论符号地址aka值是标记某个部分中的位置,还是使用.equfoo = 123定义的整数(同一事物的替代语法),这都适用。
不幸的是,在汇编add $foo, %rbx时,汇编程序还不知道该值(它只是一个链接时常量,因为它在这个文件中是一个未定义的符号)。因此它选择add $imm32, %rbx,使指令在机器码中比add $32, %rbx大3个字节,add $32, %rbx将在汇编时看到小值,并能够选择8位立即编码。(https://www.felixcloutier.com/x86/add)。出于这个原因,我不建议在asm源文件中使用.equ。使用C预处理器,这样您就可以在.h#define foo 32,而在两个文件中#include
(The在某些部分中标记地址的符号与绝对常量(第*UND*节)实际上在GNU汇编程序.intel_syntax noprefix模式中产生了歧义,但这甚至适用于在同一个源文件中比.equ更早使用它的代码,而不仅仅是跨源文件:参见 * Distinguishing memory from constant in GNU as .intel_syntax *。实际上,即使在AT&T的语法中也有一个歧义,但只是在一个没有用的角落情况下。

符号的“值”是它的地址。(一个粗略的类比是extern char foo[],所以写foo是地址,但AT&T语法在指令中隐式地取消引用裸符号名称;这个类比在NASM中更有效。)如果在该地址的内存中有字节,您可以使用该符号来组装将在运行时访问它们的指令,但您不能将字节嵌入到机器码中的立即数中,或使用它来控制.rept或类似的东西。

变量是一个高级语言概念,你可以在汇编中使用标签来定义像.byte这样的指令之前的符号,以发出一些静态存储,比如bar: .byte 123。符号bar有一个其他指令在汇编/链接时可以引用的“值”(例如,从4个字节后生成字节加载,如movzbl bar+4(%rip), %eax)。

涉及符号foo的asm源代码行将把字节组装到输出中,包括符号的“地址”(或基于它的东西,如相对寻址,或mov $foo-bar, %eax表示两个符号之间的距离。

但是你不能让汇编程序引用123字节或者任何其他恰好位于或靠近符号所附加的地址的字节。只能在运行时访问组装到输出中的字节。例如,.quad bar发出64位绝对地址,.byte bar尝试将绝对地址放入一个字节,但在链接时会失败(除非你有一个链接器脚本,将你的.data部分放在地址空间的最底部!如果你想避免重复,避免在多个地方硬编码123,你需要使用一个像.equ barval, 123这样的时间常数,并在多个地方使用它,就像在多个地方使用.byte barval一样。

使用RIP相对寻址

在64位代码中,通常不会写add symbol, %reg; 32位绝对寻址模式对于任何东西都是无效或无用的,除了在某些绝对地址处的MMIO,而不是对于您自己的.data。你总是想要add symbol(%rip), %regRIP-relative addressing if symbol is the address of static storage。使用符号地址作为32位绝对值在与其他寄存器一起用作数组索引时很有用,如add my_array(,%rdx,8), %rax或其他寄存器,但请参阅 * 32-bit absolute addresses no longer allowed in x86-64 Linux? *(您不能在PIE可执行文件中这样做)。
参见 * Referencing the contents of a memory location. (x86 addressing modes) * re:x86-64的寻址模式选择。
和 * How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? *,以了解有关foo(%rip)123(%rip)

相关问题