デバッグ用途にて、pthread がどこのスレッドから作成されたかを調べたいケースがあったので、
tidの取得方法と、pthread_create にて作成された子スレッドのtidを取得する方法を調査した。
基本的に以下の方法にて LD_PRELOAD を利用することを前提にしている。
LD_PRELOADでprintfを後から差し替える - Qiita
gettidを使用する
gettid は Linux では syscall 経由でしか使用できない様子。以下のようにする。
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
pid_t gettid(void) {
return syscall(SYS_gettid);
}
pthread の子スレッドのtidを取得したい場合 (pthread_createのラップ)
pthread_t から直接子スレッドのtidを取れれば楽なのだけれども、直接は基本的には取れないようなので (glibcのstruct pthreadから定義を取ってくれば無理やりtidを取得することは一応可能のようだけども今回は使用しない)、pthread_create
をラップして、スレッドの呼び出し対象を差し替えて、上に書いた gettid()
にてtidを取得するようにした。
extern "C" {
struct pthread_args {
void* (*f)(void*);
void* args;
};
void* thread_wrap(void* args) {
// tid を出力
printf("%s : tid => %d\n", __func__, gettid());
// 退避した構造体からもとの関数を呼び出し
auto p = (struct pthread_args*)args;
auto ret = p->f(p->args);
delete p;
return ret;
}
void* thread_excute(void* args) {
printf("%s : tid => %d\n", __func__, gettid());
return NULL;
}
// pthread_createをラップ
int pthread_create(pthread_t *__newthread, const pthread_attr_t *__attr,
void *(*__start_routine) (void *), void *__arg) {
using pthread_create_t =
int (*)(pthread_t *, const pthread_attr_t *, void *(*) (void *), void *);
static thread_local pthread_create_t real_pthread_create = nullptr;
if (!real_pthread_create) {
// create cache
real_pthread_create
= reinterpret_cast<pthread_create_t>(dlsym(RTLD_NEXT, __func__));
assert(real_pthread_create);
}
// もともとの関数ポインタと引数を構造体に一旦退避
struct pthread_args* p = new(std::nothrow) struct pthread_args();
p->f = __start_routine;
p->args = __arg;
// 差し替え用の関数を呼び出して、引数で退避用の構造体を渡す
auto ret = real_pthread_create(__newthread, __attr, &thread_wrap, (void*)p);
return ret;
}
} // extern "C"
実行サンプル
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <new>
// hook
extern "C" {
pid_t gettid(void) {
return syscall(SYS_gettid);
}
struct pthread_args {
void* (*f)(void*);
void* args;
};
void* thread_wrap(void* args) {
printf("%s : tid => %d\n", __func__, gettid());
auto p = (struct pthread_args*)args;
auto ret = p->f(p->args);
delete p;
return ret;
}
void* thread_excute(void* args) {
printf("%s : tid => %d\n", __func__, gettid());
return NULL;
}
int pthread_create(pthread_t *__newthread, const pthread_attr_t *__attr,
void *(*__start_routine) (void *), void *__arg) {
using pthread_create_t =
int (*)(pthread_t *, const pthread_attr_t *, void *(*) (void *), void *);
static thread_local pthread_create_t real_pthread_create = nullptr;
if (!real_pthread_create) {
// create cache
real_pthread_create
= reinterpret_cast<pthread_create_t>(dlsym(RTLD_NEXT, __func__));
assert(real_pthread_create);
}
Dl_info info;
dladdr(__builtin_return_address(0), &info);
printf("%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(0));
printf("%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
printf("%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
printf("%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
printf("%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);
printf("%s : tid => %d\n", __func__, gettid());
struct pthread_args* p = new(std::nothrow) struct pthread_args();
p->f = __start_routine;
p->args = __arg;
auto ret = real_pthread_create(__newthread, __attr, &thread_wrap, (void*)p);
return ret;
}
}
// main
int main(int argc, char const* argv[])
{
pthread_t thread_handler_;
puts("begin : pthread_create");
pthread_create(&thread_handler_, NULL, &thread_excute, NULL);
puts("end : pthread_create");
pthread_join(thread_handler_, NULL);
return 0;
}
$ g++ -std=c++11 tid_test.cpp -ldl -pthread -rdynamic
$ ./a.out
begin : pthread_create
pthread_create : __builtin_return_address => 0x400f40
pthread_create : Dl_info.dli_fname => ./a.out
pthread_create : Dl_info.dli_fbase => 0x400000
pthread_create : Dl_info.dli_sname => main
pthread_create : Dl_info.dli_saddr => 0x400f0c
pthread_create : tid => 4303
end : pthread_create
thread_wrap : tid => 4304
thread_excute : tid => 4304
これで親と子の tid が取得できた。
関連
LD_PRELOADでprintfを後から差し替える - Qiita
LD_PRELOADで差し替えた関数から元の関数を呼び出す - Qiita
[Linux][C/C++]関数の復帰アドレス値と呼び出し元の関数名を取得する方法 - Qiita