diff options
Diffstat (limited to 'src/cmd/nm')
-rw-r--r-- | src/cmd/nm/Makefile | 5 | ||||
-rw-r--r-- | src/cmd/nm/debug_goobj.go | 670 | ||||
-rw-r--r-- | src/cmd/nm/doc.go | 56 | ||||
-rw-r--r-- | src/cmd/nm/elf.go | 57 | ||||
-rw-r--r-- | src/cmd/nm/goobj.go | 67 | ||||
-rw-r--r-- | src/cmd/nm/macho.go | 69 | ||||
-rw-r--r-- | src/cmd/nm/nm.c | 401 | ||||
-rw-r--r-- | src/cmd/nm/nm.go | 184 | ||||
-rw-r--r-- | src/cmd/nm/nm_test.go | 99 | ||||
-rw-r--r-- | src/cmd/nm/pe.go | 98 | ||||
-rw-r--r-- | src/cmd/nm/plan9obj.go | 48 |
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 +} |