Reputation: 51
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
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
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