When writing some x64 assembly, I stumbled upon something weird. A function call works fine when executed on a main thread, but causes a segmentation fault when executed as a pthread. At first I thought I was invalidating the stack, as it only segfaults on the second call, but this does not match with the fact that it works properly on the main thread yet crashes on a newly-spawned thread.
From gdb:
[Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Value: 1337 Value: 1337 [New Thread 0x7ffff77f6700 (LWP 8717)] Return value: 0 Value: 1337 Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7ffff77f6700 (LWP 8717)] __printf (format=0x600570 <fmt> "Value: %dn") at printf.c:28 28 printf.c: No such file or directory.
Does anyone have an idea about what could be going on here?
extern printf extern pthread_create extern pthread_join extern pthread_exit section .data align 4 fmt db "Value: %d", 0x0A, 0 fmt_rval db "Return value: %d", 0x0A, 0 tID dw 0 section .text global _start _start: mov rdi, 1337 call show_value call show_value ; <- this call works fine ; CREATE THREAD mov ecx, 0 ; function argument mov edx, thread_1 ; function pointer mov esi, 0 ; attributes mov rdi, tID ; pointer to threadID call pthread_create mov rdi, rax call show_rval mov rsi, 0 ; return value mov rdi, [tID] ; id to wait on call pthread_join mov rdi, rax call show_rval call exit thread_1: mov rdi, 1337 call show_value call show_value ; <- this additional call causes a segfault ret show_value: push rdi mov rsi, rdi mov rdi, fmt call printf pop rdi ret show_rval: push rdi mov rsi, rdi mov rdi, fmt_rval call printf pop rdi ret exit: mov rax, 60 mov rdi, 0 syscall
The binary was generated on Ubuntu 14.04 (64-bit of course), with:
nasm -felf64 -g -o $1.o $1.asm ld -I/lib64/ld-linux-x86-64.so.2 -o $1.out $1.o -lc -lpthread
Advertisement
Answer
Functions that take a variable number of parameters like printf
require the RAX register to be set properly. You need to set it to the number of vector registers used, which in your case is 0. From Section 3.2.3 Parameter Passing in the System V 64-bit ABI:
RAX
- temporary register;
- with variable arguments passes information about the number of vector registers used;
- 1st return register
Section 3.5.7 contains more detailed information about the parameter passing mechanism of functions taking a variable number of arguments. That section says:
When a function taking variable-arguments is called, %rax must be set to the total number of floating point parameters passed to the function in vector registers.
Modify your code to set RAX to zero in your call to printf
:
show_value: push rdi xor rax, rax ; rax = 0 mov rsi, rdi mov rdi, fmt call printf pop rdi ret
You have a similar issue with show_rval
One other observation is that you could simplify linking your executable by using GCC instead of LD
I would recommend renaming _start
to main
and simply use GCC to link the final executable. GCC‘s C runtime code will provide a _start
label that does proper initialization of the C runtime, which could potentially be required in some scenarios. When the C runtime code is finished initialization it transfers (via a CALL) to the label main
. You could then produce your executable with:
nasm -felf64 -g -o $1.o $1.asm gcc -o $1.out $1.o -lpthread
I don’t think this is related to your problem, but was meant more as an FYI.
By not properly setting RAX for the printf
call, unwanted behavior may occur in some cases. In this case, the value of RAX not being set properly for the printf
call in an environment with threads causes a segmentation fault. The code without threads happened to work because you were lucky.