1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"bufio"
9	"flag"
10	"fmt"
11	"log"
12	"os"
13	"sort"
14
15	"cmd/internal/objfile"
16	"cmd/internal/telemetry/counter"
17)
18
19const helpText = `usage: go tool nm [options] file...
20  -n
21      an alias for -sort address (numeric),
22      for compatibility with other nm commands
23  -size
24      print symbol size in decimal between address and type
25  -sort {address,name,none,size}
26      sort output in the given order (default name)
27      size orders from largest to smallest
28  -type
29      print symbol type after name
30`
31
32func usage() {
33	fmt.Fprint(os.Stderr, helpText)
34	os.Exit(2)
35}
36
37var (
38	sortOrder = flag.String("sort", "name", "")
39	printSize = flag.Bool("size", false, "")
40	printType = flag.Bool("type", false, "")
41
42	filePrefix = false
43)
44
45func init() {
46	flag.Var(nflag(0), "n", "") // alias for -sort address
47}
48
49type nflag int
50
51func (nflag) IsBoolFlag() bool {
52	return true
53}
54
55func (nflag) Set(value string) error {
56	if value == "true" {
57		*sortOrder = "address"
58	}
59	return nil
60}
61
62func (nflag) String() string {
63	if *sortOrder == "address" {
64		return "true"
65	}
66	return "false"
67}
68
69func main() {
70	log.SetFlags(0)
71	counter.Open()
72	flag.Usage = usage
73	flag.Parse()
74	counter.Inc("nm/invocations")
75	counter.CountFlags("nm/flag:", *flag.CommandLine)
76
77	switch *sortOrder {
78	case "address", "name", "none", "size":
79		// ok
80	default:
81		fmt.Fprintf(os.Stderr, "nm: unknown sort order %q\n", *sortOrder)
82		os.Exit(2)
83	}
84
85	args := flag.Args()
86	filePrefix = len(args) > 1
87	if len(args) == 0 {
88		flag.Usage()
89	}
90
91	for _, file := range args {
92		nm(file)
93	}
94
95	os.Exit(exitCode)
96}
97
98var exitCode = 0
99
100func errorf(format string, args ...any) {
101	log.Printf(format, args...)
102	exitCode = 1
103}
104
105func nm(file string) {
106	f, err := objfile.Open(file)
107	if err != nil {
108		errorf("%v", err)
109		return
110	}
111	defer f.Close()
112
113	w := bufio.NewWriter(os.Stdout)
114
115	entries := f.Entries()
116
117	var found bool
118
119	for _, e := range entries {
120		syms, err := e.Symbols()
121		if err != nil {
122			errorf("reading %s: %v", file, err)
123		}
124		if len(syms) == 0 {
125			continue
126		}
127
128		found = true
129
130		switch *sortOrder {
131		case "address":
132			sort.Slice(syms, func(i, j int) bool { return syms[i].Addr < syms[j].Addr })
133		case "name":
134			sort.Slice(syms, func(i, j int) bool { return syms[i].Name < syms[j].Name })
135		case "size":
136			sort.Slice(syms, func(i, j int) bool { return syms[i].Size > syms[j].Size })
137		}
138
139		for _, sym := range syms {
140			if len(entries) > 1 {
141				name := e.Name()
142				if name == "" {
143					fmt.Fprintf(w, "%s(%s):\t", file, "_go_.o")
144				} else {
145					fmt.Fprintf(w, "%s(%s):\t", file, name)
146				}
147			} else if filePrefix {
148				fmt.Fprintf(w, "%s:\t", file)
149			}
150			if sym.Code == 'U' {
151				fmt.Fprintf(w, "%8s", "")
152			} else {
153				fmt.Fprintf(w, "%8x", sym.Addr)
154			}
155			if *printSize {
156				fmt.Fprintf(w, " %10d", sym.Size)
157			}
158			fmt.Fprintf(w, " %c %s", sym.Code, sym.Name)
159			if *printType && sym.Type != "" {
160				fmt.Fprintf(w, " %s", sym.Type)
161			}
162			fmt.Fprintf(w, "\n")
163		}
164	}
165
166	if !found {
167		errorf("reading %s: no symbols", file)
168	}
169
170	w.Flush()
171}
172