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

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

このコミットは、Go言語のリンカーであるliblinkにおけるamd64p32アーキテクチャでのmorestack処理のバグ修正に関するものです。具体的には、src/liblink/obj6.cファイルが変更されています。

コミット

commit 9460cf78257fd8326827f1564da5da29b3bb8089
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Mar 7 19:44:35 2014 +0100

    liblink: fix morestack handling on amd64p32.
    
    It was using MOVL to pass a 64-bit argument
    (concatenated framesize and argsize) to morestack11.
    
    LGTM=dave, rsc
    R=dave, rsc, iant
    CC=golang-codereviews
    https://golang.org/cl/72360044

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

https://github.com/golang/go/commit/9460cf78257fd8326827f1564da5da29b3bb8089

元コミット内容

liblink: fix morestack handling on amd64p32.

It was using MOVL to pass a 64-bit argument
(concatenated framesize and argsize) to morestack11.

LGTM=dave, rsc
R=dave, rsc, iant
CC=golang-codereviews
https://golang.org/cl/72360044

変更の背景

Go言語のランタイムには、ゴルーチンのスタックが不足した場合に動的にスタックを拡張するmorestackというメカニズムがあります。このメカニズムは、関数呼び出しの前にスタックの残量を確認し、不足していればmorestack関数を呼び出して新しいスタック領域を確保します。

このコミットの背景には、amd64p32という特定のアーキテクチャにおけるmorestack処理の実装上の問題がありました。amd64p32は64ビットのCPUアーキテクチャでありながら、ポインタサイズが32ビットに制限されている特殊な環境です。

問題は、morestack11という関数に引数を渡す際に、framesize(フレームサイズ)とargsize(引数サイズ)を連結した64ビットの値を渡す必要があったにもかかわらず、誤って32ビットのデータ転送命令であるMOVLを使用していた点にあります。これにより、64ビットの引数が正しく渡されず、morestack処理が正常に機能しない可能性がありました。このコミットは、この誤った命令の使用を修正し、amd64p32環境でのmorestackの信頼性を向上させることを目的としています。

前提知識の解説

Go言語のmorestack関数

Go言語のゴルーチンは、初期スタックサイズが小さく設定されており、必要に応じて動的にスタックを拡張します。この動的なスタック拡張を担うのがmorestack関数です。

  1. スタックオーバーフローチェック: Goコンパイラは、ほとんどの関数のエントリポイントにスタックオーバーフローチェックのプロローグを挿入します。これにより、現在のゴルーチンのスタックポインタが割り当てられた制限を超えていないかを確認します。
  2. morestackの呼び出し: スタック制限に達した場合、morestack関数(またはruntime.morestack_noctxt)がランタイムによって呼び出されます。
  3. スタックの拡張: morestackが呼び出されると、ゴルーチンのスタック用に新しくより大きな連続したメモリブロックが割り当てられ、通常はサイズが倍増します。古いスタックの内容は新しいメモリ領域にコピーされ、ポインタも新しいアドレスを反映するように調整されます。

このメカニズムにより、Goプログラムは比較的小さな初期スタックを使用し、必要な場合にのみスタックを拡張することでメモリ使用量を最適化できます。

liblinkはGo言語のリンカーであり、コンパイルされたGoパッケージ(オブジェクトファイル)をリンクして実行可能バイナリを生成するツールチェーンのコンポーネントです。morestackメカニズムは、Goコンパイラによって生成され、liblinkによって処理およびリンクされる特定のアセンブリコードを含んでいます。

amd64p32アーキテクチャ

amd64p32は、x32 ABIとも呼ばれる、64ビットのIntelおよびAMDハードウェア上で動作するアプリケーションバイナリインターフェース(ABI)です。このアーキテクチャは、x86-64命令セット(より多くのCPUレジスタ、改善された浮動小数点性能、より高速な位置独立コードなど)の利点を活用しながら、32ビットの整数、long型、およびポインタ(ILP32)を使用します。

つまり、CPU自体は64ビットですが、メモリのアドレス指定には32ビットのポインタが使用されます。これは、特定の組み込みシステムや、メモリアドレス指定が互換性やリソースの制約のために32ビットに制限されている環境で使用されることがあります。

amd64p32の呼び出し規約は、主にSystem V AMD64 ABIに従いますが、ポインタとlong型のサイズが32ビットである点が主な違いです。

MOVLMOVQアセンブリ命令

