Ошибка сегментации при использовании DB (определить байт) внутри функции - PullRequest
0 голосов
/ 12 апреля 2019

Я пытаюсь определить байт на языке ассемблера внутри моего раздела .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

Это дает мне ошибку:

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 Ответ

3 голосов
/ 12 апреля 2019

Если вы скажете ассемблеру где-то собрать произвольные байты, это произойдет.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.

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

...