// 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. # Security This package is not designed to be hardened against adversarial inputs, and is outside the scope of https://go.dev/security/policy. In particular, only basic validation is done when parsing object files. As such, care should be taken when parsing untrusted inputs, as parsing malformed files may consume significant resources, or cause panics. */
package plan9obj import ( ) // A FileHeader represents a Plan 9 a.out file header. type FileHeader struct { Magic uint32 Bss uint32 Entry uint64 PtrSize int LoadAddress uint64 HdrSize uint64 } // A File represents an open Plan 9 a.out file. type File struct { FileHeader Sections []*Section closer io.Closer } // A SectionHeader represents a single Plan 9 a.out section header. // This structure doesn't exist on-disk, but eases navigation // through the object file. type SectionHeader struct { Name string Size uint32 Offset uint32 } // A Section represents a single section in a 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 ( *Section) () ([]byte, error) { return saferio.ReadDataAt(.sr, uint64(.Size), 0) } // Open returns a new ReadSeeker reading the Plan 9 a.out section. func ( *Section) () io.ReadSeeker { return io.NewSectionReader(.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 */ // formatError is returned by some operations if the data does // not have the correct format for an object file. type formatError struct { off int msg string val any } func ( *formatError) () string { := .msg if .val != nil { += fmt.Sprintf(" '%v'", .val) } += fmt.Sprintf(" in record at byte %#x", .off) return } // Open opens the named file using [os.Open] and prepares it for use as a Plan 9 a.out binary. func ( string) (*File, error) { , := os.Open() if != nil { return nil, } , := NewFile() if != nil { .Close() return nil, } .closer = return , nil } // Close closes the [File]. // If the [File] was created using [NewFile] directly instead of [Open], // Close has no effect. func ( *File) () error { var error if .closer != nil { = .closer.Close() .closer = nil } return } func parseMagic( []byte) (uint32, error) { := binary.BigEndian.Uint32() switch { case Magic386, MagicAMD64, MagicARM: return , nil } return 0, &formatError{0, "bad magic number", } } // NewFile creates a new [File] for accessing a Plan 9 binary in an underlying reader. // The Plan 9 binary is expected to start at position 0 in the ReaderAt. func ( io.ReaderAt) (*File, error) { := io.NewSectionReader(, 0, 1<<63-1) // Read and decode Plan 9 magic var [4]byte if , := .ReadAt([:], 0); != nil { return nil, } , := parseMagic([:]) if != nil { return nil, } := new(prog) if := binary.Read(, binary.BigEndian, ); != nil { return nil, } := &File{FileHeader: FileHeader{ Magic: .Magic, Bss: .Bss, Entry: uint64(.Entry), PtrSize: 4, LoadAddress: 0x1000, HdrSize: 4 * 8, }} if .Magic&Magic64 != 0 { if := binary.Read(, binary.BigEndian, &.Entry); != nil { return nil, } .PtrSize = 8 .LoadAddress = 0x200000 .HdrSize += 8 } var = []struct { string uint32 }{ {"text", .Text}, {"data", .Data}, {"syms", .Syms}, {"spsz", .Spsz}, {"pcsz", .Pcsz}, } .Sections = make([]*Section, 5) := uint32(.HdrSize) for , := range { := new(Section) .SectionHeader = SectionHeader{ Name: ., Size: ., Offset: , } += . .sr = io.NewSectionReader(, int64(.Offset), int64(.Size)) .ReaderAt = .sr .Sections[] = } return , nil } func walksymtab( []byte, int, func(sym) error) error { var binary.ByteOrder = binary.BigEndian var sym := for len() >= 4 { // Symbol type, value. if len() < { return &formatError{len(), "unexpected EOF", nil} } // fixed-width value if == 8 { .value = .Uint64([0:8]) = [8:] } else { .value = uint64(.Uint32([0:4])) = [4:] } if len() < 1 { return &formatError{len(), "unexpected EOF", nil} } := [0] & 0x7F .typ = = [1:] // Name. var int var int for = 0; < len(); ++ { if [] == 0 { = 1 break } } switch { case 'z', 'Z': = [+:] for = 0; +2 <= len(); += 2 { if [] == 0 && [+1] == 0 { = 2 break } } } if len() < + { return &formatError{len(), "unexpected EOF", nil} } .name = [0:] += = [:] () } return nil } // newTable decodes the Go symbol table in data, // returning an in-memory representation. func newTable( []byte, int) ([]Sym, error) { var int := walksymtab(, , func( sym) error { ++ return nil }) if != nil { return nil, } := make(map[uint16]string) := make([]Sym, 0, ) = walksymtab(, , func( sym) error { := len() = [0 : +1] := &[] .Type = rune(.typ) .Value = .value switch .typ { default: .Name = string(.name) case 'z', 'Z': for := 0; < len(.name); += 2 { := binary.BigEndian.Uint16(.name[ : +2]) , := [] if ! { return &formatError{-1, "bad filename code", } } if := len(.Name); > 0 && .Name[-1] != '/' { .Name += "/" } .Name += } } switch .typ { case 'f': [uint16(.value)] = .Name } return nil }) if != nil { return nil, } return , nil } // ErrNoSymbols is returned by [File.Symbols] if there is no such section // in the File. var ErrNoSymbols = errors.New("no symbol section") // Symbols returns the symbol table for f. func ( *File) () ([]Sym, error) { := .Section("syms") if == nil { return nil, ErrNoSymbols } , := .Data() if != nil { return nil, errors.New("cannot load symbol section") } return newTable(, .PtrSize) } // Section returns a section with the given name, or nil if no such // section exists. func ( *File) ( string) *Section { for , := range .Sections { if .Name == { return } } return nil }