首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >指针意外传递给函数更改

指针意外传递给函数更改
EN

Stack Overflow用户
提问于 2020-11-18 21:39:57
回答 1查看 168关注 0票数 0

我正在设计一个基于预加载器的锁跟踪实用程序,它连接到p线程,我遇到了一个奇怪的问题。该程序的工作方式是提供包装器,在运行时替换相关的P螺纹函数;这些函数会进行一些日志记录,然后将args传递给实际的P螺纹函数来完成这项工作。显然,他们不会修改传递给他们的论点。但是,在测试时,我发现传递给我的pthread_cond_wait()包装器的条件变量指针与传递给基础P线程函数的条件变量指针不匹配,该函数立即崩溃,使用"futex工具返回一个意外的错误代码“,根据我收集到的信息,该指针通常表示传入的无效同步对象。来自GDB的相关堆栈跟踪:

代码语言:javascript
复制
#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()包装器的代码:

代码语言:javascript
复制
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所指向的函数):

代码语言:javascript
复制
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在我的包装函数中似乎没有改变(也就是说,它仍然等于0x5.在框架9中的崩溃点,即对REAL_FN的调用)。通过查看它们的内容,我无法真正判断哪个指针是正确的,但我假设它是从目标应用程序传入我的包装器的指针。两个指针都指向程序数据的有效段(标记为ALLOC、LOAD、HAS_CONTENTS)。

我的工具肯定是导致了错误,目标应用程序运行良好,如果它没有附加。我遗漏了什么?

UPDATE:实际上,这似乎并不是导致错误的原因,因为对我的pthread_cond_wait()包装器的调用在错误发生之前多次成功,并且每次都表现出类似的行为(指针值在帧间变化而没有解释)。不过,我不想问这个问题,因为我仍然不明白这里发生了什么,我很想学习。

更新2:根据请求,下面是tracer.add_event()的代码:

代码语言:javascript
复制
// 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];
}

历史记录是来自libcds的无锁并发散列映射(映射hist_entry的tid->每个线程向量),它的迭代器也保证线程安全。GNU说backtrace()是线程安全的,并且CPP文档中没有提到steady_clock::now()的数据竞争。get_tid()只使用与包装函数相同的方法调用pthread_self(),并将其结果转换为size_t。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-11-21 18:38:17

哈,弄明白了!问题是,为了向后兼容,Glibc公开了多个版本的pthread_cond_wait()。我在问题中复制的版本是当前的版本,我们想称之为这个版本。dlsym()正在查找的版本是向后兼容的版本:

代码语言:javascript
复制
int
__pthread_cond_wait_2_0 (pthread_cond_2_0_t *cond, pthread_mutex_t *mutex)
{
  if (cond->cond == NULL)
    {
      pthread_cond_t *newcond;

      newcond = (pthread_cond_t *) calloc (sizeof (pthread_cond_t), 1);
      if (newcond == NULL)
        return ENOMEM;

      if (atomic_compare_and_exchange_bool_acq (&cond->cond, newcond, NULL))
        /* Somebody else just initialized the condvar.  */
        free (newcond);
    }

  return __pthread_cond_wait (cond->cond, mutex);
}

正如您所看到的,这个版本的tail调用了当前的一个,这可能就是为什么要花这么长时间才能检测到: GDB通常很擅长检测由尾调用导致的帧,但我猜它没有检测到这个帧,因为函数的名称是“相同的”(而且错误不会影响互斥函数,因为它们不会公开多个版本)。这篇博客文章深入到了更多的细节,巧合的是,特别是关于pthread_cond_wait()。在调试和调整它时,我多次使用这个函数,因为对glibc的每个调用都封装在多个间接层中;只有在pthread_cond_wait符号上设置断点(而不是行号)时,它才会停止。

无论如何,这解释了指针的变化现象:会调用旧的、不正确的函数,将pthread_cond_t对象重新解释为包含指向pthread_cond_t对象的指针的结构,为该指针分配一个新的pthread_cond_t,然后将新分配的pthread_cond_t传递给新的、正确的函数。旧函数的框架被尾调用删除,在离开旧函数后返回到GDB回溯,看起来正确的函数是直接从我的包装器调用的,带有一个神秘更改的参数。

解决这个问题的方法很简单: GNU提供了libdl扩展dlvsym(),它与dlsym()类似,但也接受一个版本字符串。寻找具有版本字符串"GLIBC_2.3.2“的GLIBC_2.3.2解决了这个问题。请注意,这些版本通常不对应于当前版本(即pthread_create()/exit()有版本字符串"GLIBC_2.2.5"),因此需要根据每个函数查找它们。可以通过查看compat_symbol()或versioned_symbol()宏来确定正确的字符串,这些宏位于glibc源中函数定义的某个位置,或者使用readelf查看编译库中符号的名称( by有"pthread_cond_wait@@GLIBC_2.3.2“和"pthread_cond_wait@@GLIBC_2.2.5")。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64901828

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档