【问题标题】:gdb: set scheduler_locking on always causes deadlockgdb:设置 scheduler_locking 总是会导致死锁
【发布时间】:2020-01-06 19:50:23
【问题描述】:

我正在使用 gdb 调试由 C 程序使用 dlopen() 和 dlsym() 加载的共享对象。共享对象是用 NASM 编写的,它是用 DWARF 调试符号编译的。我在 Ubuntu 18.04 上运行。

我希望每个线程在创建后完全停止,以便在继续之前创建所有四个线程。所以我用“set scheduler-locking on”或“set scheduler-locking step”开始调试会话。

据我了解 gdb 命令“set scheduler-locking on”,它应该允许我创建一个线程,然后切换回线程 1(主线程)并创建另一个线程,依此类推,直到所有线程都已创建的。

在 NASM 代码中,我在标签 Test_fn: 处设置了一个断点:(参见下面的代码)。当线程到达该断点时,它会停止(这是线程创建后的第一个断点)。然后我切换回线程 1(主线程)并“继续”实例化下一个线程(主线程仍在 Init_Cores 的 label_0 部分)。线程 1 将执行代码(如果我单步执行它),但在创建下一个线程之前的某个时刻,它会死锁,并且总是死锁。

我也尝试过单步创建线程(避免“继续”),但它仍然死锁。

根据Employed Russian 在How to continue one thread at a time when debugging a multithreaded program in GDB? 的回答,这可能会发生,但他并没有说它会一直发生。无论我使用“设置调度程序锁定”还是“设置调度程序锁定步骤”,我都会遇到死锁。

也许这是因为它是一个共享对象,但其他一切都适用于共享对象,所以我认为这不是问题所在。

这是 NASM 代码。同样的问题也适用于 C 或 C++ 代码,特别是为什么当我在新线程上到达断点时 gdb 死锁,然后切换回线程 1 以继续创建线程。线程 2(第一个创建的线程)应该在调度程序锁定打开时停止。

; Header Section
[BITS 64]

[default rel]

global Main_Entry_fn
extern pthread_create, pthread_join, pthread_exit, pthread_self,    sched_getcpu
global FreeMem_fn
extern malloc, realloc, free
extern sprintf

section .data align=16
X_ctr: dq 0
data_master_ptr: dq 0
initial_dynamic_length: dq 0
XMM_Stack: dq 0, 0, 0, 0, 0, 0, 0
ThreadID: dq 0
X_ptr: dq 0
X_length: dq 0
X: dq 0
collect_ptr: dq 0
collect_length: dq 0
collect_ctr: dq 0
even_squares_list_ptrs: dq 0, 0, 0, 0
even_squares_list_ctr: dq 0
even_squares_list_length: dq 0
Number_Of_Cores: dq 4
pthread_attr_t: dq 0
pthread_arg: dq 0
Join_Ret_Val: dq 0
tcounter: dq 0
sched_getcpu_array: times 4 dq 0
ThreadIDLocked: dq 0
spin_lock_core: times 4 dq 0
extra_test_array: times 4 dq 0
spin_lock_iter: times 4 dq 0
spin_lock_base_addr: dq 0

; __________

section .text

Init_Cores_fn:

%include "/opt/P01_SH/_Include_Utilities/Buffer_Pointer_Arrays.asm"

mov rax,[Number_Of_Cores]
mov rbx,8
mul rbx
mov [Number_Of_Cores],rax

; _____
; Create Threads

label_0:

; THREAD 1 WORKS IN THIS SECTION TO CREATE THREADS

mov rdi,ThreadID            ; ThreadCount
mov rsi,pthread_attr_t  ; Thread Attributes
mov rdx,Test_fn         ; Function Pointer
mov rcx,pthread_arg
call pthread_create wrt ..plt

mov rdi,[ThreadID]      ; id to wait on
mov rsi,Join_Ret_Val        ; return value
call pthread_join wrt ..plt

mov rax,[tcounter]
add rax,8
mov [tcounter],rax
mov rbx,[Number_Of_Cores]
cmp rax,rbx
jl label_0

; _____

jmp label_900 ; All threads return here, and exit

; ______________________________________

Test_fn:

; Get the core number
call sched_getcpu wrt ..plt
mov rbx,8 ; multiply by 8
mul rbx
push rax

