NASM-宏本地标签作为另一个宏的参数

时间:2019-03-08 23:11:54

标签: assembly x86 macros x86-64 nasm

我正在尝试使用宏(as shown in this tutorial)打印字符串。宏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看起来像乱码,但对应于Hello World!中字符的ASCII码。在对sys_write进行syscall之前,raxrdi似乎收到了0x1的期望值,rsi的{​​{1}}的值指向字符串开头,并且0x0的值rdx是字符串长度(12 +1)...

0xd

1 个答案:

答案 0 :(得分:2)

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_EXITsection .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作为字符串arg,在这种情况下什么也不做(指针已经在RSI中),否则请使用lea rsi, [rel %%str]。但是,这对于内存中的指针是无效的,mov rsi, [rbx]会在其中工作。取决于您希望宏的样式。您可能会%if在arg字符串中寻找[并使用mov而不是lea的条件。


如果要保存/恢复所有的寄存器,请记住syscall本身会阻塞RCX(保存的RIP)和R11(保存的RFLAGS)。

通常,您只需要注册一个注册宏Clobbers的文档即可;这些都是x86-64 System V中的调用密集型寄存器。但是,如果要使用调试打印宏,则可能希望它保存/恢复所有内容?除push / pop以外,破坏RSP下的红色区域。我认为我从来没有在asm中使用过调试打印功能,只是通过调试器设置断点,然后单击“继续”以查看下一个断点。或者只是单步执行并观察寄存器值的变化,例如与GDB的layout reg