我不能展示导致这个问题的整个代码(而且我实际上不可能这样做),我认为实际解决这个问题可能会超出这里的范围。所以我要问的问题是如何我可以诊断这个问题。
基本上我有一个看起来像这样的类(请注意我not 能够重现我在 MVCE 中遇到的问题,所以我只是大致展示我在做什么,以便我可以获得帮助我需要调试的工具):
#include <memory>
#include <array>
#include <semaphore>
#include <thread>
struct SharedDataStructure{
SharedDataStructure(){
for(auto& value : semaphore_array){
value = std::make_unique<std::unique_ptr<std::counting_semaphore<2>>(2);
}
}
std::uint32_t get_latest_index(){... calculates some index};
std::array<std::atomic<uint32_t>,16> atomics_member;
std::array<std::unique_ptr<std::counting_semaphore<2>, 16> semaphore_array;
std::uint32_t dummy0;
}
struct ThreadClass{
ThreadClass(std::atomic<bool>& stop_flag, SharedDataStructure& shared_data_structure){
auto thread_function = [&shared_data_structure, control_toke](){
...
... create socket
std::array<mmsghdr, 1024> msgvec;
std::array< iovec, 1024> iovecs;
auto thread_socket = socket(...);
... //initialize initilaize mmsghdr and iovecs up here
... //set thread_socket options, bind to (0.0.0.0) and some port here etc..
timespec timeout = {};
timeout.tv_sec = 1;
while(!stop_flag){
// up until this point, shared_data_structure.semaphore_array is filled with what appear to be actual normal pointers.
auto packet_count = recvmmsg(thread_socket, msgvec.data(), msgvec.size(), 0, timeout);
// after this point, shared_data_structure.semaphore_array is filled with nullptrs, as if somethign zeroed all the values out.
for(std::size_t i = 0; i < packet_count; ++packet_count){
auto index = shared_data_structure.calculate_latest_index(); // in debugger this is 1, so no out of bounds
//causes segmentation fault, because for some reason now all the pointers in semaphore_array are nullptr...
shared_data_structure.semaphore_array[index].aquire();
//do something here.
shared_data_structure.semaphore_array[index].release();
}
}
}
m_thread = std::thread(thread_function);
}
~ThreadClass(){
m_thread.join();
}
std::thread m_thread;
}
void create_thread_class(std::atomic<bool>& stop_flag){
SharedDataStructure shared_data_structure;
ThreadClass thread_class_0(stop_flag, shared_data_structure);
//ThreadClass thread_class_1(stop_flag, shared_data_structure); happens whether this is commented out or not.
while(!stop_flag.load()){
//... at this point this just became an empty loop for debugging.
}
}
//invoke create_thread_class in a thread on it's own later.
在这行代码之前:
auto packet_count = recvmmsg(thread_socket, msgvec.data(), msgvec.size(), 0, timeout);
SharedDataStructure::semaphore_array
包含一堆“真实”指针,就像我初始化的一样。但是之后,所有的指针都变成了 nullptr。请注意,结构中的其他值似乎都没有受到影响。
我什至不知道如何调试它。很明显,
recvmmsg(...)
应该对未在其中使用的类的成员具有 zero 效果。我认为我正在调用某种未定义的行为,但我什至不知道如何找到它。结果看起来类似于缓冲区溢出,但我不明白这将如何影响堆栈变量(我不认为 recvmmsg 在堆栈上做了大量的事情?)。
如何诊断此类问题?
好的,我按照评论说的做了,发现问题了。首先,我使用 Clion,然后我尝试在那里使用 GDB,它有点工作,但它被卡住了。我会解释我做了什么。
所以我用了命令行。
我用过
(gdb) break my_file.h:[[line where for(std::size_t i = 0; i < packet_count; ++packet_count) is]]
然后我做了
(gdb) watch shared_data_structure.semaphore_array[0]
然后我运行了程序并尝试观看。起初我得到了一些奇怪的结果。首先,当我单步执行
recvmmsg
函数时,它看起来原来是错误所在,它 jumped 到 thread_function
lambda 的函数参数,然后返回到 thread_socket
所在的位置。然后,在我再次操作之后,所有东西都在 gdb 中冻结了大约 30 秒(这可能是我放弃使用 Clion 的 GDB 界面的地方)。我认为这与我没有 glibc 的调试信息有关(它早些时候警告过我)但我没有能力在我的系统上安装调试信息的东西,所以虽然这对我有帮助,这不是一个选择。
冻结后,会出现一些 python 错误,关于它没有找到
std::array<std::unique_ptr<std::counting_semaphore<2>, std::default_deleter>, 16>
的构造函数调试表示
但是在它显示此时已经设置了值之后。所以手表工作了。
问题是,我用
bt
运行了一个堆栈跟踪,然后......它只是 recvmmesg
和程序的其余部分,没有更深的东西。
基本上
recvmmsg()
some_std_invoke_stuff()
some_std_thread_stuff()
... all irrelevant out of scope
再次,调试信息可能会有所帮助,但至少在这一点上我知道
recvmmsg
问题 100% 发生了。
之后我使用 Valgrind 检查代码,过了一会儿我看到了一堆错误,比如一堆连续值的“mmsghdr[633] 访问未初始化的字节”。所以我不得不诊断
mmsghdr
初始化。
我没有在上面包括这个,因为再一次,解决我的具体问题不是我想要的(我不得不凭记忆写这个,我不记得了),但基本上这就是初始化的样子:
std::array<mmsghdr, 1024> msgvec;
std::array<iovec, 1024> iovecs;
my::aligned_vector<std::byte, buffer_size * n> buffers;
for(std::size_t i = 0; i < 1024; ++i){
iovecs[i].iov_base = (&buffers + (i * buffer_size));
iovecs[i].iov_len = buffer_size;
msgs[i].msg_hdr.msg_iov = &iovecs[i];
msgs[i].msg_hdr.msg_iovlen = 1;
}
一开始我觉得可能是我的I迭代太多了,所以断言反对
for(std::size_t i = 0; i < 1024; ++i){
assert(buffers.size() + (i * buffer_size + buffer_size));
iovecs[i].iov_base = (&buffers + (i * buffer_size));
iovecs[i].iov_len = buffer_size;
msgs[i].msg_hdr.msg_iov = &iovecs[i];
msgs[i].msg_hdr.msg_iovlen = 1;
}
在我意识到真正的问题之前。
iov_base
是一个 void*
,它将接受任何类型的指针。这意味着 (&buffers + (i * buffer_size))
是 my::aligned_vector<std::byte, buffer_size * n>
的指针,而不是一堆字节。我不得不把它改成(buffers.data() + (i * buffer_size))
for(std::size_t i = 0; i < 1024; ++i){
MY_ASSERT(buffers.size() + (i * buffer_size + buffer_size));
iovecs[i].iov_base = static_cast<std::byte>(buffers.data() + (i * buffer_size));
iovecs[i].iov_len = buffer_size;
msgs[i].msg_hdr.msg_iov = &iovecs[i];
msgs[i].msg_hdr.msg_iovlen = 1;
}
然后我的问题就解决了