質問内容
現在Go言語でプログラミングを勉強中で、cgoでDLLの関数を呼び出すようなプログラムを作っています。
呼び出したいライブラリproprietarydll.dllは下記のproprietarydll.hにあるような関数を持っており、
こちらをGo言語から呼びだそうとしていました。
ところが、proprietarydll.dllの関数を呼び出した後しばらくすると、
"fatal error: invalid stack pointer"というエラーが出てプログラムが終了してしまいました。
エラーはGCの際に起きているようだったので、
proprietarydll.dllの関数呼び出しの後にGCを呼び出すcrash.goのようなプログラムを書いたところ、
GCが行われた時に同様のエラーが発生していることが確認できました。
しかし、エラーが発生している箇所はわかったのですが、
このエラーが何故起きているのかはわかりませんでした。
類似した質問もなく、修正のための原因がわからず、質問しました。
このエラーの原因は何でしょうか。
(※なお、DLLは頂いたもので、中で何をやっているのかは分かりません。
もともとはC/C++から使っていたライブラリであったようです。)
発生している問題・エラーメッセージ
複数回funcCを呼び出すプログラムの吐くエラーから、
GCの前後でエラーが起きている様子である、というところまではわかりました。
したがって、下記のようにCの関数呼び出しの前後にruntime.GC()を呼び出すと、
同様のエラー"fatal error: invalid stack pointer"を出してプログラムが落ちました。
エラーメッセージは次のとおりです。
before: GC(funcB) 0xe630020
after: GC(funcB) 0xe630020
before: GC(funcC) 0x1a0
runtime: bad pointer in frame main.main at 0x222cdf0c: 0x1a0
fatal error: invalid stack pointer
runtime stack:
runtime.throw(0x4eeb10, 0x15)
C:/Go/src/runtime/panic.go:547 +0x7f fp=0x2a2fa0c sp=0x2a2fa00
runtime.adjustpointers(0x222cdf0c, 0x2a2facc, 0x2a2fcac, 0x50e784)
C:/Go/src/runtime/stack.go:579 +0x235 fp=0x2a2fa80 sp=0x2a2fa0c
runtime.adjustframe(0x2a2fc3c, 0x2a2fcac, 0x2a2fb01)
C:/Go/src/runtime/stack.go:644 +0x163 fp=0x2a2fae8 sp=0x2a2fa80
runtime.gentraceback(0x44aa80, 0x222cdcf8, 0x0, 0x222b8000, 0x0, 0x0, 0x7fffffff, 0x506e60, 0x2a2fcac, 0x0, ...)
C:/Go/src/runtime/traceback.go:369 +0xbce fp=0x2a2fc68 sp=0x2a2fae8
runtime.copystack(0x222b8000, 0x1000)
C:/Go/src/runtime/stack.go:759 +0x161 fp=0x2a2fda0 sp=0x2a2fc68
runtime.shrinkstack(0x222b8000)
C:/Go/src/runtime/stack.go:1026 +0x122 fp=0x2a2fdb8 sp=0x2a2fda0
runtime.markroot(0x5)
C:/Go/src/runtime/mgcmark.go:155 +0x23e fp=0x2a2fe0c sp=0x2a2fdb8
runtime.gcDrain(0x2a2fe44, 0x0)
C:/Go/src/runtime/mgcmark.go:812 +0x26a fp=0x2a2fe34 sp=0x2a2fe0c
runtime.gchelper()
C:/Go/src/runtime/mgc.go:1806 +0x87 fp=0x2a2fe64 sp=0x2a2fe34
runtime.stopm()
C:/Go/src/runtime/proc.go:1541 +0x10c fp=0x2a2fe74 sp=0x2a2fe64
runtime.findrunnable(0x222b4a00, 0x0)
C:/Go/src/runtime/proc.go:1976 +0x62a fp=0x2a2febc sp=0x2a2fe74
runtime.schedule()
C:/Go/src/runtime/proc.go:2075 +0x202 fp=0x2a2fedc sp=0x2a2febc
runtime.park_m(0x222b84e0)
C:/Go/src/runtime/proc.go:2140 +0x163 fp=0x2a2fef0 sp=0x2a2fedc
runtime.mcall(0x933658)
C:/Go/src/runtime/asm_386.s:255 +0x47 fp=0x2a2fef8 sp=0x2a2fef0
goroutine 1 [copystack]:
runtime.systemstack_switch()
C:/Go/src/runtime/asm_386.s:267 fp=0x222cdcfc sp=0x222cdcf8
runtime.gcMarkTermination()
C:/Go/src/runtime/mgc.go:1182 +0x121 fp=0x222cdec4 sp=0x222cdcfc
runtime.gcStart(0x2, 0x222c2000)
C:/Go/src/runtime/mgc.go:1018 +0x3f4 fp=0x222cdee4 sp=0x222cdec4
runtime.GC()
C:/Go/src/runtime/mgc.go:840 +0x26 fp=0x222cdef0 sp=0x222cdee4
main.main()
C:/dev/crash/crash.go:40 +0x3b4 fp=0x222cdfa8 sp=0x222cdef0
runtime.main()
C:/Go/src/runtime/proc.go:188 +0x234 fp=0x222cdfd0 sp=0x222cdfa8
runtime.goexit()
C:/Go/src/runtime/asm_386.s:1585 +0x1 fp=0x222cdfd4 sp=0x222cdfd0
goroutine 17 [syscall, locked to thread]:
runtime.goexit()
C:/Go/src/runtime/asm_386.s:1585 +0x1 fp=0x222cbfd4 sp=0x222cbfd0
goroutine 2 [force gc (idle)]:
runtime.gopark(0x506f84, 0x55b488, 0x4e7b40, 0xf, 0x14, 0x1)
C:/Go/src/runtime/proc.go:262 +0x130 fp=0x222befa4 sp=0x222bef90
runtime.goparkunlock(0x55b488, 0x4e7b40, 0xf, 0x222b8014, 0x1)
C:/Go/src/runtime/proc.go:268 +0x4b fp=0x222befc0 sp=0x222befa4
runtime.forcegchelper()
C:/Go/src/runtime/proc.go:229 +0xaa fp=0x222befd8 sp=0x222befc0
runtime.goexit()
C:/Go/src/runtime/asm_386.s:1585 +0x1 fp=0x222befdc sp=0x222befd8
created by runtime.init.4
C:/Go/src/runtime/proc.go:218 +0x2a
goroutine 3 [GC sweep wait]:
runtime.gopark(0x506f84, 0x55b500, 0x4e70b0, 0xd, 0x41c914, 0x1)
C:/Go/src/runtime/proc.go:262 +0x130 fp=0x222bff98 sp=0x222bff84
runtime.goparkunlock(0x55b500, 0x4e70b0, 0xd, 0x14, 0x1)
C:/Go/src/runtime/proc.go:268 +0x4b fp=0x222bffb4 sp=0x222bff98
runtime.bgsweep(0x222aa0c0)
C:/Go/src/runtime/mgcsweep.go:63 +0xa0 fp=0x222bffd0 sp=0x222bffb4
runtime.goexit()
C:/Go/src/runtime/asm_386.s:1585 +0x1 fp=0x222bffd4 sp=0x222bffd0
created by runtime.gcenable
C:/Go/src/runtime/mgc.go:191 +0x52
goroutine 4 [finalizer wait]:
runtime.gopark(0x506f84, 0x56b1e8, 0x4e7aa0, 0xe, 0x14, 0x1)
C:/Go/src/runtime/proc.go:262 +0x130 fp=0x222c0f84 sp=0x222c0f70
runtime.goparkunlock(0x56b1e8, 0x4e7aa0, 0xe, 0x14, 0x1)
C:/Go/src/runtime/proc.go:268 +0x4b fp=0x222c0fa0 sp=0x222c0f84
runtime.runfinq()
C:/Go/src/runtime/mfinal.go:158 +0x9e fp=0x222c0fd8 sp=0x222c0fa0
runtime.goexit()
C:/Go/src/runtime/asm_386.s:1585 +0x1 fp=0x222c0fdc sp=0x222c0fd8
created by runtime.createfing
C:/Go/src/runtime/mfinal.go:139 +0x5c
該当のソースコード
proprietarydll.h
#include <winsock2.h>
#include <windows.h>
// dummy value
#define SUCCESS 12345
// funcA is called to initialize this dll.
DWORD funcA();
// funcB returns a handle for funcC
HANDLE funcB();
// funcC requires a handle returned by funcB
HANDLE funcC(HANDLE);
crash.go
package main
import (
"fmt"
"log"
"runtime"
)
/*
#cgo LDFLAGS: -L. -lproprietarydll
#cgo CFLAGS: -I .
#include "proprietarydll.h"
*/
import "C"
func main() {
if status, err := C.funcA(); status != C.SUCCESS || err != nil {
log.Fatal(err)
}
handleB, err := C.funcB()
if handleB == nil || err != nil {
if err != nil {
log.Fatal(err)
}
log.Fatal("handleB is nil")
}
fmt.Println("before:\tGC(funcB)", handleB)
runtime.GC() // <- ここはエラーが出ない
fmt.Println("after:\tGC(funcB)", handleB)
handleC, err := C.funcC(handleB)
if handleC == nil || err != nil {
if err != nil {
log.Fatal("handleC is nil")
}
return
}
fmt.Println("before:\tGC(funcC)", handleC)
runtime.GC() // <- ここはエラーが出る
fmt.Println("after:\tGC(funcC)", handleC)
}
補足情報(言語/FW/ツール等のバージョンなど)
OS: Windows 7 64bit
言語: Go 1.6.2 32bit
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
check解決した方法
0
エラー原因
Windows APIにおいて使われるHANDLEは、Windows Data Typesによるとvoid *
として定義されています。このvoid *
はGoではunsafe.Pointer
に相当し、ポインタとして扱われます。しかし、Windows APIが返す実際の値は、ある特定のデータを指すポインタではなく、Handle TableというHandleを管理するテーブルのインデックスになっています。
ここで、runtime.GC()
によってGCを実行すると、開放すべきオブジェクトを探すためにポインタならばその先をたどる必要があります。ところがHANDLEの中身は有効なアドレスではありません。この結果エラーが出ているようです。
参考に、HANDLEを返すWindows APIを使って、質問文よりももっと短いプログラムで同じようなエラーが再現できます。
再現プログラム
package main
import (
"fmt"
"log"
"runtime"
)
/*
#include <windows.h>
HANDLE getMailslot() {
return CreateMailslot("\\\\.\\mailslot\\mailslotname", 0, 0, NULL);
}
*/
import "C"
func main() {
m, err := C.getMailslot()
if err != nil {
log.Fatal(err)
}
fmt.Println("before: ", m)
runtime.GC()
fmt.Println("after: ", m)
}
エラー出力
before: 0x198
runtime: bad pointer in frame main.main at 0xc082031e90: 0x198
fatal error: invalid stack pointer
runtime stack:
runtime.throw(0x52da20, 0x15)
C:/Go/src/runtime/panic.go:530 +0x97 fp=0x7ef680 sp=0x7ef668
runtime.adjustpointers(0xc082031e90, 0x7ef800, 0x7efb28, 0x554568)
C:/Go/src/runtime/stack.go:579 +0x2e2 fp=0x7ef778 sp=0x7ef680
runtime.adjustframe(0x7efa48, 0x7efb28, 0x7ef801)
C:/Go/src/runtime/stack.go:644 +0x1d2 fp=0x7ef838 sp=0x7ef778
runtime.gentraceback(0x453af0, 0xc082031c08, 0x0, 0xc08201c000, 0x0, 0x0, 0x7fffffff, 0x546f08, 0x7efb28, 0x0, ...)
C:/Go/src/runtime/traceback.go:369 +0xda3 fp=0x7efaa0 sp=0x7ef838
runtime.copystack(0xc08201c000, 0x2000)
C:/Go/src/runtime/stack.go:759 +0x1c5 fp=0x7efc90 sp=0x7efaa0
runtime.shrinkstack(0xc08201c000)
C:/Go/src/runtime/stack.go:1026 +0x170 fp=0x7efcc0 sp=0x7efc90
runtime.markroot(0x5)
C:/Go/src/runtime/mgcmark.go:155 +0x2d2 fp=0x7efd50 sp=0x7efcc0
runtime.gcDrain(0x7efdc0, 0x0)
C:/Go/src/runtime/mgcmark.go:812 +0x24f fp=0x7efd88 sp=0x7efd50
runtime.gcMark(0xdd715aff0e34)
C:/Go/src/runtime/mgc.go:1559 +0x100 fp=0x7efde8 sp=0x7efd88
runtime.gcMarkTermination.func1()
C:/Go/src/runtime/mgc.go:1173 +0x2a fp=0x7efdf8 sp=0x7efde8
runtime.systemstack(0x5a9e00)
C:/Go/src/runtime/asm_amd64.s:291 +0x7e fp=0x7efe00 sp=0x7efdf8
runtime.mstart()
C:/Go/src/runtime/proc.go:1048 fp=0x7efe08 sp=0x7efe00
goroutine 1 [copystack]:
runtime.systemstack_switch()
C:/Go/src/runtime/asm_amd64.s:245 fp=0xc082031c10 sp=0xc082031c08
runtime.gcMarkTermination()
C:/Go/src/runtime/mgc.go:1181 +0x14b fp=0xc082031e20 sp=0xc082031c10
runtime.gcStart(0x2, 0xc082026000)
C:/Go/src/runtime/mgc.go:1017 +0x3b5 fp=0xc082031e48 sp=0xc082031e20
runtime.GC()
C:/Go/src/runtime/mgc.go:840 +0x2c fp=0xc082031e60 sp=0xc082031e48
main.main()
C:/dev/sample/main.go:23 +0x22d fp=0xc082031f40 sp=0xc082031e60
runtime.main()
C:/Go/src/runtime/proc.go:188 +0x27e fp=0xc082031f90 sp=0xc082031f40
runtime.goexit()
C:/Go/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc082031f98 sp=0xc082031f90
goroutine 17 [syscall, locked to thread]:
runtime.goexit()
C:/Go/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc082025fa8 sp=0xc082025fa0
goroutine 2 [force gc (idle)]:
runtime.gopark(0x547160, 0x5a98f0, 0x51a050, 0xf, 0x14, 0x1)
C:/Go/src/runtime/proc.go:262 +0x17c fp=0xc082023f38 sp=0xc082023f10
runtime.goparkunlock(0x5a98f0, 0x51a050, 0xf, 0xc08201c014, 0x1)
C:/Go/src/runtime/proc.go:268 +0x5b fp=0xc082023f70 sp=0xc082023f38
runtime.forcegchelper()
C:/Go/src/runtime/proc.go:229 +0xc6 fp=0xc082023fa0 sp=0xc082023f70
runtime.goexit()
C:/Go/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc082023fa8 sp=0xc082023fa0
created by runtime.init.4
C:/Go/src/runtime/proc.go:218 +0x32
goroutine 3 [GC sweep wait]:
runtime.gopark(0x547160, 0x5a9a00, 0x5181d0, 0xd, 0x41d614, 0x1)
C:/Go/src/runtime/proc.go:262 +0x17c fp=0xc08201ff28 sp=0xc08201ff00
runtime.goparkunlock(0x5a9a00, 0x5181d0, 0xd, 0x14, 0x1)
C:/Go/src/runtime/proc.go:268 +0x5b fp=0xc08201ff60 sp=0xc08201ff28
runtime.bgsweep(0xc082032000)
C:/Go/src/runtime/mgcsweep.go:63 +0xbf fp=0xc08201ff98 sp=0xc08201ff60
runtime.goexit()
C:/Go/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc08201ffa0 sp=0xc08201ff98
created by runtime.gcenable
C:/Go/src/runtime/mgc.go:191 +0x5a
goroutine 4 [finalizer wait]:
runtime.gopark(0x547160, 0x5c3e08, 0x519db0, 0xe, 0x14, 0x1)
C:/Go/src/runtime/proc.go:262 +0x17c fp=0xc082021ef8 sp=0xc082021ed0
runtime.goparkunlock(0x5c3e08, 0x519db0, 0xe, 0x14, 0x1)
C:/Go/src/runtime/proc.go:268 +0x5b fp=0xc082021f30 sp=0xc082021ef8
runtime.runfinq()
C:/Go/src/runtime/mfinal.go:158 +0xb8 fp=0xc082021fa0 sp=0xc082021f30
runtime.goexit()
C:/Go/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc082021fa8 sp=0xc082021fa0
created by runtime.createfing
C:/Go/src/runtime/mfinal.go:139 +0x67
exit status 2
解決策
このようなHANDLEを扱うCの関数を扱いたい場合は、Go側でポインタとして扱われてしまうHANDLEではなく、ハンドルの中身の整数値を直接扱うようにすると良いです。例えば、質問文中のプログラムを参考にするならば、HANDLEを返す/受け取る関数をラップし、GoではHANDLEではなくuintptr_tを返す/受け取る関数を使います。
@@ -10,6 +10,12 @@
#cgo LDFLAGS: -L. -lproprietarydll
#cgo CFLAGS: -I .
#include "proprietarydll.h"
+uintptr_t _funcB() {
+ return (uintptr_t)funcB();
+}
+uintptr_t _funcC(uintptr_t ptr) {
+ return (uintptr_t)funcC((HANDLE)ptr));
+}
*/
import "C"
@@ -18,8 +24,8 @@
log.Fatal(err)
}
- handleB, err := C.funcB()
- if handleB == nil || err != nil {
+ handleB, err := C._funcB()
+ if handleB == 0 || err != nil {
if err != nil {
log.Fatal(err)
}
@@ -29,14 +35,14 @@
runtime.GC() // <- cause no error
fmt.Println("after:\tGC(funcB)", handleB)
- handleC, err := C.funcC(handleB)
- if handleC == nil || err != nil {
+ handleC, err := C._funcC(handleB)
+ if handleC == 0 || err != nil {
if err != nil {
log.Fatal("handleC is nil")
}
return
}
fmt.Println("before:\tGC(funcC)", handleC)
- runtime.GC() // <- ここはエラーが出る
+ runtime.GC() // <- もうエラーが出ない
fmt.Println("after:\tGC(funcC)", handleC)
}
自己解決に至るまで
上述のように、HANDLEがポインタではあるものの、Handle Tableのインデックスを表しているという点について知らなかったことが今回のエラー原因でした。この事について自力で気づけなかった私は、(マルチポストは避けるべきだとは認識しておりましたが、投稿日からの経過日数と閲覧数からもはや回答は得られないのではと思っていたという言い訳をしつつ)本家に問い合わせました。golang/Go#Issue 15794
この結果以下の様な回答をいただきました。
- Windows APIはたとえHANDLEがポインタであったとしても、一般に小さな整数値を返す
- Goのランタイムはポインタとして宣言されているものはポインタとして扱うので、有効なポインタでなければクラッシュする
- したがって、HANDLEはGoでは(ポインタそのままの)unsafe.Pointerではなく、uintptrとして扱う必要がある
本回答の解決策に載せたプログラムは、本家の方から頂いたこの回答を元に作成したものです。
また、頂いた回答を元に、そもそもHANDLEとは何かについて下記のリンク等を参考にした結果、HANDLEが表すものはHandle Tableのインデックスであり、特定のデータを指す有効なアドレスになっていない(ためエラーが出ていた)という今回の回答に行き着きました。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.36%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる