Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16906] ファイルの概要

このコミットは、GoランタイムにおけるOpenBSDビルドの問題を修正するものです。具体的には、runtime·nanotime関数のスタックフレームサイズが過剰に確保されていたために発生する、スタックオーバーフローの可能性を修正しています。nanotime関数が実際に使用するスタック領域が想定よりも少ないことを特定し、そのサイズを適切に調整することで、notetsleepのようなnosplit関数からの呼び出し時に発生しうるスタック関連の問題を解消しています。

コミット

commit ddc01d5b06b7a5206bd100e99ce72bc888da3b05
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Jul 29 22:58:58 2013 +0400

    runtime: fix openbsd build
    notetsleep: nosplit stack overflow
            120     assumed on entry to notetsleep
            96      after notetsleep uses 24
            88      on entry to runtime.semasleep
            32      after runtime.semasleep uses 56
            24      on entry to runtime.nanotime
            -8      after runtime.nanotime uses 32
    
    Nanotime seems to be using only 24 bytes of stack space.
    Unless I am missing something.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12041044
---\n src/pkg/runtime/sys_openbsd_amd64.s | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/src/pkg/runtime/sys_openbsd_amd64.s b/src/pkg/runtime/sys_openbsd_amd64.s
index 3cbf0d9343..87e557c8ba 100644
--- a/src/pkg/runtime/sys_openbsd_amd64.s
+++ b/src/pkg/runtime/sys_openbsd_amd64.s
@@ -164,7 +164,7 @@ TEXT time·now(SB), 7, $32
  	MOVL	DX, nsec+8(FP)\n \tRET\n \n-TEXT runtime·nanotime(SB),7,$32
+TEXT runtime·nanotime(SB),7,$24
  	MOVQ\t$0, DI\t\t\t// arg 1 - clock_id\n \tLEAQ\t8(SP), SI\t\t// arg 2 - tp\n \tMOVL\t$232, AX\t\t// sys_clock_gettime\n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/ddc01d5b06b7a5206bd100e99ce72bc888da3b05](https://github.com/golang/go/commit/ddc01d5b06b7a5206bd100e99ce72bc888da3b05)

## 元コミット内容

runtime: fix openbsd build notetsleep: nosplit stack overflow 120 assumed on entry to notetsleep 96 after notetsleep uses 24 88 on entry to runtime.semasleep 32 after runtime.semasleep uses 56 24 on entry to runtime.nanotime -8 after runtime.nanotime uses 32 Nanotime seems to be using only 24 bytes of stack space. Unless I am missing something.

R=golang-dev, rsc CC=golang-dev https://golang.org/cl/12041044


## 変更の背景

このコミットの背景には、GoランタイムがOpenBSD上でビルドされる際に発生していたスタック関連の問題があります。特に、`notetsleep`という関数が`nosplit`属性を持っていることが問題の根源でした。

Goランタイムでは、関数の呼び出し時にスタックの拡張が必要かどうかをチェックし、必要であればスタックを拡張するメカニズム(スタックスプリット)があります。しかし、一部の低レベルなランタイム関数、特にシステムコールを直接呼び出すような関数は、スタックスプリットのチェックを行わないようにマークされます。これを`nosplit`関数と呼びます。`nosplit`関数は、スタックの拡張ができないため、呼び出し時に十分なスタック領域が確保されていることを前提とします。

コミットメッセージのスタック使用量の内訳を見ると、`notetsleep`が呼び出された時点で120バイトのスタックが利用可能であると仮定されています。しかし、`notetsleep`が24バイトを使用した後、`runtime.semasleep`が呼び出される際には88バイトが残っています。さらに`runtime.semasleep`が56バイトを使用した後、`runtime.nanotime`が呼び出される際には32バイトが残っています。問題は、`runtime.nanotime`が32バイトのスタックを必要とすると宣言しているにもかかわらず、実際に使用するのは24バイトであるという点です。この食い違いにより、`nanotime`がスタックを使い果たし、結果としてスタックオーバーフローが発生する可能性がありました。

