CLOVER🍀

That was when it all began.

go tool nmコマンドで、Goの実行可能ファイルの定義やシンボルを表示する

これは、なにをしたくて書いたもの?

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

GitHub - google/uuid: Go package for UUIDs based on RFC 4122 and DCE 1.1: Authentication and Security Services.

なんとなく、てっとり早く使えそうなものを選んだだけです。

こちらを使って作成した実行可能ファイルを、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

func NewRandom

ビルドして起動。ビルドして生成される実行可能ファイルの名前は、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の実行可能ファイルに含まれている内容を確認してみました。

実行可能ファイルの状態からでも、いろいろわかるものですねぇ。