问题
I am trying to use a macro (as shown in this tutorial) to print a string. The macro PRINT
creates local labels to define the string content (str
) and length (strlen
), and then passes these as parameters to a second macro _syscall_write
which makes the syscall.
However running the code fails and I get a Segmentation fault (core dumped)
message.
I suspect the problem to be this particular lines, but I don't understand why.
mov rsi, %1 ; str
mov rdx, %2 ; strln
Here is the full code:
%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
Here is a disassembly of the object file (from a version with the push/pop commented out).
Looking at the expanded code I still cannot see what is wrong. The bytes 0x0..0xC look like gibberish but correspond to the ascii code of the characters in Hello World!
. Before the syscall to sys_write, rax
and rdi
seem to receive the expected value of 0x1
, rsi
the value of 0x0
which points to the string start, and rdx
the value of 0xd
which is the string length (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:
rex.W gs ins
is a privileged instruction, and faults in user-space. This is the first instruction of your program, from the expansion of %%str db %1, 0
in your macro without changing sections.
Don't put data where it will be executed as instructions; use section .rodata
for read-only data.
GAS would let you do .pushsection .rodata
/ .popsection
to expand the macro correctly inside any section, but for NASM I'm not sure if we can do better than unconditionally switch to section .text
after the data.
The NASM preprocessor has %push [optional context-name] / %pop to save/restore preprocessor context, e.g. for nested repeat-until preprocessor stuff. But that's only for the preprocessor, and doesn't include restoring the old 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
So after using the macro, you're unconditionally in the .text
section, not in .text.cold
, or whatever other custom section.
Also note that equ
directives don't care what section they're in (unless they use $
in their definition). So strln
needs to be in the same section as str
, but SYS_EXIT
has nothing to do with section .data
. It's an assemble-time constant that turns into an immediate when you use it.
mov r64, imm64
is an inefficient way to put an absolute address in a register. It needs a load-time fixup in a PIE executable, and is longer than position-independent lea rsi, [rel %%str]
. NASM assembles mov rsi, str
into 10-byte mov r64, imm64
, while YASM uses mov r/m64, sign_extended_imm32
(which doesn't even work in a PIE executable). https://nasm.us/doc/nasmdo11.html#section-11.2
You could maybe write a macro that uses %ifidn
string-identical condition to check for rsi
as the string arg, and in that case do nothing (the pointer is already in RSI), otherwise use lea rsi, [rel %%str]
. That won't work for a pointer in memory, where mov rsi, [rbx]
would have worked, though. Depends how fancy you want your macro to be. You could maybe %if
a condition that looked for [
in the arg string and use mov
instead of lea
.
If you want to save/restore all the registers you clobber, remember that syscall
itself clobbers RCX (saved RIP) and R11 (saved RFLAGS).
Normally you'd just document which registers a macro clobbers; those are all call-clobbered registers in x86-64 System V. But if you want a debug-print macro, you probably want it to save/restore everything? Except push
/pop
destroy the red-zone below RSP. I don't think I've ever used debug-prints in asm, just setting breakpoints with a debugger and hitting "continue" to see which breakpoint is hit next. Or just single-step and watch register values change, e.g. with GDB's layout reg
.
来源:https://stackoverflow.com/questions/55072277/nasm-macro-local-label-as-parameter-to-another-macro