summaryrefslogtreecommitdiff
path: root/src/cmd/nm
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/nm')
-rw-r--r--src/cmd/nm/Makefile5
-rw-r--r--src/cmd/nm/debug_goobj.go670
-rw-r--r--src/cmd/nm/doc.go56
-rw-r--r--src/cmd/nm/elf.go57
-rw-r--r--src/cmd/nm/goobj.go67
-rw-r--r--src/cmd/nm/macho.go69
-rw-r--r--src/cmd/nm/nm.c401
-rw-r--r--src/cmd/nm/nm.go184
-rw-r--r--src/cmd/nm/nm_test.go99
-rw-r--r--src/cmd/nm/pe.go98
-rw-r--r--src/cmd/nm/plan9obj.go48
11 files changed, 1329 insertions, 425 deletions
diff --git a/src/cmd/nm/Makefile b/src/cmd/nm/Makefile
deleted file mode 100644
index 3f528d751..000000000
--- a/src/cmd/nm/Makefile
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright 2012 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.
-
-include ../../Make.dist
diff --git a/src/cmd/nm/debug_goobj.go b/src/cmd/nm/debug_goobj.go
new file mode 100644
index 000000000..9a067e2b9
--- /dev/null
+++ b/src/cmd/nm/debug_goobj.go
@@ -0,0 +1,670 @@
+// DO NOT EDIT. Generated by code.google.com/p/rsc/cmd/bundle
+// bundle -p main -x goobj_ debug/goobj
+
+/* read.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 goobj implements reading of Go object files and archives.
+//
+// TODO(rsc): Decide where this package should live. (golang.org/issue/6932)
+// TODO(rsc): Decide the appropriate integer types for various fields.
+// TODO(rsc): Write tests. (File format still up in the air a little.)
+
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+// A SymKind describes the kind of memory represented by a symbol.
+type goobj_SymKind int
+
+// This list is taken from include/link.h.
+
+// Defined SymKind values.
+// TODO(rsc): Give idiomatic Go names.
+// TODO(rsc): Reduce the number of symbol types in the object files.
+const (
+ _ goobj_SymKind = iota
+
+ // readonly, executable
+ goobj_STEXT
+ goobj_SELFRXSECT
+
+ // readonly, non-executable
+ goobj_STYPE
+ goobj_SSTRING
+ goobj_SGOSTRING
+ goobj_SGOFUNC
+ goobj_SRODATA
+ goobj_SFUNCTAB
+ goobj_STYPELINK
+ goobj_SSYMTAB // TODO: move to unmapped section
+ goobj_SPCLNTAB
+ goobj_SELFROSECT
+
+ // writable, non-executable
+ goobj_SMACHOPLT
+ goobj_SELFSECT
+ goobj_SMACHO // Mach-O __nl_symbol_ptr
+ goobj_SMACHOGOT
+ goobj_SNOPTRDATA
+ goobj_SINITARR
+ goobj_SDATA
+ goobj_SWINDOWS
+ goobj_SBSS
+ goobj_SNOPTRBSS
+ goobj_STLSBSS
+
+ // not mapped
+ goobj_SXREF
+ goobj_SMACHOSYMSTR
+ goobj_SMACHOSYMTAB
+ goobj_SMACHOINDIRECTPLT
+ goobj_SMACHOINDIRECTGOT
+ goobj_SFILE
+ goobj_SFILEPATH
+ goobj_SCONST
+ goobj_SDYNIMPORT
+ goobj_SHOSTOBJ
+)
+
+var goobj_symKindStrings = []string{
+ goobj_SBSS: "SBSS",
+ goobj_SCONST: "SCONST",
+ goobj_SDATA: "SDATA",
+ goobj_SDYNIMPORT: "SDYNIMPORT",
+ goobj_SELFROSECT: "SELFROSECT",
+ goobj_SELFRXSECT: "SELFRXSECT",
+ goobj_SELFSECT: "SELFSECT",
+ goobj_SFILE: "SFILE",
+ goobj_SFILEPATH: "SFILEPATH",
+ goobj_SFUNCTAB: "SFUNCTAB",
+ goobj_SGOFUNC: "SGOFUNC",
+ goobj_SGOSTRING: "SGOSTRING",
+ goobj_SHOSTOBJ: "SHOSTOBJ",
+ goobj_SINITARR: "SINITARR",
+ goobj_SMACHO: "SMACHO",
+ goobj_SMACHOGOT: "SMACHOGOT",
+ goobj_SMACHOINDIRECTGOT: "SMACHOINDIRECTGOT",
+ goobj_SMACHOINDIRECTPLT: "SMACHOINDIRECTPLT",
+ goobj_SMACHOPLT: "SMACHOPLT",
+ goobj_SMACHOSYMSTR: "SMACHOSYMSTR",
+ goobj_SMACHOSYMTAB: "SMACHOSYMTAB",
+ goobj_SNOPTRBSS: "SNOPTRBSS",
+ goobj_SNOPTRDATA: "SNOPTRDATA",
+ goobj_SPCLNTAB: "SPCLNTAB",
+ goobj_SRODATA: "SRODATA",
+ goobj_SSTRING: "SSTRING",
+ goobj_SSYMTAB: "SSYMTAB",
+ goobj_STEXT: "STEXT",
+ goobj_STLSBSS: "STLSBSS",
+ goobj_STYPE: "STYPE",
+ goobj_STYPELINK: "STYPELINK",
+ goobj_SWINDOWS: "SWINDOWS",
+ goobj_SXREF: "SXREF",
+}
+
+func (k goobj_SymKind) String() string {
+ if k < 0 || int(k) >= len(goobj_symKindStrings) {
+ return fmt.Sprintf("SymKind(%d)", k)
+ }
+ return goobj_symKindStrings[k]
+}
+
+// A Sym is a named symbol in an object file.
+type goobj_Sym struct {
+ goobj_SymID // symbol identifier (name and version)
+ Kind goobj_SymKind // kind of symbol
+ DupOK bool // are duplicate definitions okay?
+ Size int // size of corresponding data
+ Type goobj_SymID // symbol for Go type information
+ Data goobj_Data // memory image of symbol
+ Reloc []goobj_Reloc // relocations to apply to Data
+ Func *goobj_Func // additional data for functions
+}
+
+// A SymID - the combination of Name and Version - uniquely identifies
+// a symbol within a package.
+type goobj_SymID struct {
+ // Name is the name of a symbol.
+ Name string
+
+ // Version is zero for symbols with global visibility.
+ // Symbols with only file visibility (such as file-level static
+ // declarations in C) have a non-zero version distinguishing
+ // a symbol in one file from a symbol of the same name
+ // in another file
+ Version int
+}
+
+func (s goobj_SymID) String() string {
+ if s.Version == 0 {
+ return s.Name
+ }
+ return fmt.Sprintf("%s<%d>", s.Name, s.Version)
+}
+
+// A Data is a reference to data stored in an object file.
+// It records the offset and size of the data, so that a client can
+// read the data only if necessary.
+type goobj_Data struct {
+ Offset int64
+ Size int64
+}
+
+// A Reloc describes a relocation applied to a memory image to refer
+// to an address within a particular symbol.
+type goobj_Reloc struct {
+ // The bytes at [Offset, Offset+Size) within the memory image
+ // should be updated to refer to the address Add bytes after the start
+ // of the symbol Sym.
+ Offset int
+ Size int
+ Sym goobj_SymID
+ Add int
+
+ // The Type records the form of address expected in the bytes
+ // described by the previous fields: absolute, PC-relative, and so on.
+ // TODO(rsc): The interpretation of Type is not exposed by this package.
+ Type int
+}
+
+// A Var describes a variable in a function stack frame: a declared
+// local variable, an input argument, or an output result.
+type goobj_Var struct {
+ // The combination of Name, Kind, and Offset uniquely
+ // identifies a variable in a function stack frame.
+ // Using fewer of these - in particular, using only Name - does not.
+ Name string // Name of variable.
+ Kind int // TODO(rsc): Define meaning.
+ Offset int // Frame offset. TODO(rsc): Define meaning.
+
+ Type goobj_SymID // Go type for variable.
+}
+
+// Func contains additional per-symbol information specific to functions.
+type goobj_Func struct {
+ Args int // size in bytes of argument frame: inputs and outputs
+ Frame int // size in bytes of local variable frame
+ Leaf bool // function omits save of link register (ARM)
+ NoSplit bool // function omits stack split prologue
+ Var []goobj_Var // detail about local variables
+ PCSP goobj_Data // PC → SP offset map
+ PCFile goobj_Data // PC → file number map (index into File)
+ PCLine goobj_Data // PC → line number map
+ PCData []goobj_Data // PC → runtime support data map
+ FuncData []goobj_FuncData // non-PC-specific runtime support data
+ File []string // paths indexed by PCFile
+}
+
+// TODO: Add PCData []byte and PCDataIter (similar to liblink).
+
+// A FuncData is a single function-specific data value.
+type goobj_FuncData struct {
+ Sym goobj_SymID // symbol holding data
+ Offset int64 // offset into symbol for funcdata pointer
+}
+
+// A Package is a parsed Go object file or archive defining a Go package.
+type goobj_Package struct {
+ ImportPath string // import path denoting this package
+ Imports []string // packages imported by this package
+ Syms []*goobj_Sym // symbols defined by this package
+ MaxVersion int // maximum Version in any SymID in Syms
+}
+
+var (
+ goobj_archiveHeader = []byte("!<arch>\n")
+ goobj_archiveMagic = []byte("`\n")
+ goobj_goobjHeader = []byte("go objec") // truncated to size of archiveHeader
+
+ goobj_errCorruptArchive = errors.New("corrupt archive")
+ goobj_errTruncatedArchive = errors.New("truncated archive")
+ goobj_errNotArchive = errors.New("unrecognized archive format")
+
+ goobj_errCorruptObject = errors.New("corrupt object file")
+ goobj_errTruncatedObject = errors.New("truncated object file")
+ goobj_errNotObject = errors.New("unrecognized object file format")
+)
+
+// An objReader is an object file reader.
+type goobj_objReader struct {
+ p *goobj_Package
+ b *bufio.Reader
+ f io.ReadSeeker
+ err error
+ offset int64
+ limit int64
+ tmp [256]byte
+ pkg string
+ pkgprefix string
+}
+
+// importPathToPrefix returns the prefix that will be used in the
+// final symbol table for the given import path.
+// We escape '%', '"', all control characters and non-ASCII bytes,
+// and any '.' after the final slash.
+//
+// See ../../../cmd/ld/lib.c:/^pathtoprefix and
+// ../../../cmd/gc/subr.c:/^pathtoprefix.
+func goobj_importPathToPrefix(s string) string {
+ // find index of last slash, if any, or else -1.
+ // used for determining whether an index is after the last slash.
+ slash := strings.LastIndex(s, "/")
+
+ // check for chars that need escaping
+ n := 0
+ for r := 0; r < len(s); r++ {
+ if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
+ n++
+ }
+ }
+
+ // quick exit
+ if n == 0 {
+ return s
+ }
+
+ // escape
+ const hex = "0123456789abcdef"
+ p := make([]byte, 0, len(s)+2*n)
+ for r := 0; r < len(s); r++ {
+ if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
+ p = append(p, '%', hex[c>>4], hex[c&0xF])
+ } else {
+ p = append(p, c)
+ }
+ }
+
+ return string(p)
+}
+
+// init initializes r to read package p from f.
+func (r *goobj_objReader) init(f io.ReadSeeker, p *goobj_Package) {
+ r.f = f
+ r.p = p
+ r.offset, _ = f.Seek(0, 1)
+ r.limit, _ = f.Seek(0, 2)
+ f.Seek(r.offset, 0)
+ r.b = bufio.NewReader(f)
+ r.pkgprefix = goobj_importPathToPrefix(p.ImportPath) + "."
+}
+
+// error records that an error occurred.
+// It returns only the first error, so that an error
+// caused by an earlier error does not discard information
+// about the earlier error.
+func (r *goobj_objReader) error(err error) error {
+ if r.err == nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ r.err = err
+ }
+ // panic("corrupt") // useful for debugging
+ return r.err
+}
+
+// readByte reads and returns a byte from the input file.
+// On I/O error or EOF, it records the error but returns byte 0.
+// A sequence of 0 bytes will eventually terminate any
+// parsing state in the object file. In particular, it ends the
+// reading of a varint.
+func (r *goobj_objReader) readByte() byte {
+ if r.err != nil {
+ return 0
+ }
+ if r.offset >= r.limit {
+ r.error(io.ErrUnexpectedEOF)
+ return 0
+ }
+ b, err := r.b.ReadByte()
+ if err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ r.error(err)
+ b = 0
+ } else {
+ r.offset++
+ }
+ return b
+}
+
+// read reads exactly len(b) bytes from the input file.
+// If an error occurs, read returns the error but also
+// records it, so it is safe for callers to ignore the result
+// as long as delaying the report is not a problem.
+func (r *goobj_objReader) readFull(b []byte) error {
+ if r.err != nil {
+ return r.err
+ }
+ if r.offset+int64(len(b)) > r.limit {
+ return r.error(io.ErrUnexpectedEOF)
+ }
+ n, err := io.ReadFull(r.b, b)
+ r.offset += int64(n)
+ if err != nil {
+ return r.error(err)
+ }
+ return nil
+}
+
+// readInt reads a zigzag varint from the input file.
+func (r *goobj_objReader) readInt() int {
+ var u uint64
+
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ r.error(goobj_errCorruptObject)
+ return 0
+ }
+ c := r.readByte()
+ u |= uint64(c&0x7F) << shift
+ if c&0x80 == 0 {
+ break
+ }
+ }
+
+ v := int64(u>>1) ^ (int64(u) << 63 >> 63)
+ if int64(int(v)) != v {
+ r.error(goobj_errCorruptObject) // TODO
+ return 0
+ }
+ return int(v)
+}
+
+// readString reads a length-delimited string from the input file.
+func (r *goobj_objReader) readString() string {
+ n := r.readInt()
+ buf := make([]byte, n)
+ r.readFull(buf)
+ return string(buf)
+}
+
+// readSymID reads a SymID from the input file.
+func (r *goobj_objReader) readSymID() goobj_SymID {
+ name, vers := r.readString(), r.readInt()
+
+ // In a symbol name in an object file, "". denotes the
+ // prefix for the package in which the object file has been found.
+ // Expand it.
+ name = strings.Replace(name, `"".`, r.pkgprefix, -1)
+
+ // An individual object file only records version 0 (extern) or 1 (static).
+ // To make static symbols unique across all files being read, we
+ // replace version 1 with the version corresponding to the current
+ // file number. The number is incremented on each call to parseObject.
+ if vers != 0 {
+ vers = r.p.MaxVersion
+ }
+
+ return goobj_SymID{name, vers}
+}
+
+// readData reads a data reference from the input file.
+func (r *goobj_objReader) readData() goobj_Data {
+ n := r.readInt()
+ d := goobj_Data{Offset: r.offset, Size: int64(n)}
+ r.skip(int64(n))
+ return d
+}
+
+// skip skips n bytes in the input.
+func (r *goobj_objReader) skip(n int64) {
+ if n < 0 {
+ r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip"))
+ }
+ if n < int64(len(r.tmp)) {
+ // Since the data is so small, a just reading from the buffered
+ // reader is better than flushing the buffer and seeking.
+ r.readFull(r.tmp[:n])
+ } else if n <= int64(r.b.Buffered()) {
+ // Even though the data is not small, it has already been read.
+ // Advance the buffer instead of seeking.
+ for n > int64(len(r.tmp)) {
+ r.readFull(r.tmp[:])
+ n -= int64(len(r.tmp))
+ }
+ r.readFull(r.tmp[:n])
+ } else {
+ // Seek, giving up buffered data.
+ _, err := r.f.Seek(r.offset+n, 0)
+ if err != nil {
+ r.error(err)
+ }
+ r.offset += n
+ r.b.Reset(r.f)
+ }
+}
+
+// Parse parses an object file or archive from r,
+// assuming that its import path is pkgpath.
+func goobj_Parse(r io.ReadSeeker, pkgpath string) (*goobj_Package, error) {
+ if pkgpath == "" {
+ pkgpath = `""`
+ }
+ p := new(goobj_Package)
+ p.ImportPath = pkgpath
+
+ var rd goobj_objReader
+ rd.init(r, p)
+ err := rd.readFull(rd.tmp[:8])
+ if err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return nil, err
+ }
+
+ switch {
+ default:
+ return nil, goobj_errNotObject
+
+ case bytes.Equal(rd.tmp[:8], goobj_archiveHeader):
+ if err := rd.parseArchive(); err != nil {
+ return nil, err
+ }
+ case bytes.Equal(rd.tmp[:8], goobj_goobjHeader):
+ if err := rd.parseObject(goobj_goobjHeader); err != nil {
+ return nil, err
+ }
+ }
+
+ return p, nil
+}
+
+// trimSpace removes trailing spaces from b and returns the corresponding string.
+// This effectively parses the form used in archive headers.
+func goobj_trimSpace(b []byte) string {
+ return string(bytes.TrimRight(b, " "))
+}
+
+// parseArchive parses a Unix archive of Go object files.
+// TODO(rsc): Need to skip non-Go object files.
+// TODO(rsc): Maybe record table of contents in r.p so that
+// linker can avoid having code to parse archives too.
+func (r *goobj_objReader) parseArchive() error {
+ for r.offset < r.limit {
+ if err := r.readFull(r.tmp[:60]); err != nil {
+ return err
+ }
+ data := r.tmp[:60]
+
+ // Each file is preceded by this text header (slice indices in first column):
+ // 0:16 name
+ // 16:28 date
+ // 28:34 uid
+ // 34:40 gid
+ // 40:48 mode
+ // 48:58 size
+ // 58:60 magic - `\n
+ // We only care about name, size, and magic.
+ // The fields are space-padded on the right.
+ // The size is in decimal.
+ // The file data - size bytes - follows the header.
+ // Headers are 2-byte aligned, so if size is odd, an extra padding
+ // byte sits between the file data and the next header.
+ // The file data that follows is padded to an even number of bytes:
+ // if size is odd, an extra padding byte is inserted betw the next header.
+ if len(data) < 60 {
+ return goobj_errTruncatedArchive
+ }
+ if !bytes.Equal(data[58:60], goobj_archiveMagic) {
+ return goobj_errCorruptArchive
+ }
+ name := goobj_trimSpace(data[0:16])
+ size, err := strconv.ParseInt(goobj_trimSpace(data[48:58]), 10, 64)
+ if err != nil {
+ return goobj_errCorruptArchive
+ }
+ data = data[60:]
+ fsize := size + size&1
+ if fsize < 0 || fsize < size {
+ return goobj_errCorruptArchive
+ }
+ switch name {
+ case "__.SYMDEF", "__.GOSYMDEF", "__.PKGDEF":
+ r.skip(size)
+ default:
+ oldLimit := r.limit
+ r.limit = r.offset + size
+ if err := r.parseObject(nil); err != nil {
+ return fmt.Errorf("parsing archive member %q: %v", name, err)
+ }
+ r.skip(r.limit - r.offset)
+ r.limit = oldLimit
+ }
+ if size&1 != 0 {
+ r.skip(1)
+ }
+ }
+ return nil
+}
+
+// parseObject parses a single Go object file.
+// The prefix is the bytes already read from the file,
+// typically in order to detect that this is an object file.
+// The object file consists of a textual header ending in "\n!\n"
+// and then the part we want to parse begins.
+// The format of that part is defined in a comment at the top
+// of src/liblink/objfile.c.
+func (r *goobj_objReader) parseObject(prefix []byte) error {
+ // TODO(rsc): Maybe use prefix and the initial input to
+ // record the header line from the file, which would
+ // give the architecture and other version information.
+
+ r.p.MaxVersion++
+ var c1, c2, c3 byte
+ for {
+ c1, c2, c3 = c2, c3, r.readByte()
+ if c3 == 0 { // NUL or EOF, either is bad
+ return goobj_errCorruptObject
+ }
+ if c1 == '\n' && c2 == '!' && c3 == '\n' {
+ break
+ }
+ }
+
+ r.readFull(r.tmp[:8])
+ if !bytes.Equal(r.tmp[:8], []byte("\x00\x00go13ld")) {
+ return r.error(goobj_errCorruptObject)
+ }
+
+ b := r.readByte()
+ if b != 1 {
+ return r.error(goobj_errCorruptObject)
+ }
+
+ // Direct package dependencies.
+ for {
+ s := r.readString()
+ if s == "" {
+ break
+ }
+ r.p.Imports = append(r.p.Imports, s)
+ }
+
+ // Symbols.
+ for {
+ if b := r.readByte(); b != 0xfe {
+ if b != 0xff {
+ return r.error(goobj_errCorruptObject)
+ }
+ break
+ }
+
+ typ := r.readInt()
+ s := &goobj_Sym{goobj_SymID: r.readSymID()}
+ r.p.Syms = append(r.p.Syms, s)
+ s.Kind = goobj_SymKind(typ)
+ s.DupOK = r.readInt() != 0
+ s.Size = r.readInt()
+ s.Type = r.readSymID()
+ s.Data = r.readData()
+ s.Reloc = make([]goobj_Reloc, r.readInt())
+ for i := range s.Reloc {
+ rel := &s.Reloc[i]
+ rel.Offset = r.readInt()
+ rel.Size = r.readInt()
+ rel.Type = r.readInt()
+ rel.Add = r.readInt()
+ r.readInt() // Xadd - ignored
+ rel.Sym = r.readSymID()
+ r.readSymID() // Xsym - ignored
+ }
+
+ if s.Kind == goobj_STEXT {
+ f := new(goobj_Func)
+ s.Func = f
+ f.Args = r.readInt()
+ f.Frame = r.readInt()
+ f.Leaf = r.readInt() != 0
+ f.NoSplit = r.readInt() != 0
+ f.Var = make([]goobj_Var, r.readInt())
+ for i := range f.Var {
+ v := &f.Var[i]
+ v.Name = r.readSymID().Name
+ v.Offset = r.readInt()
+ v.Kind = r.readInt()
+ v.Type = r.readSymID()
+ }
+
+ f.PCSP = r.readData()
+ f.PCFile = r.readData()
+ f.PCLine = r.readData()
+ f.PCData = make([]goobj_Data, r.readInt())
+ for i := range f.PCData {
+ f.PCData[i] = r.readData()
+ }
+ f.FuncData = make([]goobj_FuncData, r.readInt())
+ for i := range f.FuncData {
+ f.FuncData[i].Sym = r.readSymID()
+ }
+ for i := range f.FuncData {
+ f.FuncData[i].Offset = int64(r.readInt()) // TODO
+ }
+ f.File = make([]string, r.readInt())
+ for i := range f.File {
+ f.File[i] = r.readSymID().Name
+ }
+ }
+ }
+
+ r.readFull(r.tmp[:7])
+ if !bytes.Equal(r.tmp[:7], []byte("\xffgo13ld")) {
+ return r.error(goobj_errCorruptObject)
+ }
+
+ return nil
+}
diff --git a/src/cmd/nm/doc.go b/src/cmd/nm/doc.go
index 8e88e2e63..b62da47c0 100644
--- a/src/cmd/nm/doc.go
+++ b/src/cmd/nm/doc.go
@@ -1,23 +1,41 @@
-// Copyright 2009 The Go Authors. All rights reserved.
+// 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.
-// +build ignore
-
-/*
-
-Nm is a version of the Plan 9 nm command. The original is documented at
-
- http://plan9.bell-labs.com/magic/man2html/1/nm
-
-It prints the name list (symbol table) for programs compiled by gc as well as the
-Plan 9 C compiler.
-
-This implementation adds the flag -S, which prints each symbol's size
-in decimal after its address.
-
-Usage:
- go tool nm [-aghnsSTu] 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/elf.go b/src/cmd/nm/elf.go
new file mode 100644
index 000000000..5aaa194dd
--- /dev/null
+++ b/src/cmd/nm/elf.go
@@ -0,0 +1,57 @@
+// 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.
+
+// Parsing of ELF executables (Linux, FreeBSD, and so on).
+
+package main
+
+import (
+ "debug/elf"
+ "os"
+)
+
+func elfSymbols(f *os.File) []Sym {
+ p, err := elf.NewFile(f)
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ elfSyms, err := p.Symbols()
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ var syms []Sym
+ for _, s := range elfSyms {
+ sym := Sym{Addr: s.Value, Name: s.Name, Size: int64(s.Size), Code: '?'}
+ switch s.Section {
+ case elf.SHN_UNDEF:
+ sym.Code = 'U'
+ case elf.SHN_COMMON:
+ sym.Code = 'B'
+ default:
+ i := int(s.Section)
+ if i < 0 || i >= len(p.Sections) {
+ break
+ }
+ sect := p.Sections[i]
+ switch sect.Flags & (elf.SHF_WRITE | elf.SHF_ALLOC | elf.SHF_EXECINSTR) {
+ case elf.SHF_ALLOC | elf.SHF_EXECINSTR:
+ sym.Code = 'T'
+ case elf.SHF_ALLOC:
+ sym.Code = 'R'
+ case elf.SHF_ALLOC | elf.SHF_WRITE:
+ sym.Code = 'D'
+ }
+ }
+ if elf.ST_BIND(s.Info) == elf.STB_LOCAL {
+ sym.Code += 'a' - 'A'
+ }
+ syms = append(syms, sym)
+ }
+
+ return syms
+}
diff --git a/src/cmd/nm/goobj.go b/src/cmd/nm/goobj.go
new file mode 100644
index 000000000..5e0817d95
--- /dev/null
+++ b/src/cmd/nm/goobj.go
@@ -0,0 +1,67 @@
+// 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.
+
+// Parsing of Go intermediate object files and archives.
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func goobjName(id goobj_SymID) string {
+ if id.Version == 0 {
+ return id.Name
+ }
+ return fmt.Sprintf("%s<%d>", id.Name, id.Version)
+}
+
+func goobjSymbols(f *os.File) []Sym {
+ pkg, err := goobj_Parse(f, `""`)
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ seen := make(map[goobj_SymID]bool)
+
+ var syms []Sym
+ for _, s := range pkg.Syms {
+ seen[s.goobj_SymID] = true
+ sym := Sym{Addr: uint64(s.Data.Offset), Name: goobjName(s.goobj_SymID), Size: int64(s.Size), Type: s.Type.Name, Code: '?'}
+ switch s.Kind {
+ case goobj_STEXT, goobj_SELFRXSECT:
+ sym.Code = 'T'
+ case goobj_STYPE, goobj_SSTRING, goobj_SGOSTRING, goobj_SGOFUNC, goobj_SRODATA, goobj_SFUNCTAB, goobj_STYPELINK, goobj_SSYMTAB, goobj_SPCLNTAB, goobj_SELFROSECT:
+ sym.Code = 'R'
+ case goobj_SMACHOPLT, goobj_SELFSECT, goobj_SMACHO, goobj_SMACHOGOT, goobj_SNOPTRDATA, goobj_SINITARR, goobj_SDATA, goobj_SWINDOWS:
+ sym.Code = 'D'
+ case goobj_SBSS, goobj_SNOPTRBSS, goobj_STLSBSS:
+ sym.Code = 'B'
+ case goobj_SXREF, goobj_SMACHOSYMSTR, goobj_SMACHOSYMTAB, goobj_SMACHOINDIRECTPLT, goobj_SMACHOINDIRECTGOT, goobj_SFILE, goobj_SFILEPATH, goobj_SCONST, goobj_SDYNIMPORT, goobj_SHOSTOBJ:
+ sym.Code = 'X' // should not see
+ }
+ if s.Version != 0 {
+ sym.Code += 'a' - 'A'
+ }
+ syms = append(syms, sym)
+ }
+
+ for _, s := range pkg.Syms {
+ for _, r := range s.Reloc {
+ if !seen[r.Sym] {
+ seen[r.Sym] = true
+ sym := Sym{Name: goobjName(r.Sym), Code: 'U'}
+ if s.Version != 0 {
+ // should not happen but handle anyway
+ sym.Code = 'u'
+ }
+ syms = append(syms, sym)
+ }
+ }
+ }
+
+ return syms
+}
diff --git a/src/cmd/nm/macho.go b/src/cmd/nm/macho.go
new file mode 100644
index 000000000..c60bde55b
--- /dev/null
+++ b/src/cmd/nm/macho.go
@@ -0,0 +1,69 @@
+// 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.
+
+// Parsing of Mach-O executables (OS X).
+
+package main
+
+import (
+ "debug/macho"
+ "os"
+ "sort"
+)
+
+func machoSymbols(f *os.File) []Sym {
+ p, err := macho.NewFile(f)
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ if p.Symtab == nil {
+ errorf("%s: no symbol table", f.Name())
+ return nil
+ }
+
+ // Build sorted list of addresses of all symbols.
+ // We infer the size of a symbol by looking at where the next symbol begins.
+ var addrs []uint64
+ for _, s := range p.Symtab.Syms {
+ addrs = append(addrs, s.Value)
+ }
+ sort.Sort(uint64s(addrs))
+
+ var syms []Sym
+ for _, s := range p.Symtab.Syms {
+ sym := Sym{Name: s.Name, Addr: s.Value, Code: '?'}
+ i := sort.Search(len(addrs), func(x int) bool { return addrs[x] > s.Value })
+ if i < len(addrs) {
+ sym.Size = int64(addrs[i] - s.Value)
+ }
+ if s.Sect == 0 {
+ sym.Code = 'U'
+ } else if int(s.Sect) <= len(p.Sections) {
+ sect := p.Sections[s.Sect-1]
+ switch sect.Seg {
+ case "__TEXT":
+ sym.Code = 'R'
+ case "__DATA":
+ sym.Code = 'D'
+ }
+ switch sect.Seg + " " + sect.Name {
+ case "__TEXT __text":
+ sym.Code = 'T'
+ case "__DATA __bss", "__DATA __noptrbss":
+ sym.Code = 'B'
+ }
+ }
+ syms = append(syms, sym)
+ }
+
+ return syms
+}
+
+type uint64s []uint64
+
+func (x uint64s) Len() int { return len(x) }
+func (x uint64s) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x uint64s) Less(i, j int) bool { return x[i] < x[j] }
diff --git a/src/cmd/nm/nm.c b/src/cmd/nm/nm.c
deleted file mode 100644
index 820942426..000000000
--- a/src/cmd/nm/nm.c
+++ /dev/null
@@ -1,401 +0,0 @@
-// Inferno utils/nm/nm.c
-// http://code.google.com/p/inferno-os/source/browse/utils/nm/nm.c
-//
-// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
-// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
-// Portions Copyright © 1997-1999 Vita Nuova Limited
-// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
-// Portions Copyright © 2004,2006 Bruce Ellis
-// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
-// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
-// Portions Copyright © 2009 The Go Authors. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-/*
- * nm.c -- drive nm
- */
-#include <u.h>
-#include <libc.h>
-#include <ar.h>
-#include <bio.h>
-#include <mach.h>
-
-enum{
- CHUNK = 256 /* must be power of 2 */
-};
-
-char *errs; /* exit status */
-char *filename; /* current file */
-char symname[]="__.GOSYMDEF"; /* table of contents file name */
-int multifile; /* processing multiple files */
-int aflag;
-int gflag;
-int hflag;
-int nflag;
-int sflag;
-int Sflag;
-int uflag;
-int Tflag;
-int tflag;
-
-Sym **fnames; /* file path translation table */
-Sym **symptr;
-int nsym;
-Biobuf bout;
-
-int cmp(void*, void*);
-void error(char*, ...);
-void execsyms(int);
-void psym(Sym*, void*);
-void printsyms(Sym**, long);
-void doar(Biobuf*);
-void dofile(Biobuf*);
-void zenter(Sym*);
-
-void
-usage(void)
-{
- fprint(2, "usage: nm [-aghnsSTu] file ...\n");
- exits("usage");
-}
-
-void
-main(int argc, char *argv[])
-{
- int i;
- Biobuf *bin;
-
- Binit(&bout, 1, OWRITE);
- argv0 = argv[0];
- ARGBEGIN {
- default: usage();
- case 'a': aflag = 1; break;
- case 'g': gflag = 1; break;
- case 'h': hflag = 1; break;
- case 'n': nflag = 1; break;
- case 's': sflag = 1; break;
- case 'S': nflag = Sflag = 1; break;
- case 'u': uflag = 1; break;
- case 't': tflag = 1; break;
- case 'T': Tflag = 1; break;
- } ARGEND
- if (argc == 0)
- usage();
- if (argc > 1)
- multifile++;
- for(i=0; i<argc; i++){
- filename = argv[i];
- bin = Bopen(filename, OREAD);
- if(bin == 0){
- error("cannot open %s", filename);
- continue;
- }
- if (isar(bin))
- doar(bin);
- else{
- Bseek(bin, 0, 0);
- dofile(bin);
- }
- Bterm(bin);
- }
- exits(errs);
-}
-
-/*
- * read an archive file,
- * processing the symbols for each intermediate file in it.
- */
-void
-doar(Biobuf *bp)
-{
- int offset, size, obj;
- char name[SARNAME];
-
- multifile = 1;
- for (offset = Boffset(bp);;offset += size) {
- size = nextar(bp, offset, name);
- if (size < 0) {
- error("phase error on ar header %d", offset);
- return;
- }
- if (size == 0)
- return;
- if (strcmp(name, symname) == 0)
- continue;
- obj = objtype(bp, 0);
- if (obj < 0) {
- // perhaps foreign object
- if(strlen(name) > 2 && strcmp(name+strlen(name)-2, ".o") == 0)
- return;
- error("inconsistent file %s in %s",
- name, filename);
- return;
- }
- if (!readar(bp, obj, offset+size, 1)) {
- error("invalid symbol reference in file %s",
- name);
- return;
- }
- filename = name;
- nsym=0;
- objtraverse(psym, 0);
- printsyms(symptr, nsym);
- }
-}
-
-/*
- * process symbols in a file
- */
-void
-dofile(Biobuf *bp)
-{
- int obj;
-
- obj = objtype(bp, 0);
- if (obj < 0)
- execsyms(Bfildes(bp));
- else
- if (readobj(bp, obj)) {
- nsym = 0;
- objtraverse(psym, 0);
- printsyms(symptr, nsym);
- }
-}
-
-/*
- * comparison routine for sorting the symbol table
- * this screws up on 'z' records when aflag == 1
- */
-int
-cmp(void *vs, void *vt)
-{
- Sym **s, **t;
-
- s = vs;
- t = vt;
- if(nflag) // sort on address (numeric) order
- if((*s)->value < (*t)->value)
- return -1;
- else
- return (*s)->value > (*t)->value;
- if(sflag) // sort on file order (sequence)
- return (*s)->sequence - (*t)->sequence;
- return strcmp((*s)->name, (*t)->name);
-}
-/*
- * enter a symbol in the table of filename elements
- */
-void
-zenter(Sym *s)
-{
- static int maxf = 0;
-
- if (s->value > maxf) {
- maxf = (s->value+CHUNK-1) &~ (CHUNK-1);
- fnames = realloc(fnames, (maxf+1)*sizeof(*fnames));
- if(fnames == 0) {
- error("out of memory", argv0);
- exits("memory");
- }
- }
- fnames[s->value] = s;
-}
-
-/*
- * get the symbol table from an executable file, if it has one
- */
-void
-execsyms(int fd)
-{
- Fhdr f;
- Sym *s;
- int32 n;
-
- seek(fd, 0, 0);
- if (crackhdr(fd, &f) == 0) {
- error("Can't read header for %s", filename);
- return;
- }
- if (syminit(fd, &f) < 0)
- return;
- s = symbase(&n);
- nsym = 0;
- while(n--)
- psym(s++, 0);
-
- printsyms(symptr, nsym);
-}
-
-void
-psym(Sym *s, void* p)
-{
- USED(p);
- switch(s->type) {
- case 'T':
- case 'L':
- case 'D':
- case 'B':
- if (uflag)
- return;
- if (!aflag && ((s->name[0] == '.' || s->name[0] == '$')))
- return;
- break;
- case 'b':
- case 'd':
- case 'l':
- case 't':
- if (uflag || gflag)
- return;
- if (!aflag && ((s->name[0] == '.' || s->name[0] == '$')))
- return;
- break;
- case 'U':
- if (gflag)
- return;
- break;
- case 'Z':
- if (!aflag)
- return;
- break;
- case 'm':
- if(!aflag || uflag || gflag)
- return;
- break;
- case 'f': /* we only see a 'z' when the following is true*/
- if(!aflag || uflag || gflag)
- return;
- zenter(s);
- break;
- case 'a':
- case 'p':
- case 'z':
- default:
- if(!aflag || uflag || gflag)
- return;
- break;
- }
- symptr = realloc(symptr, (nsym+1)*sizeof(Sym*));
- if (symptr == 0) {
- error("out of memory");
- exits("memory");
- }
- symptr[nsym++] = s;
-}
-
-const char *skipnames[] = {
- "bss",
- "data",
- "ebss",
- "edata",
- "egcbss",
- "egcdata",
- "enoptrbss",
- "enoptrdata",
- "epclntab",
- "erodata",
- "esymtab",
- "etext",
- "etypelink",
- "noptrbss",
- "noptrdata",
- "rodata",
- "text",
-};
-
-int
-skipsize(char *name)
-{
- int i;
-
- for(i=0; i<nelem(skipnames); i++)
- if(strcmp(skipnames[i], name) == 0)
- return 1;
- return 0;
-}
-
-void
-printsyms(Sym **symptr, long nsym)
-{
- int i, j, wid;
- Sym *s;
- char *cp;
- char path[512];
-
- qsort(symptr, nsym, sizeof(*symptr), (void*)cmp);
-
- wid = 0;
- for (i=0; i<nsym; i++) {
- s = symptr[i];
- if (s->value && wid == 0)
- wid = 8;
- else if (s->value >= 0x100000000LL && wid == 8)
- wid = 16;
- }
- for (i=0; i<nsym; i++) {
- s = symptr[i];
- if (multifile && !hflag)
- Bprint(&bout, "%s:", filename);
- if (s->type == 'z') {
- fileelem(fnames, (uchar *) s->name, path, 512);
- cp = path;
- } else
- cp = s->name;
- if (Tflag)
- Bprint(&bout, "%8ux ", s->sig);
- if (s->value || s->type == 'a' || s->type == 'p')
- Bprint(&bout, "%*llux ", wid, s->value);
- else
- Bprint(&bout, "%*s ", wid, "");
- if(Sflag && !skipsize(cp)) {
- vlong siz;
-
- siz = 0;
- for(j=i+1; j<nsym; j++) {
- if(!skipsize(symptr[j]->name) && symptr[j]->type != 'a' && symptr[j]->type != 'p') {
- siz = symptr[j]->value - s->value;
- break;
- }
- }
- if(siz > 0)
- Bprint(&bout, "%*llud ", wid, siz);
- }
- Bprint(&bout, "%c %s", s->type, cp);
- if(tflag && s->gotype)
- Bprint(&bout, " %*llux", wid, s->gotype);
- Bprint(&bout, "\n");
- }
-}
-
-void
-error(char *fmt, ...)
-{
- Fmt f;
- char buf[128];
- va_list arg;
-
- fmtfdinit(&f, 2, buf, sizeof buf);
- fmtprint(&f, "%s: ", argv0);
- va_start(arg, fmt);
- fmtvprint(&f, fmt, arg);
- va_end(arg);
- fmtprint(&f, "\n");
- fmtfdflush(&f);
- errs = "errors";
-}
diff --git a/src/cmd/nm/nm.go b/src/cmd/nm/nm.go
new file mode 100644
index 000000000..a4036184e
--- /dev/null
+++ b/src/cmd/nm/nm.go
@@ -0,0 +1,184 @@
+// 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"
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "sort"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go tool nm [-n] [-size] [-sort order] [-type] file...\n")
+ 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 ...interface{}) {
+ log.Printf(format, args...)
+ exitCode = 1
+}
+
+type Sym struct {
+ Addr uint64
+ Size int64
+ Code rune
+ Name string
+ Type string
+}
+
+var parsers = []struct {
+ prefix []byte
+ parse func(*os.File) []Sym
+}{
+ {[]byte("!<arch>\n"), goobjSymbols},
+ {[]byte("go object "), goobjSymbols},
+ {[]byte("\x7FELF"), elfSymbols},
+ {[]byte("\xFE\xED\xFA\xCE"), machoSymbols},
+ {[]byte("\xFE\xED\xFA\xCF"), machoSymbols},
+ {[]byte("\xCE\xFA\xED\xFE"), machoSymbols},
+ {[]byte("\xCF\xFA\xED\xFE"), machoSymbols},
+ {[]byte("MZ"), peSymbols},
+ {[]byte("\x00\x00\x01\xEB"), plan9Symbols}, // 386
+ {[]byte("\x00\x00\x04\x07"), plan9Symbols}, // mips
+ {[]byte("\x00\x00\x06\x47"), plan9Symbols}, // arm
+ {[]byte("\x00\x00\x8A\x97"), plan9Symbols}, // amd64
+}
+
+func nm(file string) {
+ f, err := os.Open(file)
+ if err != nil {
+ errorf("%v", err)
+ return
+ }
+ defer f.Close()
+
+ buf := make([]byte, 16)
+ io.ReadFull(f, buf)
+ f.Seek(0, 0)
+
+ var syms []Sym
+ for _, p := range parsers {
+ if bytes.HasPrefix(buf, p.prefix) {
+ syms = p.parse(f)
+ goto HaveSyms
+ }
+ }
+ errorf("%v: unknown file format", file)
+ return
+
+HaveSyms:
+ switch *sortOrder {
+ case "address":
+ sort.Sort(byAddr(syms))
+ case "name":
+ sort.Sort(byName(syms))
+ case "size":
+ sort.Sort(bySize(syms))
+ }
+
+ w := bufio.NewWriter(os.Stdout)
+ for _, sym := range syms {
+ 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")
+ }
+ w.Flush()
+}
+
+type byAddr []Sym
+
+func (x byAddr) Len() int { return len(x) }
+func (x byAddr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr }
+
+type byName []Sym
+
+func (x byName) Len() int { return len(x) }
+func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x byName) Less(i, j int) bool { return x[i].Name < x[j].Name }
+
+type bySize []Sym
+
+func (x bySize) Len() int { return len(x) }
+func (x bySize) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x bySize) Less(i, j int) bool { return x[i].Size > x[j].Size }
diff --git a/src/cmd/nm/nm_test.go b/src/cmd/nm/nm_test.go
new file mode 100644
index 000000000..f4e47a42d
--- /dev/null
+++ b/src/cmd/nm/nm_test.go
@@ -0,0 +1,99 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+var testData uint32
+
+func checkSymbols(t *testing.T, nmoutput []byte) {
+ var checkSymbolsFound, testDataFound bool
+ scanner := bufio.NewScanner(bytes.NewBuffer(nmoutput))
+ for scanner.Scan() {
+ f := strings.Fields(scanner.Text())
+ if len(f) < 3 {
+ continue
+ }
+ switch f[2] {
+ case "cmd/nm.checkSymbols":
+ checkSymbolsFound = true
+ addr := "0x" + f[0]
+ if addr != fmt.Sprintf("%p", checkSymbols) {
+ t.Errorf("nm shows wrong address %v for checkSymbols (%p)", addr, checkSymbols)
+ }
+ case "cmd/nm.testData":
+ testDataFound = true
+ addr := "0x" + f[0]
+ if addr != fmt.Sprintf("%p", &testData) {
+ t.Errorf("nm shows wrong address %v for testData (%p)", addr, &testData)
+ }
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ t.Errorf("error while reading symbols: %v", err)
+ return
+ }
+ if !checkSymbolsFound {
+ t.Error("nm shows no checkSymbols symbol")
+ }
+ if !testDataFound {
+ t.Error("nm shows no testData symbol")
+ }
+}
+
+func TestNM(t *testing.T) {
+ if runtime.GOOS == "nacl" {
+ t.Skip("skipping on nacl")
+ }
+
+ tmpDir, err := ioutil.TempDir("", "TestNM")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ testnmpath := filepath.Join(tmpDir, "testnm.exe")
+ out, err := exec.Command("go", "build", "-o", testnmpath, "cmd/nm").CombinedOutput()
+ if err != nil {
+ t.Fatalf("go build -o %v cmd/nm: %v\n%s", testnmpath, err, string(out))
+ }
+
+ testfiles := []string{
+ "elf/testdata/gcc-386-freebsd-exec",
+ "elf/testdata/gcc-amd64-linux-exec",
+ "macho/testdata/gcc-386-darwin-exec",
+ "macho/testdata/gcc-amd64-darwin-exec",
+ "pe/testdata/gcc-amd64-mingw-exec",
+ "pe/testdata/gcc-386-mingw-exec",
+ "plan9obj/testdata/amd64-plan9-exec",
+ "plan9obj/testdata/386-plan9-exec",
+ }
+ for _, f := range testfiles {
+ exepath := filepath.Join(runtime.GOROOT(), "src", "pkg", "debug", f)
+ cmd := exec.Command(testnmpath, exepath)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool nm %v: %v\n%s", exepath, err, string(out))
+ }
+ }
+
+ cmd := exec.Command(testnmpath, os.Args[0])
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool nm %v: %v\n%s", os.Args[0], err, string(out))
+ }
+ checkSymbols(t, out)
+}
diff --git a/src/cmd/nm/pe.go b/src/cmd/nm/pe.go
new file mode 100644
index 000000000..52d05e51d
--- /dev/null
+++ b/src/cmd/nm/pe.go
@@ -0,0 +1,98 @@
+// 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.
+
+// Parsing of PE executables (Microsoft Windows).
+
+package main
+
+import (
+ "debug/pe"
+ "os"
+ "sort"
+)
+
+func peSymbols(f *os.File) []Sym {
+ p, err := pe.NewFile(f)
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ // Build sorted list of addresses of all symbols.
+ // We infer the size of a symbol by looking at where the next symbol begins.
+ var addrs []uint64
+
+ var imageBase uint64
+ switch oh := p.OptionalHeader.(type) {
+ case *pe.OptionalHeader32:
+ imageBase = uint64(oh.ImageBase)
+ case *pe.OptionalHeader64:
+ imageBase = oh.ImageBase
+ default:
+ errorf("parsing %s: file format not recognized", f.Name())
+ return nil
+ }
+
+ var syms []Sym
+ for _, s := range p.Symbols {
+ const (
+ N_UNDEF = 0 // An undefined (extern) symbol
+ N_ABS = -1 // An absolute symbol (e_value is a constant, not an address)
+ N_DEBUG = -2 // A debugging symbol
+ )
+ sym := Sym{Name: s.Name, Addr: uint64(s.Value), Code: '?'}
+ switch s.SectionNumber {
+ case N_UNDEF:
+ sym.Code = 'U'
+ case N_ABS:
+ sym.Code = 'C'
+ case N_DEBUG:
+ sym.Code = '?'
+ default:
+ if s.SectionNumber < 0 {
+ errorf("parsing %s: invalid section number %d", f.Name(), s.SectionNumber)
+ return nil
+ }
+ if len(p.Sections) < int(s.SectionNumber) {
+ errorf("parsing %s: section number %d is large then max %d", f.Name(), s.SectionNumber, len(p.Sections))
+ return nil
+ }
+ sect := p.Sections[s.SectionNumber-1]
+ const (
+ text = 0x20
+ data = 0x40
+ bss = 0x80
+ permX = 0x20000000
+ permR = 0x40000000
+ permW = 0x80000000
+ )
+ ch := sect.Characteristics
+ switch {
+ case ch&text != 0:
+ sym.Code = 'T'
+ case ch&data != 0:
+ if ch&permW == 0 {
+ sym.Code = 'R'
+ } else {
+ sym.Code = 'D'
+ }
+ case ch&bss != 0:
+ sym.Code = 'B'
+ }
+ sym.Addr += imageBase + uint64(sect.VirtualAddress)
+ }
+ syms = append(syms, sym)
+ addrs = append(addrs, sym.Addr)
+ }
+
+ sort.Sort(uint64s(addrs))
+ for i := range syms {
+ j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr })
+ if j < len(addrs) {
+ syms[i].Size = int64(addrs[j] - syms[i].Addr)
+ }
+ }
+
+ return syms
+}
diff --git a/src/cmd/nm/plan9obj.go b/src/cmd/nm/plan9obj.go
new file mode 100644
index 000000000..006c66ebf
--- /dev/null
+++ b/src/cmd/nm/plan9obj.go
@@ -0,0 +1,48 @@
+// 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.
+
+// Parsing of Plan 9 a.out executables.
+
+package main
+
+import (
+ "debug/plan9obj"
+ "os"
+ "sort"
+)
+
+func plan9Symbols(f *os.File) []Sym {
+ p, err := plan9obj.NewFile(f)
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ plan9Syms, err := p.Symbols()
+ if err != nil {
+ errorf("parsing %s: %v", f.Name(), err)
+ return nil
+ }
+
+ // Build sorted list of addresses of all symbols.
+ // We infer the size of a symbol by looking at where the next symbol begins.
+ var addrs []uint64
+ for _, s := range plan9Syms {
+ addrs = append(addrs, s.Value)
+ }
+ sort.Sort(uint64s(addrs))
+
+ var syms []Sym
+
+ for _, s := range plan9Syms {
+ sym := Sym{Addr: s.Value, Name: s.Name, Code: rune(s.Type)}
+ i := sort.Search(len(addrs), func(x int) bool { return addrs[x] > s.Value })
+ if i < len(addrs) {
+ sym.Size = int64(addrs[i] - s.Value)
+ }
+ syms = append(syms, sym)
+ }
+
+ return syms
+}