我正在尝试使用宏(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之前,rax
和rdi
似乎收到了0x1
的期望值,rsi
的{{1}}的值指向字符串开头,并且0x0
的值rdx
是字符串长度(12 +1)...
0xd
答案 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_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
作为字符串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
。