在函数内部使用 DB(定义字节)时出现分段错误

Segmentation fault when using DB (define byte) inside a function

我正在尝试在我的 .text 部分中用汇编语言定义一个字节。我知道数据应该转到 .data 部分,但我想知道为什么在我这样做时它会给我一个分段错误。如果我在 .data 中定义字节,它不会给我任何错误,这与 .text 不同。我正在使用 Linux 机器 运行 Mint 19.1 并使用 NASM + LD 编译和 link 可执行文件。

这运行没有分段错误:

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

我正在使用以下脚本进行编译并link它:

nasm -felf64 main.s -o main.o
ld main.o -o main

我希望该程序可以正常运行而不会出现任何分段错误,但是当我在 .text 中使用 DB 时却没有。 我怀疑 .text 是只读的,这可能是这个问题的原因,我说得对吗?有人可以向我解释为什么我的第二个代码示例会出现段错误吗?

如果你告诉 assembler 到 assemble 某处的任意字节,它会。 db 是发出字节的伪指令,因此 mov eax, 60db 0xb8, 0x3c, 0, 0, 0 就 NASM 而言完全等价。任何一个都会将这 5 个字节发送到当前位置的输出中。

如果您不希望您的数据被解码为(部分)指令,请不要将其放在执行将到达的位置。


由于您使用的是 NASM1,它将 mov rax,60 优化为 mov eax,60,因此该指令没有 REX您期望从源代码中得到的前缀。

您为 mov 手动编码的 REX 前缀将其更改为 mov 到 R8D 而不是 EAX:
41 b8 3c 00 00 00 mov r8d,0x3c

(我用objdump -drwC -Mintel而不是looking up which bit is which in the REX prefix检查过。我只记得REX.W是0x48。但是0x41是一个REX.B x86-64 中的前缀)。

所以不是进行 sys_exit 系统调用,你的代码 运行s syscall 与 EAX=0,即 __NR_read。 (Linux 内核在进程启动之前将除 RSP 之外的所有寄存器清零,并且在静态链接的可执行文件中,_start 是真正的入口点,首先没有动态链接器代码 运行ning。所以 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) +++

然后执行落入 syscall 之后,在本例中是 00 00 字节,解码为 add [rax], al,因此出现段错误。 如果你 运行 你的代码在 GDB 中,你就会看到这个。


脚注 1:如果您使用的 YASM 未针对 32 位操作数大小进行优化

Intel 的手册说在一条指令上有 2 个 REX 前缀是非法的。我预计会出现非法指令错误(#UD 机器异常 -> 内核传递 SIGILL),但我的 Skylake CPU 忽略了第一个 REX 前缀并将其解码为 mov rax, sign_extended_imm32.

单步执行,它被视为一个长指令,所以我猜 Skylake 选择像其他多个前缀的情况一样处理它,其中只有最后一个类型有效果。 (但请记住,这不是面向未来的,其他 x86 CPUs 可能会以不同的方式处理它。)


其他情况下的相关/相同错误:

  • 在 BIOS MBR 引导扇区
  • Unknown opcode skipped: 66, not 8086 instruction - not supported yet EMU8086