我正在阅读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时,会出现分段错误。我不明白似乎有些不一致。是不是我不知道的其他寻址模式?(请记住,我已经学了几个星期的汇编,我不是一个软件工程师每天的基础上)。我敢肯定,这是一些细节,我错过了。
1条答案
按热度按时间m1m5dgzv1#
.equ foo, 32
定义了一个字节时间常数,在输出的当前部分中不组装任何字节。实际上,它定义了一个像
foo:
标签一样的符号,但是“地址”是32
,这就是为什么在GAS中,你可以用.globl foo
导出它,这样它就可以在.o
文件的符号表中对 * 链接器 * 可见。add $foo, %rbx
加上32,即符号的“值”(也称为地址)。add foo, %rbx
将从绝对地址32
加载,使用符号作为存储器操作数的地址。在这两种情况下,符号“地址”成为正在汇编的指令的机器代码的一部分,区别仅在于操作码是将其用作立即数还是用作内存操作数。如果您使用
.quad foo
发出8个具有该值(地址)的字节,也是如此。无论符号地址aka值是标记某个部分中的位置,还是使用.equ
或foo = 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), %reg
RIP-relative addressing ifsymbol
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)
。