我正在尝试在 .text 部分中用汇编语言定义一个字节。我知道数据应该进入 .data 部分,但我想知道为什么当我这样做时它会给我一个分段错误。如果我在 .data 中定义字节,它不会给我任何错误,与 .text 不同。我使用的是运行 Mint 19.1 的 Linux 机器,并使用 NASM + LD 来编译和链接可执行文件。
运行时不会出现分段错误:
global _start
section .data
db 0x41
section .text
_start:
mov rax, 60 ; Exit(0) syscall
xor rdi, rdi
syscall
这给了我一个段错误:
global _start
section .text
_start:
db 0x41
mov rax, 60 ; Exit(0) syscall
xor rdi, rdi
syscall
我使用以下脚本来编译和链接它:
nasm -felf64 main.s -o main.o
ld main.o -o main
我希望程序能够在没有任何分段错误的情况下运行,但当我在 .text 中使用 DB 时却不会。 我怀疑 .text 是只读的,这可能是这个问题的原因,我是对的吗?有人可以向我解释为什么我的第二个代码示例出现段错误吗?
如果您告诉汇编器在某处汇编任意字节,它就会这样做。
db
是一个发出字节的伪指令,因此 mov eax, 60
和 db 0xb8, 0x3c, 0, 0, 0
就 NASM 而言是完全相同的。任一者都会将这 5 个字节发送到当前位置的输出中。
如果您不希望数据被解码为指令(部分),请不要将其放在执行将到达的位置。
由于您使用的是 NASM1,它会将
mov rax,60
优化为 mov eax,60
,因此该指令没有您期望从源中获得的 REX 前缀。
您手动编码的
mov
的 REX 前缀将其更改为 R8D 的 mov
,而不是 EAX:41 b8 3c 00 00 00 mov r8d,0x3c
(我用
objdump -drwC -Mintel
进行了检查,而不是查找 REX 前缀中的哪个位。我只记得 REX.W 是 0x48
。但是 0x41
是 x86-64 中的 REX.B 前缀) .
因此,您的代码不是进行
sys_exit
系统调用,而是以 EAX=0 运行 syscall
,即
__NR_read
。 (Linux 内核在进程启动之前将除 RSP 之外的所有寄存器清零,并且在静态链接的可执行文件中,_start
是真正的入口点,没有首先运行动态链接器代码。因此 RAX 仍然为零)。
$ strace ./rex
execve("./rex", ["./rex"], 0x7fffbbadad60 /* 54 vars */) = 0
read(0, NULL, 0) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=NULL} ---
+++ killed by SIGSEGV (core dumped) +++
然后执行会进入aftersyscall
,在本例中是
00 00
解码为
add [rax], al
的字节,从而出现段错误。如果你运行你的代码,你会看到这个GDB内部。
如果您使用 YASM,它不能优化到 32 位操作数大小:
Intel 手册规定,一条指令上有 2 个 REX 前缀是非法的。我预计会出现非法指令错误(#UD 机器异常 -> 内核发出 SIGILL),但我的 Skylake CPU 忽略第一个 REX 前缀并将其解码为mov rax, sign_extended_imm32
。单步执行,它被视为一个长指令,所以我猜 Skylake 选择像其他多个前缀的情况一样处理它,其中只有最后一个类型有效。 (但请记住,这并不是面向未来的,其他 x86 CPU 可能会以不同的方式处理它。)