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

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

[编辑:这个问题有点老了,我仍在学习低级计算机的基础知识。我没有看到

db 0x41
被视为指令而不是实际定义字节。]

我试图在我的

.text
部分中定义 ASM x64(Intel 语法)中的一个字节,并故意触发段错误。我知道数据应该在
.data
中。我只是想知道为什么当我将其移动到
.text
时会出现段错误。

下面的代码是在 Mint 19.1 中使用

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
chmod +x main
./main

有人可以启发我吗?

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

如果您告诉汇编器在某处汇编任意字节,它就会这样做。

db
是一个发出字节的伪指令,因此
mov eax, 60
db 0xb8, 0x3c, 0, 0, 0
就 NASM 而言是完全相同的。任一者都会将这 5 个字节发送到当前位置的输出中。

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

section .rodata
section .data
之前或之后代码,就像问题中一样。如果您希望您的 asm 源在每个使用某些数据的函数附近定义静态/全局数据,您可以在
section .text
和其他部分之间来回切换。)


由于您使用的是 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内部。


脚注 1:

如果您使用 YASM,它不能优化到 32 位操作数大小

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

mov rax, sign_extended_imm32

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


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

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