【问题标题】:Difference in behaviour between code executed by a pthread and the main thread in x64-assemblypthread 执行的代码与 x64-assembly 中的主线程之间的行为差​​异
【发布时间】:2016-02-03 17:56:09
【问题描述】:

在编写一些 x64 程序集时,我偶然发现了一些奇怪的东西。函数调用在主线程上执行时工作正常,但在作为 pthread 执行时会导致分段错误。起初我以为我正在使堆栈无效,因为它仅在第二次调用时出现段错误,但这与它在主线程上正常工作但在新生成的线程上崩溃的事实不符。

来自 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: %d\n") at printf.c:28
28  printf.c: No such file or directory.

有人知道这里会发生什么吗?

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

二进制文件是在 Ubuntu 14.04(当然是 64 位)上生成的,带有:

nasm -felf64 -g -o $1.o $1.asm
ld  -I/lib64/ld-linux-x86-64.so.2 -o $1.out $1.o -lc -lpthread

【问题讨论】:

  • 在 Linux 64 位调用约定中,rdi 是 not 调用保留。哦,我明白你为什么要花费 insns 推送/弹出它:因为你正在制定自己的非标准调用约定来保留 rdi,所以你可以保存第二个 mov edi, 1337 (是的,你应该移动到 edi 和让零扩展处理它,以确保您的汇编程序不使用 REX 前缀)。不过,迈克尔对实际问题的看法是正确的。

标签: linux multithreading assembly pthreads x86-64


【解决方案1】:

printf 这样采用可变数量参数的函数需要正确设置RAX 寄存器。您需要将其设置为使用的向量寄存器的数量,在您的情况下为 0。来自System V 64-bit ABI 中的 Section 3.2.3 Parameter Passing

RAX

  • 临时寄存器;
  • 使用可变参数传递有关使用的向量寄存器数量的信息;
  • 第一个返回寄存器

第 3.5.7 节 包含有关采用可变数量参数的函数的参数传递机制的更多详细信息。该部分说:

当调用采用可变参数的函数时,%rax 必须设置为向量寄存器中传递给函数的浮点参数的总数。

修改你的代码,在你对printf的调用中将RAX设置为零:

show_value:
  push    rdi

  xor     rax, rax     ; rax = 0
  mov     rsi, rdi
  mov     rdi, fmt
  call    printf

  pop       rdi
  ret

show_rval 也有类似的问题


另一个观察结果是,您可以通过使用 GCC 而不是 LD

来简化链接可执行文件

我建议将 _start 重命名为 main 并简单地使用 GCC 来链接最终的可执行文件。 GCCC 运行时代码将提供一个 _start 标签,用于正确初始化 C 运行时,在某些情况下可能需要情景。当 C 运行时代码完成初始化后,它会传输(通过 CALL)到标签 main。然后,您可以使用以下命令生成可执行文件:

nasm -felf64 -g -o $1.o $1.asm
gcc -o $1.out $1.o -lpthread

我认为这与您的问题无关,但更多的是作为仅供参考。


如果没有为printf 调用正确设置RAX,在某些情况下可能会出现不需要的行为。在这种情况下,在有线程的环境中没有为printf 调用正确设置RAX 的值会导致分段错误。没有线程的代码碰巧可以工作,因为你很幸运。

【讨论】:

  • 但即使有了这个修复,对我来说,第一次调用pthread_create 后代码会出现段错误。
  • @mercury0114 :我建议对您的所有代码、您使用的环境等提出一个新问题,以便我们了解哪些代码不适合您以及原因。
猜你喜欢
  • 2016-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-04
  • 1970-01-01
  • 2018-10-25
  • 1970-01-01
相关资源
最近更新 更多