20210908のGoに関する記事は2件です。

Go で、デバッガの attach まで実行を待機させる

Node.js や Java (JVM) 等はランタイムが大きいこともあり、本体側にデバッギ支援の機能を持っている。 node --inspect か、 NODE_OPTIONS="--inspect" ~ で、Node.js 側がポートを開いてデバッガの接続を待ち、 --inspect であれば、接続後にすぐ実行を開始する。 --inspect-brk であれば、接続後に即 break する。 一方で、Golang などのネイティブバイナリへコンパイル&リンクをする言語では、実行バイナリ本体にデバッギ支援の機能を持たせるような富豪なことはできない。別途、dlv(1) がデバッガである。よって、以下のいずれかでデバッガを動かしてやる必要がある。 dlv 下でコマンドを実行する 既存プロセスへ dlv が attach する その上で、dlv が開くポートへインターフェイス(IDE 等)で接続する。前者ができる環境があるならば、話は簡単だ。だが、後者のようなケースも多い。思いついたオプションや引数を指定してコマンドラインから実行したいとか、他のツールの配下で動いている、などのケースで。 その場合は、何とかして dlv が接続してくるまで、プロセスを待たせる必要がある。 main へ入るやいなや、下記のようなルーチンを呼んでおけば良い。インターバルを置きつつプロセスのリストを眺めて、自身の PID に対して dlv が接続しているプロセスが見つかったらループを抜ける。 func waitForDebugger() { if os.Getenv("DEBUG") == "" { return } log.Println("PID", os.Getpid(), "is waiting the debugger to attach...") for { err := exec.Command("sh", "-c", fmt.Sprintf("ps w | grep '\\b[d]lv\\b.*\\battach\\b.*\\b%d\\b'", os.Getpid())).Run() time.Sleep(1 * time.Second) if err == nil { log.Println("Restarted.") break } } } 後は、dlv で breakpoint を指定してから、PID で attach してやれば良い。 $ go build -gcflags=all="-N -l" -o ~/go/bin/foo ~ // 最適化は切っておいた方が良い $ foo hoge fuga ... ← 何か変だと気づいてデバッグしたくなった $ DEBUG=1 foo hoge fuga # 環境変数 DEBUG を設定して実行すると、デバッガを待つ 2021/09/08 14:25:34 PID 64122 is waiting the debugger to attach... 2021/09/08 14:26:41 Restarted. ← デバッガが接続すると再開する ... 上記で、sleep の位置をエラーチェックの後にしてしまうと、dlv がまだデバッグの準備が済んでいない間にプログラムが再開してしまい、breakpoint を先に抜けてしまうことがある。grep(1) の正規表現が dlv ではなく [d]lv となっているのは、検索をしている grep(1) 自身のプロセスにマッチさせないため。 引数まで含んだプロセス一覧を取得できる golang ライブラリってありませんかね? そうすれば上記、もうすこしきれいに、なおかつ OS 非依存に書けると思うんですが。 デバッギが USR1 あたりのシグナルを受信したら再始動という案もあったが、毎度シグナルを手投げするのがめんどくさかったので、タイマーにした。 // go - How can I see if the GoLand debugger is running in the program? - Stack Overflow
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

`go install` で、コマンドのパッケージパスとバージョンを保持しておきたい

どうも。昨日初冠雪した富士山を眺めつつ記事を書いている、アユタヤドットコムの那賀です。 go 1.16 から、グローバルへのツールのインストールには go install を使い、開発中のライブラリの管理には go get を使うということで、きっちり分かりやすくなりました(参考: Go1.16からの go get と go install について - Qiita ) しかしこの go install は、 $GOPATH/bin にパッケージ末尾の名前でコロンと実行ファイルを転げてくれるだけというとても思い切りの良い仕様なので、いくつかの情報が落ちます。 // go command - cmd/go - pkg.go.dev 具体的には、以下がやや辛い。特に、今入っているコマンドが最新なのかを確認したい場合や、バージョンアップを検討したい時などに。まあ、定期的に全部のコマンドを、問答無用で @latest で上書きするような go install を列挙したスクリプトを実行するのでも、運用上は特に困らないとは思うんですけども。 パッケージ名のフルパスが分からなくなるので、どこから入れたか分からなくなる どのバージョンを入れたか分からなくなる 簡易的にそれらの情報をファイル名として保持し、コマンド名へシンボリックリンクを張るようなシェルスクリプトを書いてみました。 $ go-install github.com/ericchiang/pup@v0.3.8 $ ls -l ~/go/bin/pup* pup -> pup-github.com%2Fericchiang%2Fpup@v0.3.8 pup-github.com%2Fericchiang%2Fpup@v0.3.8 $ go-install github.com/ericchiang/pup@latest $ ls -l ~/go/bin/pup* pup -> pup-github.com%2Fericchiang%2Fpup@v0.4.0 pup-github.com%2Fericchiang%2Fpup@v0.3.8 pup-github.com%2Fericchiang%2Fpup@v0.4.0 要点としては、パッケージ名を後ろ側から切り詰めながらモジュール名を探すところと、 go list -m --versions で対象のモジュールのバージョン情報を取り寄せているところだけです。簡易なものなので、複数指定に対応していない、インポートパスのワイルドカードを扱えない等の問題がありますが、ひとまず用をなすのでこのまま使ってみます。 go-install #!/bin/bash set -o nounset -o errexit -o pipefail : ${bindir:=$(go env BINDIR)} : ${bindir:=$(go env GOPATH)/bin} : ${bindir:=$HOME/go/bin} read -r package target_ver <<< $(echo "$1" | sed -nEe 's/^([^@]+)(@(.*))?/\1 \3/p') module="$package" while true do modversions=($(go list -m --versions "$module")) test ${#modversions[@]} -gt 1 && break module=$(echo "$module"| sed -nEe 's@/[^/]+$@@p') done latest_ver="${modversions[${#modversions[@]}-1]}" test -z "$target_ver" -o "$target_ver" = "latest" && target_ver=${latest_ver} basename=$(basename ${package}) rm -f ${bindir}/${basename} go install ${package}@${target_ver} name=$(echo "${basename}-${package}@${target_ver}" | sed -Ee 's@/@%2F@g') mv -f ${bindir}/${basename} ${bindir}/${name} ln -sf ${name} ${bindir}/${basename} 本当は、Golang のライブラリでパッケージ情報を問い合わせることができれば Go でツールを書こうかと思ったのですが、それらは軒並み Go 本体の internal の機能なので、呼べないんですよね…どこかに良いライブラリは無いかしら。 では。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む