特に、`nosplit`関数である`notetsleep`から`nanotime`が呼び出される場合、スタックの拡張が行われないため、このスタック使用量の不一致が致命的な問題となり、OpenBSD上でのGoプログラムの安定性に影響を与えていました。このコミットは、`nanotime`が実際に使用するスタックサイズに合わせて宣言を修正することで、このスタックオーバーフローの可能性を排除することを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1.  **Goランタイム (Go Runtime)**: Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ガベージコレクション、スケジューリング(ゴルーチンの管理)、メモリ管理、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルな機能を提供します。Goランタイムのコードの多くはGoで書かれていますが、パフォーマンスが要求される部分やOSとの直接的なインターフェース部分はアセンブリ言語で書かれています。

2.  **スタック (Stack)**: プログラムの実行中に、関数呼び出しの引数、ローカル変数、戻りアドレスなどを一時的に保存するために使用されるメモリ領域です。関数が呼び出されるたびに、その関数のための「スタックフレーム」がスタックにプッシュされ、関数が終了するとポップされます。

3.  **スタックフレーム (Stack Frame)**: 関数が呼び出されたときにスタック上に確保される領域で、その関数の実行に必要な情報(引数、ローカル変数、レジスタの退避領域、戻りアドレスなど)を格納します。

4.  **スタックスプリット (Stack Split)**: Goのゴルーチンは、非常に小さなスタック(通常は数KB)から開始し、必要に応じて自動的にスタックを拡張するメカニズムを持っています。これにより、多数のゴルーチンを効率的に実行できます。関数が呼び出される際、Goコンパイラはスタックの残りの領域が十分であるかをチェックするコード(スタックチェックプリアンブル)を挿入します。もし足りなければ、ランタイムがスタックを拡張します。

5.  **`nosplit`関数**: Goランタイムの特定の関数は、スタックスプリットのチェックを行わないようにマークされます。これは、これらの関数が非常に短く、スタックをほとんど使用しないか、あるいはスタックチェック自体がオーバーヘッドとなるようなクリティカルパスにあるためです。`nosplit`関数は、呼び出し時に十分なスタック領域が確保されていることを前提とするため、スタック使用量の見積もりが非常に重要になります。もし`nosplit`関数が宣言されたスタックサイズを超えて使用しようとすると、スタックオーバーフローが発生します。

6.  **Goアセンブリ言語 (`.s`ファイル)**: Goは、独自の擬似アセンブリ言語を使用しています。これは、特定のCPUアーキテクチャ(例: AMD64)の命令セットにマッピングされますが、Goのツールチェーンによって抽象化された構文を持っています。Goランタイムの低レベルな部分や、特定のパフォーマンス最適化が必要な部分で利用されます。ファイル拡張子は通常`.s`です。

7.  **`TEXT`ディレクティブ**: Goアセンブリ言語において、関数の定義を開始するために使用されるディレクティブです。`TEXT symbol(SB), flags, $framesize`のような形式を取ります。
    *   `symbol(SB)`: 関数のシンボル名。`SB`は「Static Base」レジスタを表し、グローバルシンボルへのオフセットを示します。
    *   `flags`: 関数の特性を示すフラグ(例: `7`は`NOSPLIT`と`RODATA`の組み合わせ)。
    *   `$framesize`: この関数が使用するスタックフレームのサイズ(バイト単位)。この値は、関数がローカル変数やレジスタの退避などに必要なスタック領域の最大値を正確に反映している必要があります。

8.  **`runtime·nanotime`**: Goランタイムが提供する、高精度な時間計測を行うための関数です。通常、OSの提供する`clock_gettime`のようなシステムコールを呼び出して、ナノ秒単位の時間を取得します。

9.  **OpenBSD**: セキュリティとコード品質に重点を置いたUnix系オペレーティングシステムです。Goランタイムは、様々なOS上で動作するように設計されており、それぞれのOSのシステムコールインターフェースに合わせてアセンブリコードが記述されています。

## 技術的詳細

