Ошибка сегментации при использовании БД (определить байт) в сборке (NASM)

Я пытаюсь определить байт на языке ассемблера внутри моего раздела.text. Я знаю, что данные должны идти в раздел.data, но мне было интересно, почему это вызывает ошибку сегментации, когда я это делаю. Если я определю байт внутри.data, он не выдаст мне ошибок, в отличие от.text. Я использую Linux-машину с Mint 19.1 и NASM + LD для компиляции и компоновки исполняемого файла.

Это работает без ошибок сегментации:

global _start
section .data
db 0x41
section .text
_start:
    mov rax, 60    ; Exit(0) syscall
    xor rdi, rdi
    syscall

Это дает мне segfault:

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

Я ожидаю, что программа будет работать без каких-либо ошибок сегментации, но не работает, когда я использую DB внутри.text. Я подозреваю, что.text только для чтения, и это может быть причиной этой проблемы, я прав? Может кто-нибудь объяснить мне, почему мой второй пример кода segfaults?

1 ответ

Если вы скажете ассемблеру собрать произвольные байты где-нибудь, это произойдет. db это псевдоинструкция, которая испускает байты, так mov eax, 60 а также db 0xb8, 0x3c, 0, 0, 0 в значительной степени эквивалентны в отношении NASM. Любой из них выпустит эти 5 байтов на выход в этой позиции.

Если вы не хотите, чтобы ваши данные были декодированы как (часть) инструкций, не помещайте их там, где они будут достигнуты при исполнении.


Поскольку вы используете NASM 1, он оптимизирует mov rax,60 в mov eax,60 таким образом, инструкция не имеет префикса REX, который вы ожидаете от источника.

Ваш кодированный вручную префикс REX для mov меняет его в 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 системный вызов, ваш код работает 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 и, таким образом, segfault. Вы бы видели это, если бы запускали свой код внутри GDB.


Сноска 1: Если вы использовали YASM, который не оптимизирован до размера 32-битного операнда:

В руководствах Intel говорится, что нельзя иметь 2 префикса REX для одной инструкции. Я ожидал ошибку недопустимой инструкции (машинное исключение #UD -> ядро ​​доставляет SIGILL), но мой процессор Skylake игнорирует первый префикс REX и декодирует его как mov rax, sign_extended_imm32,

Одноступенчатый, он обрабатывается как одна длинная инструкция, поэтому я думаю, что Skylake решит обработать его, как и другие случаи с множественными префиксами, в которых действует только последний тип. (Но помните, что это не будущее, другие процессоры x86 могли бы справиться с этим по-другому.)

Другие вопросы по тегам