pthread 和 x64-assembly 中的主线程执行的代码之间的行为差异
Difference in behaviour between code executed by a pthread and the main thread in x64-assembly
在编写一些 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 .o .asm
ld -I/lib64/ld-linux-x86-64.so.2 -o .out .o -lc -lpthread
采用可变数量参数的函数,如 printf
需要正确设置 RAX 寄存器。您需要将其设置为使用的矢量寄存器的数量,在您的情况下为 0。来自 节 3.2.3 参数传递 在 System V 64-bit ABI:
RAX
- temporary register;
- with variable arguments passes information about the number of vector registers used;
- 1st return register
第 3.5.7 节 包含有关采用可变数量参数的函数的参数传递机制的更多详细信息。该部分说:
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.
修改您的代码,在对 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[=24 来简化 linking 可执行文件=]
我建议将 _start
重命名为 main
并简单地使用 GCC 到 link 最终的可执行文件。 GCC 的 C 运行时代码将提供一个 _start
标签来正确初始化 C 运行时,在某些情况下可能需要。当 C 运行时代码完成初始化后,它将(通过 CALL)传输到标签 main
。然后你可以生成你的可执行文件:
nasm -felf64 -g -o .o .asm
gcc -o .out .o -lpthread
我认为这与您的问题无关,但更像是一个仅供参考。
由于没有为 printf
调用正确设置 RAX,在某些情况下可能会出现不需要的行为。在这种情况下,RAX 的值在线程环境中没有为 printf
调用正确设置会导致分段错误。没有线程的代码碰巧能运行,因为你很幸运。
在编写一些 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 .o .asm
ld -I/lib64/ld-linux-x86-64.so.2 -o .out .o -lc -lpthread
采用可变数量参数的函数,如 printf
需要正确设置 RAX 寄存器。您需要将其设置为使用的矢量寄存器的数量,在您的情况下为 0。来自 节 3.2.3 参数传递 在 System V 64-bit ABI:
RAX
- temporary register;
- with variable arguments passes information about the number of vector registers used;
- 1st return register
第 3.5.7 节 包含有关采用可变数量参数的函数的参数传递机制的更多详细信息。该部分说:
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.
修改您的代码,在对 printf
:
show_value:
push rdi
xor rax, rax ; rax = 0
mov rsi, rdi
mov rdi, fmt
call printf
pop rdi
ret
您对 show_rval
另一个观察结果是,您可以通过使用 GCC 而不是 LD[=24 来简化 linking 可执行文件=]
我建议将 _start
重命名为 main
并简单地使用 GCC 到 link 最终的可执行文件。 GCC 的 C 运行时代码将提供一个 _start
标签来正确初始化 C 运行时,在某些情况下可能需要。当 C 运行时代码完成初始化后,它将(通过 CALL)传输到标签 main
。然后你可以生成你的可执行文件:
nasm -felf64 -g -o .o .asm
gcc -o .out .o -lpthread
我认为这与您的问题无关,但更像是一个仅供参考。
由于没有为 printf
调用正确设置 RAX,在某些情况下可能会出现不需要的行为。在这种情况下,RAX 的值在线程环境中没有为 printf
调用正确设置会导致分段错误。没有线程的代码碰巧能运行,因为你很幸运。