chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

TinyGoでGDBを起動できない原因を探る

課題:tinygo gdbgdbを起動できない(Cygwin環境下)
目標:tinygo gdbgdbを起動できるようにする
取り組み:tinygoのgdb起動コードを探して、なぜエラーと判断されるのかを特定して、回避手段を検討する
結論: PATH変数に、arm-none-eabi-gdbの実行形式が置かれているフォルダ名を追加することで解消
残る課題:新しいエラーに遭遇、いろんな問題を考えCygwin環境からUbuntu環境に切り替える

詳細:
tinygoのソース一式はGitHubで公開されている。それを一旦落として、grepでエラーコードを検索する。結果、以下のコードでエラー判断されているようであった。
file: compileopts/target.go

// LookupGDB looks up a gdb executable.
func (spec *TargetSpec) LookupGDB() (string, error) {
        if len(spec.GDB) == 0 {
                return "", errors.New("gdb not configured in the target specification")
        }
        for _, d := range spec.GDB {
                _, err := exec.LookPath(d)
                if err == nil {
                        return d, nil
                }
        }
        return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
}

自分の知識では、引数見つけられないのだが、、メッセージから判断すると、spec.GDBには、[gdb-multiarch, arm-none-eabi-gdb]の2要素が入っていて、順番にexec.LookPath(d)で探しているのだろう。exec.LookPath(d)で該当のパスを見つけられず、エラーになっていると判断

Spiegel氏の記事によると、exec.LookPath関数は、os/exec標準パッケージらしい。だったら、この部分だけ切り出してgoで実行すれば、何がおかしいのか分かるはず。

LookPathを使ったサンプルを掲載してくれている人*1がいて、このコードを参考にgdbを探すコードを書いてみる。先人に倣って書くと以下のような感じか。変数binaryにはパスとコマンドが入っているはずなのに、argsで再度コマンドを指定するのが正しいのか、ちょっと理解できず。。

package main
import (
    "os"
    "os/exec"
    "syscall"
)

func main() {

    binary, lookErr := exec.LookPath("arm-none-eabi-gdb")
    if lookErr != nil {
        panic(lookErr)
    }
    args := []string{"arm-none-eabi-gdb", "-v"}
    env := os.Environ()
    execErr := syscall.Exec(binary, args, env)
    if execErr != nil {
        panic(execErr)
    }
}

実行してみる。確かに、PATHを通していないので怒られる

$ go run gdb.go
panic: exec: "arm-none-eabi-gdb": executable file not found in %PATH%

goroutine 1 [running]:
main.main()
        C:/cygwin64/home/<user_id>/lang/go/gdb_test/gdb.go:12 +0x3b
exit status 2

/usr/local/binにarm-none-eabi-gdbを置いていてPATHが通っているので、コマンド名だけで起動できる状態

$ arm-none-eabi-gdb
GNU gdb (GNU Tools for ARM Embedded Processors) 7.6.0.20131129-cvs
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i586-mingw32 --target=arm-none-eabi".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
(gdb) quit

なのに、LookPathだと見つけられない。LookPathの検索ディレクトリは環境変数($PATH)なのか、それとも、どこかで指定されているのか。。

$ go run gdb.go
panic: exec: "arm-none-eabi-gdb": executable file not found in %PATH%

goroutine 1 [running]:
main.main()
        C:/cygwin64/home/<user_id>/lang/go/gdb_test/gdb.go:12 +0x3b
exit status 2

os/execの仕様書によると、環境変数からPATHを取得しているらしい。cygwin環境でgoを走らせた場合の環境変数とは何を見ているのか。
試しに、/usr/binにtouchでarm-none-eabi-gdbを置くと再度エラーとなり、/usr/bin/にarm-none-eabi-gdb.exeを置くとファイルを見つけるのには成功した。だから、、LookPathで検索するディレクトリには/usr/local/binが含まれていなさそうである。

/usr/bin $ touch arm-none-eabi-gdb
/usr/bin $ touch arm-none-eabi-gdb.exe

多分検索PATHを環境変数から取っているので、以下のコードでGoが見ているPATHを確認できると思われる(LookPathのソースコードまでは裏取っていない)

    fmt.Printf(os.Getenv("PATH"))

実行結果は以下の通りで、cygwinの/usr/local/binがWindows環境のPATHとして見ている。だからPATH変数は実行時の環境を取ってきている。

C:\cygwin64\usr\local\tinygo0.26.0.windows-amd64\tinygo\bin;
C:\cygwin64\usr\local\bin;

根本原因は分からないが、、cygwinでsymblic linkで配置していたが、この場合、ファイルとしてみなされないのが原因のようであった。実体をコピーするとOK

$ pwd
/usr/local/bin
$ ls -la
total 1106
drwxr-xr-x 1 sumi Unknown+Group      0 Dec 10 19:30 .
drwxr-xr-x 1 sumi Unknown+Group      0 Dec 11 08:51 ..
drwxr-xr-x 1 sumi なし               0 Oct 13 22:30 __pycache__
-rwxr-xr-x 1 sumi なし             205 Mar  5  2022 ampy
lrwxrwxrwx 1 sumi なし              67 Dec 10 19:30 arm-none-eabi-gdb.exe -> /usr/local/GNUToolsARMEmbedded/4.8_2013q4/bin/arm-none-eabi-gdb.exe

