我们在软件中存在一个以可怕的代码结束的错误:
futex工具返回了意外的错误代码。
我们将其追溯到一个问题,其中std :: condition_variable在内存分配区域内的位置会导致futex错误。如果std :: condition_variable未与16字节字对齐-当您尝试wait
时,它将导致futex错误。在该示例中,前两个wait_for
调用起作用,但最后一个调用因futex错误而中止程序。
void futex_error()
{
/* init */
std::mutex mtx;
/* Normal one works */
std::cout << "Doing normal" << "\n";
std::condition_variable* con_var = (std::condition_variable*)malloc(sizeof(std::condition_variable));
new (con_var) std::condition_variable{};
{
std::unique_lock<std::mutex> lck(mtx);
con_var->wait_for(lck, std::chrono::seconds(1));
}
/* Clean */
con_var->std::condition_variable::~condition_variable();
free(con_var);
std::cout << "Doing 16 bytes" << "\n";
/* Works on 16 byte alignment */
uint8_t* ptr_16 = (uint8_t*)malloc(sizeof(std::condition_variable) + 16);
std::condition_variable* con_var_16 = new (ptr_16 + 16) std::condition_variable{};
{
std::unique_lock<std::mutex> lck(mtx);
con_var_16->wait_for(lck, std::chrono::seconds(1));
}
/* Clean */
con_var_16->std::condition_variable::~condition_variable();
free(ptr_16);
std::cout << "Doing 1 byte" << "\n";
/* Futex error */
uint8_t* bad_ptr = (uint8_t*)malloc(sizeof(std::condition_variable) + 1);
std::condition_variable* bad = new (bad_ptr + 1) std::condition_variable{};
{
std::unique_lock<std::mutex> lck(mtx);
bad->wait_for(lck, std::chrono::seconds(1)); //<--- error here?
}
/* Clean */
bad->std::condition_variable::~condition_variable();
free(con_var);
}
我似乎找不到有关futex错误以及为什么对齐会导致此错误的文档。有谁知道为什么会这样?这是在Linux(Arch和Ubuntu)上,同时使用gcc 9.3。
为什么对齐会导致此问题
对象类型具有对齐要求([basic.fundamental],[basic.compound]),这些条件对可以分配该类型的对象的地址施加了限制。
表达式:
new (bad_ptr + 1) std::condition_variable{};
在bad_ptr + 1
与alignof(std::condition_variable)
不对齐的系统上调用未定义的行为。使用gcc10在godbolt上进行测试,alignof(std::confition_variable)
等于8
。
bad->
两者都是未对齐的访问,并且都是未定义的行为。
有人知道为什么会这样吗?
在执行可执行文件时检查strace
输出,我们可以看到:
futex(0x557da3e262e9, FUTEX_WAIT_BITSET_PRIVATE, 0, {tv_sec=2439, tv_nsec=619296657}, FUTEX_BITSET_MATCH_ANY) = -1 EINVAL (Invalid argument)
因为uaddr
应作为指向int
调用的futex
的指针的第一个参数未与_Alignof(int)
对齐,所以内核将其检测为here,并且futex返回了EINVAL
。然后,标准库将退出应用程序,对于未定义的行为,这是一个非常好的行为。