栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Libhybris之Glibc和Bionic共存时的TLS问题(四),安卓面试题初级

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Libhybris之Glibc和Bionic共存时的TLS问题(四),安卓面试题初级

    int gscope_flag;

    #ifndef __ASSUME_PRIVATE_FUTEX

    int private_futex;

    #else

    int __glibc_reserved1;

    #endif

    void *__private_tm[4];

    void *__private_ss;

    } tcbhead_t;

如何去获得上述代码保存的tcbhead_t,以pthread_self的实现为例:

    # define THREAD_SELF

    ({ struct pthread *__self;

    asm ("movl %%gs:%c1,%0" : "=r" (__self)

    : "i" (offsetof (struct pthread, header.self)));

    __self;})

PS:x86时,pthread结构体的第一个元素就是tcbhead_t,所以他们的地址相同:

    struct pthread

    {

    union

    {

    #if !TLS_DTV_AT_TP

    tcbhead_t header;

    #else

glibc中的TLS,可以分为两类,三种。

第一类通过dtv_t *dtv实现,这是一个数组,数组里面每一项都是dtv_t联合体。

    typedef union dtv

    {

    size_t counter;

    struct

    {

    void *val;

    bool is_static;

    } pointer;

    } dtv_t;

dtv[-1]为申请的数组的大小,dtv[0]是max generation number,不知道表示什么。这两个都是counter类型的,之后的都是pointer类型的。

每个pointer类型的dtv_t联合体,都和一个被打开的有__thread变量的.so相关(dtv[1]除外,表示程序本身)。其val指向一个数组,也就是该.so中的保存所有__thread变量的一段连续空间。dtv数组的下标是l_tls_modid,表示被打开的有__thread变量的.so的序号。

保存__thread变量的连续空间的大小在编译时就确定好了,已初始化的__thread保存在.tdata段,未初始化的__thread保存在.tbss段,类似于.data和.bss的概念。

可以readelf -S 看看.tdata和.tbss的信息。

pointer类型的dtv_t联合体有静态和动态两种。

在线程创建之前被打开的.so对应的dtv_t是静态的,具体的位置在tcbhead_t前面的内存中。

在线程创建后被dlopen打开的.so对应的dtv_t是动态的,动态申请内存,具体位置在线程栈中。

gdb调试验证可以看:http://codemacro.com/2014/10/07/pthread-tls-bug/

第二类的实现在struct pthread中:

    struct pthread

    {

    tcbhead_t header;

    struct pthread_key_data

    {

    uintptr_t seq;

    void *data;

    } specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];

    struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];

    }

specific是一个二维数组,specific_1stblock是第一个一维数组,用于加快访问速度的。

通过pthread_key_create, pthread_setspecific和pthread_getspecific三个函数来折腾。

以pthread_getspecific来看怎么找到specific(gs—>gdt6—>tcbhead_t—>self—>THREAD_SELF—>specific)和使用二维数组的,比较简单:

    void *

    __pthread_getspecific (key)

    pthread_key_t key;

    {

    struct pthread_key_data *data;

    if (__glibc_likely (key < PTHREAD_KEY_2NDLEVEL_SIZE))

    data = &THREAD_SELF->specific_1stblock[key];

    else

    {

    if (key >= PTHREAD_KEYS_MAX)

    return NULL;

    unsigned int idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;

    unsigned int idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;

    struct pthread_key_data *level2 = T![](https://www.hualigs.cn/image/61dba891ed8ee.jpg) HREAD_GETMEM_NC (THREAD_SELF,

    specific, idx1st);

    if (level2 == NULL)

    return NULL;

    data = &level2[idx2nd];

    }

    void *result = data->data;

    if (result != NULL)

    {

    uintptr_t seq = data->seq;

    if (__glibc_unlikely (seq != __pthread_keys[key].seq))

    result = data->data = NULL;

    }

    return result;

    }

三、bionic中的TLS

===========================================================================

