Hugo Eynard
Hugo Eynard

Reputation: 51

NASM x86_64 printf 7th argument

I have a simple dprintf program written in NASM which prints a long format with more than 6 arguments. I am passing the arguments as the calling convention requires (RDI, RSI, RDX, RCX, R8, R9). As long as I use only those my program works fine.

I can't figure out why I get a segfault each time I try to push something to the stack as additional arguments. Here is the source:

;a comment
%macro DATA 0
section .data
    string: db "%6$ca comment%1$c%4$cmacro DATA 0%1$csection .data%1$c%2$cstring: db %3$c%5$s%3$c, 0%1$c%2$cpath: db %3$cGrace_kid.s%3$c, 0%1$c%4$cendmacro%1$c%4$cdefine SC_OPEN 0x2000005%1$c%4$cmacro MAIN 0%1$c%1$cDATA%1$c%1$csection .text%1$c%2$cglobal start%1$c%2$cglobal _main%1$c%2$cextern _dprintf%1$c%1$cstart:%1$c%2$ccall _main%1$c%2$cret%1$c%1$c_main:%1$c%2$cpush rbp%1$c%2$cmov rbp, rsp%1$c%2$cmov rax, SC_OPEN%1$c%2$clea rdi, [rel path]%1$c%2$cmov rsi, 0x0200%1$c%2$cxor rsi, 0x0002%1$c%2$cmov rdx, 0640o%1$c%2$cclc%1$c%2$csyscall%1$c%2$cjc ret%1$c%2$ccmp rax, 0%1$c%2$cjle ret%1$c%2$cmov rdi,rax%1$c%2$clea rsi, [rel string]%1$c%2$cmov rdx, 10%1$c%2$cmov rcx, 9%1$c%2$ccall _dprintf%1$c%2$cxor rax, rax%1$cret:%1$c%2$cleave%1$c%2$cret%1$c%4$cendmacro%1$c%1$cMAIN%1$c", 0
    path: db "Grace_kid.s", 0
%endmacro
%define SC_OPEN 0x2000005
%macro MAIN 0

DATA

section .text
    global start
    global _main
    extern _dprintf

start:
    call _main
    ret

_main:
    push rbp
    mov rbp, rsp
    ;sub rsp, 16
    mov rax, SC_OPEN
    lea rdi, [rel path]
    mov rsi, 0x0200
    xor rsi, 0x0002
    mov rdx, 0640o
    clc
    syscall
    jc ret
    cmp rax, 0
    jle ret
    mov rdi, rax
    lea rsi, [rel string]
    mov rdx, 10
    mov rcx, 9
    mov r8, 34
    mov r9, 37
    mov rbx, 59
    push rbx
    xor rax, rax
    call _dprintf
    xor rax, rax
ret:
    leave
    ret
%endmacro

MAIN

I assemble and link with these commands:

nasm -fmacho64 file.s
ld file.o -macosx_version_min 10.14 -lSystem

This works just fine but I would like to add extra parameters. I tried to push it on the stack using:

    mov rbx, 59
    push rbx

It segfaults whether I sub some bytes to RSP or not.

I am under MacOS Mojave and I'm using the latest version of NASM.

Upvotes: 2

Views: 1393

Answers (2)

Michael Petch
Michael Petch

Reputation: 47573

Since this question ended up having some value it probably could use an answer. There are two significant problems:

  • You call _dprintf with 7 parameters that have the C equivalent of:

    dprintf (fd, format_str, 10, 9, 34, 47, 59)
    

    The problem is that in your format string you have %5$s. The 5th variadic parameter is the value 59, not a pointer to a string. dprintf is trying to access memory it doesn't have permission to and you get the error EXC_BAD_ACCESS and a segment fault. You also have %6$c in the format string but don't have a 6th variadic parameter. From your comments it became clear you wanted the format_str itself to be the 5th parameter and the value 59 to be the 6th. The code to push the last 2 parameters should have looked like:

    push 59
    lea rbx, [rel string]
    push rbx
    xor rax, rax
    call _dprintf
    

    The corresponding C call would have been:

    dprintf (fd, format_str, 10, 9, 34, 47, format_str, 59)
    

    Note: When pushing the parameters on the stack that don't fit in the registers they must be pushed in reverse order

  • The x86-64 System V ABI calling convention requires at least 16-byte alignment of the stack before making a call to a conforming function (that includes the System and C library). On MacOS the System library is very sensitive to stack alignment issues as it uses aligned SIMD instructions wherever it can for performance reasons even when using just integer class parameters.

    _main conforms to this standard as well. The ABI requires 16-bye alignment at the point just before a call. 32-byte alignment is required if you pass parameters requiring 256-bit SIMD vectors - but that is not the case here. After entering _main (or any function that complies with the x86-64 calling convention rules) the stack is misaligned by 8 because the return address is now on the stack. push RBP subtracts 8 from RSP and the stack is now aligned on a 16-byte boundary again. If you push an even number of parameters on the stack to satisfy a call like dprintf alignment will still be intact. If you pass an odd number you are misaligned once again. In those cases you have to subtract 8 from RSP before pushing parameters.

    If you had truly meant to do:

    dprintf (fd, format_str, 10, 9, 34, 47, 59)
    

    You would have had to subtract 8 from RSP prior to pushing the extra 1 parameter on the stack. The code would have looked like:

    push rax         ; Push any register on stack or use `add rsp, -8` to align parameters
    push 59
    xor rax, rax
    call _dprintf
    

    In the case where you pass 2 extra parameters to dprintf no such stack adjustment is required as an even number of parameters being pushed will not break the 16-byte alignment

Upvotes: 2

Chris Dodd
Chris Dodd

Reputation: 126203

Your format string contains %5$s, which means it will try to print the 5th vararg (the 7th arg overall) as a string. Since that 5th argument is the constant 59 (presumably the character constant ';') and not a string, this ends up crashing inside printf (generally, inside strlen called from printf)

Upvotes: 2

Related Questions