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

问题描述 投票:0回答:1

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

assembly x86-64 nasm machine-code
1个回答
3
投票

如果你告诉汇编程序在某处汇编任意字节,它就会。 db是一个发出字节的伪指令,因此就NASM而言,mov eax, 60db 0xb8, 0x3c, 0, 0, 0几乎完全相同。任何一个都会将那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而不是查找REX前缀中的哪一位。我只记得REX.W是0x48。但0x41是x86-64中的REX.B前缀)。

因此,代码不是进行sys_exit系统调用,而是运行syscall,其中EAX = 0,即__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,因此是段错误。如果你在GDB中运行你的代码,你会看到这个。


脚注1:如果您使用的YASM没有优化到32位操作数大小:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.