このコミットの技術的な核心は、`runtime·nanotime`関数がOpenBSD/AMD64環境で実際に使用するスタック領域の正確な見積もりと、それに基づくアセンブリコードの修正です。

Goのアセンブリ関数は、`TEXT`ディレクティブの第三引数でその関数が使用するスタックフレームのサイズを宣言します。このサイズは、関数が呼び出し時に確保するローカル変数、引数、レジスタの退避領域などの合計です。Goコンパイラとランタイムは、この宣言されたサイズを基にスタックの健全性を管理します。

元のコードでは、`runtime·nanotime`関数は`$32`、つまり32バイトのスタックフレームを使用すると宣言されていました。しかし、コミットメッセージに示されているスタック使用量の分析によると、`nanotime`は実際に24バイトしかスタックを使用していませんでした。

    24      on entry to runtime.nanotime
    -8      after runtime.nanotime uses 32
この`-8`という値は、`nanotime`が32バイトを消費すると仮定した場合に、スタックが8バイト不足するということを示唆しています。これは、`nanotime`が実際に24バイトしか使わないにもかかわらず、32バイトを要求しているという誤解釈から生じるものです。

`runtime·nanotime`のOpenBSD/AMD64実装を見てみると、以下の操作が行われています。
*   `MOVQ $0, DI`: 第一引数`clock_id`をセット。
*   `LEAQ 8(SP), SI`: 第二引数`tp`(timespec構造体へのポインタ)をセット。`8(SP)`はスタックポインタから8バイトオフセットしたアドレスを指します。これは、`nanotime`が呼び出された時点のスタックフレーム内に、`tp`が指す領域(通常は`timespec`構造体、8バイトの`tv_sec`と8バイトの`tv_nsec`で計16バイト)を確保していることを示唆しています。
*   `MOVL $232, AX`: システムコール番号`232`(OpenBSDの`SYS_clock_gettime`)をセット。
*   `SYSCALL`: システムコールを実行。
*   `MOVL AX, sec+0(FP)`: 戻り値の秒部分をフレームポインタからのオフセットで格納。
*   `MOVL DX, nsec+8(FP)`: 戻り値のナノ秒部分をフレームポインタからのオフセットで格納。
*   `RET`: 関数から戻る。

この一連の操作で、`nanotime`関数自体が直接使用するスタック領域は、`LEAQ 8(SP), SI`で確保される`timespec`構造体分の16バイトと、戻りアドレスやレジスタ退避などのオーバーヘッドを考慮しても、32バイトは過剰であり、24バイトで十分であると判断されたようです。

`nosplit`関数(例: `notetsleep`)から`nanotime`が呼び出される場合、呼び出し元の関数は、呼び出される関数が必要とするスタック領域を事前に確保しておく必要があります。もし`nanotime`が32バイトを要求すると宣言しているのに、実際には24バイトしか使わない場合、呼び出し元は不必要に大きなスタック領域を確保しようとします。しかし、より深刻なのは、この誤った宣言がスタックチェックのロジックに影響を与え、スタックが足りていると誤って判断される可能性があることです。

このコミットは、`runtime·nanotime`のスタックフレームサイズを`$32`から`$24`に修正することで、実際のスタック使用量と宣言されたスタック使用量を一致させ、`nosplit`関数からの呼び出し時に発生しうるスタックオーバーフローのリスクを排除しています。これにより、OpenBSD上でのGoランタイムの安定性が向上します。

## コアとなるコードの変更箇所

変更は`src/pkg/runtime/sys_openbsd_amd64.s`ファイルの一箇所のみです。

```diff
--- a/src/pkg/runtime/sys_openbsd_amd64.s
+++ b/src/pkg/runtime/sys_openbsd_amd64.s
@@ -164,7 +164,7 @@ TEXT time·now(SB), 7, $32
  	MOVL	DX, nsec+8(FP)\n \tRET\n \n-TEXT runtime·nanotime(SB),7,$32
+TEXT runtime·nanotime(SB),7,$24
  	MOVQ\t$0, DI\t\t\t// arg 1 - clock_id\n \tLEAQ\t8(SP), SI\t\t// arg 2 - tp\n \tMOVL\t$232, AX\t\t// sys_clock_gettime\n```