Symbolic linkせずに実体を/usr/local/binに置く。

$ ls -la
total 5342
drwxr-xr-x 1 sumi Unknown+Group       0 Dec 11 10:51 .
drwxr-xr-x 1 sumi Unknown+Group       0 Dec 11 08:51 ..
drwxr-xr-x 1 sumi なし                0 Oct 13 22:30 __pycache__
-rwxr-xr-x 1 sumi なし              205 Mar  5  2022 ampy
-rw-r--r-- 1 sumi なし          4334592 Dec 11 10:54 arm-none-eabi-gdb.exe

実行時点でpanicになったが、gdbを見つけることはできた

$ go run gdb.go
panic: not supported by windows

goroutine 1 [running]:
main.main()
        C:/cygwin64/home/sumi/lang/go/gdb_test/gdb.go:21 +0x58
exit status 2

まとめ
(1) cygwinのSymbolic linkは lookPathではファイルとみなされない
(2)回避策として、/usr/local/bin/に gdbの実体を置くか、$PATHにgdbが置かれているパスを追加する

/usr/local/binにgdbの実体を置くのは避けたいので、以下のように$PATHにgdbの実体が置かれているPATHを追加する

export PATH=/usr/local/GNUToolsARMEmbedded/4.8_2013q4/bin:$PATH

GDBを見つけられない問題は解消したと判断して、tinygo gdbを実行する。以下の通り、gdbが無いとは怒れなくなった。が、次のエラーにでくわした。go.modが無いと。。

$ tinygo gdb --target pico
go: go.mod file not found in current directory or any parent directory; see 'go
help modules'

go mod initを実行するらしい。実行してみる

$ go mod init blinky
go: creating new go.mod: module blinky
go: to add module requirements and sums:
        go mod tidy

go.modが無いと怒られる件は解消した。次はopenocdが無いと怒られる。PATH通してないのでこれはその通りなのだが。。openocdはWindows上ではなく、RPi上で動かすつもりなので、今の設定ではまずい。設定上はリモート接続にしたい。

$ tinygo gdb  -target pico
error: failed to run openocd: exec: "openocd": executable file not found in %PATH%

tiny.goのメインと思われる、main.goを読む限り、リモート上で稼働するOpenOCDとTCP接続してgdbを起動するようなコードは見当たらず、gdbとopenOCDはどちらもローカル環境で動かすのが前提のようだ。だから、、(1)自分のような特殊な環境でも動作するように、main.goを改修する、(2)手動でopenOCDとgdbを起動してデバッグする、のいずれかとなる。go言語はほとんど分かっていないので、当面は後者の方法でデバッグすることにする。ただ、、シンボリックデバッグ等が可能なのかどうかも不明。実行形式に対して、デバッグ情報を含める、含めないを指定するオプションがあるのかどうか分からない。

公式サイトによると、-no-debug オプションを付けるとデバッグ情報を含めないようなので、このオプションがなければデバッグ情報が入っているはず。
Misc. Build Options | TinyGo

今後の開発手順は以下
tinygo buildで実行形式を作って、それをPicoに焼く(仮想ファイルシステムにコピーしてもいいし、OpenOCDを使ってFlash書き込みも可能)。デバッグ時、RPi上でOpenOCDを動作させ、Windows環境で起動したgdbからリモートで接続する。gdbデバッグ情報を含む実行形式を読み込んで、Pico上のプログラムをデバッグする。

第3の方法として、接続不可能なWindows上のOpenOCDを起動して、エラーを起こさせてから、gdbで再接続するという方法も考えられる。これをやってみた。

$ export PATH=/usr/local/openocd-0.10.0/bin:$PATH

$ tinygo gdb --target pico
GNU gdb (GNU Tools for ARM Embedded Processors) 7.6.0.20131129-cvs
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i586-mingw32 --target=arm-none-eabi".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from C:\cygwin64\tmp\tinygo3499458027\main...Dwarf Error: wrong
version in compilation unit header (is 5, should be 2, 3, or 4) [in module C:\cy
gwin64\tmp\tinygo3499458027\main]
(no debugging symbols found)...done.
:3333: 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続
できませんでした。または接続済みのホストが応答しなかったため、確立された接続は失
敗しました。

すると、、compilation unit headerというエラーが発生して、debugging symbolが読めない状況になった。
想像するに、LLVM経由でコンパイルされたDrawfのフォーマットが新しく、自分のgdbでは未対応ということではなかろうか。
デバッグシンボルのないデバッグってほとんど意味をなさないので、ここは解消が必要。Cygwin環境に最新のGDBを入れたら修正されるのではと想像するがだんだんと面倒になってきた。そもそもCygwin環境でどうにかしようというアプローチがまずいのではと思えてきた。Ubuntu 等の標準環境でコンパイラとデバッガ一式をそろえた方が最新版に統一されていいのではと思える。

■参考URL
Go でサブプロセスを起動する際は LookPath に気をつけろ!
サンプルで学ぶ Go 言語:Exec'ing Processes
exec package - os/exec - Go Packages

*1:原著者 Mark McGranaghan | 翻訳者:spinute