【发布时间】:2021-03-03 07:43:33
【问题描述】:
我正在设计一个附加到 Pthreads 的基于预加载器的锁跟踪实用程序,但我遇到了一个奇怪的问题。该程序通过提供在运行时替换相关 Pthreads 函数的包装器来工作;这些做一些日志记录,然后将参数传递给真正的 Pthreads 函数来完成工作。显然,它们不会修改传递给它们的参数。但是,在测试时,我发现传递给我的 pthread_cond_wait() 包装器的条件变量指针与传递给底层 Pthreads 函数的指针不匹配,该函数立即崩溃并出现“futex 工具返回了意外的错误代码”,该代码来自我收集到的内容,通常表示传入的同步对象无效。来自 GDB 的相关堆栈跟踪:
#8 __pthread_cond_wait (cond=0x7f1b14000d12, mutex=0x55a2b961eec0) at pthread_cond_wait.c:638
#9 0x00007f1b1a47b6ae in pthread_cond_wait (cond=0x55a2b961f290, lk=0x55a2b961eec0)
at pthread_trace.cpp:56
我很困惑。这是我的 pthread_cond_wait() 包装器的代码:
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* lk) {
// log arrival at wait
the_tracer.add_event(lktrace::event::COND_WAIT, (size_t) cond);
// run pthreads function
GET_REAL_FN(pthread_cond_wait, int, pthread_cond_t*, pthread_mutex_t*);
int e = REAL_FN(cond, lk);
if (e == 0) the_tracer.add_event(lktrace::event::COND_LEAVE, (size_t) cond);
else {
the_tracer.add_event(lktrace::event::COND_ERR, (size_t) cond);
}
return e;
}
// GET_REAL_FN is defined as:
#define GET_REAL_FN(name, rtn, params...) \
typedef rtn (*real_fn_t)(params); \
static const real_fn_t REAL_FN = (real_fn_t) dlsym(RTLD_NEXT, #name); \
assert(REAL_FN != NULL) // semicolon absence intentional
这是 glibc 2.31 中 __pthread_cond_wait 的代码(如果您正常调用 pthread_cond_wait ,就会调用该函数,由于版本控制,它具有不同的名称。上面的堆栈跟踪确认这是 REAL_FN 指向的函数):
int
__pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex)
{
/* clockid is unused when abstime is NULL. */
return __pthread_cond_wait_common (cond, mutex, 0, NULL);
}
如您所见,这两个函数都没有修改 cond,但在两个帧中并不相同。检查核心转储中的两个不同指针表明它们也指向不同的内容。我还可以在核心转储中看到 cond 在我的包装函数中似乎没有改变(即它在崩溃点的第 9 帧中仍然等于 0x5...,这是对 REAL_FN 的调用)。通过查看它们的内容,我无法真正判断哪个指针是正确的,但我假设它是从目标应用程序传入我的包装器的那个。两个指针都指向程序数据的有效段(标记为 ALLOC、LOAD、HAS_CONTENTS)。
我的工具肯定会以某种方式导致错误,如果没有附加目标应用程序,它可以正常运行。我错过了什么?
更新:实际上,这似乎不是导致错误的原因,因为在错误发生之前对我的 pthread_cond_wait() 包装器的调用多次成功,并且每次都表现出类似的行为(指针值在帧之间更改而没有解释) .不过,我将问题悬而未决,因为我仍然不明白这里发生了什么,我想学习。
更新 2:根据要求,这是 tracer.add_event() 的代码:
// add an event to the calling thread's history
// hist_entry ctor gets timestamp & stack trace
void tracer::add_event(event e, size_t obj_addr) {
size_t tid = get_tid();
hist_map::iterator hist = histories.contains(tid);
assert(hist != histories.end());
hist_entry ev (e, obj_addr);
hist->second.push_back(ev);
}
// hist_entry ctor:
hist_entry::hist_entry(event e, size_t obj_addr) :
ts(chrono::steady_clock::now()), ev(e), addr(obj_addr) {
// these are set in the tracer ctor
assert(start_addr && end_addr);
void* buf[TRACE_DEPTH];
int v = backtrace(buf, TRACE_DEPTH);
int a = 0;
// find first frame outside of our own code
while (a < v && start_addr < (size_t) buf[a] &&
end_addr > (size_t) buf[a]) ++a;
// skip requested amount of frames
a += TRACE_SKIP;
if (a >= v) a = v-1;
caller = buf[a];
}
histories 是来自 libcds 的无锁并发 hashmap(映射 hist_entry 的 tid->per-thread 向量),并且它的迭代器也保证是线程安全的。 GNU 文档说 backtrace() 是线程安全的,并且 CPP 文档中没有提到 stable_clock::now() 的数据竞争。 get_tid() 只是使用与包装函数相同的方法调用 pthread_self(),并将其结果转换为 size_t。
【问题讨论】:
-
似乎是时候调试汇编代码了?
-
the_tracer.add_event是做什么的?特别想知道它如何获取第二个参数(按值或按引用)。 -
参数是按值传递的(而不是作为指针,从演员表中可以看出)
-
您使用的是哪个编译器?您似乎没有正确使用可变宏参数,我认为这会导致您只将一个参数传递给
REAL_FN。 -
我使用的是 gcc 9.3。该宏定义格式正确;如果你愿意,你可以命名你的可变参数:gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
标签: c++ pointers pthreads ld-preload