file.mx raw
1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 /*
6 Package plan9obj implements access to Plan 9 a.out object files.
7
8 # Security
9
10 This package is not designed to be hardened against adversarial inputs, and is
11 outside the scope of https://go.dev/security/policy. In particular, only basic
12 validation is done when parsing object files. As such, care should be taken when
13 parsing untrusted inputs, as parsing malformed files may consume significant
14 resources, or cause panics.
15 */
16 package plan9obj
17
18 import (
19 "encoding/binary"
20 "errors"
21 "fmt"
22 "internal/saferio"
23 "io"
24 "os"
25 )
26
27 // A FileHeader represents a Plan 9 a.out file header.
28 type FileHeader struct {
29 Magic uint32
30 Bss uint32
31 Entry uint64
32 PtrSize int
33 LoadAddress uint64
34 HdrSize uint64
35 }
36
37 // A File represents an open Plan 9 a.out file.
38 type File struct {
39 FileHeader
40 Sections []*Section
41 closer io.Closer
42 }
43
44 // A SectionHeader represents a single Plan 9 a.out section header.
45 // This structure doesn't exist on-disk, but eases navigation
46 // through the object file.
47 type SectionHeader struct {
48 Name string
49 Size uint32
50 Offset uint32
51 }
52
53 // A Section represents a single section in a Plan 9 a.out file.
54 type Section struct {
55 SectionHeader
56
57 // Embed ReaderAt for ReadAt method.
58 // Do not embed SectionReader directly
59 // to avoid having Read and Seek.
60 // If a client wants Read and Seek it must use
61 // Open() to avoid fighting over the seek offset
62 // with other clients.
63 io.ReaderAt
64 sr *io.SectionReader
65 }
66
67 // Data reads and returns the contents of the Plan 9 a.out section.
68 func (s *Section) Data() ([]byte, error) {
69 return saferio.ReadDataAt(s.sr, uint64(s.Size), 0)
70 }
71
72 // Open returns a new ReadSeeker reading the Plan 9 a.out section.
73 func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
74
75 // A Symbol represents an entry in a Plan 9 a.out symbol table section.
76 type Sym struct {
77 Value uint64
78 Type rune
79 Name string
80 }
81
82 /*
83 * Plan 9 a.out reader
84 */
85
86 // formatError is returned by some operations if the data does
87 // not have the correct format for an object file.
88 type formatError struct {
89 off int
90 msg string
91 val any
92 }
93
94 func (e *formatError) Error() string {
95 msg := e.msg
96 if e.val != nil {
97 msg += fmt.Sprintf(" '%v'", e.val)
98 }
99 msg += fmt.Sprintf(" in record at byte %#x", e.off)
100 return msg
101 }
102
103 // Open opens the named file using [os.Open] and prepares it for use as a Plan 9 a.out binary.
104 func Open(name string) (*File, error) {
105 f, err := os.Open(name)
106 if err != nil {
107 return nil, err
108 }
109 ff, err := NewFile(f)
110 if err != nil {
111 f.Close()
112 return nil, err
113 }
114 ff.closer = f
115 return ff, nil
116 }
117
118 // Close closes the [File].
119 // If the [File] was created using [NewFile] directly instead of [Open],
120 // Close has no effect.
121 func (f *File) Close() error {
122 var err error
123 if f.closer != nil {
124 err = f.closer.Close()
125 f.closer = nil
126 }
127 return err
128 }
129
130 func parseMagic(magic []byte) (uint32, error) {
131 m := binary.BigEndian.Uint32(magic)
132 switch m {
133 case Magic386, MagicAMD64, MagicARM:
134 return m, nil
135 }
136 return 0, &formatError{0, "bad magic number", magic}
137 }
138
139 // NewFile creates a new [File] for accessing a Plan 9 binary in an underlying reader.
140 // The Plan 9 binary is expected to start at position 0 in the ReaderAt.
141 func NewFile(r io.ReaderAt) (*File, error) {
142 sr := io.NewSectionReader(r, 0, 1<<63-1)
143 // Read and decode Plan 9 magic
144 var magic [4]byte
145 if _, err := r.ReadAt(magic[:], 0); err != nil {
146 return nil, err
147 }
148 _, err := parseMagic(magic[:])
149 if err != nil {
150 return nil, err
151 }
152
153 ph := &prog{}
154 if err := binary.Read(sr, binary.BigEndian, ph); err != nil {
155 return nil, err
156 }
157
158 f := &File{FileHeader: FileHeader{
159 Magic: ph.Magic,
160 Bss: ph.Bss,
161 Entry: uint64(ph.Entry),
162 PtrSize: 4,
163 LoadAddress: 0x1000,
164 HdrSize: 4 * 8,
165 }}
166
167 if ph.Magic&Magic64 != 0 {
168 if err := binary.Read(sr, binary.BigEndian, &f.Entry); err != nil {
169 return nil, err
170 }
171 f.PtrSize = 8
172 f.LoadAddress = 0x200000
173 f.HdrSize += 8
174 }
175
176 var sects = []struct {
177 name string
178 size uint32
179 }{
180 {"text", ph.Text},
181 {"data", ph.Data},
182 {"syms", ph.Syms},
183 {"spsz", ph.Spsz},
184 {"pcsz", ph.Pcsz},
185 }
186
187 f.Sections = []*Section{:5}
188
189 off := uint32(f.HdrSize)
190
191 for i, sect := range sects {
192 s := &Section{}
193 s.SectionHeader = SectionHeader{
194 Name: sect.name,
195 Size: sect.size,
196 Offset: off,
197 }
198 off += sect.size
199 s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Size))
200 s.ReaderAt = s.sr
201 f.Sections[i] = s
202 }
203
204 return f, nil
205 }
206
207 func walksymtab(data []byte, ptrsz int, fn func(sym) error) error {
208 var order binary.ByteOrder = binary.BigEndian
209 var s sym
210 p := data
211 for len(p) >= 4 {
212 // Symbol type, value.
213 if len(p) < ptrsz {
214 return &formatError{len(data), "unexpected EOF", nil}
215 }
216 // fixed-width value
217 if ptrsz == 8 {
218 s.value = order.Uint64(p[0:8])
219 p = p[8:]
220 } else {
221 s.value = uint64(order.Uint32(p[0:4]))
222 p = p[4:]
223 }
224
225 if len(p) < 1 {
226 return &formatError{len(data), "unexpected EOF", nil}
227 }
228 typ := p[0] & 0x7F
229 s.typ = typ
230 p = p[1:]
231
232 // Name.
233 var i int
234 var nnul int
235 for i = 0; i < len(p); i++ {
236 if p[i] == 0 {
237 nnul = 1
238 break
239 }
240 }
241 switch typ {
242 case 'z', 'Z':
243 p = p[i+nnul:]
244 for i = 0; i+2 <= len(p); i += 2 {
245 if p[i] == 0 && p[i+1] == 0 {
246 nnul = 2
247 break
248 }
249 }
250 }
251 if len(p) < i+nnul {
252 return &formatError{len(data), "unexpected EOF", nil}
253 }
254 s.name = p[0:i]
255 i += nnul
256 p = p[i:]
257
258 fn(s)
259 }
260 return nil
261 }
262
263 // newTable decodes the Go symbol table in data,
264 // returning an in-memory representation.
265 func newTable(symtab []byte, ptrsz int) ([]Sym, error) {
266 var n int
267 err := walksymtab(symtab, ptrsz, func(s sym) error {
268 n++
269 return nil
270 })
271 if err != nil {
272 return nil, err
273 }
274
275 fname := map[uint16]string{}
276 syms := []Sym{:0:n}
277 err = walksymtab(symtab, ptrsz, func(s sym) error {
278 n := len(syms)
279 syms = syms[0 : n+1]
280 ts := &syms[n]
281 ts.Type = rune(s.typ)
282 ts.Value = s.value
283 switch s.typ {
284 default:
285 ts.Name = string(s.name)
286 case 'z', 'Z':
287 for i := 0; i < len(s.name); i += 2 {
288 eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
289 elt, ok := fname[eltIdx]
290 if !ok {
291 return &formatError{-1, "bad filename code", eltIdx}
292 }
293 if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
294 ts.Name += "/"
295 }
296 ts.Name += elt
297 }
298 }
299 switch s.typ {
300 case 'f':
301 fname[uint16(s.value)] = ts.Name
302 }
303 return nil
304 })
305 if err != nil {
306 return nil, err
307 }
308
309 return syms, nil
310 }
311
312 // ErrNoSymbols is returned by [File.Symbols] if there is no such section
313 // in the File.
314 var ErrNoSymbols = errors.New("no symbol section")
315
316 // Symbols returns the symbol table for f.
317 func (f *File) Symbols() ([]Sym, error) {
318 symtabSection := f.Section("syms")
319 if symtabSection == nil {
320 return nil, ErrNoSymbols
321 }
322
323 symtab, err := symtabSection.Data()
324 if err != nil {
325 return nil, errors.New("cannot load symbol section")
326 }
327
328 return newTable(symtab, f.PtrSize)
329 }
330
331 // Section returns a section with the given name, or nil if no such
332 // section exists.
333 func (f *File) Section(name string) *Section {
334 for _, s := range f.Sections {
335 if s.Name == name {
336 return s
337 }
338 }
339 return nil
340 }
341