pop rax
mov rbx,rax
push rax

Next_Stop: ; THIS IS WHERE EACH THREAD STOPS
mov rdi,extra_test_array
mov [rdi+rbx],rbx
jmp label_899 ; ******************

;__________

label_899:

pop rax

ret

; __________

label_900:

%include "/opt/P01_SH/_Include_Utilities/Sys_Close_Include.asm"

mov rdi,extra_test_array
mov rax,rdi

ret

;__________
;Free the memory

FreeMem_fn:

;The pointer is passed back in rcx (of course)

sub rsp,40
call free wrt ..plt
add rsp,40
ret

; __________
; Main Entry


Main_Entry_fn:
push rdi
push rbp
push rbx
push r15
xor r15,r15
push r14
xor r14,r14
push r13
xor r13,r13
push r12
xor r12,r12
push r11
xor r11,r11
push r10
xor r10,r10
push r9
xor r9,r9
push r8
xor r8,r8
movsd [XMM_Stack+0],xmm13
movsd [XMM_Stack+8],xmm12
movsd [XMM_Stack+16],xmm11
movsd [XMM_Stack+24],xmm15
movsd [XMM_Stack+32],xmm14
movsd [XMM_Stack+40],xmm10
mov [X_ptr],rdi
mov [data_master_ptr],rsi
; Now assign lengths
lea rdi,[data_master_ptr]
mov rbp,[rdi]
xor rcx,rcx
movsd xmm0,qword[rbp+rcx]
cvttsd2si rax,xmm0
mov [X_length],rax
add rcx,8

; __________
; Write variables to assigned registers

mov r15,0
lea rdi,[rel collect_ptr]
mov r14,qword[rdi]
mov r13,[collect_ctr]
mov r12,[collect_length]
lea rdi,[rel X_ptr]
mov r11,qword[rdi]
mov r10,[X_length]

; __________

call Init_Cores_fn

movsd xmm10,[XMM_Stack+0]
movsd xmm14,[XMM_Stack+8]
movsd xmm15,[XMM_Stack+16]
movsd xmm11,[XMM_Stack+24]
movsd xmm12,[XMM_Stack+32]
movsd xmm13,[XMM_Stack+40]
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
pop rbx
pop rbp
pop rdi
ret
;__________

NASM 代码很长,但重点是 label_0(创建线程的地方)和 Test_fn(新线程到达的第一个断点。

我非常感谢任何有关 gdb 调度程序锁定问题的意见。谢谢。

【问题讨论】:

    标签: linux multithreading gdb


    【解决方案1】:

    您的汇编程序似乎是这样做的:

    pthread_create(&ThreadID, ..., Test_fn, ...);
    pthread_join(ThreadID, ...);
    

    首先,这实际上并没有给您任何并行性,因为您创建并立即等待您刚刚创建的线程。您可以直接调用Test_fn(),而且开销更少。

    其次,如果您停止新创建的线程(就像您使用调度程序锁定所做的那样),那么您的主线程将阻塞等待加入该新创建的线程,并且它将永远阻塞。所以当然你的程序会死锁。

    更新:

    我想我需要在 pthread_create 之后立即调用 pthread_join,这样所有线程都会在主线程退出之前完成。

    没错。但他们这样做的方式通常是这样的:

    const int NThreads = ...;
    pthread_t tids[NThreads];
    
    for (int j = 0; j < NThreads; j++)
      pthread_create(&tids[j], ...);
    
    // All threads have started, and are now running in parallel with the main thread.
    // Wait for them to finish.
    for (int j = 0; j < NThreads; j++)
      pthread_join(tids[j], ...);
    
    // All done.
    return 0;
    

    【讨论】:

    • 感谢您的回答,@Employed 俄语。我想我需要在 pthread_create 之后立即调用 pthread_join ,这样所有线程都会在主线程退出之前完成。如果我创建线程并且不调用 pthread_join,那么四个线程将如何在将控制权返回给主线程之前完成?从您的回答中,我不完全明白这里需要更改什么。
    • 在阅读了您的回答后,我做了更多的研究并找到了一个您在上面展示的示例——在循环中创建线程,然后加入循环,所以我正要尝试,我现在就做。感谢您提供更多信息并确认我刚刚阅读的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 2019-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多