package plan9obj
import (
"encoding/binary"
"errors"
"fmt"
"internal/saferio"
"io"
"os"
)
type FileHeader struct {
Magic uint32
Bss uint32
Entry uint64
PtrSize int
LoadAddress uint64
HdrSize uint64
}
type File struct {
FileHeader
Sections []*Section
closer io .Closer
}
type SectionHeader struct {
Name string
Size uint32
Offset uint32
}
type Section struct {
SectionHeader
io .ReaderAt
sr *io .SectionReader
}
func (s *Section ) Data () ([]byte , error ) {
return saferio .ReadDataAt (s .sr , uint64 (s .Size ), 0 )
}
func (s *Section ) Open () io .ReadSeeker { return io .NewSectionReader (s .sr , 0 , 1 <<63 -1 ) }
type Sym struct {
Value uint64
Type rune
Name string
}
type formatError struct {
off int
msg string
val any
}
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
}
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
}
func (f *File ) Close () error {
var err error
if f .closer != nil {
err = f .closer .Close ()
f .closer = nil
}
return err
}
func parseMagic(magic []byte ) (uint32 , error ) {
m := binary .BigEndian .Uint32 (magic )
switch m {
case Magic386 , MagicAMD64 , MagicARM :
return m , nil
}
return 0 , &formatError {0 , "bad magic number" , magic }
}
func NewFile (r io .ReaderAt ) (*File , error ) {
sr := io .NewSectionReader (r , 0 , 1 <<63 -1 )
var magic [4 ]byte
if _ , err := r .ReadAt (magic [:], 0 ); err != nil {
return nil , err
}
_ , err := parseMagic (magic [:])
if err != nil {
return nil , err
}
ph := new (prog )
if err := binary .Read (sr , binary .BigEndian , ph ); err != nil {
return nil , err
}
f := &File {FileHeader : FileHeader {
Magic : ph .Magic ,
Bss : ph .Bss ,
Entry : uint64 (ph .Entry ),
PtrSize : 4 ,
LoadAddress : 0x1000 ,
HdrSize : 4 * 8 ,
}}
if ph .Magic &Magic64 != 0 {
if err := binary .Read (sr , binary .BigEndian , &f .Entry ); err != nil {
return nil , err
}
f .PtrSize = 8
f .LoadAddress = 0x200000
f .HdrSize += 8
}
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 := uint32 (f .HdrSize )
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 .Offset ), int64 (s .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 {
if len (p ) < ptrsz {
return &formatError {len (data ), "unexpected EOF" , nil }
}
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 :]
}
if len (p ) < 1 {
return &formatError {len (data ), "unexpected EOF" , nil }
}
typ := p [0 ] & 0x7F
s .typ = typ
p = p [1 :]
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
}
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
}
var ErrNoSymbols = errors .New ("no symbol section" )
func (f *File ) Symbols () ([]Sym , error ) {
symtabSection := f .Section ("syms" )
if symtabSection == nil {
return nil , ErrNoSymbols
}
symtab , err := symtabSection .Data ()
if err != nil {
return nil , errors .New ("cannot load symbol section" )
}
return newTable (symtab , f .PtrSize )
}
func (f *File ) Section (name string ) *Section {
for _ , s := range f .Sections {
if s .Name == name {
return s
}
}
return nil
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .