我正在为非常小且静态链接的程序实现一小部分libc
,我认为添加TLS支持将是一个很好的学习体验。我使用Ulrich Drepper's TLS document作为参考。
我设置了两个字符串来试试这个:
static __thread const char msg1[] = "TLS (1).\n"; /* 10 bytes */
static __thread const char msg2[] = "TLS (2).\n"; /* 10 bytes */
编译器生成以下指令以访问它们:
mov rbx, QWORD PTR fs:0x0 ; Load TLS.
lea rsi, [rbx-0x14] ; Get a pointer to 'msg1'. 20 byte offset.
lea rsi, [rbx-0xa] ; Get a pointer to 'msg2'. 10 byte offset.
我们假设我将TCB放在堆栈的某个位置:
struct tcb {
void* self; /* Points to self. I read that this was necessary somewhere. */
int errno; /* Per-thread errno variable. */
int padding;
};
然后将TLS区域放在tls = &tcb - tls_size
旁边。然后我将FS寄存器设置为fs = tls + tls_size
,并将TLS初始化映像复制到tls
。
然而,这不起作用。我已通过将tls_image
的20个字节写入stdout
来验证我是否正确找到了TLS初始化映像。这或者让我相信我错误地放置了TCB和/或TLS区域,或者说我不符合ABI。
arch_prctl(2)
设置FS寄存器。我是否需要以某种方式使用set_thread_area(2)
?dtv
。我假设这不是必要的,因为我是静态链接。关于我做错了什么的任何想法?非常感谢!
答案 0 :(得分:2)
我正在实现一个非常小且静态的libc的一小部分 链接的程序,我认为添加TLS支持将是一个很好的 学习经历。
很棒的主意!我必须在项目中实现自己的TLS,因为我无法使用任何常见的线程库,如pthread。我没有完全解决您的问题,但分享我的经验可能会有用。
再看看这个link,它可能会有用。
我使用arch_prctl(2)设置FS寄存器。我需要使用吗? set_thread_area(2)不知何故?
答案取决于您实际使用的架构。如果您使用的是x86-64位,则应该使用arch_prctl专用来将FS寄存器设置为您要用作TLS的内存区域(它允许您处理大于4GB的内存区域)。对于x86-32,必须使用set_thread_area,因为它是内核支持的唯一系统调用。
我的实现背后的想法是为每个线程分配一个私有内存区域,并将其地址保存到%GS
寄存器中。这是一个相当简单的方法,但在我的情况下,它运作得很好。每次要访问线程的私有区域时,只需要将%GS
中保存的值和标识内存位置的偏移量用作基址。我通常为每个线程分配一个内存页面(4096),然后将它分成8个字节的块。所以,我为每个线程提供了512个私有内存插槽,可以像索引范围从0到511的数组一样访问。
这是我使用的代码:
#
define _GNU_SOURCE 1
#include "tls.h"
#include <asm/ldt.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <asm/prctl.h>
#include <sys/syscall.h>
#include <unistd.h>
void * install_tls() {
void *addr = mmap(0, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (syscall(SYS_arch_prctl,ARCH_SET_GS, addr) < 0)
return NULL;
return addr;
}
void freeTLS() {
void *addr;
syscall(SYS_arch_prctl,ARCH_GET_GS, &addr);
munmap(addr, 4096);
}
bool set_tls_value(int idx, unsigned long val) {
if (idx < 0 || idx >= 4096/8) {
return false;
}
asm volatile(
"movq %0, %%gs:(%1)\n"
:
: "q"((void *)val), "q"(8ll * idx));
return true;
}
unsigned long get_tls_value(int idx) {
long long rc;
if (idx < 0 || idx >= 4096/8) {
return 0;
}
asm volatile(
"movq %%gs:(%1), %0\n"
: "=q"(rc)
: "q"(8ll * idx));
return rc;
}
这是带有一些宏的标题:
#ifndef TLS_H
#define TLS_H
#include <stdbool.h>
void *install_tls();
void freeTLS();
bool set_tls_value (int, unsigned long);
unsigned long get_tls_value(int );
/*
*macros used to set and retrieve the values
from the tls area
*/
#define TLS_TID 0x0
#define TLS_FD 0x8
#define TLS_MONITORED 0x10
#define set_local_tid(_x) \
set_tls_value(TLS_TID, (unsigned long)_x)
#define set_local_fd(_x) \
set_tls_value(TLS_FD, (unsigned long)_x)
#define set_local_monitored(_x) \
set_tls_value(TLS_MONITORED, (unsigned long)_x)
#define get_local_tid() \
get_tls_value(TLS_TID)
#define get_local_fd() \
get_tls_value(TLS_FD)
#define get_local_monitored() \
get_tls_value(TLS_MONITORED)
#endif /* end of include guard: TLS_H */
每个线程要完成的第一个操作是安装TLS内存区域。一旦TLS被初始化,每个线程就可以开始使用该区域作为私有TLS。