如何解决多线程 C++ 应用程序崩溃的崩溃转储分析
这是一个关于通用场景的问题。我有一个多线程 C++ 应用程序崩溃了,我有崩溃转储。可能有数百个线程在运行,其中任何一个都可能导致崩溃。
- 开始分析故障转储的好方法是什么。
- 在许多线程中(已在转储文件下记录信息)如何找到导致崩溃的任何特定线程。我是否应该寻找任何特定标准,因为我无法继续分析所有线程及其堆栈。
- 您想建议的任何其他有用信息/线索。
非常感谢您提前
解决方法
我们将以下代码称为 minimum reproducible example,您应该在以后的问题中提供它。
它创建了 100 个线程,同步它们,以便它们同时开始运行(需要 C++ 20)。其中一个线程会随机产生一个异常,所以我们不知道是哪一个。
#include <random>
#include <thread>
#include <vector>
using namespace std::chrono_literals;
std::random_device rd;
std::mt19937 twister(rd());
std::uniform_int_distribution<int> dist(0,100);
std::counting_semaphore<100> synchronizer(0);
void randomCrash()
{
synchronizer.acquire();
if (dist(twister) < 2)
{
throw std::exception();
}
std::this_thread::sleep_for(1000ms); // Ensure the thread is still there when we analyze the dump
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 100; i++)
{
std::thread t(&randomCrash);
threads.push_back(std::move(t)); // Threads can't be copied,so move it
}
std::cout << "Created 100 threads.\r\n";
synchronizer.release(100);
std::cout << "100 threads running now.\r\n";
for (std::thread& th : threads)
{
if (th.joinable())
{
th.join();
}
}
std::cout << "Done. Ooops ... no exception happened? Well,that's randomness.\r\n";
}
如果我们现在打开崩溃转储,我们可以通过查看提示看到它已经切换到导致异常的线程 92:
0:092>
但是让我们假设使用命令 ~0s
不起作用,所以我们回到主线程。
0:092> ~0s
ntdll!NtWaitForSingleObject+0x14:
00007ffc`cbcacc94 c3 ret
0:000>
使用 ~
命令,您可以识别导致异常的线程:
0:000> ~
. 0 Id: 3edc.cc8 Suspend: 1 Teb: 0000004a`fb029000 Unfrozen
[...]
91 Id: 3edc.38d0 Suspend: 1 Teb: 0000004a`fb0df000 Unfrozen
# 92 Id: 3edc.2418 Suspend: 1 Teb: 0000004a`fb0e1000 Unfrozen
93 Id: 3edc.4788 Suspend: 1 Teb: 0000004a`fb0e3000 Unfrozen
[...]
103 Id: 3edc.43e4 Suspend: 1 Teb: 0000004a`fb0f7000 Unfrozen
当前线程有一个点(.
),有异常的线程有一个散列(#
)。请注意,如果当前线程是引发异常的线程,则该点可能会隐藏哈希。所以你可以轻松切换到线程
0:000> ~92s
ucrtbase!abort+0x4e:
00007ffc`c960286e cd29 int 29h
并查看调用堆栈
0:092> k
# Child-SP RetAddr Call Site
00 0000004a`80efe500 00007ffc`c9601f9f ucrtbase!abort+0x4e
01 0000004a`80efe530 00007ffc`b6e01aab ucrtbase!terminate+0x1f
02 0000004a`80efe560 00007ffc`b6e02317 VCRUNTIME140_1!FindHandler<__FrameHandler4>+0x45b [D:\...\frame.cpp @ 693]
03 0000004a`80efe730 00007ffc`b6e040d9 VCRUNTIME140_1!__InternalCxxFrameHandler<__FrameHandler4>+0x267 [D:\...\frame.cpp @ 357]
04 0000004a`80efe7d0 00007ffc`cbcb1f6f VCRUNTIME140_1!__CxxFrameHandler4+0xa9 [D:\...\risctrnsctrl.cpp @ 306]
05 0000004a`80efe840 00007ffc`cbc61454 ntdll!RtlpExecuteHandlerForException+0xf
06 0000004a`80efe870 00007ffc`cbcb0a9e ntdll!RtlDispatchException+0x244
07 0000004a`80efef80 00007ffc`c96bd759 ntdll!KiUserExceptionDispatch+0x2e
08 0000004a`80eff6b0 00007ffc`a9f36480 KERNELBASE!RaiseException+0x69
09 0000004a`80eff790 00007ff7`49ec13fd VCRUNTIME140!_CxxThrowException+0x90 [D:\...\throw.cpp @ 75]
0a 0000004a`80eff7f0 00007ff7`49ec1ecb WhichThreadCrashes!randomCrash+0x1bd [C:\...\WhichThreadCrashes.cpp @ 19]
0b (Inline Function) --------`-------- WhichThreadCrashes!std::invoke+0x2 [C:\...\type_traits @ 1585]
0c 0000004a`80eff850 00007ffc`c95b1bb2 WhichThreadCrashes!std::thread::_Invoke<std::tuple<void (__cdecl*)(void)>,0>+0xb [C:\...\thread @ 55]
0d 0000004a`80eff880 00007ffc`cb7c7034 ucrtbase!thread_start<unsigned int (__cdecl*)(void *),1>+0x42
0e 0000004a`80eff8b0 00007ffc`cbc62651 kernel32!BaseThreadInitThunk+0x14
0f 0000004a`80eff8e0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
所以我们可以看到它在 randomCrash()
中崩溃了。
一旦你知道它是如何工作的,你也可以使用~#s
直接切换到有异常的线程:
0:092> ~0s
ntdll!NtWaitForSingleObject+0x14:
00007ffc`cbcacc94 c3 ret
0:000> ~#s
ucrtbase!abort+0x4e:
00007ffc`c960286e cd29 int 29h
0:092>
另外,!analyze -v
应该给你
0:000> !analyze -v
[...]
STACK_COMMAND: ~92s ; .ecxr ; kb
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。