NASM - локальная метка макроса как параметр для другого макроса - PullRequest
0 голосов
/ 09 марта 2019

Я пытаюсь использовать макрос (, как показано в этом уроке ) для печати строки. Макрос PRINT создает локальные метки для определения содержимого строки (str) и длины (strlen), а затем передает их в качестве параметров во второй макрос _syscall_write, который выполняет системный вызов.

Однако выполнение кода завершается ошибкой, и я получаю сообщение Segmentation fault (core dumped).

Я подозреваю, что проблема именно в этих строках, но я не понимаю, почему.

mov rsi, %1  ; str
mov rdx, %2  ; strln

Вот полный код:

%macro PRINT 1

    ; Save state
    push rax
    push rdi
    push rsi
    push rdx

    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start

    ; Write
    _syscall_write %%str, %%strln

    ; Restore state
    pop rdx
    pop rsi
    pop rdi
    pop rax

%endmacro

%macro _syscall_write 2
    mov rax, 1
    mov rdi, 1
    mov rsi, %1  ; str
    mov rdx, %2  ; strln
    syscall
%endmacro


global _start


section .data

    SYS_EXIT   equ 60
    EXIT_CODE  equ 0


section .text

    _start:

        PRINT "Hello World!"


    exit:

        mov rax, SYS_EXIT
        mov rdi, EXIT_CODE
        syscall


Вот разборка объектного файла (из версии с закомментированным push / pop).

Глядя на расширенный код, я все еще не вижу, что не так. Байты 0x0..0xC выглядят как бред, но соответствуют ascii-коду символов в Hello World!. До системного вызова sys_write rax и rdi, кажется, получают ожидаемое значение 0x1, rsi значение 0x0, которое указывает на начало строки, и rdx значение 0xd длина строки (12 + 1) ...

Disassembly of section .text:

0000000000000000 <_start>:
   0:   48                      rex.W
   1:   65                      gs
   2:   6c                      ins    BYTE PTR es:[rdi],dx
   3:   6c                      ins    BYTE PTR es:[rdi],dx
   4:   6f                      outs   dx,DWORD PTR ds:[rsi]
   5:   20 57 6f                and    BYTE PTR [rdi+0x6f],dl
   8:   72 6c                   jb     76 <SYS_EXIT+0x3a>
   a:   64 21 00                and    DWORD PTR fs:[rax],eax

   d:   b8 01 00 00 00          mov    eax,0x1
  12:   bf 01 00 00 00          mov    edi,0x1
  17:   48 be 00 00 00 00 00    movabs rsi,0x0
  1e:   00 00 00
  21:   ba 0d 00 00 00          mov    edx,0xd
  26:   0f 05                   syscall

0000000000000028 <exit>:
  28:   b8 3c 00 00 00          mov    eax,0x3c
  2d:   bf 00 00 00 00          mov    edi,0x0
  32:   0f 05                   syscall

1 Ответ

2 голосов
/ 09 марта 2019

rex.W gs ins является привилегированной инструкцией и ошибками в пространстве пользователя. Это первая инструкция вашей программы, начиная с расширения %%str db %1, 0 в вашем макросе без изменения разделов.

Не помещайте данные там, где они будут выполняться как инструкции; используйте section .rodata для данных только для чтения.

GAS позволил бы вам сделать .pushsection .rodata / .popsection для правильного расширения макроса внутри любого раздела, но для NASM я не уверен, что мы сможем сделать лучше, чем безоговорочно, переключиться на section .text после данных.

Препроцессор NASM имеет %push [optional context-name] / %pop для сохранения / восстановления контекста препроцессора, например, для вложенного повторения до препроцессора. Но это только для препроцессора, и не включает в себя восстановление старого section.

%macro PRINT 1
  ...
section .rodata 
    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start
section .text
  ... rest of the macro

Таким образом, после использования макроса вы безоговорочно находитесь в разделе .text, а не в .text.cold или любом другом пользовательском разделе.


Также обратите внимание, что директивам equ не важно, в каком разделе они находятся (если они не используют $ в своем определении). Так что strln должен находиться в том же разделе, что и str, но SYS_EXIT не имеет ничего общего с section .data. Это константа времени сборки, которая превращается в мгновенную при использовании.


mov r64, imm64 - это неэффективный способ поместить абсолютный адрес в регистр. Требуется исправление времени загрузки в исполняемом файле PIE, и оно длиннее, чем независимое от позиции lea rsi, [rel %%str]. NASM собирает mov rsi, str в 10-байтовый mov r64, imm64, тогда как YASM использует mov r/m64, sign_extended_imm32 (который даже не работает в исполняемом файле PIE). https://nasm.us/doc/nasmdo11.html#section-11.2

Возможно, вы могли бы написать макрос, который использует %ifidn идентичное строковому условию для проверки rsi в качестве строкового аргумента, и в этом случае ничего не делать (указатель уже находится в RSI), в противном случае используйте lea rsi, [rel %%str]. Это не будет работать для указателя в памяти, где mov rsi, [rbx] работало бы, хотя. Зависит от того, насколько вы хотите, чтобы ваш макрос был. Вы можете %if условие, которое ищет [ в строке аргумента и использовать mov вместо lea.


Если вы хотите сохранить / восстановить все регистры, которые вы закрываете, помните, что syscall сам блокирует RCX (сохраненный RIP) и R11 (сохраненный RFLAGS).

Обычно вы просто документируете, что регистрирует макро-клобберы; это все регистры с замкнутым вызовом в x86-64 System V. Но если вы хотите макрос отладочной печати, вы, вероятно, хотите, чтобы он все сохранял / восстанавливал? За исключением push / pop уничтожить красную зону под RSP. Я не думаю, что когда-либо использовал отладочные отпечатки в asm, просто установил точки останова с помощью отладчика и нажал «продолжить», чтобы увидеть, какая точка останова будет следующей. Или просто пошаговое и наблюдаемое изменение значений регистра, например с GDB layout reg.

...