x86アセンブリにおいて、MOVLMOVQはどちらもデータ転送命令ですが、操作するデータのサイズが異なります。

  • MOVL (Move Long):

    • 「ロングワード」を移動するために使用され、**32ビット(4バイト)**のデータに対応します。
    • 通常、32ビットレジスタ(EAX, EBX, ECX, EDXなど)または32ビットメモリ位置を扱う際に使用されます。
  • MOVQ (Move Quadword):

    • 「クアッドワード」を移動するために使用され、**64ビット(8バイト)**のデータに対応します。
    • 主に64ビット環境(x86-64)で、64ビットレジスタ(RAX, RBX, RCX, RDXなど)または64ビットメモリ位置を操作するために使用されます。

AMOVQは標準的なx86命令ニーモニックではありませんが、これはMOVQを指している可能性が高いです。MOV命令のサフィックス(Lはロング、Qはクアッドワード)は、転送されるデータのサイズを明示的に示します。

技術的詳細

このコミットの技術的な問題は、amd64p32アーキテクチャにおいて、morestack11関数に渡される引数のサイズと、その引数を転送するために使用されるアセンブリ命令の不一致にありました。

morestack11関数は、framesize(現在のスタックフレームのサイズ)とargsize(関数に渡される引数の合計サイズ)を連結した64ビットの単一の引数を受け取ります。この64ビットの値は、スタックの拡張に必要な情報をmorestackランタイムに提供するために重要です。

しかし、元のコードでは、この64ビットの引数を転送するためにMOVL命令が使用されていました。MOVLは32ビットのデータを扱う命令であるため、64ビットの値を正しく転送することができません。これにより、morestack11関数は不完全または誤った引数を受け取り、結果としてスタックの拡張処理が失敗したり、予期せぬ動作を引き起こしたりする可能性がありました。

この修正は、MOVLAMOVQ(実質的にはMOVQ)に置き換えることで、この問題を解決します。MOVQは64ビットのデータを転送するための命令であるため、framesizeargsizeを連結した64ビットの引数がmorestack11に正しく渡されるようになります。これにより、amd64p32環境におけるGoランタイムのスタック管理の堅牢性が向上します。

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

--- a/src/liblink/obj6.c
+++ b/src/liblink/obj6.c
@@ -862,7 +862,8 @@ stacksplit(Link *ctxt, Prog *p, int32 framesize, int32 textarg, int noctxt, Prog
 		p->to.type = D_BRANCH;
 		p->to.sym = ctxt->symmorestack[2*2+noctxt];
 	} else {
-		p->as = mov;
+		// Pass framesize and argsize.
+		p->as = AMOVQ;
 		p->from.type = D_CONST;
 		p->from.offset = (uint64)moreconst2 << 32;
 		p->from.offset |= moreconst1;

コアとなるコードの解説

変更はsrc/liblink/obj6.cファイルのstacksplit関数内で行われています。この関数は、Goのリンカーがスタック分割チェック(morestackへのジャンプ)を生成する際に関連するコードです。

元のコードでは、以下の行がありました。

p->as = mov;

ここでmovは、アセンブリ命令の種類を示す変数であり、このコンテキストでは32ビットのデータ転送を意味するMOVLに解決されていました。

修正後のコードでは、この行が以下のように変更されています。

// Pass framesize and argsize.
p->as = AMOVQ;
  • // Pass framesize and argsize.:このコメントは、この命令がframesizeargsizeという2つの重要な情報をmorestack11関数に渡すためのものであることを明確に示しています。
  • p->as = AMOVQ;:この行が修正の核心です。movMOVL)をAMOVQに置き換えることで、生成されるアセンブリ命令が32ビットのデータ転送から64ビットのデータ転送に変わります。これにより、p->from.offsetに格納されているframesizeargsizeを連結した64ビットの値が、正しくmorestack11関数に引数として渡されるようになります。

p->from.offset = (uint64)moreconst2 << 32; p->from.offset |= moreconst1; の行は、framesizeargsizeを連結して64ビットの定数を作成している部分です。moreconst2が上位32ビット、moreconst1が下位32ビットを構成し、これらを結合して単一の64ビット値として扱っています。この64ビット値を正しく転送するためにAMOVQが必要でした。

この変更により、amd64p32環境でGoプログラムがスタックを動的に拡張する際の信頼性が確保され、潜在的なランタイムエラーやクラッシュが防止されます。

関連リンク

参考にした情報源リンク