Skip to content
Advertisement

Difference in behaviour between code executed by a pthread and the main thread in x64-assembly

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.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement