0

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
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Jet Blue
  • 5,109
  • 7
  • 36
  • 48
  • 3
    When troubleshooting macros always look at the expansions. When troubleshooting segfaults always use a debugger. One obvious problem is that you define your string in the code path so the cpu will try to execute it as instructions and that will likely fault. Use a different section, normally `.data` or `.rodata` for your string or at the very least make sure your code jumps around it. – Jester Mar 08 '19 at 23:17
  • @Jester How can I view the macro expansion? Edit: just remembered objdump – Jet Blue Mar 08 '19 at 23:26
  • Use GDB's disassembly view to single-step through instructions, not source lines. e.g. `layout reg`. See the bottom of https://stackoverflow.com/tags/x86/info for debugging tips. – Peter Cordes Mar 08 '19 at 23:51
  • @PeterCordes Ah, though the expansion is what I expect it to be, 0x48 ('H') 0x65 ('e') etc. aren't valid instructions... The tutorial had a jump to the syscall before these local definitions, which I ignored but now I understand why it was there. Thanks! – Jet Blue Mar 09 '19 at 00:19
  • Your machine code doesn't match your source. Your macro uses 4x `push` instructions before the `db`, but your `objdump` starts with the string data. Also, if you were trying to preserve all regs, you forgot RCX and R11 (clobbered by `syscall` itself.) – Peter Cordes Mar 09 '19 at 00:23
  • @PeterCordes When compiling I used the `-f elf64` flag, and when using objdump the `-M x86-64` flag with the goal of both being 64 bit...I'm not sure where/why the mismatch happens...Thanks for the heads up on the shortcoming of saving just the registers I directly use. – Jet Blue Mar 09 '19 at 00:37
  • I added a section to my answer with my objdump output. Yours appears to be broken somehow. – Peter Cordes Mar 09 '19 at 00:54
  • @PeterCordes Ah sorry, I realized the source of the discrepancy (facepalm)...I had the push and pop statements commented out in an attempt to narrow down the cause. – Jet Blue Mar 09 '19 at 01:17

1 Answers1

2

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, (or .rdata on Windows) then switch back to the original 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

This unconditionally switches to the .text section, regardless of what section you were in when you used this macro (e.g. .text.cold or some other custom section).

GAS would let you do .pushsection .rodata / .popsection to expand the macro correctly inside any section. NASM has a different mechanism to allow this, which doesn't nest. See the NASM manual for details on how section foo also defines __?SECT?__ as [section foo], and that the "raw directive" [section bar] will switch without doing that.

Safer version that switches back to the original section

%macro PRINT 1
  ...
[section .rodata]        ; switch to .rodata without updating __?SECT?__
    %%str    db  %1, 0       ; arg0 + null terminator
    %%strln  equ $ - %%str   ; current position - string start
__?SECT?__               ; switch back to original section, likely [section .text]
  ... rest of the macro

Nesting of macros that switch sections might actually be possible by using %push [optional context-name] / %pop to save/restore __?SECT?__ on the context stack, so you can use a user-level section not [section]. But that might be incompatible with usage of macros that also %define things that you want visible later. Since this is unlikely to be needed in practice, I didn't try it. (e.g. you switch to .rodata and use another macro which itself switches somewhere else.) I mention this mostly because GAS .pushsection / .popsection does nest easily.


If any of your static data is the same every time, you can either just pull it outside the macro, or use a %ifndef / %define guard so the first use of the macro (in one file) emits the data (with a plain label not using %%, like debugprint_str) for later expansions to reference. (See Defining variables within a macro causes multiple definitions of the same variable). On upside to that is that the data won't be part of your program at all if the macro is used zero times. But for macro-arg strings, unless you bake the string into the symbol name, you won't be able to do duplicate elimination/folding at assemble time.


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 that way.


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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847