是什么导致程序在反汇编视图中的系统调用后卡住?
What causes a program to stuck after a syscall in disassembler view?
我正在使用 QT-Creator
开发桌面应用程序。该项目在 C
中编程并在 gtest
中测试。它 运行 在 Linux (Debian)
机器上。
在尝试 运行 google 测试时,有时它会卡在反汇编代码中,如下所示:
0x7ffff780e16b 0f 94 c0 sete %al
0x7ffff780e16e eb 0f jmp 0x7ffff780e17f
0x7ffff780e170 be 80 00 00 00 mov [=10=]x80,%esi
0x7ffff780e175 45 30 c0 xor %r8b,%r8b
0x7ffff780e178 b8 ca 00 00 00 mov [=10=]xca,%eax
0x7ffff780e17d 0f 05 syscall
0x7ffff780e17f 8b 3c 24 mov (%rsp),%edi
每次卡住都是在反汇编代码的最后一行找的。我不知道它是关于什么的,但显然它与 syscall
有关。我试图跳转到其等效的 C 代码,但没有。我认为它与启动代码有关,但我不确定。
知道什么会导致这个问题以及我必须检查哪些方向吗?
注意:我的项目是多线程项目,里面使用了很多指针。我尽力用互斥体保护共享资源。
这可能是一个僵局。尝试使用 -fsanitize=thread
或 运行 使用 valgrind 进行编译。你必须调试它;检查 运行ning 线程的堆栈跟踪以查看程序正在尝试获取哪个互斥量,以及哪些互斥量已从何处锁定。
Valgrind 和 -fsanitize=thread
都能够检测 一些 死锁情况。 运行 您使用 valgrind 编写的程序既快速又简单,如果显示错误,那是您的幸运;如果没有,您将不得不求助于“正确的”调试技术。
根据要求,关于具体情况的一些细节:
反汇编显示了对 futex()
函数的系统调用。此函数阻塞当前线程,直到某个条件为真。如果没有其他线程设置此条件,则该线程将无限期阻塞。所以实际发生的是线程挂起 inside syscall
,即在内核级别。调试器是在告诉你它在 syscall
之后阻塞了 ,因为它不知道内核内部发生了什么。 std::mutex
等标准线程同步机制在内部调用 futex()
,因此使用这些机制将导致您的代码调用 futex()
.
这是一个非常简单的例子,其中 threadFun
函数总是在 futex()
系统调用中挂起:
#include <thread>
#include <mutex>
void threadFun (std::mutex& m) {
m.lock ();
}
int main () {
std::mutex m;
m.lock ();
std::thread t (threadFun, std::ref(m));
t.join ();
}
当运行在本地通过GDB将其挂起并中断程序时,可以清楚地看到反汇编内部构成futex()调用的mov [=21=]xca,%eax
和syscall
指令。该程序将永远挂起,直到被杀死。
futex()
的 syscall
指令如何在生成的代码中结束?
C 或 C++ 标准库也可能包含(内联)汇编代码。在这种情况下,std::mutex
调用 pthread
函数 pthread_mutex_lock
which is implemented in the glibc here, which calls __lll_lock_wait
which instantiates lll_futex_timed_wait
which in turn instantiates internal_syscall4
,它使用内联汇编生成 syscall
指令。由于您可能没有安装 glibc 调试符号和源代码,调试器看不到这段代码,只能向您展示反汇编。
但是,所有这些细节可能对您的调试帮助不大。当中断 GDB 中的示例程序时,回溯显示从 threadFun
中调用 std::mutex::lock()
,这就是您真正需要知道的是死锁:程序请求等待从未发生的事情, 运行时间环境(即 C++ 标准库 + glibc + 内核)符合。它究竟如何做到这一点并不重要。如果你写 std::puts(nullptr)
你会崩溃,而这究竟是如何发生的对于应用程序开发来说并不是很重要;您必须在 您的 代码中找到错误调用。
当您发现您的代码在 std::mutex::lock()
等锁定函数中挂起时,您知道这可能是一个死锁,然后可以使用上述工具,例如 -fsanitize=thread
和 [=37= 】 寻找真正的原因。如果这些没有帮助,请使用 GDB 命令 info threads
和 thread X
以及 bt
来获取所有线程的回溯。通过检查这些,您应该能够找到哪个线程锁定了哪个互斥体。
我正在使用 QT-Creator
开发桌面应用程序。该项目在 C
中编程并在 gtest
中测试。它 运行 在 Linux (Debian)
机器上。
在尝试 运行 google 测试时,有时它会卡在反汇编代码中,如下所示:
0x7ffff780e16b 0f 94 c0 sete %al
0x7ffff780e16e eb 0f jmp 0x7ffff780e17f
0x7ffff780e170 be 80 00 00 00 mov [=10=]x80,%esi
0x7ffff780e175 45 30 c0 xor %r8b,%r8b
0x7ffff780e178 b8 ca 00 00 00 mov [=10=]xca,%eax
0x7ffff780e17d 0f 05 syscall
0x7ffff780e17f 8b 3c 24 mov (%rsp),%edi
每次卡住都是在反汇编代码的最后一行找的。我不知道它是关于什么的,但显然它与 syscall
有关。我试图跳转到其等效的 C 代码,但没有。我认为它与启动代码有关,但我不确定。
知道什么会导致这个问题以及我必须检查哪些方向吗?
注意:我的项目是多线程项目,里面使用了很多指针。我尽力用互斥体保护共享资源。
这可能是一个僵局。尝试使用 -fsanitize=thread
或 运行 使用 valgrind 进行编译。你必须调试它;检查 运行ning 线程的堆栈跟踪以查看程序正在尝试获取哪个互斥量,以及哪些互斥量已从何处锁定。
Valgrind 和 -fsanitize=thread
都能够检测 一些 死锁情况。 运行 您使用 valgrind 编写的程序既快速又简单,如果显示错误,那是您的幸运;如果没有,您将不得不求助于“正确的”调试技术。
根据要求,关于具体情况的一些细节:
反汇编显示了对 futex()
函数的系统调用。此函数阻塞当前线程,直到某个条件为真。如果没有其他线程设置此条件,则该线程将无限期阻塞。所以实际发生的是线程挂起 inside syscall
,即在内核级别。调试器是在告诉你它在 syscall
之后阻塞了 ,因为它不知道内核内部发生了什么。 std::mutex
等标准线程同步机制在内部调用 futex()
,因此使用这些机制将导致您的代码调用 futex()
.
这是一个非常简单的例子,其中 threadFun
函数总是在 futex()
系统调用中挂起:
#include <thread>
#include <mutex>
void threadFun (std::mutex& m) {
m.lock ();
}
int main () {
std::mutex m;
m.lock ();
std::thread t (threadFun, std::ref(m));
t.join ();
}
当运行在本地通过GDB将其挂起并中断程序时,可以清楚地看到反汇编内部构成futex()调用的mov [=21=]xca,%eax
和syscall
指令。该程序将永远挂起,直到被杀死。
futex()
的 syscall
指令如何在生成的代码中结束?
C 或 C++ 标准库也可能包含(内联)汇编代码。在这种情况下,std::mutex
调用 pthread
函数 pthread_mutex_lock
which is implemented in the glibc here, which calls __lll_lock_wait
which instantiates lll_futex_timed_wait
which in turn instantiates internal_syscall4
,它使用内联汇编生成 syscall
指令。由于您可能没有安装 glibc 调试符号和源代码,调试器看不到这段代码,只能向您展示反汇编。
但是,所有这些细节可能对您的调试帮助不大。当中断 GDB 中的示例程序时,回溯显示从 threadFun
中调用 std::mutex::lock()
,这就是您真正需要知道的是死锁:程序请求等待从未发生的事情, 运行时间环境(即 C++ 标准库 + glibc + 内核)符合。它究竟如何做到这一点并不重要。如果你写 std::puts(nullptr)
你会崩溃,而这究竟是如何发生的对于应用程序开发来说并不是很重要;您必须在 您的 代码中找到错误调用。
当您发现您的代码在 std::mutex::lock()
等锁定函数中挂起时,您知道这可能是一个死锁,然后可以使用上述工具,例如 -fsanitize=thread
和 [=37= 】 寻找真正的原因。如果这些没有帮助,请使用 GDB 命令 info threads
和 thread X
以及 bt
来获取所有线程的回溯。通过检查这些,您应该能够找到哪个线程锁定了哪个互斥体。