Commit 021c1168 authored by David du Colombier's avatar David du Colombier

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
parent f7245c06
......@@ -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) {
......
// 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
}
// 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
}
// 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)
}
}
// 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},
}
#include <u.h>
#include <libc.h>
void
main(void)
{
print("hello, world\n");
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment