Skip to content

Commit

Permalink
cmd/nm: reimplement in Go
Browse files Browse the repository at this point in the history
The immediate goal is to support the new object file format,
which libmach (nm's support library) does not understand.
Rather than add code to libmach or reengineer liblink to
support this new use, just write it in Go.

The C version of nm reads the Plan 9 symbol table stored in
Go binaries, now otherwise unused.

This reimplementation uses the standard symbol table for
the corresponding file format instead, bringing us one step
closer to removing the Plan 9 symbol table from Go binaries.

Tell cmd/dist not to build cmd/nm anymore.
Tell cmd/go to install cmd/nm in the tool directory.

R=golang-dev, r, iant, alex.brainman
CC=golang-dev
https://golang.org/cl/40600043
  • Loading branch information
rsc committed Dec 16, 2013
1 parent a9f6db5 commit 500547f
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 428 deletions.
4 changes: 1 addition & 3 deletions src/cmd/dist/build.c
Expand Up @@ -1246,7 +1246,6 @@ static char *buildorder[] = {
"misc/pprof",

"cmd/addr2line",
"cmd/nm",
"cmd/objdump",
"cmd/pack",
"cmd/prof",
Expand Down Expand Up @@ -1325,8 +1324,7 @@ static char *cleantab[] = {
"cmd/addr2line",
"cmd/cc",
"cmd/gc",
"cmd/go",
"cmd/nm",
"cmd/go",
"cmd/objdump",
"cmd/pack",
"cmd/prof",
Expand Down
1 change: 1 addition & 0 deletions src/cmd/go/pkg.go
Expand Up @@ -307,6 +307,7 @@ var goTools = map[string]targetDir{
"cmd/api": toTool,
"cmd/cgo": toTool,
"cmd/fix": toTool,
"cmd/nm": toTool,
"cmd/yacc": toTool,
"code.google.com/p/go.tools/cmd/cover": toTool,
"code.google.com/p/go.tools/cmd/godoc": toBin,
Expand Down
5 changes: 0 additions & 5 deletions src/cmd/nm/Makefile

This file was deleted.

52 changes: 33 additions & 19 deletions src/cmd/nm/doc.go
@@ -1,23 +1,37 @@
// 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
// D data segment symbol
// d static data segment symbol
// B bss segment symbol
// b static bss segment symbol
// 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 compatiblity with other nm commands
// -size
// print symbol size in decimal between address and type
// -sort {address,name,none}
// sort output in the given order (default name)
// -type
// print symbol type after name
//
package main
54 changes: 54 additions & 0 deletions src/cmd/nm/elf.go
@@ -0,0 +1,54 @@
// 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-1]
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'
}
}
syms = append(syms, sym)
}

return syms
}
68 changes: 68 additions & 0 deletions src/cmd/nm/goobj.go
@@ -0,0 +1,68 @@
// 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 (
"debug/goobj"
"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.SymID] = true
sym := Sym{Addr: uint64(s.Data.Offset), Name: goobjName(s.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'
}
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
}
69 changes: 69 additions & 0 deletions 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] }

0 comments on commit 500547f

Please sign in to comment.