summaryrefslogtreecommitdiffstats
path: root/src/cmd/nm
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/nm')
-rw-r--r--src/cmd/nm/doc.go41
-rw-r--r--src/cmd/nm/nm.go167
-rw-r--r--src/cmd/nm/nm_cgo_test.go57
-rw-r--r--src/cmd/nm/nm_test.go364
4 files changed, 629 insertions, 0 deletions
diff --git a/src/cmd/nm/doc.go b/src/cmd/nm/doc.go
new file mode 100644
index 0000000..b11a2a8
--- /dev/null
+++ b/src/cmd/nm/doc.go
@@ -0,0 +1,41 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Nm lists the symbols defined or used by an object file, archive, or executable.
+//
+// Usage:
+//
+// go tool nm [options] file...
+//
+// The default output prints one line per symbol, with three space-separated
+// fields giving the address (in hexadecimal), type (a character), and name of
+// the symbol. The types are:
+//
+// 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
+//
+// Following established convention, the address is omitted for undefined
+// symbols (type U).
+//
+// The options control the printed output:
+//
+// -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
+package main
diff --git a/src/cmd/nm/nm.go b/src/cmd/nm/nm.go
new file mode 100644
index 0000000..78fa600
--- /dev/null
+++ b/src/cmd/nm/nm.go
@@ -0,0 +1,167 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "sort"
+
+ "cmd/internal/objfile"
+)
+
+const helpText = `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
+`
+
+func usage() {
+ fmt.Fprint(os.Stderr, helpText)
+ os.Exit(2)
+}
+
+var (
+ sortOrder = flag.String("sort", "name", "")
+ printSize = flag.Bool("size", false, "")
+ printType = flag.Bool("type", false, "")
+
+ filePrefix = false
+)
+
+func init() {
+ flag.Var(nflag(0), "n", "") // alias for -sort address
+}
+
+type nflag int
+
+func (nflag) IsBoolFlag() bool {
+ return true
+}
+
+func (nflag) Set(value string) error {
+ if value == "true" {
+ *sortOrder = "address"
+ }
+ return nil
+}
+
+func (nflag) String() string {
+ if *sortOrder == "address" {
+ return "true"
+ }
+ return "false"
+}
+
+func main() {
+ log.SetFlags(0)
+ flag.Usage = usage
+ flag.Parse()
+
+ switch *sortOrder {
+ case "address", "name", "none", "size":
+ // ok
+ default:
+ fmt.Fprintf(os.Stderr, "nm: unknown sort order %q\n", *sortOrder)
+ os.Exit(2)
+ }
+
+ args := flag.Args()
+ filePrefix = len(args) > 1
+ if len(args) == 0 {
+ flag.Usage()
+ }
+
+ for _, file := range args {
+ nm(file)
+ }
+
+ os.Exit(exitCode)
+}
+
+var exitCode = 0
+
+func errorf(format string, args ...any) {
+ log.Printf(format, args...)
+ exitCode = 1
+}
+
+func nm(file string) {
+ f, err := objfile.Open(file)
+ if err != nil {
+ errorf("%v", err)
+ return
+ }
+ defer f.Close()
+
+ w := bufio.NewWriter(os.Stdout)
+
+ entries := f.Entries()
+
+ var found bool
+
+ for _, e := range entries {
+ syms, err := e.Symbols()
+ if err != nil {
+ errorf("reading %s: %v", file, err)
+ }
+ if len(syms) == 0 {
+ continue
+ }
+
+ found = true
+
+ switch *sortOrder {
+ case "address":
+ sort.Slice(syms, func(i, j int) bool { return syms[i].Addr < syms[j].Addr })
+ case "name":
+ sort.Slice(syms, func(i, j int) bool { return syms[i].Name < syms[j].Name })
+ case "size":
+ sort.Slice(syms, func(i, j int) bool { return syms[i].Size > syms[j].Size })
+ }
+
+ for _, sym := range syms {
+ if len(entries) > 1 {
+ name := e.Name()
+ if name == "" {
+ fmt.Fprintf(w, "%s(%s):\t", file, "_go_.o")
+ } else {
+ fmt.Fprintf(w, "%s(%s):\t", file, name)
+ }
+ } else if filePrefix {
+ fmt.Fprintf(w, "%s:\t", file)
+ }
+ if sym.Code == 'U' {
+ fmt.Fprintf(w, "%8s", "")
+ } else {
+ fmt.Fprintf(w, "%8x", sym.Addr)
+ }
+ if *printSize {
+ fmt.Fprintf(w, " %10d", sym.Size)
+ }
+ fmt.Fprintf(w, " %c %s", sym.Code, sym.Name)
+ if *printType && sym.Type != "" {
+ fmt.Fprintf(w, " %s", sym.Type)
+ }
+ fmt.Fprintf(w, "\n")
+ }
+ }
+
+ if !found {
+ errorf("reading %s: no symbols", file)
+ }
+
+ w.Flush()
+}
diff --git a/src/cmd/nm/nm_cgo_test.go b/src/cmd/nm/nm_cgo_test.go
new file mode 100644
index 0000000..210577e
--- /dev/null
+++ b/src/cmd/nm/nm_cgo_test.go
@@ -0,0 +1,57 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build cgo
+
+package main
+
+import (
+ "runtime"
+ "testing"
+)
+
+func canInternalLink() bool {
+ switch runtime.GOOS {
+ case "aix":
+ return false
+ case "dragonfly":
+ return false
+ case "freebsd":
+ switch runtime.GOARCH {
+ case "arm64", "riscv64":
+ return false
+ }
+ case "linux":
+ switch runtime.GOARCH {
+ case "arm64", "loong64", "mips64", "mips64le", "mips", "mipsle", "ppc64", "ppc64le", "riscv64":
+ return false
+ }
+ case "openbsd":
+ switch runtime.GOARCH {
+ case "arm64", "mips64":
+ return false
+ }
+ case "windows":
+ switch runtime.GOARCH {
+ case "arm64":
+ return false
+ }
+ }
+ return true
+}
+
+func TestInternalLinkerCgoExec(t *testing.T) {
+ if !canInternalLink() {
+ t.Skip("skipping; internal linking is not supported")
+ }
+ testGoExec(t, true, false)
+}
+
+func TestExternalLinkerCgoExec(t *testing.T) {
+ testGoExec(t, true, true)
+}
+
+func TestCgoLib(t *testing.T) {
+ testGoLib(t, true)
+}
diff --git a/src/cmd/nm/nm_test.go b/src/cmd/nm/nm_test.go
new file mode 100644
index 0000000..7d8358e
--- /dev/null
+++ b/src/cmd/nm/nm_test.go
@@ -0,0 +1,364 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "internal/obscuretestdata"
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+ "text/template"
+)
+
+// TestMain executes the test binary as the nm command if
+// GO_NMTEST_IS_NM is set, and runs the tests otherwise.
+func TestMain(m *testing.M) {
+ if os.Getenv("GO_NMTEST_IS_NM") != "" {
+ main()
+ os.Exit(0)
+ }
+
+ os.Setenv("GO_NMTEST_IS_NM", "1") // Set for subprocesses to inherit.
+ os.Exit(m.Run())
+}
+
+// nmPath returns the path to the "nm" binary to run.
+func nmPath(t testing.TB) string {
+ t.Helper()
+ testenv.MustHaveExec(t)
+
+ nmPathOnce.Do(func() {
+ nmExePath, nmPathErr = os.Executable()
+ })
+ if nmPathErr != nil {
+ t.Fatal(nmPathErr)
+ }
+ return nmExePath
+}
+
+var (
+ nmPathOnce sync.Once
+ nmExePath string
+ nmPathErr error
+)
+
+func TestNonGoExecs(t *testing.T) {
+ t.Parallel()
+ testfiles := []string{
+ "debug/elf/testdata/gcc-386-freebsd-exec",
+ "debug/elf/testdata/gcc-amd64-linux-exec",
+ "debug/macho/testdata/gcc-386-darwin-exec.base64", // golang.org/issue/34986
+ "debug/macho/testdata/gcc-amd64-darwin-exec.base64", // golang.org/issue/34986
+ // "debug/pe/testdata/gcc-amd64-mingw-exec", // no symbols!
+ "debug/pe/testdata/gcc-386-mingw-exec",
+ "debug/plan9obj/testdata/amd64-plan9-exec",
+ "debug/plan9obj/testdata/386-plan9-exec",
+ "internal/xcoff/testdata/gcc-ppc64-aix-dwarf2-exec",
+ }
+ for _, f := range testfiles {
+ exepath := filepath.Join(testenv.GOROOT(t), "src", f)
+ if strings.HasSuffix(f, ".base64") {
+ tf, err := obscuretestdata.DecodeToTempFile(exepath)
+ if err != nil {
+ t.Errorf("obscuretestdata.DecodeToTempFile(%s): %v", exepath, err)
+ continue
+ }
+ defer os.Remove(tf)
+ exepath = tf
+ }
+
+ cmd := testenv.Command(t, nmPath(t), exepath)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Errorf("go tool nm %v: %v\n%s", exepath, err, string(out))
+ }
+ }
+}
+
+func testGoExec(t *testing.T, iscgo, isexternallinker bool) {
+ t.Parallel()
+ tmpdir, err := os.MkdirTemp("", "TestGoExec")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ src := filepath.Join(tmpdir, "a.go")
+ file, err := os.Create(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = template.Must(template.New("main").Parse(testexec)).Execute(file, iscgo)
+ if e := file.Close(); err == nil {
+ err = e
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ exe := filepath.Join(tmpdir, "a.exe")
+ args := []string{"build", "-o", exe}
+ if iscgo {
+ linkmode := "internal"
+ if isexternallinker {
+ linkmode = "external"
+ }
+ args = append(args, "-ldflags", "-linkmode="+linkmode)
+ }
+ args = append(args, src)
+ out, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput()
+ if err != nil {
+ t.Fatalf("building test executable failed: %s %s", err, out)
+ }
+
+ out, err = testenv.Command(t, exe).CombinedOutput()
+ if err != nil {
+ t.Fatalf("running test executable failed: %s %s", err, out)
+ }
+ names := make(map[string]string)
+ for _, line := range strings.Split(string(out), "\n") {
+ if line == "" {
+ continue
+ }
+ f := strings.Split(line, "=")
+ if len(f) != 2 {
+ t.Fatalf("unexpected output line: %q", line)
+ }
+ names["main."+f[0]] = f[1]
+ }
+
+ runtimeSyms := map[string]string{
+ "runtime.text": "T",
+ "runtime.etext": "T",
+ "runtime.rodata": "R",
+ "runtime.erodata": "R",
+ "runtime.epclntab": "R",
+ "runtime.noptrdata": "D",
+ }
+
+ if runtime.GOOS == "aix" && iscgo {
+ // pclntab is moved to .data section on AIX.
+ runtimeSyms["runtime.epclntab"] = "D"
+ }
+
+ out, err = testenv.Command(t, nmPath(t), exe).CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool nm: %v\n%s", err, string(out))
+ }
+
+ relocated := func(code string) bool {
+ if runtime.GOOS == "aix" {
+ // On AIX, .data and .bss addresses are changed by the loader.
+ // Therefore, the values returned by the exec aren't the same
+ // than the ones inside the symbol table.
+ // In case of cgo, .text symbols are also changed.
+ switch code {
+ case "T", "t", "R", "r":
+ return iscgo
+ case "D", "d", "B", "b":
+ return true
+ }
+ }
+ if runtime.GOOS == "windows" {
+ return true
+ }
+ if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+ return true // On darwin/arm64 everything is PIE
+ }
+ return false
+ }
+
+ dups := make(map[string]bool)
+ for _, line := range strings.Split(string(out), "\n") {
+ f := strings.Fields(line)
+ if len(f) < 3 {
+ continue
+ }
+ name := f[2]
+ if addr, found := names[name]; found {
+ if want, have := addr, "0x"+f[0]; have != want {
+ if !relocated(f[1]) {
+ t.Errorf("want %s address for %s symbol, but have %s", want, name, have)
+ }
+ }
+ delete(names, name)
+ }
+ if _, found := dups[name]; found {
+ t.Errorf("duplicate name of %q is found", name)
+ }
+ if stype, found := runtimeSyms[name]; found {
+ if runtime.GOOS == "plan9" && stype == "R" {
+ // no read-only data segment symbol on Plan 9
+ stype = "D"
+ }
+ if want, have := stype, strings.ToUpper(f[1]); have != want {
+ t.Errorf("want %s type for %s symbol, but have %s", want, name, have)
+ }
+ delete(runtimeSyms, name)
+ }
+ }
+ if len(names) > 0 {
+ t.Errorf("executable is missing %v symbols", names)
+ }
+ if len(runtimeSyms) > 0 {
+ t.Errorf("executable is missing %v symbols", runtimeSyms)
+ }
+}
+
+func TestGoExec(t *testing.T) {
+ testGoExec(t, false, false)
+}
+
+func testGoLib(t *testing.T, iscgo bool) {
+ t.Parallel()
+ tmpdir, err := os.MkdirTemp("", "TestGoLib")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ gopath := filepath.Join(tmpdir, "gopath")
+ libpath := filepath.Join(gopath, "src", "mylib")
+
+ err = os.MkdirAll(libpath, 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+ src := filepath.Join(libpath, "a.go")
+ file, err := os.Create(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = template.Must(template.New("mylib").Parse(testlib)).Execute(file, iscgo)
+ if e := file.Close(); err == nil {
+ err = e
+ }
+ if err == nil {
+ err = os.WriteFile(filepath.Join(libpath, "go.mod"), []byte("module mylib\n"), 0666)
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=archive", "-o", "mylib.a", ".")
+ cmd.Dir = libpath
+ cmd.Env = append(os.Environ(), "GOPATH="+gopath)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("building test lib failed: %s %s", err, out)
+ }
+ mylib := filepath.Join(libpath, "mylib.a")
+
+ out, err = testenv.Command(t, nmPath(t), mylib).CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool nm: %v\n%s", err, string(out))
+ }
+ type symType struct {
+ Type string
+ Name string
+ CSym bool
+ Found bool
+ }
+ var syms = []symType{
+ {"B", "mylib.Testdata", false, false},
+ {"T", "mylib.Testfunc", false, false},
+ }
+ if iscgo {
+ syms = append(syms, symType{"B", "mylib.TestCgodata", false, false})
+ syms = append(syms, symType{"T", "mylib.TestCgofunc", false, false})
+ if runtime.GOOS == "darwin" || runtime.GOOS == "ios" || (runtime.GOOS == "windows" && runtime.GOARCH == "386") {
+ syms = append(syms, symType{"D", "_cgodata", true, false})
+ syms = append(syms, symType{"T", "_cgofunc", true, false})
+ } else if runtime.GOOS == "aix" {
+ syms = append(syms, symType{"D", "cgodata", true, false})
+ syms = append(syms, symType{"T", ".cgofunc", true, false})
+ } else {
+ syms = append(syms, symType{"D", "cgodata", true, false})
+ syms = append(syms, symType{"T", "cgofunc", true, false})
+ }
+ }
+
+ for _, line := range strings.Split(string(out), "\n") {
+ f := strings.Fields(line)
+ var typ, name string
+ var csym bool
+ if iscgo {
+ if len(f) < 4 {
+ continue
+ }
+ csym = !strings.Contains(f[0], "_go_.o")
+ typ = f[2]
+ name = f[3]
+ } else {
+ if len(f) < 3 {
+ continue
+ }
+ typ = f[1]
+ name = f[2]
+ }
+ for i := range syms {
+ sym := &syms[i]
+ if sym.Type == typ && sym.Name == name && sym.CSym == csym {
+ if sym.Found {
+ t.Fatalf("duplicate symbol %s %s", sym.Type, sym.Name)
+ }
+ sym.Found = true
+ }
+ }
+ }
+ for _, sym := range syms {
+ if !sym.Found {
+ t.Errorf("cannot found symbol %s %s", sym.Type, sym.Name)
+ }
+ }
+}
+
+func TestGoLib(t *testing.T) {
+ testGoLib(t, false)
+}
+
+const testexec = `
+package main
+
+import "fmt"
+{{if .}}import "C"
+{{end}}
+
+func main() {
+ testfunc()
+}
+
+var testdata uint32
+
+func testfunc() {
+ fmt.Printf("main=%p\n", main)
+ fmt.Printf("testfunc=%p\n", testfunc)
+ fmt.Printf("testdata=%p\n", &testdata)
+}
+`
+
+const testlib = `
+package mylib
+
+{{if .}}
+// int cgodata = 5;
+// void cgofunc(void) {}
+import "C"
+
+var TestCgodata = C.cgodata
+
+func TestCgofunc() {
+ C.cgofunc()
+}
+{{end}}
+
+var Testdata uint32
+
+func Testfunc() {}
+`