## コアとなるコードの解説

変更された行は以下の通りです。

```assembly
-TEXT runtime·nanotime(SB),7,$32
+TEXT runtime·nanotime(SB),7,$24
  • TEXT: Goアセンブリにおける関数の開始を示すディレクティブです。
  • runtime·nanotime(SB): 定義している関数の名前です。runtimeパッケージのnanotime関数を指します。SBはStatic Baseレジスタを示し、グローバルシンボルへのオフセットを計算するために使用されます。
  • 7: これは関数のフラグです。Goアセンブリでは、フラグはビットマスクで表現されます。7はバイナリで0111であり、以下のフラグの組み合わせを示します。
    • 1 (NOSPLIT): この関数はスタックスプリットチェックを行いません。つまり、スタックが足りなくても自動的に拡張されません。そのため、スタックフレームサイズを正確に宣言することが非常に重要です。
    • 2 (RODATA): この関数は読み取り専用データセクションに配置されます。
    • 4 (DUPOK): このシンボルは重複しても問題ありません。 このコミットの文脈では、NOSPLITフラグが最も重要です。
  • $32 から $24: これがこのコミットの核心的な変更点です。この値は、runtime·nanotime関数が実行時に使用するスタックフレームのサイズをバイト単位で宣言しています。
    • 元の$32は、この関数が最大32バイトのスタック領域を使用すると宣言していました。
    • 新しい$24は、この関数が最大24バイトのスタック領域を使用すると宣言しています。

この変更は、runtime·nanotimeが実際に必要とするスタック領域が32バイトではなく24バイトであることを正確に反映しています。NOSPLIT関数であるため、この正確な宣言は、呼び出し元がスタックを適切に管理し、スタックオーバーフローを防ぐ上で不可欠です。特に、notetsleepのような他のNOSPLIT関数から呼び出される場合に、スタックの過剰な消費や不足による問題を防ぎます。

関連リンク

参考にした情報源リンク

このコミットは、GoランタイムにおけるOpenBSDビルドの問題を修正するものです。具体的には、runtime·nanotime関数のスタックフレームサイズが過剰に確保されていたために発生する、スタックオーバーフローの可能性を修正しています。nanotime関数が実際に使用するスタック領域が想定よりも少ないことを特定し、そのサイズを適切に調整することで、notetsleepのようなnosplit関数からの呼び出し時に発生しうるスタック関連の問題を解消しています。

コミット

commit ddc01d5b06b7a5206bd100e99ce72bc888da3b05
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Jul 29 22:58:58 2013 +0400

    runtime: fix openbsd build
    notetsleep: nosplit stack overflow
            120     assumed on entry to notetsleep
            96      after notetsleep uses 24
            88      on entry to runtime.semasleep
            32      after runtime.semasleep uses 56
            24      on entry to runtime.nanotime
            -8      after runtime.nanotime uses 32
    
    Nanotime seems to be using only 24 bytes of stack space.
    Unless I am missing something.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12041044
---\n src/pkg/runtime/sys_openbsd_amd64.s | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/src/pkg/runtime/sys_openbsd_amd64.s b/src/pkg/runtime/sys_openbsd_amd64.s
index 3cbf0d9343..87e557c8ba 100644
--- a/src/pkg/runtime/sys_openbsd_amd64.s
+++ b/src/pkg/runtime/sys_openbsd_amd64.s
@@ -164,7 +164,7 @@ TEXT time·now(SB), 7, $32
  	MOVL	DX, nsec+8(FP)\n \tRET\n \n-TEXT runtime·nanotime(SB),7,$32
+TEXT runtime·nanotime(SB),7,$24
  	MOVQ\t$0, DI\t\t\t// arg 1 - clock_id\n \tLEAQ\t8(SP), SI\t\t// arg 2 - tp\n \tMOVL\t$232, AX\t\t// sys_clock_gettime\n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/ddc01d5b06b7a5206bd100e99ce72bc888da3b05](https://github.com/golang/go/commit/ddc01d5b06b7a5206bd100e99ce72bc888da3b05)

## 元コミット内容

runtime: fix openbsd build notetsleep: nosplit stack overflow 120 assumed on entry to notetsleep 96 after notetsleep uses 24 88 on entry to runtime.semasleep 32 after runtime.semasleep uses 56 24 on entry to runtime.nanotime -8 after runtime.nanotime uses 32 Nanotime seems to be using only 24 bytes of stack space. Unless I am missing something.

R=golang-dev, rsc CC=golang-dev https://golang.org/cl/12041044


## 変更の背景

このコミットの背景には、GoランタイムがOpenBSD上でビルドされる際に発生していたスタック関連の問題があります。特に、`notetsleep`という関数が`nosplit`属性を持っていることが問題の根源でした。

Goランタイムでは、関数の呼び出し時にスタックの拡張が必要かどうかをチェックし、必要であればスタックを拡張するメカニズム(スタックスプリット)があります。しかし、一部の低レベルなランタイム関数、特にシステムコールを直接呼び出すような関数は、スタックスプリットのチェックを行わないようにマークされます。これを`nosplit`関数と呼びます。`nosplit`関数は、スタックの拡張ができないため、呼び出し時に十分なスタック領域が確保されていることを前提とします。

コミットメッセージのスタック使用量の内訳を見ると、`notetsleep`が呼び出された時点で120バイトのスタックが利用可能であると仮定されています。しかし、`notetsleep`が24バイトを使用した後、`runtime.semasleep`が呼び出される際には88バイトが残っています。さらに`runtime.semasleep`が56バイトを使用した後、`runtime.nanotime`が呼び出される際には32バイトが残っています。問題は、`runtime.nanotime`が32バイトのスタックを必要とすると宣言しているにもかかわらず、実際に使用するのは24バイトであるという点です。この食い違いにより、`nanotime`がスタックを使い果たし、結果としてスタックオーバーフローが発生する可能性がありました。

特に、`nosplit`関数である`notetsleep`から`nanotime`が呼び出される場合、スタックの拡張が行われないため、このスタック使用量の不一致が致命的な問題となり、OpenBSD上でのGoプログラムの安定性に影響を与えていました。このコミットは、`nanotime`が実際に使用するスタックサイズに合わせて宣言を修正することで、このスタックオーバーフローの可能性を排除することを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1.  **Goランタイム (Go Runtime)**: Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ガベージコレクション、スケジューリング(ゴルーチンの管理)、メモリ管理、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルな機能を提供します。Goランタイムのコードの多くはGoで書かれていますが、パフォーマンスが要求される部分やOSとの直接的なインターフェース部分はアセンブリ言語で書かれています。

2.  **スタック (Stack)**: プログラムの実行中に、関数呼び出しの引数、ローカル変数、戻りアドレスなどを一時的に保存するために使用されるメモリ領域です。関数が呼び出されるたびに、その関数のための「スタックフレーム」がスタックにプッシュされ、関数が終了するとポップされます。

3.  **スタックフレーム (Stack Frame)**: 関数が呼び出されたときにスタック上に確保される領域で、その関数の実行に必要な情報(引数、ローカル変数、レジスタの退避領域、戻りアドレスなど)を格納します。

4.  **スタックスプリット (Stack Split)**: Goのゴルーチンは、非常に小さなスタック(通常は数KB)から開始し、必要に応じて自動的にスタックを拡張するメカニズムを持っています。これにより、多数のゴルーチンを効率的に実行できます。関数が呼び出される際、Goコンパイラはスタックの残りの領域が十分であるかをチェックするコード(スタックチェックプリアンブル)を挿入します。もし足りなければ、ランタイムがスタックを拡張します。

5.  **`nosplit`関数**: Goランタイムの特定の関数は、スタックスプリットのチェックを行わないようにマークされます。これは、これらの関数が非常に短く、スタックをほとんど使用しないか、あるいはスタックチェック自体がオーバーヘッドとなるようなクリティカルパスにあるためです。`nosplit`関数は、呼び出し時に十分なスタック領域が確保されていることを前提とするため、スタック使用量の見積もりが非常に重要になります。もし`nosplit`関数が宣言されたスタックサイズを超えて使用しようとすると、スタックオーバーフローが発生します。

6.  **Goアセンブリ言語 (`.s`ファイル)**: Goは、独自の擬似アセンブリ言語を使用しています。これは、特定のCPUアーキテクチャ(例: AMD64)の命令セットにマッピングされますが、Goのツールチェーンによって抽象化された構文を持っています。Goランタイムの低レベルな部分や、特定のパフォーマンス最適化が必要な部分で利用されます。ファイル拡張子は通常`.s`です。

7.  **`TEXT`ディレクティブ**: Goアセンブリ言語において、関数の定義を開始するために使用されるディレクティブです。`TEXT symbol(SB), flags, $framesize`のような形式を取ります。
    *   `symbol(SB)`: 関数のシンボル名。`SB`は「Static Base」レジスタを表し、グローバルシンボルへのオフセットを示します。
    *   `flags`: 関数の特性を示すフラグ(例: `7`は`NOSPLIT`と`RODATA`の組み合わせ)。
    *   `$framesize`: この関数が使用するスタックフレームのサイズ(バイト単位)。この値は、関数がローカル変数やレジスタの退避などに必要なスタック領域の最大値を正確に反映している必要があります。

8.  **`runtime·nanotime`**: Goランタイムが提供する、高精度な時間計測を行うための関数です。通常、OSの提供する`clock_gettime`のようなシステムコールを呼び出して、ナノ秒単位の時間を取得します。

9.  **OpenBSD**: セキュリティとコード品質に重点を置いたUnix系オペレーティングシステムです。Goランタイムは、様々なOS上で動作するように設計されており、それぞれのOSのシステムコールインターフェースに合わせてアセンブリコードが記述されています。

## 技術的詳細

このコミットの技術的な核心は、`runtime·nanotime`関数がOpenBSD/AMD64環境で実際に使用するスタック領域の正確な見積もりと、それに基づくアセンブリコードの修正です。

Goのアセンブリ関数は、`TEXT`ディレクティブの第三引数でその関数が使用するスタックフレームのサイズを宣言します。このサイズは、関数が呼び出し時に確保するローカル変数、引数、レジスタの退避領域などの合計です。Goコンパイラとランタイムは、この宣言されたサイズを基にスタックの健全性を管理します。

元のコードでは、`runtime·nanotime`関数は`$32`、つまり32バイトのスタックフレームを使用すると宣言されていました。しかし、コミットメッセージに示されているスタック使用量の分析によると、`nanotime`は実際に24バイトしかスタックを使用していませんでした。

    24      on entry to runtime.nanotime
    -8      after runtime.nanotime uses 32
この`-8`という値は、`nanotime`が32バイトを消費すると仮定した場合に、スタックが8バイト不足するということを示唆しています。これは、`nanotime`が実際に24バイトしか使わないにもかかわらず、32バイトを要求しているという誤解釈から生じるものです。

`runtime·nanotime`のOpenBSD/AMD64実装を見てみると、以下の操作が行われています。
*   `MOVQ $0, DI`: 第一引数`clock_id`をセット。
*   `LEAQ 8(SP), SI`: 第二引数`tp`(timespec構造体へのポインタ)をセット。`8(SP)`はスタックポインタから8バイトオフセットしたアドレスを指します。これは、`nanotime`が呼び出された時点のスタックフレーム内に、`tp`が指す領域(通常は`timespec`構造体、8バイトの`tv_sec`と8バイトの`tv_nsec`で計16バイト)を確保していることを示唆しています。
*   `MOVL $232, AX`: システムコール番号`232`(OpenBSDの`SYS_clock_gettime`)をセット。
*   `SYSCALL`: システムコールを実行。
*   `MOVL AX, sec+0(FP)`: 戻り値の秒部分をフレームポインタからのオフセットで格納。
*   `MOVL DX, nsec+8(FP)`: 戻り値のナノ秒部分をフレームポインタからのオフセットで格納。
*   `RET`: 関数から戻る。

この一連の操作で、`nanotime`関数自体が直接使用するスタック領域は、`LEAQ 8(SP), SI`で確保される`timespec`構造体分の16バイトと、戻りアドレスやレジスタ退避などのオーバーヘッドを考慮しても、32バイトは過剰であり、24バイトで十分であると判断されたようです。

`nosplit`関数(例: `notetsleep`)から`nanotime`が呼び出される場合、呼び出し元の関数は、呼び出される関数が必要とするスタック領域を事前に確保しておく必要があります。もし`nanotime`が32バイトを要求すると宣言しているのに、実際には24バイトしか使わない場合、呼び出し元は不必要に大きなスタック領域を確保しようとします。しかし、より深刻なのは、この誤った宣言がスタックチェックのロジックに影響を与え、スタックが足りていると誤って判断される可能性があることです。

このコミットは、`runtime·nanotime`のスタックフレームサイズを`$32`から`$24`に修正することで、実際のスタック使用量と宣言されたスタック使用量を一致させ、`nosplit`関数からの呼び出し時に発生しうるスタックオーバーフローのリスクを排除しています。これにより、OpenBSD上でのGoランタイムの安定性が向上します。

## コアとなるコードの変更箇所

変更は`src/pkg/runtime/sys_openbsd_amd64.s`ファイルの一箇所のみです。

```diff
--- a/src/pkg/runtime/sys_openbsd_amd64.s
+++ b/src/pkg/runtime/sys_openbsd_amd64.s
@@ -164,7 +164,7 @@ TEXT time·now(SB), 7, $32
  	MOVL	DX, nsec+8(FP)\n \tRET\n \n-TEXT runtime·nanotime(SB),7,$32