设置gs的值,以及gdt[6]中的地址,是在如下代码中进行设置的:

    void __libc_init_tls(KernelArgumentBlock& args) {

    __libc_auxv = args.auxv;

    static void* tls[BIONIC_TLS_SLOTS];

    static pthread_internal_t main_thread;

    main_thread.tls = tls;

    __set_tls(main_thread.tls);

    tls[TLS_SLOT_BIONIC_PREINIT] = &args;

    __init_alternate_signal_stack(&main_thread);

    }

    __LIBC_HIDDEN__ int __set_tls(void* ptr) {

    struct user_desc tls_descriptor;

    __init_user_desc(&tls_descriptor, true, ptr);

    int rc = __set_thread_area(&tls_descriptor);

    if (rc != -1) {

    // Change %gs to be new GDT entry.

    uint16_t table_indicator = 0; // GDT

    uint16_t rpl = 3; // Requested privilege level

    uint16_t selector = (tls_descriptor.entry_number << 3) | table_indicator | rpl;

    __asm__ __volatile__("movw %w0, %%gs" : : "q"(selector) : );

    }

    return rc;

    }

__init_user_desc第二个参数为true,表示动态申请gdt中的位置,一般为6,所以gs=0x33。

后续在__set_thread_area函数里将指向tls[BIONIC_TLS_SLOTS]的地址写到了gdt[6]中。

数组前几项是固定的:

    enum {

    TLS_SLOT_SELF = 0, // The kernel requires this specific slot for x86.

    TLS_SLOT_THREAD_ID,

    TLS_SLOT_ERRNO,

    // These two aren't used by bionic itself, but allow the graphics code to

    // access TLS directly rather than using the pthread API.

    TLS_SLOT_OPENGL_API = 3,

    TLS_SLOT_OPENGL = 4,

    TLS_SLOT_BIONIC_PREINIT = TLS_SLOT_OPENGL_API,

    TLS_SLOT_STACK_GUARD = 5, // GCC requires this specific slot for x86.

    TLS_SLOT_DLERROR,

    TLS_SLOT_FIRST_USER_SLOT // Must come last!

    };

bionic中的TLS表相当于一个一维数组。

bionic中不支持__thread语法,pthread_key_create, pthread_setspecific和pthread_getspecific三个函数直接折腾TLS_SLOT_FIRST_USER_SLOT之后的位置,目前TLS个数限制为64个。

以pthread_getspecific为例,看看bionic中的实现,比glibc简单多了:

    void* pthread_getspecific(pthread_key_t key) {

    if (!IsValidUserKey(key)) {

    return NULL;

    }

    // For performance reasons, we do not lock/unlock the global TLS map

    // to check that the key is properly allocated. If the key was not

    // allocated, the value read from the TLS should always be NULL

    // due to pthread_key_delete() clearing the values for all threads.

    return __get_tls()[key];

    }

define __get_tls() ({ void** __val; asm(“movl %%gs:0, %0” : “=r”(__val)); __val; })

四、什么是libhybris

============================================================================

libhybris简而言之,就是glibc想使用bionic中的.so。但是pthread,ipc等很多东西又不兼容,所以就整了这么一套东西。

libhybris实现了类似于android bionic的linker, 加一些glue code和wrap,hook之类的东西,去处理不兼容的部分。

demo程序,android端提供一个libfoo.c,里面有foo和bar两个函数:

Android.mk:

    LOCAL_PATH:=$(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE:= libfoo

    LOCAL_SRC_FILES:= foo.cpp

    include $(BUILD_SHARED_LIBRARY)

foo.cpp:

    #include

    #include

    void foo(void)

    {

    printf("foon");

    printf("%sn", getenv("PATH"));

    }

    void bar(void)

    {

    foo();

    printf("barn");

    }

其他系统端,通过libhybris,调用bar函数:

    #include

    #include

    #include

    #include

    #include

    int main(void)

    {

    void *handle;

    void (*bar)(void);

    handle = android_dlopen("libfoo.so", RTLD_NOW);

    if (NULL == handle)

    {

    fprintf(stderr, "android_dlopen failed: %sn", strerror(errno));

    return -1;

    }

    bar = (void (*)(void))android_dlsym(handle, "_Z3barv");

    if (NULL == bar)

    {

    fprintf(stderr, "fail to dlsym: %sn", strerror(errno));

    return -1;

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/715343.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号