[インデックス 18318] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるネットワーク関連のシステムコール(setsockopt
, getsockopt
, bind
, connect
など)において、引数の型をuintptr
からunsafe.Pointer
へ変更するものです。これにより、Goのガベージコレクタがポインタの参照を正しく追跡できるようになり、メモリ安全性と堅牢性が向上します。
コミット
- コミットハッシュ:
f00af3da1cd6b3f09f5d61a6d8bca438c77b34c6
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Tue Jan 21 18:54:49 2014 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f00af3da1cd6b3f09f5d61a6d8bca438c77b34c6
元コミット内容
syscall: use unsafe.Pointer instead of uintptr in net syscalls
In particular: setsockopt, getsockopt, bind, connect.
There are probably more.
All platforms cross-compile with make.bash, and all.bash still
pases on linux/amd64.
Update #7169
R=rsc
CC=golang-codereviews
https://golang.org/cl/55410043
変更の背景
Go言語のsyscall
パッケージは、オペレーティングシステムのシステムコールを直接呼び出すためのインターフェースを提供します。これらのシステムコールは、C言語のAPIと密接に連携しており、しばしばメモリ上のデータ構造へのポインタを引数として受け取ります。
以前のGoのsyscall
実装では、これらのポインタをuintptr
型として扱っていました。uintptr
は、ポインタの値を整数として表現する型であり、Goのガベージコレクタからは単なる数値として認識されます。このため、uintptr
を介して参照されているメモリ領域が、Goのガベージコレクタによって誤って解放されてしまう可能性がありました。特に、システムコールが実行中にそのメモリ領域を参照している場合、Use-After-Freeのような深刻なメモリ破壊バグを引き起こすリスクがありました。
このコミットは、この潜在的なメモリ安全性の問題を解決するために導入されました。uintptr
の代わりにunsafe.Pointer
を使用することで、Goのガベージコレクタに対して、その値がポインタであり、参照先のメモリ領域を保護する必要があることを明示的に伝えます。これにより、システムコールが使用するメモリがガベージコレクションの対象から適切に除外され、プログラムの安定性と信頼性が向上します。
前提知識の解説
Go言語のunsafe
パッケージ
unsafe
パッケージは、Go言語の型安全性をバイパスするための機能を提供します。通常、Goは厳格な型システムを持ち、ポインタ演算や型変換を制限することでメモリ安全性を保証します。しかし、システムプログラミングや特定の最適化のシナリオでは、これらの制限を緩和する必要が生じます。unsafe
パッケージは、そのような高度なユースケースのために提供されており、以下の主要な型と関数を含みます。
unsafe.Pointer
: 任意の型のポインタを保持できる特殊なポインタ型です。Goのガベージコレクタはunsafe.Pointer
が指すメモリを追跡し、そのメモリが使用中である限り解放しません。これは、C言語のvoid*
に似ていますが、ガベージコレクタのセーフティネットが提供される点が異なります。uintptr
: ポインタの値を符号なし整数として表現する型です。uintptr
はポインタの値を保持できますが、Goのガベージコレクタはuintptr
が指すメモリを追跡しません。そのため、uintptr
を介してメモリを操作する際には、開発者が手動でメモリのライフサイクルを管理する必要があります。
uintptr
とunsafe.Pointer
の違い
特徴 | uintptr | unsafe.Pointer |
---|---|---|
ガベージコレクション | ガベージコレクタは追跡しない。 | ガベージコレクタは追跡し、参照先のメモリを保護する。 |
型安全性 | 型安全ではない。任意の整数値として扱われる。 | 型安全ではないが、ポインタとしての意味を持つ。 |
用途 | ポインタの値を整数として扱う場合(例: アドレス計算) | 任意の型のポインタを安全に扱う場合(例: CとのFFI、システムコール) |
この違いが、システムコールにおけるメモリ安全性の問題の根源でした。uintptr
を使用すると、Goのランタイムはそれがポインタであることを認識せず、ガベージコレクションの対象となり得るためです。
Goのsyscall
パッケージとシステムコール
syscall
パッケージは、GoプログラムからOSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。これにより、ファイル操作、ネットワーク通信、プロセス管理など、OSカーネルが提供する機能にアクセスできます。システムコールは通常、C言語で定義されたAPIに対応しており、GoからこれらのAPIを呼び出す際には、Goのデータ型をC言語のデータ型にマッピングする必要があります。このマッピングにおいて、ポインタの扱いが重要になります。
ネットワーク関連のシステムコール
setsockopt
, getsockopt
, bind
, connect
などは、ソケットプログラミングにおいて基本的なシステムコールです。
setsockopt
: ソケットのオプションを設定します。getsockopt
: ソケットのオプションを取得します。bind
: ソケットにアドレスを割り当てます。connect
: ソケットをリモートアドレスに接続します。
これらの関数は、ソケットアドレス構造体(例: sockaddr_in
, sockaddr_in6
)へのポインタを引数として受け取ることが一般的です。
技術的詳細
このコミットの技術的な核心は、Goのガベージコレクタとシステムコール間の相互作用におけるメモリ安全性の保証です。
Goのガベージコレクタは、到達可能なオブジェクトを特定し、到達不能なオブジェクトを解放することでメモリを管理します。ここでいう「到達可能」とは、プログラムがそのオブジェクトにアクセスできるパスが存在することを意味します。Goのポインタは、ガベージコレクタが到達可能性を判断するための重要な情報源です。
しかし、uintptr
は単なる整数値であるため、それがメモリ上の特定のアドレスを指しているとしても、ガベージコレクタはそのアドレスがポインタとして使用されていることを認識できません。結果として、uintptr
を介してシステムコールに渡されたメモリ領域が、システムコールが完了する前にガベージコレクタによって解放されてしまう可能性があります。これは、特にシステムコールが非同期的に動作する場合や、長時間実行される場合に問題となります。
unsafe.Pointer
を使用することで、開発者はGoのガベージコレクタに対して、その値がポインタであり、参照先のメモリ領域を保護する必要があることを明示的に伝えます。これにより、unsafe.Pointer
が指すメモリは、そのポインタが有効である限りガベージコレクションの対象から除外されます。
この変更は、特にネットワークシステムコールにおいて重要です。bind
やconnect
のようなシステムコールは、ソケットアドレス構造体へのポインタをカーネルに渡します。カーネルがこのポインタを使用している間に、Goのランタイムがそのメモリを解放してしまうと、カーネルパニックや予期せぬ動作を引き起こす可能性があります。unsafe.Pointer
への変更は、このようなシナリオを防ぎ、Goのネットワークスタックの堅牢性を大幅に向上させます。
また、この変更は、Goのクロスコンパイル環境においても重要です。異なるアーキテクチャやOS間でシステムコールを扱う場合、ポインタのサイズやアライメントが異なることがあります。unsafe.Pointer
は、これらのプラットフォーム固有の詳細を抽象化し、より移植性の高いコードを記述するのに役立ちます。
コアとなるコードの変更箇所
このコミットでは、src/pkg/syscall
ディレクトリ以下の多数のファイルが変更されています。主な変更は、setsockopt
, getsockopt
, bind
, connect
, sendto
などのシステムコール関数のシグネチャにおいて、uintptr
型だった引数がunsafe.Pointer
型に変更されている点です。また、Sockaddr
インターフェースのsockaddr()
メソッドの戻り値の型も変更されています。
具体的な変更例をいくつか示します。
src/pkg/syscall/lsf_linux.go
:
--- a/src/pkg/syscall/lsf_linux.go
+++ b/src/pkg/syscall/lsf_linux.go
@@ -69,10 +69,10 @@ func AttachLsf(fd int, i []SockFilter) error {
var p SockFprog
p.Len = uint16(len(i))
p.Filter = (*SockFilter)(unsafe.Pointer(&i[0]))
- return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, uintptr(unsafe.Pointer(&p)), unsafe.Sizeof(p))\n+\treturn setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, unsafe.Pointer(&p), unsafe.Sizeof(p))
}
func DetachLsf(fd int) error {
var dummy int
- return setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, uintptr(unsafe.Pointer(&dummy)), unsafe.Sizeof(dummy))\n+\treturn setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, unsafe.Pointer(&dummy), unsafe.Sizeof(dummy))
}
src/pkg/syscall/syscall_bsd.go
:
--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -134,18 +134,18 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int,\n }\n \n //sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)\n-//sys bind(s int, addr uintptr, addrlen _Socklen) (err error)\n-//sys connect(s int, addr uintptr, addrlen _Socklen) (err error)\n+//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)\n+//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)\n //sysnb socket(domain int, typ int, proto int) (fd int, err error)\n-//sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error)\n-//sys setsockopt(s int, level int, name int, val uintptr, vallen uintptr) (err error)\n+//sys getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *_Socklen) (err error)\n+//sys setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) (err error)\n //sysnb getpeername(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error)\n //sysnb getsockname(fd int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error)\n //sys Shutdown(s int, how int) (err error)\n \n-func (sa *SockaddrInet4) sockaddr() (uintptr, _Socklen, error) {\n+func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) {\n \tif sa.Port < 0 || sa.Port > 0xFFFF {\n-\t\treturn 0, 0, EINVAL\n+\t\treturn nil, 0, EINVAL
src/pkg/syscall/syscall_unix.go
:
--- a/src/pkg/syscall/syscall_unix.go
+++ b/src/pkg/syscall/syscall_unix.go
@@ -160,7 +160,7 @@ func Write(fd int, p []byte) (n int, err error) {
var SocketDisableIPv6 bool
type Sockaddr interface {
-\tsockaddr() (ptr uintptr, len _Socklen, err error) // lowercase; only we can define Sockaddrs\n+\tsockaddr() (ptr unsafe.Pointer, len _Socklen, err error) // lowercase; only we can define Sockaddrs
}
type SockaddrInet4 struct {
@@ -209,7 +209,7 @@ func Getpeername(fd int) (sa Sockaddr, err error) {
func GetsockoptInt(fd, level, opt int) (value int, err error) {
var n int32
vallen := _Socklen(4)
-\terr = getsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), &vallen)\n+\terr = getsockopt(fd, level, opt, unsafe.Pointer(&n), &vallen)
return int(n), err
}
@@ -234,40 +234,40 @@ func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error) {
}
func SetsockoptByte(fd, level, opt int, value byte) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value)), 1)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&value), 1)
}
func SetsockoptInt(fd, level, opt int, value int) (err error) {
var n = int32(value)
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&n)), 4)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&n), 4)
}
func SetsockoptInet4Addr(fd, level, opt int, value [4]byte) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&value[0])), 4)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&value[0]), 4)
}
func SetsockoptIPMreq(fd, level, opt int, mreq *IPMreq) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), SizeofIPMreq)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(mreq), SizeofIPMreq)
}
func SetsockoptIPv6Mreq(fd, level, opt int, mreq *IPv6Mreq) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(mreq)), SizeofIPv6Mreq)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(mreq), SizeofIPv6Mreq)
}
func SetsockoptICMPv6Filter(fd, level, opt int, filter *ICMPv6Filter) error {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(filter)), SizeofICMPv6Filter)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(filter), SizeofICMPv6Filter)
}
func SetsockoptLinger(fd, level, opt int, l *Linger) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(l)), SizeofLinger)\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(l), SizeofLinger)
}
func SetsockoptString(fd, level, opt int, s string) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(&[]byte(s)[0])), uintptr(len(s)))\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(&[]byte(s)[0]), uintptr(len(s)))
}
func SetsockoptTimeval(fd, level, opt int, tv *Timeval) (err error) {
-\treturn setsockopt(fd, level, opt, uintptr(unsafe.Pointer(tv)), unsafe.Sizeof(*tv))\n+\treturn setsockopt(fd, level, opt, unsafe.Pointer(tv), unsafe.Sizeof(*tv))
}
func Socket(domain, typ, proto int) (fd int, err error) {
コアとなるコードの解説
上記の変更箇所は、Goのsyscall
パッケージがOSのシステムコールを呼び出す際に、メモリ上のデータへのポインタをどのように扱うかを根本的に変更しています。
-
システムコール関数の引数型の変更:
bind
,connect
,getsockopt
,setsockopt
,sendto
といった関数は、ソケットアドレス構造体やオプション値へのポインタを引数として受け取ります。これらの引数の型がuintptr
からunsafe.Pointer
に変更されました。- 変更前 (
uintptr
):uintptr
は単なる整数であり、Goのガベージコレクタはそれが指すメモリを追跡しませんでした。これにより、システムコールがそのメモリを使用している間に、ガベージコレクタがメモリを解放してしまう可能性がありました。 - 変更後 (
unsafe.Pointer
):unsafe.Pointer
は、ガベージコレクタに対して、その値がポインタであり、参照先のメモリを保護する必要があることを明示的に伝えます。これにより、システムコールが完了するまで、そのメモリ領域が安全に保持されることが保証されます。
- 変更前 (
-
Sockaddr
インターフェースのsockaddr()
メソッドの戻り値の変更:Sockaddr
インターフェースは、様々な種類のソケットアドレス(IPv4, IPv6, Unixドメインなど)を抽象化するためのものです。このインターフェースのsockaddr()
メソッドは、具体的なソケットアドレス構造体へのポインタと、その長さを返します。このメソッドのポインタの戻り値の型もuintptr
からunsafe.Pointer
に変更されました。- この変更は、
bind
やsendto
などのシステムコールにソケットアドレスを渡す際に、unsafe.Pointer
を直接使用できるようにするためです。これにより、uintptr(unsafe.Pointer(&sa.raw))
のような冗長な変換が不要になり、コードがよりクリーンになります。また、sockaddr()
メソッドが返すポインタもガベージコレクタによって追跡されるようになり、メモリ安全性が向上します。
- この変更は、
これらの変更は、Goのネットワークスタックの基盤となるsyscall
パッケージのメモリ管理を改善し、より堅牢で信頼性の高いネットワークアプリケーションの開発を可能にします。特に、低レベルなシステムコールを扱う際に発生しうる、ガベージコレクションに起因する潜在的なバグを排除することを目的としています。
関連リンク
- https://github.com/golang/go/commit/f00af3da1cd6b3f09f5d61a6d8bca438c77b34c6
- Go Issue #7169: https://github.com/golang/go/issues/7169
- Go Code Review: https://golang.org/cl/55410043
参考にした情報源リンク
- Go Documentation:
unsafe
package: https://pkg.go.dev/unsafe - Go Documentation:
syscall
package: https://pkg.go.dev/syscall - The Go Programming Language Specification - Conversions: https://go.dev/ref/spec#Conversions
- Understanding
unsafe.Pointer
in Go: https://yourbasic.org/golang/unsafe-pointer/ (一般的な情報源として) - Go:
uintptr
vsunsafe.Pointer
: https://medium.com/@joshua.s.a.g.e/go-uintptr-vs-unsafe-pointer-a-deep-dive-into-memory-management-and-interoperability-c71212121212 (一般的な情報源として)