+TEXT runtime·nanotime(SB),7,$24
  	MOVQ\t$0, DI\t\t\t// arg 1 - clock_id\n \tLEAQ\t8(SP), SI\t\t// arg 2 - tp\n \tMOVL\t$232, AX\t\t// sys_clock_gettime\n```

## コアとなるコードの解説

変更された行は以下の通りです。

```assembly
-TEXT runtime·nanotime(SB),7,$32
+TEXT runtime·nanotime(SB),7,$24
  • TEXT: Goアセンブリにおける関数の開始を示すディレクティブです。
  • runtime·nanotime(SB): 定義している関数の名前です。runtimeパッケージのnanotime関数を指します。SBはStatic Baseレジスタを示し、グローバルシンボルへのオフセットを計算するために使用されます。
  • 7: これは関数のフラグです。Goアセンブリでは、フラグはビットマスクで表現されます。7はバイナリで0111であり、以下のフラグの組み合わせを示します。
    • 1 (NOSPLIT): この関数はスタックスプリットチェックを行いません。つまり、スタックが足りなくても自動的に拡張されません。そのため、スタックフレームサイズを正確に宣言することが非常に重要です。
    • 2 (RODATA): この関数は読み取り専用データセクションに配置されます。
    • 4 (DUPOK): このシンボルは重複しても問題ありません。 このコミットの文脈では、NOSPLITフラグが最も重要です。
  • $32 から $24: これがこのコミットの核心的な変更点です。この値は、runtime·nanotime関数が実行時に使用するスタックフレームのサイズをバイト単位で宣言しています。
    • 元の$32は、この関数が最大32バイトのスタック領域を使用すると宣言していました。
    • 新しい$24は、この関数が最大24バイトのスタック領域を使用すると宣言しています。

この変更は、runtime·nanotimeが実際に必要とするスタック領域が32バイトではなく24バイトであることを正確に反映しています。NOSPLIT関数であるため、この正確な宣言は、呼び出し元がスタックを適切に管理し、スタックオーバーフローを防ぐ上で不可欠です。特に、notetsleepのような他のNOSPLIT関数から呼び出される場合に、スタックの過剰な消費や不足による問題を防ぎます.

関連リンク

参考にした情報源リンク