8

私はlibc非常に小規模で静的にリンクされたプログラム用に の小さなサブセットを実装していますが、TLS サポートを追加することは良い学習体験になると考えました。Ulrich Drepper の TLS ドキュメントを参照として使用します。

これを試すために、2 つの文字列を設定しました。

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

ただし、これは機能しません。に 20 バイトを書き込むことで、TLS 初期化イメージを正しく見つけられることを確認しましtls_imagestdout。これにより、TCB および/または TLS 領域を間違って配置したか、そうでなければ ABI に準拠していないと信じるようになります。

  • を使用して FS レジスタを設定しarch_prctl(2)ます。どういうわけか使用する必要がありますset_thread_area(2)か?
  • 私は持っていませんdtv。静的にリンクしているので、これは必要ないと思います。

私が間違っていることについてのアイデアはありますか?どうもありがとう!

4

1 に答える 1

3

私は、非常に小規模で静的にリンクされたプログラム用に libc の小さなサブセットを実装していますが、TLS サポートを追加することは良い学習体験になると考えました。

素晴らしいアイデア!pthread のような一般的なスレッド ライブラリを使用できなかったため、プロジェクトに独自の TLS を実装する必要がありました。あなたの問題に対する完全な解決策はありませんが、私の経験を共有することは役に立ちます.

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 として使用できるようになります。

于 2013-10-07T21:50:31.870 に答える