nm
6 分钟阅读
nm
Overview 概述
Nm lists the symbols defined or used by an object file, archive, or executable.
nm
列出对象文件、存案或可执行文件所定义或使用的符号。
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
=> bss 段符号
b static bss segment symbol
=> 静态bss 段符号
C constant address
=> 常量地址
U referenced but undefined symbol
=> 被引用但未定义的符号
Following established convention, the address is omitted for undefined symbols (type U).
按照既定惯例,未定义的符号(U
类型)省略了地址。
The options control the printed output:
以下这些选项(options )控制打印输出:
-n
an alias for -sort address (numeric),
for compatibility with other nm commands
=> 是 -sort 地址(数字)的别名,
为了与其他nm命令兼容
-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
=> 按照给定的顺序对输出进行排序(默认为名称)。
size的顺序从大到小排序
-type
print symbol type after name
=> 在名称之后打印符号类型
=== “doc.go”
```go
// 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
```
=== “nm.go”
```go
// 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.Fprintf(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()
}
```
=== “nm_test.go”
```go
// 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":
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)
}
```
=== “nm_cgo_test.go”
```go
// 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 (
"fmt"
"internal/obscuretestdata"
"internal/testenv"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"text/template"
)
var testnmpath string // path to nm command created for testing purposes
// The TestMain function creates a nm command for testing purposes and
// deletes it after the tests have been run.
func TestMain(m *testing.M) {
os.Exit(testMain(m))
}
func testMain(m *testing.M) int {
if !testenv.HasGoBuild() {
return 0
}
tmpDir, err := os.MkdirTemp("", "TestNM")
if err != nil {
fmt.Println("TempDir failed:", err)
return 2
}
defer os.RemoveAll(tmpDir)
testnmpath = filepath.Join(tmpDir, "testnm.exe")
gotool, err := testenv.GoTool()
if err != nil {
fmt.Println("GoTool failed:", err)
return 2
}
out, err := exec.Command(gotool, "build", "-o", testnmpath, "cmd/nm").CombinedOutput()
if err != nil {
fmt.Printf("go build -o %v cmd/nm: %v\n%s", testnmpath, err, string(out))
return 2
}
return m.Run()
}
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 := exec.Command(testnmpath, 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 := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
if err != nil {
t.Fatalf("building test executable failed: %s %s", err, out)
}
out, err = exec.Command(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 = exec.Command(testnmpath, 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 := exec.Command(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 = exec.Command(testnmpath, 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() {}
`
```