在Assembly(NASM)中使用DB(定义字节)时出现分段错误

时间:2019-04-11 23:42:27

标签: assembly x86-64 nasm machine-code

我正在尝试在我的.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是只读的,可能是此问题的原因,我正确吗?有人可以向我解释为什么我的第二个代码示例段错误吗?

1 个答案:

答案 0 :(得分:3)

如果您告诉汇编器在某处汇编任意字节,它将执行。 db是发出字节的伪指令,因此就NASM而言,mov eax, 60db 0xb8, 0x3c, 0, 0, 0几乎完全等效。要么将这5个字节发送到该位置的输出。

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


由于您使用的是NASM 1 ,因此会将mov rax,60优化为mov eax,60,因此该指令没有您期望的REX前缀。

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

(我用objdump -drwC -Mintel进行了检查,而不是查找REX前缀中的哪一位。我只记得REX.W是0x48。但是0x41是REX.B x86-64中的前缀)。

因此,您的代码没有运行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) +++

,然后执行进入之后 syscall,在这种情况下,它们是00 00个字节,它们被解码为add [rax], al,因此segfault。如果您要在GDB中运行代码,您会看到的。


脚注1:如果您使用的YASM无法将操作数大小优化为32位

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

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