これは、なにをしたくて書いたもの?
Goで作ったアプリケーションに含まれるライブラリとかの情報ってわかるのかな?と思って調べてみたのですが、コマンドが
あるようです。
go tool nm
というコマンドが近い感じですね。
nm - The Go Programming Language
ちょっと試してみましょう。
環境
今回の環境は、こちら。
$ go version go version go1.15.6 linux/amd64
go tool nmコマンド
go tool nm
コマンドのドキュメントはこちら。
nm - The Go Programming Language
ヘルプも載せておきましょう。
$ go tool nm usage: go tool nm [options] file... -n an alias for -sort address (numeric), for compatibility with other nm commands -size print symbol size in decimal between address and type -sort {address,name,none,size} sort output in the given order (default name) size orders from largest to smallest -type print symbol type after name
このコマンドは、オブジェクトファイル、アーカイブファイル、実行可能ファイルに含まれるシンボルを表示するコマンドです。
なので、go build
して生成したファイルなどが対象になりますね。
コマンドを実行すると、アドレス、タイプ、シンボルの名前が表示されます。このうちタイプは、以下の1文字で表現されます。
- T … text (code) segment symbol
- t … static text segment symbol
- R … read-only data segment symbol
- r … static read-only data segment symbol
- D … data segment symbol
- d … static data segment symbol
- B … bss segment symbol
- b … static bss segment symbol
- C … constant address
- U … referenced but undefined symbol
とりあえず、試してみるとしましょう。
お題
なにかしら依存するモジュールを含めてみようかなということで、次の2つを使ってみることにします。
Echo - High performance, minimalist Go web framework
なんとなく、てっとり早く使えそうなものを選んだだけです。
こちらを使って作成した実行可能ファイルを、go tool nm
コマンドで解析してみたいと思います。
サンプルプログラムの作成と動作確認
では、サンプルプログラムを作っていきます。
モジュールの作成。
$ go mod init nm-command-example go: creating new go.mod: module nm-command-example
go.mod
module nm-command-example go 1.15 require ( github.com/google/uuid v1.1.4 // indirect github.com/labstack/echo/v4 v4.1.17 // indirect )
go.sum
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/labstack/echo v1.4.4 h1:1bEiBNeGSUKxcPDGfZ/7IgdhJJZx8wV/pICJh4W2NJI= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo/v4 v4.1.17 h1:PQIBaRplyRy3OjwILGkPg89JRtH2x5bssi59G2EL3fo= github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
ソースコードは、こんな感じにしました。
main.go
package main import ( "github.com/google/uuid" "github.com/labstack/echo/v4" "net/http" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { uuid4, _ := uuid.NewRandom() return c.String(http.StatusOK, "UUID v4 = "+uuid4.String()) }) e.Logger.Fatal(e.Start(":1323")) }
EchoのQuick Startの内容を少し変え、UUID Version 4を使った感じですね。
Guide | Echo - High performance, minimalist Go web framework
ビルドして起動。ビルドして生成される実行可能ファイルの名前は、nm-command-example
です。
$ go build $ ./nm-command-example ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v4.1.17 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:1323
動作確認。
$ curl localhost:1323 UUID v4 = 62ca37ea-598f-485c-932e-1ac4c30c8b19
OKです。
では、この実行可能ファイルをgo tool nm
コマンドで解析してみます。
$ go tool nm nm-command-example
すると、大量のシンボルが表示されます…。
7889fc r $f32.00000001 788a00 r $f32.358637bd 788a04 r $f32.3f800000 788a08 r $f32.4b189680 788a0c r $f32.5f000000 788a10 r $f32.6258d727 788a14 r $f32.7f800000 788a18 r $f32.80000000 788a1c r $f32.80000001 788a20 r $f32.cb189680 788a24 r $f32.ff800000 788ae8 r $f64.0000000000000001 788af0 r $f64.0010000000000000 788af8 r $f64.0020000000000000 788b00 r $f64.0080000000000000 788b08 r $f64.3b90000000000000 788b10 r $f64.3c00000000000000 788b18 r $f64.3c70000000000000 〜省略〜 5221e0 T bufio.(*Scanner).Scan 521600 T bufio.(*Writer).Flush 521d20 T bufio.(*Writer).ReadFrom 5215e0 T bufio.(*Writer).Size 521800 T bufio.(*Writer).Write 521a60 T bufio.(*Writer).WriteByte 521b40 T bufio.(*Writer).WriteString 8f6700 D bufio..inittask 93f9a0 D bufio.ErrAdvanceTooFar 93f9b0 D bufio.ErrBadReadCount 93f9c0 D bufio.ErrBufferFull 93f9d0 D bufio.ErrFinalToken 〜省略〜 53f900 T github.com/google/uuid.(*UUID).MarshalBinary 53f9a0 T github.com/google/uuid.(*UUID).MarshalText 53fa80 T github.com/google/uuid.(*UUID).String 53e540 T github.com/google/uuid.(*UUID).UnmarshalBinary 78da00 R github.com/google/uuid.(*UUID).UnmarshalBinary.stkobj 53e420 T github.com/google/uuid.(*UUID).UnmarshalText 53fb80 T github.com/google/uuid.(*UUID).Value 53fcc0 T github.com/google/uuid.(*invalidLengthError).Error 78da20 R github.com/google/uuid.(*invalidLengthError).Error.stkobj 8f99e0 D github.com/google/uuid..inittask 53f2a0 T github.com/google/uuid.Must 971970 D github.com/google/uuid.NameSpaceDNS 971980 D github.com/google/uuid.NameSpaceOID 971990 D github.com/google/uuid.NameSpaceURL 9719a0 D github.com/google/uuid.NameSpaceX500 53f640 T github.com/google/uuid.NewRandomFromReader 9719b0 D github.com/google/uuid.Nil 53e800 T github.com/google/uuid.Parse 78da40 R github.com/google/uuid.Parse.stkobj 53ed20 T github.com/google/uuid.ParseBytes 78da60 R github.com/google/uuid.ParseBytes.stkobj 53e4c0 T github.com/google/uuid.UUID.MarshalBinary 53e380 T github.com/google/uuid.UUID.MarshalText 53f320 T github.com/google/uuid.UUID.String 53e640 T github.com/google/uuid.UUID.Value 53f3e0 T github.com/google/uuid.encodeHex 53f740 T github.com/google/uuid.init 53e740 T github.com/google/uuid.invalidLengthError.Error 78da80 R github.com/google/uuid.invalidLengthError.Error.stkobj 93fdf0 D github.com/google/uuid.rander 8faa00 D github.com/google/uuid.xvalues 69e900 T github.com/labstack/echo/v4.(*DefaultBinder).Bind 7990c0 R github.com/labstack/echo/v4.(*DefaultBinder).Bind.stkobj 6a0500 T github.com/labstack/echo/v4.(*DefaultBinder).bindData 798280 R github.com/labstack/echo/v4.(*DefaultBinder).bindData.stkobj 6a75c0 T github.com/labstack/echo/v4.(*Echo).Close 〜省略〜
表示される内容は3つあり、それぞれアドレス、タイプ、シンボル、ですね。
ここで、go.mod
の内容からドメイン部分を抜き出して、これを元にgo tool nm
の結果を絞り込むと、実行可能ファイルが
依存しているモジュールを見れるのでは?と思いまして。
確認してみましょう。まずは、go.sum
からドメイン部分のみ表示
$ perl -wp -e 's!^(.+?)/.+!$1!' go.sum | sort -u github.com golang.org gopkg.in
こちらのドメインの内容を使って、go tool nm
の結果を絞り込み、整形してみます。
$ go tool nm nm-command-example | \ grep -E 'github.com|golang.org|gopkg.in' | \ perl -wp -e 's!.+((github.com|golang.org|gopkg.in)/[^/.]+/([^/.]+)?).+!$1!' | \ sort -u github.com/google/uuid github.com/labstack/echo github.com/labstack/gommon github.com/mattn/go-colorable github.com/mattn/go-isatty github.com/valyala/bytebufferpool github.com/valyala/fasttemplate golang.org/x/crypto golang.org/x/net golang.org/x/sys golang.org/x/text
お、抽出できましたね。推移的な依存関係含めて入ってそうな感じです。
go.sum
ファイルの内容と比較してみましょう。
$ perl -wp -e 's!^([^/]+/[^/.]+(/[^/. ]+)?).*!$1!' go.sum | sort -u github.com/davecgh/go-spew github.com/dgrijalva/jwt-go github.com/google/uuid github.com/labstack/echo github.com/labstack/gommon github.com/mattn/go-colorable github.com/mattn/go-isatty github.com/pmezard/go-difflib github.com/stretchr/objx github.com/stretchr/testify github.com/valyala/bytebufferpool github.com/valyala/fasttemplate golang.org/x/crypto golang.org/x/net golang.org/x/sys golang.org/x/text golang.org/x/tools gopkg.in/check gopkg.in/yaml
go.sum
ファイルから作った結果の方が、go tool nm
の結果よりも多いですね。
実行可能ファイルに含まれていないものもある、という感じでしょうか。確認してみましょう。
$ go tool nm nm-command-example | grep 'gopkg.in' $ go tool nm nm-command-example | grep 'golang.org/x/tools' $ go tool nm nm-command-example | grep 'github.com/stretchr/testify'
確かに入っていなさそうです。
では、自分が書いたコードで使っているモジュールも外してみましょうか。
UUID version 4を使うのをやめてみましょう。
main.go
package main import ( // "github.com/google/uuid" "github.com/labstack/echo/v4" "net/http" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { // uuid4, _ := uuid.NewRandom() // return c.String(http.StatusOK, "UUID v4 = "+uuid4.String()) return c.String(http.StatusOK, "Hello, World!") }) e.Logger.Fatal(e.Start(":1323")) }
ビルド。
$ go build
再度確認してみます。
$ go tool nm nm-command-example | \ grep -E 'github.com|golang.org|gopkg.in' | \ perl -wp -e 's!.+((github.com|golang.org|gopkg.in)/[^/.]+/([^/.]+)?).+!$1!' | \ sort -u github.com/labstack/echo github.com/labstack/gommon github.com/mattn/go-colorable github.com/mattn/go-isatty github.com/valyala/bytebufferpool github.com/valyala/fasttemplate golang.org/x/crypto golang.org/x/net golang.org/x/sys golang.org/x/text
go tool nm
の結果から、github.com/google/uuid
がなくなりましたね。実行可能ファイルを作成する際に、本当に使っているものしか
含まれないんでしょうねぇ。
というわけで、確認できました、と。
しかしこの情報、どうやって取ってるんでしょうねぇ。コマンドのソースを見てみました。
https://github.com/golang/go/blob/go1.15.6/src/cmd/nm/nm.go#L111
すごく簡単に取れそうなのですが、internal
パッケージなので外部からアクセスできません…。
Go 1.5 is released - The Go Blog
Go 1.4 "Internal" Packages - Google ドキュメント
まとめ
go tool nm
コマンドで、Goの実行可能ファイルに含まれている内容を確認してみました。
実行可能ファイルの状態からでも、いろいろわかるものですねぇ。