[インデックス 11047] ファイルの概要
このコミットでは、Windows環境におけるGoランタイムのThread Local Storage (TLS) の実装が変更されています。具体的には、TLSスロットとしてArbitraryUserPointer
を使用するように修正され、それに伴いTLSデータへのアクセスオフセットが更新されています。
変更されたファイルは以下の通りです。
src/cmd/6l/pass.c
: Goリンカー (amd64向け) のTLSアクセスオフセットの修正src/cmd/8l/pass.c
: Goリンカー (386向け) のTLSアクセスオフセットの修正src/pkg/runtime/cgo/gcc_windows_386.c
: Windows 386環境におけるCGOランタイムのTLSアクセスオフセットの修正src/pkg/runtime/cgo/gcc_windows_amd64.c
: Windows amd64環境におけるCGOランタイムのTLSアクセスオフセットの修正src/pkg/runtime/mkasmh.sh
: アセンブリヘッダ生成スクリプトにおけるTLSアクセスオフセットの修正src/pkg/runtime/sys_windows_386.s
: Windows 386環境におけるランタイムアセンブリコードのTLSアクセスオフセットの修正src/pkg/runtime/sys_windows_amd64.s
: Windows amd64環境におけるランタイムアセンブリコードのTLSアクセスオフセットの修正
コミット
commit 9569c67a6ba61501f5c8ce58f20139e64100585e
Author: Wei Guangjing <vcc.163@gmail.com>
Date: Mon Jan 9 11:23:07 2012 +1100
windows: use ArbitraryUserPointer as TLS slot
R=hectorchu, alex.brainman
CC=golang-dev
https://golang.org/cl/5519054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9569c67a6ba61501f5c8ce58f20139e64100585e
元コミット内容
windows: use ArbitraryUserPointer as TLS slot
R=hectorchu, alex.brainman
CC=golang-dev
https://golang.org/cl/5519054
変更の背景
このコミットの背景には、WindowsにおけるThread Local Storage (TLS) の効率的かつ安定した利用があります。Goランタイムは、各ゴルーチン(Goにおける軽量スレッド)やスレッド固有のデータを管理するためにTLSのようなメカニズムを必要とします。Windowsでは、TLSは通常、TlsAlloc
、TlsSetValue
、TlsGetValue
といったAPIを通じて管理されますが、これらはシステムコールを伴いオーバーヘッドが発生する可能性があります。
より低レベルで効率的なTLSアクセス方法として、Windowsのx86およびx64アーキテクチャでは、FS
セグメントレジスタ(x86)またはGS
セグメントレジスタ(x64)がThread Information Block (TIB) を指すように設定されています。TIBは、現在のスレッドに関する様々な情報を含むデータ構造です。TIB内には、ユーザーが自由に利用できるポインタ領域がいくつか存在します。このコミットは、その中の一つであるArbitraryUserPointer
をGoランタイムのTLSスロットとして利用することで、TLSアクセスを最適化しようとしています。
以前の実装では、おそらくTIB内の別のオフセットや、より汎用的なTLS APIを使用していたと考えられますが、ArbitraryUserPointer
はOSによって特定の用途に予約されていないため、Goランタイムがスレッド固有のデータを格納するのに適した場所となります。この変更により、TLSデータへのアクセスがより直接的かつ高速になり、GoプログラムのWindows環境でのパフォーマンス向上に寄与することが期待されます。
前提知識の解説
TLS (Thread Local Storage)
TLS(Thread Local Storage)は、マルチスレッドプログラミングにおいて、各スレッドがそれぞれ独立したデータを持つためのメカニズムです。通常、グローバル変数や静的変数はプロセス内のすべてのスレッドで共有されますが、TLSを使用すると、同じ変数名であっても各スレッドが独自の値を保持できます。これは、スレッドセーフなプログラミングや、スレッド固有の状態管理に不可欠です。
Windows TLS
WindowsオペレーティングシステムにおけるTLSは、いくつかの方法で実装されています。
- TLS API:
TlsAlloc
,TlsSetValue
,TlsGetValue
,TlsFree
といった高レベルAPI。これらはシステムコールを介してTLSスロットを管理します。 - PEB/TEB (Process Environment Block / Thread Environment Block): Windowsの各プロセスにはPEBが、各スレッドにはTEB(x86ではTIBとも呼ばれる)が存在します。TEBは、現在のスレッドに関する様々な情報(スタックのベースアドレス、スレッドID、エラー情報など)を格納するデータ構造です。
- FS/GS セグメントレジスタ: x86アーキテクチャでは
FS
セグメントレジスタが、x64アーキテクチャではGS
セグメントレジスタが、現在のスレッドのTEB(TIB)のベースアドレスを指すように設定されています。これにより、アセンブリレベルでTEB内のデータに直接アクセスできます。
ArbitraryUserPointer
ArbitraryUserPointer
は、WindowsのTEB(TIB)構造体内に存在するフィールドの一つです。このポインタは、OSによって特定の用途に予約されておらず、アプリケーション開発者が自由に利用できる領域として提供されています。Goランタイムは、このArbitraryUserPointer
を、GoのゴルーチンやM(マシン、OSスレッド)構造体へのポインタを格納するためのTLSスロットとして利用することで、各スレッドから現在のゴルーチンやMの情報を効率的に取得できるようにします。
Go Runtime
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ゴルーチン(軽量スレッド)のスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、システムコールインターフェースなどが含まれます。Goランタイムは、OSのスレッド(M)上でゴルーチン(G)を実行し、必要に応じてMとGの関連付けを切り替えます。このMとGの情報をスレッド固有に保持するためにTLSが利用されます。
リンカー (6l, 8l)
Goのビルドシステムでは、6l
はamd64アーキテクチャ向けのリンカー、8l
は386アーキテクチャ向けのリンカーです。リンカーは、コンパイルされたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを生成する役割を担います。このコミットでは、リンカーが生成するコード内でTLSデータへのアクセス方法が変更されるため、リンカー自身もその変更を反映するように修正されています。
CGO
CGOは、GoプログラムからC言語のコードを呼び出すためのメカニズムです。Windows環境では、CGOがGoランタイムとCライブラリ間の橋渡しを行います。このコミットでは、CGOランタイムがTLSデータを設定・取得する部分も、新しいTLSスロットのオフセットに合わせて修正されています。
アセンブリ (.s ファイル)
Goランタイムの一部は、パフォーマンスが重要な部分やOSとの低レベルなインタラクションが必要な部分でアセンブリ言語で記述されています。src/pkg/runtime/sys_windows_386.s
とsrc/pkg/runtime/sys_windows_amd64.s
は、それぞれWindows 386およびamd64環境におけるシステムコールやスレッド管理などの低レベル処理を実装しています。これらのファイルでは、FS
やGS
セグメントレジスタを介したTLSデータへの直接的なアクセスが行われるため、オフセットの変更が直接影響します。
mkasmh.sh
mkasmh.sh
は、Goランタイムのアセンブリコードで使用されるマクロや定数を生成するためのシェルスクリプトです。このスクリプトは、get_tls
のようなTLSアクセスに関連するマクロを定義しており、このコミットでは、新しいTLSオフセットに合わせてこれらのマクロの定義が更新されています。
技術的詳細
このコミットの核心は、WindowsのTEB(Thread Environment Block)内のArbitraryUserPointer
フィールドをGoランタイムのTLSスロットとして利用することです。
WindowsのTEB構造体は、アーキテクチャによってそのレイアウトが異なります。
- x86 (32-bit):
FS
セグメントレジスタがTEBのベースアドレスを指します。以前は0x2C(FS)
というオフセットが使用されていましたが、これはTEB内のTlsSlots
配列の一部を指していた可能性があります。このコミットでは、0x14(FS)
に変更されています。0x14
はTEB構造体内のArbitraryUserPointer
フィールドのオフセットに相当します。 - x64 (64-bit):
GS
セグメントレジスタがTEBのベースアドレスを指します。以前は0x58(GS)
というオフセットが使用されていましたが、これも同様にTEB内の別の領域を指していた可能性があります。このコミットでは、0x28(GS)
に変更されています。0x28
はTEB構造体内のArbitraryUserPointer
フィールドのオフセットに相当します。
これらのオフセット変更は、Goランタイムが各OSスレッド(M)に紐付けられたGoのg
(ゴルーチン)およびm
(OSスレッド)構造体へのポインタを格納するために、ArbitraryUserPointer
を専用のTLSスロットとして使用することを意味します。
具体的な変更点としては、以下のファイルでオフセット値が更新されています。
- リンカー (
src/cmd/6l/pass.c
,src/cmd/8l/pass.c
):- リンカーは、Goのソースコードから生成されたアセンブリコードをパッチ適用する際に、TLSアクセス命令のオフセットを修正します。例えば、
n(GS)
のようなTLSアクセスを、MOVL 0x28(GS), reg
のような命令に変換し、そのレジスタを介してTLSデータにアクセスするように変更します。これにより、Goのコンパイラが生成するコードが、新しいTLSスロットを利用できるようになります。
- リンカーは、Goのソースコードから生成されたアセンブリコードをパッチ適用する際に、TLSアクセス命令のオフセットを修正します。例えば、
- CGOランタイム (
src/pkg/runtime/cgo/gcc_windows_386.c
,src/pkg/runtime/cgo/gcc_windows_amd64.c
):- CGOは、GoとCのコード間でスレッドコンテキストを切り替える際に、TLSデータを正しく設定・取得する必要があります。これらのファイルでは、インラインアセンブリを使用して
FS
またはGS
レジスタを介してTLSスロットに値を書き込んだり読み込んだりする部分のオフセットが変更されています。
- CGOは、GoとCのコード間でスレッドコンテキストを切り替える際に、TLSデータを正しく設定・取得する必要があります。これらのファイルでは、インラインアセンブリを使用して
- アセンブリコード (
src/pkg/runtime/sys_windows_386.s
,src/pkg/runtime/sys_windows_amd64.s
):- Goランタイムの低レベルなスレッド管理やコンテキスト切り替えを行うアセンブリコードでは、
PUSHL/PUSHQ
やMOVL/MOVQ
命令で直接FS:0x14
やGS:0x28
といったオフセットが使用されています。これらのオフセットは、スレッドの初期化時やコンテキスト切り替え時に、Goのg
やm
構造体へのポインタをTLSに保存したり、そこから読み出したりするために使われます。
- Goランタイムの低レベルなスレッド管理やコンテキスト切り替えを行うアセンブリコードでは、
- アセンブリヘッダ生成スクリプト (
src/pkg/runtime/mkasmh.sh
):- このスクリプトは、アセンブリコードで共通して使用される
get_tls
マクロを定義しています。このマクロは、TLSスロットから現在のスレッドのTLSベースアドレスを取得するためのもので、その定義内のオフセットが新しい値に更新されています。
- このスクリプトは、アセンブリコードで共通して使用される
この変更により、GoランタイムはWindows環境でより効率的にスレッド固有のデータにアクセスできるようになり、特にCGOを使用するシナリオや、多数のゴルーチンが動作する環境でのパフォーマンス改善に貢献します。
コアとなるコードの変更箇所
src/cmd/6l/pass.c
(amd64リンカー)
--- a/src/cmd/6l/pass.c
+++ b/src/cmd/6l/pass.c
@@ -276,7 +276,7 @@ patch(void)\
// Convert
// op n(GS), reg
// to
- // MOVL 0x58(GS), reg
+ // MOVL 0x28(GS), reg
// op n(reg), reg
// The purpose of this patch is to fix some accesses
// to extern register variables (TLS) on Windows, as
@@ -291,7 +291,7 @@ patch(void)\
q->as = p->as;
p->as = AMOVQ;
p->from.type = D_INDIR+D_GS;
- p->from.offset = 0x58;
+ p->from.offset = 0x28;
}
}
if(HEADTYPE == Hlinux || HEADTYPE == Hfreebsd
@@ -428,11 +428,11 @@ dostkoff(void)\
p->from.offset = tlsoffset+0;
p->to.type = D_CX;
if(HEADTYPE == Hwindows) {
- // movq %gs:0x58, %rcx
+ // movq %gs:0x28, %rcx
// movq (%rcx), %rcx
p->as = AMOVQ;
p->from.type = D_INDIR+D_GS;
- p->from.offset = 0x58;
+ p->from.offset = 0x28;
p->to.type = D_CX;
src/pkg/runtime/cgo/gcc_windows_amd64.c
(amd64 CGOランタイム)
--- a/src/pkg/runtime/cgo/gcc_windows_amd64.c
+++ b/src/pkg/runtime/cgo/gcc_windows_amd64.c
@@ -45,8 +45,8 @@ threadentry(void *v)\
*/
tls0 = (void*)LocalAlloc(LPTR, 64);
asm volatile (
- "movq %0, %%gs:0x58\\n" // MOVL tls0, 0x58(GS)
- "movq %%gs:0x58, %%rax\\n" // MOVQ 0x58(GS), tmp
+ "movq %0, %%gs:0x28\\n" // MOVL tls0, 0x28(GS)
+ "movq %%gs:0x28, %%rax\\n" // MOVQ 0x28(GS), tmp
"movq %1, 0(%%rax)\\n" // MOVQ g, 0(GS)
"movq %2, 8(%%rax)\\n" // MOVQ m, 8(GS)
:: "r"(tls0), "r"(ts.g), "r"(ts.m) : "%rax"
src/pkg/runtime/sys_windows_amd64.s
(amd64アセンブリ)
--- a/src/pkg/runtime/sys_windows_amd64.s
+++ b/src/pkg/runtime/sys_windows_amd64.s
@@ -121,7 +121,7 @@ TEXT runtime·externalthreadhandler(SB),7,$0
PUSHQ BX
PUSHQ SI
PUSHQ DI
- PUSHQ 0x58(GS)
+ PUSHQ 0x28(GS)
MOVQ SP, DX
// setup dummy m, g
@@ -131,7 +131,7 @@ TEXT runtime·externalthreadhandler(SB),7,$0
CALL runtime·memclr(SB) // smashes AX,BX,CX
LEAQ m_tls(SP), CX
- MOVQ CX, 0x58(GS)
+ MOVQ CX, 0x28(GS)
MOVQ SP, m(CX)
MOVQ SP, BX
SUBQ $g_end, SP // space for G
@@ -152,7 +152,7 @@ TEXT runtime·externalthreadhandler(SB),7,$0
get_tls(CX)
MOVQ g(CX), CX
MOVQ g_stackbase(CX), SP
- POPQ 0x58(GS)
+ POPQ 0x28(GS)
POPQ DI
POPQ SI
POPQ BX
@@ -254,7 +254,7 @@ TEXT runtime·tstart_stdcall(SB),7,$0
// Set up tls.
LEAQ m_tls(CX), SI
- MOVQ SI, 0x58(GS)
+ MOVQ SI, 0x28(GS)
MOVQ CX, m(SI)
MOVQ DX, g(SI)
@@ -276,5 +276,5 @@ TEXT runtime·notok(SB),7,$0
// set tls base to DI
TEXT runtime·settls(SB),7,$0
CALL runtime·setstacklimits(SB)
- MOVQ DI, 0x58(GS)
+ MOVQ DI, 0x28(GS)
RET
コアとなるコードの解説
上記のコード変更は、Windows環境におけるGoランタイムがTLSデータを扱う際のオフセットを、ArbitraryUserPointer
が指す領域に合わせるためのものです。
-
リンカー (
src/cmd/6l/pass.c
,src/cmd/8l/pass.c
):- これらのファイルでは、Goのコンパイラが生成したコード内でTLS変数へのアクセス(例:
n(GS)
)を検出した場合に、それをWindowsのTLSメカニズムに適合するように変換する処理が行われます。具体的には、GS
(amd64)またはFS
(386)セグメントレジスタからのオフセットを、以前の0x58
(amd64)や0x2C
(386)から、それぞれ0x28
や0x14
に変更しています。これにより、リンカーが生成する最終的な実行可能ファイルが、ArbitraryUserPointer
を介してTLSデータにアクセスするようになります。
- これらのファイルでは、Goのコンパイラが生成したコード内でTLS変数へのアクセス(例:
-
CGOランタイム (
src/pkg/runtime/cgo/gcc_windows_amd64.c
,src/pkg/runtime/cgo/gcc_windows_386.c
):threadentry
関数は、CGOが新しいスレッドを開始する際に呼び出されるエントリポイントです。この関数内で、tls0
というポインタ(Goのg
とm
構造体へのポインタを格納する領域)が確保され、そのアドレスがGS
(amd64)またはFS
(386)セグメントレジスタの特定のオフセットに書き込まれます。このコミットでは、その書き込み先のオフセットが0x58
から0x28
(amd64)または0x2C
から0x14
(386)に変更されています。これにより、CGOがGoランタイムのスレッド固有データを正しく設定できるようになります。
-
アセンブリコード (
src/pkg/runtime/sys_windows_amd64.s
,src/pkg/runtime/sys_windows_386.s
):- これらのアセンブリファイルには、Goランタイムの低レベルなスレッド管理やコンテキスト切り替えに関連するコードが含まれています。例えば、
runtime·externalthreadhandler
関数は、外部からGoランタイムにコールバックされるスレッドのエントリポイントです。この関数や他の関連する関数内で、GS
またはFS
セグメントレジスタからのオフセットを使用して、Goのg
(ゴルーチン)やm
(OSスレッド)構造体へのポインタをTLSに保存したり、そこから読み出したりする操作が行われます。これらのオフセットが、ArbitraryUserPointer
のオフセットである0x28
(amd64)や0x14
(386)に統一されています。これにより、Goランタイムがスレッド固有のコンテキストを効率的かつ正確に管理できるようになります。
- これらのアセンブリファイルには、Goランタイムの低レベルなスレッド管理やコンテキスト切り替えに関連するコードが含まれています。例えば、
これらの変更は、GoランタイムがWindowsの低レベルなスレッドメカニズムとより密接に連携し、TLSを介したスレッド固有データへのアクセスを最適化するための重要なステップです。
関連リンク
- Go Change-Id:
5519054
(Gerrit Code Review): https://golang.org/cl/5519054
参考にした情報源リンク
- Windows Thread Information Block (TIB) / TEB Structure:
- x86/x64 Segment Registers (FS/GS):
- Thread Local Storage (TLS) on Windows:
- Go Runtime Source Code (for context on
g
andm
structs): - Go Linker Source Code (for context on
6l
and8l
): - CGO Source Code:
ArbitraryUserPointer
in TEB:- https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/teb/index.htm (Geoff Chappell's documentation on Windows internals)
- https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/the-thread-environment-block-teb
I have completed the request.
# [インデックス 11047] ファイルの概要
このコミットでは、Windows環境におけるGoランタイムのThread Local Storage (TLS) の実装が変更されています。具体的には、TLSスロットとして`ArbitraryUserPointer`を使用するように修正され、それに伴いTLSデータへのアクセスオフセットが更新されています。
変更されたファイルは以下の通りです。
- `src/cmd/6l/pass.c`: Goリンカー (amd64向け) のTLSアクセスオフセットの修正
- `src/cmd/8l/pass.c`: Goリンカー (386向け) のTLSアクセスオフセットの修正
- `src/pkg/runtime/cgo/gcc_windows_386.c`: Windows 386環境におけるCGOランタイムのTLSアクセスオフセットの修正
- `src/pkg/runtime/cgo/gcc_windows_amd64.c`: Windows amd64環境におけるCGOランタイムのTLSアクセスオフセットの修正
- `src/pkg/runtime/mkasmh.sh`: アセンブリヘッダ生成スクリプトにおけるTLSアクセスオフセットの修正
- `src/pkg/runtime/sys_windows_386.s`: Windows 386環境におけるランタイムアセンブリコードのTLSアクセスオフセットの修正
- `src/pkg/runtime/sys_windows_amd64.s`: Windows amd64環境におけるランタイムアセンブリコードのTLSアクセスオフセットの修正
## コミット
commit 9569c67a6ba61501f5c8ce58f20139e64100585e Author: Wei Guangjing vcc.163@gmail.com Date: Mon Jan 9 11:23:07 2012 +1100
windows: use ArbitraryUserPointer as TLS slot
R=hectorchu, alex.brainman
CC=golang-dev
https://golang.org/cl/5519054
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/9569c67a6ba61501f5c8ce58f20139e64100585e](https://github.com/golang/go/commit/9569c67a6ba61501f5c8ce58f20139e64100585e)
## 元コミット内容
windows: use ArbitraryUserPointer as TLS slot
R=hectorchu, alex.brainman CC=golang-dev https://golang.org/cl/5519054
## 変更の背景
このコミットの背景には、WindowsにおけるThread Local Storage (TLS) の効率的かつ安定した利用があります。Goランタイムは、各ゴルーチン(Goにおける軽量スレッド)やスレッド固有のデータを管理するためにTLSのようなメカニズムを必要とします。Windowsでは、TLSは通常、`TlsAlloc`、`TlsSetValue`、`TlsGetValue`といったAPIを通じて管理されますが、これらはシステムコールを伴いオーバーヘッドが発生する可能性があります。
より低レベルで効率的なTLSアクセス方法として、Windowsのx86およびx64アーキテクチャでは、`FS`セグメントレジスタ(x86)または`GS`セグメントレジスタ(x64)がThread Information Block (TIB) を指すように設定されています。TIBは、現在のスレッドに関する様々な情報を含むデータ構造です。TIB内には、ユーザーが自由に利用できるポインタ領域がいくつか存在します。このコミットは、その中の一つである`ArbitraryUserPointer`をGoランタイムのTLSスロットとして利用することで、TLSアクセスを最適化しようとしています。
以前の実装では、おそらくTIB内の別のオフセットや、より汎用的なTLS APIを使用していたと考えられますが、`ArbitraryUserPointer`はOSによって特定の用途に予約されていないため、Goランタイムがスレッド固有のデータを格納するのに適した場所となります。この変更により、TLSデータへのアクセスがより直接的かつ高速になり、GoプログラムのWindows環境でのパフォーマンス向上に寄与することが期待されます。
## 前提知識の解説
### TLS (Thread Local Storage)
TLS(Thread Local Storage)は、マルチスレッドプログラミングにおいて、各スレッドがそれぞれ独立したデータを持つためのメカニズムです。通常、グローバル変数や静的変数はプロセス内のすべてのスレッドで共有されますが、TLSを使用すると、同じ変数名であっても各スレッドが独自の値を保持できます。これは、スレッドセーフなプログラミングや、スレッド固有の状態管理に不可欠です。
### Windows TLS
WindowsオペレーティングシステムにおけるTLSは、いくつかの方法で実装されています。
1. **TLS API**: `TlsAlloc`, `TlsSetValue`, `TlsGetValue`, `TlsFree` といった高レベルAPI。これらはシステムコールを介してTLSスロットを管理します。
2. **PEB/TEB (Process Environment Block / Thread Environment Block)**: Windowsの各プロセスにはPEBが、各スレッドにはTEB(x86ではTIBとも呼ばれる)が存在します。TEBは、現在のスレッドに関する様々な情報(スタックのベースアドレス、スレッドID、エラー情報など)を格納するデータ構造です。
3. **FS/GS セグメントレジスタ**: x86アーキテクチャでは`FS`セグメントレジスタが、x64アーキテクチャでは`GS`セグメントレジスタが、現在のスレッドのTEB(TIB)のベースアドレスを指すように設定されています。これにより、アセンブリレベルでTEB内のデータに直接アクセスできます。
### ArbitraryUserPointer
`ArbitraryUserPointer`は、WindowsのTEB(TIB)構造体内に存在するフィールドの一つです。このポインタは、OSによって特定の用途に予約されておらず、アプリケーション開発者が自由に利用できる領域として提供されています。Goランタイムは、この`ArbitraryUserPointer`を、GoのゴルーチンやM(マシン、OSスレッド)構造体へのポインタを格納するためのTLSスロットとして利用することで、各スレッドから現在のゴルーチンやMの情報を効率的に取得できるようにします。
### Go Runtime
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ゴルーチン(軽量スレッド)のスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、システムコールインターフェースなどが含まれます。Goランタイムは、OSのスレッド(M)上でゴルーチン(G)を実行し、必要に応じてMとGの関連付けを切り替えます。このMとGの情報をスレッド固有に保持するためにTLSが利用されます。
### リンカー (6l, 8l)
Goのビルドシステムでは、`6l`はamd64アーキテクチャ向けのリンカー、`8l`は386アーキテクチャ向けのリンカーです。リンカーは、コンパイルされたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを生成する役割を担います。このコミットでは、リンカーが生成するコード内でTLSデータへのアクセス方法が変更されるため、リンカー自身もその変更を反映するように修正されています。
### CGO
CGOは、GoプログラムからC言語のコードを呼び出すためのメカニズムです。Windows環境では、CGOがGoランタイムとCライブラリ間の橋渡しを行います。このコミットでは、CGOランタイムがTLSデータを設定・取得する部分も、新しいTLSスロットのオフセットに合わせて修正されています。
### アセンブリ (.s ファイル)
Goランタイムの一部は、パフォーマンスが重要な部分やOSとの低レベルなインタラクションが必要な部分でアセンブリ言語で記述されています。`src/pkg/runtime/sys_windows_386.s`と`src/pkg/runtime/sys_windows_amd64.s`は、それぞれWindows 386およびamd64環境におけるシステムコールやスレッド管理などの低レベル処理を実装しています。これらのファイルでは、`FS`や`GS`セグメントレジスタを介したTLSデータへの直接的なアクセスが行われるため、オフセットの変更が直接影響します。
### mkasmh.sh
`mkasmh.sh`は、Goランタイムのアセンブリコードで使用されるマクロや定数を生成するためのシェルスクリプトです。このスクリプトは、`get_tls`のようなTLSアクセスに関連するマクロを定義しており、このコミットでは、新しいTLSオフセットに合わせてこれらのマクロの定義が更新されています。
## 技術的詳細
このコミットの核心は、WindowsのTEB(Thread Environment Block)内の`ArbitraryUserPointer`フィールドをGoランタイムのTLSスロットとして利用することです。
WindowsのTEB構造体は、アーキテクチャによってそのレイアウトが異なります。
- **x86 (32-bit)**: `FS`セグメントレジスタがTEBのベースアドレスを指します。以前は`0x2C(FS)`というオフセットが使用されていましたが、これはTEB内の`TlsSlots`配列の一部を指していた可能性があります。このコミットでは、`0x14(FS)`に変更されています。`0x14`はTEB構造体内の`ArbitraryUserPointer`フィールドのオフセットに相当します。
- **x64 (64-bit)**: `GS`セグメントレジスタがTEBのベースアドレスを指します。以前は`0x58(GS)`というオフセットが使用されていましたが、これも同様にTEB内の別の領域を指していた可能性があります。このコミットでは、`0x28(GS)`に変更されています。`0x28`はTEB構造体内の`ArbitraryUserPointer`フィールドのオフセットに相当します。
これらのオフセット変更は、Goランタイムが各OSスレッド(M)に紐付けられたGoの`g`(ゴルーチン)および`m`(OSスレッド)構造体へのポインタを格納するために、`ArbitraryUserPointer`を専用のTLSスロットとして使用することを意味します。
具体的な変更点としては、以下のファイルでオフセット値が更新されています。
- **リンカー (`src/cmd/6l/pass.c`, `src/cmd/8l/pass.c`)**:
- リンカーは、Goのソースコードから生成されたアセンブリコードをパッチ適用する際に、TLSアクセス命令のオフセットを修正します。例えば、`n(GS)`のようなTLSアクセスを、`MOVL 0x28(GS), reg`のような命令に変換し、そのレジスタを介してTLSデータにアクセスするように変更します。これにより、Goのコンパイラが生成するコードが、新しいTLSスロットを利用できるようになります。
- **CGOランタイム (`src/pkg/runtime/cgo/gcc_windows_386.c`, `src/pkg/runtime/cgo/gcc_windows_amd64.c`)**:
- CGOは、GoとCのコード間でスレッドコンテキストを切り替える際に、TLSデータを正しく設定・取得する必要があります。これらのファイルでは、インラインアセンブリを使用して`FS`または`GS`レジスタを介してTLSスロットに値を書き込んだり読み込んだりする部分のオフセットが変更されています。
- **アセンブリコード (`src/pkg/runtime/sys_windows_386.s`, `src/pkg/runtime/sys_windows_amd64.s`)**:
- Goランタイムの低レベルなスレッド管理やコンテキスト切り替えを行うアセンブリコードでは、`PUSHL/PUSHQ`や`MOVL/MOVQ`命令で直接`FS:0x14`や`GS:0x28`といったオフセットが使用されています。これらのオフセットは、スレッドの初期化時やコンテキスト切り替え時に、Goの`g`や`m`構造体へのポインタをTLSに保存したり、そこから読み出したりするために使われます。
- **アセンブリヘッダ生成スクリプト (`src/pkg/runtime/mkasmh.sh`)**:
- このスクリプトは、アセンブリコードで共通して使用される`get_tls`マクロを定義しています。このマクロは、TLSスロットから現在のスレッドのTLSベースアドレスを取得するためのもので、その定義内のオフセットが新しい値に更新されています。
これらの変更は、GoランタイムがWindowsの低レベルなスレッドメカニズムとより密接に連携し、TLSを介したスレッド固有データへのアクセスを最適化するための重要なステップです。
## コアとなるコードの変更箇所
### `src/cmd/6l/pass.c` (amd64リンカー)
```diff
--- a/src/cmd/6l/pass.c
+++ b/src/cmd/6l/pass.c
@@ -276,7 +276,7 @@ patch(void)\
// Convert
// op n(GS), reg
// to
- // MOVL 0x58(GS), reg
+ // MOVL 0x28(GS), reg
// op n(reg), reg
// The purpose of this patch is to fix some accesses
// to extern register variables (TLS) on Windows, as
@@ -291,7 +291,7 @@ patch(void)\
q->as = p->as;
p->as = AMOVQ;
p->from.type = D_INDIR+D_GS;
- p->from.offset = 0x58;
+ p->from.offset = 0x28;
}
}
if(HEADTYPE == Hlinux || HEADTYPE == Hfreebsd
@@ -428,11 +428,11 @@ dostkoff(void)\
p->from.offset = tlsoffset+0;
p->to.type = D_CX;
if(HEADTYPE == Hwindows) {
- // movq %gs:0x58, %rcx
+ // movq %gs:0x28, %rcx
// movq (%rcx), %rcx
p->as = AMOVQ;
p->from.type = D_INDIR+D_GS;
- p->from.offset = 0x58;
+ p->from.offset = 0x28;
p->to.type = D_CX;
src/pkg/runtime/cgo/gcc_windows_amd64.c
(amd64 CGOランタイム)
--- a/src/pkg/runtime/cgo/gcc_windows_amd64.c
+++ b/src/pkg/runtime/cgo/gcc_windows_amd64.c
@@ -45,8 +45,8 @@ threadentry(void *v)\
*/
tls0 = (void*)LocalAlloc(LPTR, 64);
asm volatile (
- "movq %0, %%gs:0x58\\n" // MOVL tls0, 0x58(GS)
- "movq %%gs:0x58, %%rax\\n" // MOVQ 0x58(GS), tmp
+ "movq %0, %%gs:0x28\\n" // MOVL tls0, 0x28(GS)
+ "movq %%gs:0x28, %%rax\\n" // MOVQ 0x28(GS), tmp
"movq %1, 0(%%rax)\\n" // MOVQ g, 0(GS)
"movq %2, 8(%%rax)\\n" // MOVQ m, 8(GS)
:: "r"(tls0), "r"(ts.g), "r"(ts.m) : "%rax"
src/pkg/runtime/sys_windows_amd64.s
(amd64アセンブリ)
--- a/src/pkg/runtime/sys_windows_amd64.s
+++ b/src/pkg/runtime/sys_windows_amd64.s
@@ -121,7 +121,7 @@ TEXT runtime·externalthreadhandler(SB),7,$0
PUSHQ BX
PUSHQ SI
PUSHQ DI
- PUSHQ 0x58(GS)
+ PUSHQ 0x28(GS)
MOVQ SP, DX
// setup dummy m, g
@@ -131,7 +131,7 @@ TEXT runtime·externalthreadhandler(SB),7,$0
CALL runtime·memclr(SB) // smashes AX,BX,CX
LEAQ m_tls(SP), CX
- MOVQ CX, 0x58(GS)
+ MOVQ CX, 0x28(GS)
MOVQ SP, m(CX)
MOVQ SP, BX
SUBQ $g_end, SP // space for G
@@ -152,7 +152,7 @@ TEXT runtime·externalthreadhandler(SB),7,$0
get_tls(CX)
MOVQ g(CX), CX
MOVQ g_stackbase(CX), SP
- POPQ 0x58(GS)
+ POPQ 0x28(GS)
POPQ DI
POPQ SI
POPQ BX
@@ -254,7 +254,7 @@ TEXT runtime·tstart_stdcall(SB),7,$0
// Set up tls.
LEAQ m_tls(CX), SI
- MOVQ SI, 0x58(GS)
+ MOVQ SI, 0x28(GS)
MOVQ CX, m(SI)
MOVQ DX, g(SI)
@@ -276,5 +276,5 @@ TEXT runtime·notok(SB),7,$0
// set tls base to DI
TEXT runtime·settls(SB),7,$0
CALL runtime·setstacklimits(SB)
- MOVQ DI, 0x58(GS)
+ MOVQ DI, 0x28(GS)
RET
コアとなるコードの解説
上記のコード変更は、Windows環境におけるGoランタイムがTLSデータを扱う際のオフセットを、ArbitraryUserPointer
が指す領域に合わせるためのものです。
-
リンカー (
src/cmd/6l/pass.c
,src/cmd/8l/pass.c
):- これらのファイルでは、Goのコンパイラが生成したコード内でTLS変数へのアクセス(例:
n(GS)
)を検出した場合に、それをWindowsのTLSメカニズムに適合するように変換する処理が行われます。具体的には、GS
(amd64)またはFS
(386)セグメントレジスタからのオフセットを、以前の0x58
(amd64)や0x2C
(386)から、それぞれ0x28
や0x14
に変更しています。これにより、リンカーが生成する最終的な実行可能ファイルが、ArbitraryUserPointer
を介してTLSデータにアクセスするようになります。
- これらのファイルでは、Goのコンパイラが生成したコード内でTLS変数へのアクセス(例:
-
CGOランタイム (
src/pkg/runtime/cgo/gcc_windows_amd64.c
,src/pkg/runtime/cgo/gcc_windows_386.c
):threadentry
関数は、CGOが新しいスレッドを開始する際に呼び出されるエントリポイントです。この関数内で、tls0
というポインタ(Goのg
とm
構造体へのポインタを格納する領域)が確保され、そのアドレスがGS
(amd64)またはFS
(386)セグメントレジスタの特定のオフセットに書き込まれます。このコミットでは、その書き込み先のオフセットが0x58
から0x28
(amd64)または0x2C
から0x14
(386)に変更されています。これにより、CGOがGoランタイムのスレッド固有データを正しく設定できるようになります。
-
アセンブリコード (
src/pkg/runtime/sys_windows_amd64.s
,src/pkg/runtime/sys_windows_386.s
):- これらのアセンブリファイルには、Goランタイムの低レベルなスレッド管理やコンテキスト切り替えに関連するコードが含まれています。例えば、
runtime·externalthreadhandler
関数は、外部からGoランタイムにコールバックされるスレッドのエントリポイントです。この関数や他の関連する関数内で、GS
またはFS
セグメントレジスタからのオフセットを使用して、Goのg
(ゴルーチン)やm
(OSスレッド)構造体へのポインタをTLSに保存したり、そこから読み出したりする操作が行われます。これらのオフセットが、ArbitraryUserPointer
のオフセットである0x28
(amd64)や0x14
(386)に統一されています。これにより、Goランタイムがスレッド固有のコンテキストを効率的かつ正確に管理できるようになります。
- これらのアセンブリファイルには、Goランタイムの低レベルなスレッド管理やコンテキスト切り替えに関連するコードが含まれています。例えば、
これらの変更は、GoランタイムがWindowsの低レベルなスレッドメカニズムとより密接に連携し、TLSを介したスレッド固有データへのアクセスを最適化するための重要なステップです。
関連リンク
- Go Change-Id:
5519054
(Gerrit Code Review): https://golang.org/cl/5519054
参考にした情報源リンク
- Windows Thread Information Block (TIB) / TEB Structure:
- x86/x64 Segment Registers (FS/GS):
- Thread Local Storage (TLS) on Windows:
- Go Runtime Source Code (for context on
g
andm
structs): - Go Linker Source Code (for context on
6l
and8l
): - CGO Source Code:
ArbitraryUserPointer
in TEB:- https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/teb/index.htm (Geoff Chappell's documentation on Windows internals)
- https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/the-thread-environment-block-teb