我正在阅读 Jo Van Hoey 所著的 2019 年书籍《开始 x64 汇编编程:从新手到 AVX 专业人士》的章节末尾...
这里是摘录(跳到问题的粗体文本):
1 ; hello.asm
2 section .data
3 00000000 68656C6C6F2C20776F- msg db "hello, world",0
3 00000009 726C6400
4 section .bss
5 section .text
6 global main
7 main:
8
9 00000000 B801000000 mov rax, 1 ; 1 = write
10 00000005 BF01000000 mov rdi, 1 ; 1 = to stdout
11 0000000A 48BE- mov rsi, msg ; string to display in rsi
11 0000000C [0000000000000000]
12 00000014 BA0C000000 mov rdx, 12 ; length of the string, without 0
13 00000019 0F05 syscall ; display the string
14 0000001B B83C000000 mov rax, 60 ; 60 = exit
15 00000020 BF00000000 mov rdi, 0 ; 0 = success exit code
16 00000025 0F05 syscall ; quit
图1-4
图 1-4 显示了我们的
。您有一列包含行号,然后一列包含八位数字。该列代表内存位置。当汇编器构建目标文件时,它还不知道将使用哪些内存位置。因此,它从不同部分的位置 0 开始。 .bss 部分没有内存。hello.lst
我们在第二列中看到汇编指令转换为十六进制代码的结果。例如,
转换为 B8,mov rax
转换为 BF。这些是机器指令的十六进制表示。另请注意mov rdi
字符串到十六进制 ASCII 字符的转换。稍后您将了解有关十六进制表示法的更多信息。要执行的第一条指令从地址 00000000 [八个零]开始,占用五个字节:B8 01 00 00 00。双零用于填充和内存对齐msg
...
下一条指令从地址 00000005 [七个零]开始,依此类推。 内存地址为八位(即8个字节);每个字节有 8 位。因此,地址有 64 位;事实上,我们使用的是 64 位汇编器。 看看
是如何引用的。由于msg
的存储位置尚不清楚,因此将其称为[0000000000000000] [16个零]msg
我是汇编新手,所以我的理解是图1-4中的第二列(例如
00000005
)有八位数字,并且由于每个数字都是十六进制的数字,那么每个数字代表4位,或者最大0xF 的值,即 2^4=16。
这是
hello.asm
:
; hello.asm
section .data
msg db "hello, world",0
section .bss
section .text
global main
main:
mov rax, 1 ; 1 = write
mov rdi, 1 ; 1 = to stdout
mov rsi, msg ; string to display in rsi
mov rdx, 12 ; length of the string, without 0
syscall ; display the string
mov rax, 60 ; 60 = exit
mov rdi, 0 ; 0 = success exit code
syscall ; quit
而且,这是
makefile
:
#makefile for hello.asm
hello: hello.o
gcc -o hello hello.o -no-pie
hello.o: hello.asm
nasm -f elf64 -g -F dwarf hello.asm -l hello.lst
有人可以帮我理解图1-4中的第二列,或者汇编器创建的内存地址吗?
这是 NASM 列表,就像您从
nasm -l /dev/stdout -f elf64 hello.asm
进入终端一样。 第二列(行号之后的第一列)相对于该部分的开头有偏移。 8 个十六进制数字是 32 位偏移量或地址,书上对此是错误的。 每个十六进制数字确实只代表 4 位,而不是书上声称的 8 位。
看看msg是如何引用的。因为 msg 的内存位置尚不清楚,所以称为 [0000000000000000] [16 个零]
这部分是正确的,与前面的句子不同。
mov r64, imm64
是将地址放入寄存器的效率最低的方法,但它是你从asm源中得到的,它按照你在16或16中的方式做事32 位模式。
事实上,链接器将填写该 64 位绝对地址。 使用
objdump -drwC -Mintel
反汇编链接的可执行文件以查看它。 (GAS .intel_syntax noprefix
与 MASM 类似,但与 NASM 的区别主要仅在于寻址模式。Agner Fog 的 objconv
可以反汇编为 NASM 语法。)
现代 64 位代码通常使用 RIP 相对 LEA,例如
lea rsi, [rel msg]
,它在运行时从包含程序计数器(指令指针)的 32 位相对位移的机器代码生成 64 位绝对地址。 请参阅如何将函数或标签的地址加载到寄存器中了解三个选项,对于-no-pie
Linux可执行文件还包括mov esi, msg
。 当您使用 -fno-pie
(代码生成选项)进行编译时,编译器将会执行此操作。
这将汇编为 NASM 用于
mov r32, imm32
的相同 32 位操作数大小 mov rax, 1
(已优化为 mov eax, 1
)请注意,其他指令开头缺少 0x4?
REX 前缀字节。 为什么 Linux 上的 NASM 更改 x86_64 程序集中的寄存器涵盖了mov
的 3 种编码,可以写入整个 64 位寄存器。