mirror of
https://github.com/golang/go
synced 2024-11-23 19:20:03 -07:00
debug/plan9obj: implement parsing of Plan 9 a.out executables
It implements parsing of the header and symbol table for both 32-bit and 64-bit Plan 9 binaries. The nm tool was updated to use this package. R=rsc, aram CC=golang-codereviews https://golang.org/cl/49970044
This commit is contained in:
parent
f7245c0626
commit
021c11683c
@ -105,6 +105,10 @@ var parsers = []struct {
|
||||
{[]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) {
|
||||
|
48
src/cmd/nm/plan9obj.go
Normal file
48
src/cmd/nm/plan9obj.go
Normal file
@ -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
|
||||
}
|
346
src/pkg/debug/plan9obj/file.go
Normal file
346
src/pkg/debug/plan9obj/file.go
Normal file
@ -0,0 +1,346 @@
|
||||
// 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 plan9obj implements access to Plan 9 a.out object files.
|
||||
package plan9obj
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A FileHeader represents an Plan 9 a.out file header.
|
||||
type FileHeader struct {
|
||||
Ptrsz int
|
||||
}
|
||||
|
||||
// A File represents an open Plan 9 a.out file.
|
||||
type File struct {
|
||||
FileHeader
|
||||
Sections []*Section
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
type SectionHeader struct {
|
||||
Name string
|
||||
Size uint32
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
// A Section represents a single section in an Plan 9 a.out file.
|
||||
type Section struct {
|
||||
SectionHeader
|
||||
|
||||
// Embed ReaderAt for ReadAt method.
|
||||
// Do not embed SectionReader directly
|
||||
// to avoid having Read and Seek.
|
||||
// If a client wants Read and Seek it must use
|
||||
// Open() to avoid fighting over the seek offset
|
||||
// with other clients.
|
||||
io.ReaderAt
|
||||
sr *io.SectionReader
|
||||
}
|
||||
|
||||
// Data reads and returns the contents of the Plan 9 a.out section.
|
||||
func (s *Section) Data() ([]byte, error) {
|
||||
dat := make([]byte, s.sr.Size())
|
||||
n, err := s.sr.ReadAt(dat, 0)
|
||||
return dat[0:n], err
|
||||
}
|
||||
|
||||
// Open returns a new ReadSeeker reading the Plan 9 a.out section.
|
||||
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
|
||||
|
||||
// A ProgHeader represents a single Plan 9 a.out program header.
|
||||
type ProgHeader struct {
|
||||
Magic uint32
|
||||
Text uint32
|
||||
Data uint32
|
||||
Bss uint32
|
||||
Syms uint32
|
||||
Entry uint64
|
||||
Spsz uint32
|
||||
Pcsz uint32
|
||||
}
|
||||
|
||||
// A Prog represents the program header in an Plan 9 a.out binary.
|
||||
type Prog struct {
|
||||
ProgHeader
|
||||
|
||||
// Embed ReaderAt for ReadAt method.
|
||||
// Do not embed SectionReader directly
|
||||
// to avoid having Read and Seek.
|
||||
// If a client wants Read and Seek it must use
|
||||
// Open() to avoid fighting over the seek offset
|
||||
// with other clients.
|
||||
io.ReaderAt
|
||||
sr *io.SectionReader
|
||||
}
|
||||
|
||||
// Open returns a new ReadSeeker reading the Plan 9 a.out program body.
|
||||
func (p *Prog) Open() io.ReadSeeker { return io.NewSectionReader(p.sr, 0, 1<<63-1) }
|
||||
|
||||
// A Symbol represents an entry in a Plan 9 a.out symbol table section.
|
||||
type Sym struct {
|
||||
Value uint64
|
||||
Type rune
|
||||
Name string
|
||||
}
|
||||
|
||||
/*
|
||||
* Plan 9 a.out reader
|
||||
*/
|
||||
|
||||
type FormatError struct {
|
||||
off int
|
||||
msg string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func (e *FormatError) Error() string {
|
||||
msg := e.msg
|
||||
if e.val != nil {
|
||||
msg += fmt.Sprintf(" '%v'", e.val)
|
||||
}
|
||||
msg += fmt.Sprintf(" in record at byte %#x", e.off)
|
||||
return msg
|
||||
}
|
||||
|
||||
// Open opens the named file using os.Open and prepares it for use as an Plan 9 a.out binary.
|
||||
func Open(name string) (*File, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ff, err := NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
ff.closer = f
|
||||
return ff, nil
|
||||
}
|
||||
|
||||
// Close closes the File.
|
||||
// If the File was created using NewFile directly instead of Open,
|
||||
// Close has no effect.
|
||||
func (f *File) Close() error {
|
||||
var err error
|
||||
if f.closer != nil {
|
||||
err = f.closer.Close()
|
||||
f.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseMagic(magic [4]byte) (*ExecTable, error) {
|
||||
for _, e := range exectab {
|
||||
if string(magic[:]) == e.Magic {
|
||||
return &e, nil
|
||||
}
|
||||
}
|
||||
return nil, &FormatError{0, "bad magic number", magic[:]}
|
||||
}
|
||||
|
||||
// NewFile creates a new File for accessing an Plan 9 binary in an underlying reader.
|
||||
// The Plan 9 binary is expected to start at position 0 in the ReaderAt.
|
||||
func NewFile(r io.ReaderAt) (*File, error) {
|
||||
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
||||
// Read and decode Plan 9 magic
|
||||
var magic [4]byte
|
||||
if _, err := r.ReadAt(magic[:], 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp, err := parseMagic(magic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := &File{FileHeader{mp.Ptrsz}, nil, nil}
|
||||
|
||||
ph := new(prog)
|
||||
if err := binary.Read(sr, binary.BigEndian, ph); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := new(Prog)
|
||||
p.ProgHeader = ProgHeader{
|
||||
Magic: ph.Magic,
|
||||
Text: ph.Text,
|
||||
Data: ph.Data,
|
||||
Bss: ph.Bss,
|
||||
Syms: ph.Syms,
|
||||
Entry: uint64(ph.Entry),
|
||||
Spsz: ph.Spsz,
|
||||
Pcsz: ph.Pcsz,
|
||||
}
|
||||
|
||||
if mp.Ptrsz == 8 {
|
||||
if err := binary.Read(sr, binary.BigEndian, &p.Entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var sects = []struct {
|
||||
name string
|
||||
size uint32
|
||||
}{
|
||||
{"text", ph.Text},
|
||||
{"data", ph.Data},
|
||||
{"syms", ph.Syms},
|
||||
{"spsz", ph.Spsz},
|
||||
{"pcsz", ph.Pcsz},
|
||||
}
|
||||
|
||||
f.Sections = make([]*Section, 5)
|
||||
|
||||
off := mp.Hsize
|
||||
|
||||
for i, sect := range sects {
|
||||
s := new(Section)
|
||||
s.SectionHeader = SectionHeader{
|
||||
Name: sect.name,
|
||||
Size: sect.size,
|
||||
Offset: off,
|
||||
}
|
||||
off += sect.size
|
||||
s.sr = io.NewSectionReader(r, int64(s.SectionHeader.Offset), int64(s.SectionHeader.Size))
|
||||
s.ReaderAt = s.sr
|
||||
f.Sections[i] = s
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func walksymtab(data []byte, ptrsz int, fn func(sym) error) error {
|
||||
var order binary.ByteOrder = binary.BigEndian
|
||||
var s sym
|
||||
p := data
|
||||
for len(p) >= 4 {
|
||||
// Symbol type, value.
|
||||
if len(p) < ptrsz {
|
||||
return &FormatError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// fixed-width value
|
||||
if ptrsz == 8 {
|
||||
s.value = order.Uint64(p[0:8])
|
||||
p = p[8:]
|
||||
} else {
|
||||
s.value = uint64(order.Uint32(p[0:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
|
||||
var typ byte
|
||||
typ = p[0] & 0x7F
|
||||
s.typ = typ
|
||||
p = p[1:]
|
||||
|
||||
// Name.
|
||||
var i int
|
||||
var nnul int
|
||||
for i = 0; i < len(p); i++ {
|
||||
if p[i] == 0 {
|
||||
nnul = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
switch typ {
|
||||
case 'z', 'Z':
|
||||
p = p[i+nnul:]
|
||||
for i = 0; i+2 <= len(p); i += 2 {
|
||||
if p[i] == 0 && p[i+1] == 0 {
|
||||
nnul = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p) < i+nnul {
|
||||
return &FormatError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
s.name = p[0:i]
|
||||
i += nnul
|
||||
p = p[i:]
|
||||
|
||||
fn(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTable decodes the Go symbol table in data,
|
||||
// returning an in-memory representation.
|
||||
func newTable(symtab []byte, ptrsz int) ([]Sym, error) {
|
||||
var n int
|
||||
err := walksymtab(symtab, ptrsz, func(s sym) error {
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fname := make(map[uint16]string)
|
||||
syms := make([]Sym, 0, n)
|
||||
err = walksymtab(symtab, ptrsz, func(s sym) error {
|
||||
n := len(syms)
|
||||
syms = syms[0 : n+1]
|
||||
ts := &syms[n]
|
||||
ts.Type = rune(s.typ)
|
||||
ts.Value = s.value
|
||||
switch s.typ {
|
||||
default:
|
||||
ts.Name = string(s.name[:])
|
||||
case 'z', 'Z':
|
||||
for i := 0; i < len(s.name); i += 2 {
|
||||
eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
|
||||
elt, ok := fname[eltIdx]
|
||||
if !ok {
|
||||
return &FormatError{-1, "bad filename code", eltIdx}
|
||||
}
|
||||
if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
|
||||
ts.Name += "/"
|
||||
}
|
||||
ts.Name += elt
|
||||
}
|
||||
}
|
||||
switch s.typ {
|
||||
case 'f':
|
||||
fname[uint16(s.value)] = ts.Name
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
}
|
||||
|
||||
// Symbols returns the symbol table for f.
|
||||
func (f *File) Symbols() ([]Sym, error) {
|
||||
symtabSection := f.Section("syms")
|
||||
if symtabSection == nil {
|
||||
return nil, errors.New("no symbol section")
|
||||
}
|
||||
|
||||
symtab, err := symtabSection.Data()
|
||||
if err != nil {
|
||||
return nil, errors.New("cannot load symbol section")
|
||||
}
|
||||
|
||||
return newTable(symtab, f.Ptrsz)
|
||||
}
|
||||
|
||||
// Section returns a section with the given name, or nil if no such
|
||||
// section exists.
|
||||
func (f *File) Section(name string) *Section {
|
||||
for _, s := range f.Sections {
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
81
src/pkg/debug/plan9obj/file_test.go
Normal file
81
src/pkg/debug/plan9obj/file_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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 plan9obj
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fileTest struct {
|
||||
file string
|
||||
hdr FileHeader
|
||||
sections []*SectionHeader
|
||||
}
|
||||
|
||||
var fileTests = []fileTest{
|
||||
{
|
||||
"testdata/386-plan9-exec",
|
||||
FileHeader{4},
|
||||
[]*SectionHeader{
|
||||
{"text", 0x4c5f, 0x20},
|
||||
{"data", 0x94c, 0x4c7f},
|
||||
{"syms", 0x2c2b, 0x55cb},
|
||||
{"spsz", 0x0, 0x81f6},
|
||||
{"pcsz", 0xf7a, 0x81f6},
|
||||
},
|
||||
},
|
||||
{
|
||||
"testdata/amd64-plan9-exec",
|
||||
FileHeader{8},
|
||||
[]*SectionHeader{
|
||||
{"text", 0x4213, 0x28},
|
||||
{"data", 0xa80, 0x423b},
|
||||
{"syms", 0x2c8c, 0x4cbb},
|
||||
{"spsz", 0x0, 0x7947},
|
||||
{"pcsz", 0xca0, 0x7947},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
for i := range fileTests {
|
||||
tt := &fileTests[i]
|
||||
|
||||
f, err := Open(tt.file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
|
||||
t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr)
|
||||
continue
|
||||
}
|
||||
|
||||
for i, sh := range f.Sections {
|
||||
if i >= len(tt.sections) {
|
||||
break
|
||||
}
|
||||
have := &sh.SectionHeader
|
||||
want := tt.sections[i]
|
||||
if !reflect.DeepEqual(have, want) {
|
||||
t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
|
||||
}
|
||||
}
|
||||
tn := len(tt.sections)
|
||||
fn := len(f.Sections)
|
||||
if tn != fn {
|
||||
t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFailure(t *testing.T) {
|
||||
filename := "file.go" // not a Plan 9 a.out file
|
||||
_, err := Open(filename) // don't crash
|
||||
if err == nil {
|
||||
t.Errorf("open %s: succeeded unexpectedly", filename)
|
||||
}
|
||||
}
|
91
src/pkg/debug/plan9obj/plan9obj.go
Normal file
91
src/pkg/debug/plan9obj/plan9obj.go
Normal file
@ -0,0 +1,91 @@
|
||||
// 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.
|
||||
|
||||
/*
|
||||
* Plan 9 a.out constants and data structures
|
||||
*/
|
||||
|
||||
package plan9obj
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Plan 9 Program header.
|
||||
type prog struct {
|
||||
Magic uint32 /* magic number */
|
||||
Text uint32 /* size of text segment */
|
||||
Data uint32 /* size of initialized data */
|
||||
Bss uint32 /* size of uninitialized data */
|
||||
Syms uint32 /* size of symbol table */
|
||||
Entry uint32 /* entry point */
|
||||
Spsz uint32 /* size of pc/sp offset table */
|
||||
Pcsz uint32 /* size of pc/line number table */
|
||||
}
|
||||
|
||||
// Plan 9 symbol table entries.
|
||||
type sym struct {
|
||||
value uint64
|
||||
typ byte
|
||||
name []byte
|
||||
}
|
||||
|
||||
const (
|
||||
hsize = 4 * 8
|
||||
_HDR_MAGIC = 0x00008000 /* header expansion */
|
||||
)
|
||||
|
||||
func magic(f, b int) string {
|
||||
buf := new(bytes.Buffer)
|
||||
var i uint32 = uint32((f) | ((((4 * (b)) + 0) * (b)) + 7))
|
||||
binary.Write(buf, binary.BigEndian, i)
|
||||
return string(buf.Bytes())
|
||||
}
|
||||
|
||||
var (
|
||||
_A_MAGIC = magic(0, 8) /* 68020 (retired) */
|
||||
_I_MAGIC = magic(0, 11) /* intel 386 */
|
||||
_J_MAGIC = magic(0, 12) /* intel 960 (retired) */
|
||||
_K_MAGIC = magic(0, 13) /* sparc */
|
||||
_V_MAGIC = magic(0, 16) /* mips 3000 BE */
|
||||
_X_MAGIC = magic(0, 17) /* att dsp 3210 (retired) */
|
||||
_M_MAGIC = magic(0, 18) /* mips 4000 BE */
|
||||
_D_MAGIC = magic(0, 19) /* amd 29000 (retired) */
|
||||
_E_MAGIC = magic(0, 20) /* arm */
|
||||
_Q_MAGIC = magic(0, 21) /* powerpc */
|
||||
_N_MAGIC = magic(0, 22) /* mips 4000 LE */
|
||||
_L_MAGIC = magic(0, 23) /* dec alpha (retired) */
|
||||
_P_MAGIC = magic(0, 24) /* mips 3000 LE */
|
||||
_U_MAGIC = magic(0, 25) /* sparc64 (retired) */
|
||||
_S_MAGIC = magic(_HDR_MAGIC, 26) /* amd64 */
|
||||
_T_MAGIC = magic(_HDR_MAGIC, 27) /* powerpc64 */
|
||||
_R_MAGIC = magic(_HDR_MAGIC, 28) /* arm64 */
|
||||
)
|
||||
|
||||
type ExecTable struct {
|
||||
Magic string
|
||||
Ptrsz int
|
||||
Hsize uint32
|
||||
}
|
||||
|
||||
var exectab = []ExecTable{
|
||||
{_A_MAGIC, 4, hsize},
|
||||
{_I_MAGIC, 4, hsize},
|
||||
{_J_MAGIC, 4, hsize},
|
||||
{_K_MAGIC, 4, hsize},
|
||||
{_V_MAGIC, 4, hsize},
|
||||
{_X_MAGIC, 4, hsize},
|
||||
{_M_MAGIC, 4, hsize},
|
||||
{_D_MAGIC, 4, hsize},
|
||||
{_E_MAGIC, 4, hsize},
|
||||
{_Q_MAGIC, 4, hsize},
|
||||
{_N_MAGIC, 4, hsize},
|
||||
{_L_MAGIC, 4, hsize},
|
||||
{_P_MAGIC, 4, hsize},
|
||||
{_U_MAGIC, 4, hsize},
|
||||
{_S_MAGIC, 8, hsize + 8},
|
||||
{_T_MAGIC, 8, hsize + 8},
|
||||
{_R_MAGIC, 8, hsize + 8},
|
||||
}
|
BIN
src/pkg/debug/plan9obj/testdata/386-plan9-exec
vendored
Executable file
BIN
src/pkg/debug/plan9obj/testdata/386-plan9-exec
vendored
Executable file
Binary file not shown.
BIN
src/pkg/debug/plan9obj/testdata/amd64-plan9-exec
vendored
Executable file
BIN
src/pkg/debug/plan9obj/testdata/amd64-plan9-exec
vendored
Executable file
Binary file not shown.
8
src/pkg/debug/plan9obj/testdata/hello.c
vendored
Normal file
8
src/pkg/debug/plan9obj/testdata/hello.c
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
void
|
||||
main(void)
|
||||
{
|
||||
print("hello, world\n");
|
||||
}
|
Loading…
Reference in New Issue
Block a user