1// Copyright 2018 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// Package xcoff implements access to XCOFF (Extended Common Object File Format) files.
6package xcoff
7
8import (
9	"debug/dwarf"
10	"encoding/binary"
11	"errors"
12	"fmt"
13	"internal/saferio"
14	"io"
15	"os"
16	"strings"
17)
18
19// SectionHeader holds information about an XCOFF section header.
20type SectionHeader struct {
21	Name           string
22	VirtualAddress uint64
23	Size           uint64
24	Type           uint32
25	Relptr         uint64
26	Nreloc         uint32
27}
28
29type Section struct {
30	SectionHeader
31	Relocs []Reloc
32	io.ReaderAt
33	sr *io.SectionReader
34}
35
36// AuxiliaryCSect holds information about an XCOFF symbol in an AUX_CSECT entry.
37type AuxiliaryCSect struct {
38	Length              int64
39	StorageMappingClass int
40	SymbolType          int
41}
42
43// AuxiliaryFcn holds information about an XCOFF symbol in an AUX_FCN entry.
44type AuxiliaryFcn struct {
45	Size int64
46}
47
48type Symbol struct {
49	Name          string
50	Value         uint64
51	SectionNumber int
52	StorageClass  int
53	AuxFcn        AuxiliaryFcn
54	AuxCSect      AuxiliaryCSect
55}
56
57type Reloc struct {
58	VirtualAddress   uint64
59	Symbol           *Symbol
60	Signed           bool
61	InstructionFixed bool
62	Length           uint8
63	Type             uint8
64}
65
66// ImportedSymbol holds information about an imported XCOFF symbol.
67type ImportedSymbol struct {
68	Name    string
69	Library string
70}
71
72// FileHeader holds information about an XCOFF file header.
73type FileHeader struct {
74	TargetMachine uint16
75}
76
77// A File represents an open XCOFF file.
78type File struct {
79	FileHeader
80	Sections     []*Section
81	Symbols      []*Symbol
82	StringTable  []byte
83	LibraryPaths []string
84
85	closer io.Closer
86}
87
88// Open opens the named file using os.Open and prepares it for use as an XCOFF binary.
89func Open(name string) (*File, error) {
90	f, err := os.Open(name)
91	if err != nil {
92		return nil, err
93	}
94	ff, err := NewFile(f)
95	if err != nil {
96		f.Close()
97		return nil, err
98	}
99	ff.closer = f
100	return ff, nil
101}
102
103// Close closes the File.
104// If the File was created using NewFile directly instead of Open,
105// Close has no effect.
106func (f *File) Close() error {
107	var err error
108	if f.closer != nil {
109		err = f.closer.Close()
110		f.closer = nil
111	}
112	return err
113}
114
115// Section returns the first section with the given name, or nil if no such
116// section exists.
117// Xcoff have section's name limited to 8 bytes. Some sections like .gosymtab
118// can be trunked but this method will still find them.
119func (f *File) Section(name string) *Section {
120	for _, s := range f.Sections {
121		if s.Name == name || (len(name) > 8 && s.Name == name[:8]) {
122			return s
123		}
124	}
125	return nil
126}
127
128// SectionByType returns the first section in f with the
129// given type, or nil if there is no such section.
130func (f *File) SectionByType(typ uint32) *Section {
131	for _, s := range f.Sections {
132		if s.Type == typ {
133			return s
134		}
135	}
136	return nil
137}
138
139// cstring converts ASCII byte sequence b to string.
140// It stops once it finds 0 or reaches end of b.
141func cstring(b []byte) string {
142	var i int
143	for i = 0; i < len(b) && b[i] != 0; i++ {
144	}
145	return string(b[:i])
146}
147
148// getString extracts a string from an XCOFF string table.
149func getString(st []byte, offset uint32) (string, bool) {
150	if offset < 4 || int(offset) >= len(st) {
151		return "", false
152	}
153	return cstring(st[offset:]), true
154}
155
156// NewFile creates a new File for accessing an XCOFF binary in an underlying reader.
157func NewFile(r io.ReaderAt) (*File, error) {
158	sr := io.NewSectionReader(r, 0, 1<<63-1)
159	// Read XCOFF target machine
160	var magic uint16
161	if err := binary.Read(sr, binary.BigEndian, &magic); err != nil {
162		return nil, err
163	}
164	if magic != U802TOCMAGIC && magic != U64_TOCMAGIC {
165		return nil, fmt.Errorf("unrecognised XCOFF magic: 0x%x", magic)
166	}
167
168	f := new(File)
169	f.TargetMachine = magic
170
171	// Read XCOFF file header
172	if _, err := sr.Seek(0, io.SeekStart); err != nil {
173		return nil, err
174	}
175	var nscns uint16
176	var symptr uint64
177	var nsyms uint32
178	var opthdr uint16
179	var hdrsz int
180	switch f.TargetMachine {
181	case U802TOCMAGIC:
182		fhdr := new(FileHeader32)
183		if err := binary.Read(sr, binary.BigEndian, fhdr); err != nil {
184			return nil, err
185		}
186		nscns = fhdr.Fnscns
187		symptr = uint64(fhdr.Fsymptr)
188		nsyms = fhdr.Fnsyms
189		opthdr = fhdr.Fopthdr
190		hdrsz = FILHSZ_32
191	case U64_TOCMAGIC:
192		fhdr := new(FileHeader64)
193		if err := binary.Read(sr, binary.BigEndian, fhdr); err != nil {
194			return nil, err
195		}
196		nscns = fhdr.Fnscns
197		symptr = fhdr.Fsymptr
198		nsyms = fhdr.Fnsyms
199		opthdr = fhdr.Fopthdr
200		hdrsz = FILHSZ_64
201	}
202
203	if symptr == 0 || nsyms <= 0 {
204		return nil, fmt.Errorf("no symbol table")
205	}
206
207	// Read string table (located right after symbol table).
208	offset := symptr + uint64(nsyms)*SYMESZ
209	if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil {
210		return nil, err
211	}
212	// The first 4 bytes contain the length (in bytes).
213	var l uint32
214	if err := binary.Read(sr, binary.BigEndian, &l); err != nil {
215		return nil, err
216	}
217	if l > 4 {
218		st, err := saferio.ReadDataAt(sr, uint64(l), int64(offset))
219		if err != nil {
220			return nil, err
221		}
222		f.StringTable = st
223	}
224
225	// Read section headers
226	if _, err := sr.Seek(int64(hdrsz)+int64(opthdr), io.SeekStart); err != nil {
227		return nil, err
228	}
229	c := saferio.SliceCap[*Section](uint64(nscns))
230	if c < 0 {
231		return nil, fmt.Errorf("too many XCOFF sections (%d)", nscns)
232	}
233	f.Sections = make([]*Section, 0, c)
234	for i := 0; i < int(nscns); i++ {
235		var scnptr uint64
236		s := new(Section)
237		switch f.TargetMachine {
238		case U802TOCMAGIC:
239			shdr := new(SectionHeader32)
240			if err := binary.Read(sr, binary.BigEndian, shdr); err != nil {
241				return nil, err
242			}
243			s.Name = cstring(shdr.Sname[:])
244			s.VirtualAddress = uint64(shdr.Svaddr)
245			s.Size = uint64(shdr.Ssize)
246			scnptr = uint64(shdr.Sscnptr)
247			s.Type = shdr.Sflags
248			s.Relptr = uint64(shdr.Srelptr)
249			s.Nreloc = uint32(shdr.Snreloc)
250		case U64_TOCMAGIC:
251			shdr := new(SectionHeader64)
252			if err := binary.Read(sr, binary.BigEndian, shdr); err != nil {
253				return nil, err
254			}
255			s.Name = cstring(shdr.Sname[:])
256			s.VirtualAddress = shdr.Svaddr
257			s.Size = shdr.Ssize
258			scnptr = shdr.Sscnptr
259			s.Type = shdr.Sflags
260			s.Relptr = shdr.Srelptr
261			s.Nreloc = shdr.Snreloc
262		}
263		r2 := r
264		if scnptr == 0 { // .bss must have all 0s
265			r2 = &nobitsSectionReader{}
266		}
267		s.sr = io.NewSectionReader(r2, int64(scnptr), int64(s.Size))
268		s.ReaderAt = s.sr
269		f.Sections = append(f.Sections, s)
270	}
271
272	// Symbol map needed by relocation
273	var idxToSym = make(map[int]*Symbol)
274
275	// Read symbol table
276	if _, err := sr.Seek(int64(symptr), io.SeekStart); err != nil {
277		return nil, err
278	}
279	f.Symbols = make([]*Symbol, 0)
280	for i := 0; i < int(nsyms); i++ {
281		var numaux int
282		var ok, needAuxFcn bool
283		sym := new(Symbol)
284		switch f.TargetMachine {
285		case U802TOCMAGIC:
286			se := new(SymEnt32)
287			if err := binary.Read(sr, binary.BigEndian, se); err != nil {
288				return nil, err
289			}
290			numaux = int(se.Nnumaux)
291			sym.SectionNumber = int(se.Nscnum)
292			sym.StorageClass = int(se.Nsclass)
293			sym.Value = uint64(se.Nvalue)
294			needAuxFcn = se.Ntype&SYM_TYPE_FUNC != 0 && numaux > 1
295			zeroes := binary.BigEndian.Uint32(se.Nname[:4])
296			if zeroes != 0 {
297				sym.Name = cstring(se.Nname[:])
298			} else {
299				offset := binary.BigEndian.Uint32(se.Nname[4:])
300				sym.Name, ok = getString(f.StringTable, offset)
301				if !ok {
302					goto skip
303				}
304			}
305		case U64_TOCMAGIC:
306			se := new(SymEnt64)
307			if err := binary.Read(sr, binary.BigEndian, se); err != nil {
308				return nil, err
309			}
310			numaux = int(se.Nnumaux)
311			sym.SectionNumber = int(se.Nscnum)
312			sym.StorageClass = int(se.Nsclass)
313			sym.Value = se.Nvalue
314			needAuxFcn = se.Ntype&SYM_TYPE_FUNC != 0 && numaux > 1
315			sym.Name, ok = getString(f.StringTable, se.Noffset)
316			if !ok {
317				goto skip
318			}
319		}
320		if sym.StorageClass != C_EXT && sym.StorageClass != C_WEAKEXT && sym.StorageClass != C_HIDEXT {
321			goto skip
322		}
323		// Must have at least one csect auxiliary entry.
324		if numaux < 1 || i+numaux >= int(nsyms) {
325			goto skip
326		}
327
328		if sym.SectionNumber > int(nscns) {
329			goto skip
330		}
331		if sym.SectionNumber == 0 {
332			sym.Value = 0
333		} else {
334			sym.Value -= f.Sections[sym.SectionNumber-1].VirtualAddress
335		}
336
337		idxToSym[i] = sym
338
339		// If this symbol is a function, it must retrieve its size from
340		// its AUX_FCN entry.
341		// It can happen that a function symbol doesn't have any AUX_FCN.
342		// In this case, needAuxFcn is false and their size will be set to 0.
343		if needAuxFcn {
344			switch f.TargetMachine {
345			case U802TOCMAGIC:
346				aux := new(AuxFcn32)
347				if err := binary.Read(sr, binary.BigEndian, aux); err != nil {
348					return nil, err
349				}
350				sym.AuxFcn.Size = int64(aux.Xfsize)
351			case U64_TOCMAGIC:
352				aux := new(AuxFcn64)
353				if err := binary.Read(sr, binary.BigEndian, aux); err != nil {
354					return nil, err
355				}
356				sym.AuxFcn.Size = int64(aux.Xfsize)
357			}
358		}
359
360		// Read csect auxiliary entry (by convention, it is the last).
361		if !needAuxFcn {
362			if _, err := sr.Seek(int64(numaux-1)*SYMESZ, io.SeekCurrent); err != nil {
363				return nil, err
364			}
365		}
366		i += numaux
367		numaux = 0
368		switch f.TargetMachine {
369		case U802TOCMAGIC:
370			aux := new(AuxCSect32)
371			if err := binary.Read(sr, binary.BigEndian, aux); err != nil {
372				return nil, err
373			}
374			sym.AuxCSect.SymbolType = int(aux.Xsmtyp & 0x7)
375			sym.AuxCSect.StorageMappingClass = int(aux.Xsmclas)
376			sym.AuxCSect.Length = int64(aux.Xscnlen)
377		case U64_TOCMAGIC:
378			aux := new(AuxCSect64)
379			if err := binary.Read(sr, binary.BigEndian, aux); err != nil {
380				return nil, err
381			}
382			sym.AuxCSect.SymbolType = int(aux.Xsmtyp & 0x7)
383			sym.AuxCSect.StorageMappingClass = int(aux.Xsmclas)
384			sym.AuxCSect.Length = int64(aux.Xscnlenhi)<<32 | int64(aux.Xscnlenlo)
385		}
386		f.Symbols = append(f.Symbols, sym)
387	skip:
388		i += numaux // Skip auxiliary entries
389		if _, err := sr.Seek(int64(numaux)*SYMESZ, io.SeekCurrent); err != nil {
390			return nil, err
391		}
392	}
393
394	// Read relocations
395	// Only for .data or .text section
396	for sectNum, sect := range f.Sections {
397		if sect.Type != STYP_TEXT && sect.Type != STYP_DATA {
398			continue
399		}
400		if sect.Relptr == 0 {
401			continue
402		}
403		c := saferio.SliceCap[Reloc](uint64(sect.Nreloc))
404		if c < 0 {
405			return nil, fmt.Errorf("too many relocs (%d) for section %d", sect.Nreloc, sectNum)
406		}
407		sect.Relocs = make([]Reloc, 0, c)
408		if _, err := sr.Seek(int64(sect.Relptr), io.SeekStart); err != nil {
409			return nil, err
410		}
411		for i := uint32(0); i < sect.Nreloc; i++ {
412			var reloc Reloc
413			switch f.TargetMachine {
414			case U802TOCMAGIC:
415				rel := new(Reloc32)
416				if err := binary.Read(sr, binary.BigEndian, rel); err != nil {
417					return nil, err
418				}
419				reloc.VirtualAddress = uint64(rel.Rvaddr)
420				reloc.Symbol = idxToSym[int(rel.Rsymndx)]
421				reloc.Type = rel.Rtype
422				reloc.Length = rel.Rsize&0x3F + 1
423
424				if rel.Rsize&0x80 != 0 {
425					reloc.Signed = true
426				}
427				if rel.Rsize&0x40 != 0 {
428					reloc.InstructionFixed = true
429				}
430
431			case U64_TOCMAGIC:
432				rel := new(Reloc64)
433				if err := binary.Read(sr, binary.BigEndian, rel); err != nil {
434					return nil, err
435				}
436				reloc.VirtualAddress = rel.Rvaddr
437				reloc.Symbol = idxToSym[int(rel.Rsymndx)]
438				reloc.Type = rel.Rtype
439				reloc.Length = rel.Rsize&0x3F + 1
440				if rel.Rsize&0x80 != 0 {
441					reloc.Signed = true
442				}
443				if rel.Rsize&0x40 != 0 {
444					reloc.InstructionFixed = true
445				}
446			}
447
448			sect.Relocs = append(sect.Relocs, reloc)
449		}
450	}
451
452	return f, nil
453}
454
455type nobitsSectionReader struct{}
456
457func (*nobitsSectionReader) ReadAt(p []byte, off int64) (n int, err error) {
458	return 0, errors.New("unexpected read from section with uninitialized data")
459}
460
461// Data reads and returns the contents of the XCOFF section s.
462func (s *Section) Data() ([]byte, error) {
463	dat := make([]byte, s.sr.Size())
464	n, err := s.sr.ReadAt(dat, 0)
465	if n == len(dat) {
466		err = nil
467	}
468	return dat[:n], err
469}
470
471// CSect reads and returns the contents of a csect.
472func (f *File) CSect(name string) []byte {
473	for _, sym := range f.Symbols {
474		if sym.Name == name && sym.AuxCSect.SymbolType == XTY_SD {
475			if i := sym.SectionNumber - 1; 0 <= i && i < len(f.Sections) {
476				s := f.Sections[i]
477				if sym.Value+uint64(sym.AuxCSect.Length) <= s.Size {
478					dat := make([]byte, sym.AuxCSect.Length)
479					_, err := s.sr.ReadAt(dat, int64(sym.Value))
480					if err != nil {
481						return nil
482					}
483					return dat
484				}
485			}
486			break
487		}
488	}
489	return nil
490}
491
492func (f *File) DWARF() (*dwarf.Data, error) {
493	// There are many other DWARF sections, but these
494	// are the ones the debug/dwarf package uses.
495	// Don't bother loading others.
496	var subtypes = [...]uint32{SSUBTYP_DWABREV, SSUBTYP_DWINFO, SSUBTYP_DWLINE, SSUBTYP_DWRNGES, SSUBTYP_DWSTR}
497	var dat [len(subtypes)][]byte
498	for i, subtype := range subtypes {
499		s := f.SectionByType(STYP_DWARF | subtype)
500		if s != nil {
501			b, err := s.Data()
502			if err != nil && uint64(len(b)) < s.Size {
503				return nil, err
504			}
505			dat[i] = b
506		}
507	}
508
509	abbrev, info, line, ranges, str := dat[0], dat[1], dat[2], dat[3], dat[4]
510	return dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str)
511}
512
513// readImportID returns the import file IDs stored inside the .loader section.
514// Library name pattern is either path/base/member or base/member
515func (f *File) readImportIDs(s *Section) ([]string, error) {
516	// Read loader header
517	if _, err := s.sr.Seek(0, io.SeekStart); err != nil {
518		return nil, err
519	}
520	var istlen uint32
521	var nimpid uint32
522	var impoff uint64
523	switch f.TargetMachine {
524	case U802TOCMAGIC:
525		lhdr := new(LoaderHeader32)
526		if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil {
527			return nil, err
528		}
529		istlen = lhdr.Listlen
530		nimpid = lhdr.Lnimpid
531		impoff = uint64(lhdr.Limpoff)
532	case U64_TOCMAGIC:
533		lhdr := new(LoaderHeader64)
534		if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil {
535			return nil, err
536		}
537		istlen = lhdr.Listlen
538		nimpid = lhdr.Lnimpid
539		impoff = lhdr.Limpoff
540	}
541
542	// Read loader import file ID table
543	if _, err := s.sr.Seek(int64(impoff), io.SeekStart); err != nil {
544		return nil, err
545	}
546	table := make([]byte, istlen)
547	if _, err := io.ReadFull(s.sr, table); err != nil {
548		return nil, err
549	}
550
551	offset := 0
552	// First import file ID is the default LIBPATH value
553	libpath := cstring(table[offset:])
554	f.LibraryPaths = strings.Split(libpath, ":")
555	offset += len(libpath) + 3 // 3 null bytes
556	all := make([]string, 0)
557	for i := 1; i < int(nimpid); i++ {
558		impidpath := cstring(table[offset:])
559		offset += len(impidpath) + 1
560		impidbase := cstring(table[offset:])
561		offset += len(impidbase) + 1
562		impidmem := cstring(table[offset:])
563		offset += len(impidmem) + 1
564		var path string
565		if len(impidpath) > 0 {
566			path = impidpath + "/" + impidbase + "/" + impidmem
567		} else {
568			path = impidbase + "/" + impidmem
569		}
570		all = append(all, path)
571	}
572
573	return all, nil
574}
575
576// ImportedSymbols returns the names of all symbols
577// referred to by the binary f that are expected to be
578// satisfied by other libraries at dynamic load time.
579// It does not return weak symbols.
580func (f *File) ImportedSymbols() ([]ImportedSymbol, error) {
581	s := f.SectionByType(STYP_LOADER)
582	if s == nil {
583		return nil, nil
584	}
585	// Read loader header
586	if _, err := s.sr.Seek(0, io.SeekStart); err != nil {
587		return nil, err
588	}
589	var stlen uint32
590	var stoff uint64
591	var nsyms uint32
592	var symoff uint64
593	switch f.TargetMachine {
594	case U802TOCMAGIC:
595		lhdr := new(LoaderHeader32)
596		if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil {
597			return nil, err
598		}
599		stlen = lhdr.Lstlen
600		stoff = uint64(lhdr.Lstoff)
601		nsyms = lhdr.Lnsyms
602		symoff = LDHDRSZ_32
603	case U64_TOCMAGIC:
604		lhdr := new(LoaderHeader64)
605		if err := binary.Read(s.sr, binary.BigEndian, lhdr); err != nil {
606			return nil, err
607		}
608		stlen = lhdr.Lstlen
609		stoff = lhdr.Lstoff
610		nsyms = lhdr.Lnsyms
611		symoff = lhdr.Lsymoff
612	}
613
614	// Read loader section string table
615	if _, err := s.sr.Seek(int64(stoff), io.SeekStart); err != nil {
616		return nil, err
617	}
618	st := make([]byte, stlen)
619	if _, err := io.ReadFull(s.sr, st); err != nil {
620		return nil, err
621	}
622
623	// Read imported libraries
624	libs, err := f.readImportIDs(s)
625	if err != nil {
626		return nil, err
627	}
628
629	// Read loader symbol table
630	if _, err := s.sr.Seek(int64(symoff), io.SeekStart); err != nil {
631		return nil, err
632	}
633	all := make([]ImportedSymbol, 0)
634	for i := 0; i < int(nsyms); i++ {
635		var name string
636		var ifile uint32
637		var ok bool
638		switch f.TargetMachine {
639		case U802TOCMAGIC:
640			ldsym := new(LoaderSymbol32)
641			if err := binary.Read(s.sr, binary.BigEndian, ldsym); err != nil {
642				return nil, err
643			}
644			if ldsym.Lsmtype&0x40 == 0 {
645				continue // Imported symbols only
646			}
647			zeroes := binary.BigEndian.Uint32(ldsym.Lname[:4])
648			if zeroes != 0 {
649				name = cstring(ldsym.Lname[:])
650			} else {
651				offset := binary.BigEndian.Uint32(ldsym.Lname[4:])
652				name, ok = getString(st, offset)
653				if !ok {
654					continue
655				}
656			}
657			ifile = ldsym.Lifile
658		case U64_TOCMAGIC:
659			ldsym := new(LoaderSymbol64)
660			if err := binary.Read(s.sr, binary.BigEndian, ldsym); err != nil {
661				return nil, err
662			}
663			if ldsym.Lsmtype&0x40 == 0 {
664				continue // Imported symbols only
665			}
666			name, ok = getString(st, ldsym.Loffset)
667			if !ok {
668				continue
669			}
670			ifile = ldsym.Lifile
671		}
672		var sym ImportedSymbol
673		sym.Name = name
674		if ifile >= 1 && int(ifile) <= len(libs) {
675			sym.Library = libs[ifile-1]
676		}
677		all = append(all, sym)
678	}
679
680	return all, nil
681}
682
683// ImportedLibraries returns the names of all libraries
684// referred to by the binary f that are expected to be
685// linked with the binary at dynamic link time.
686func (f *File) ImportedLibraries() ([]string, error) {
687	s := f.SectionByType(STYP_LOADER)
688	if s == nil {
689		return nil, nil
690	}
691	all, err := f.readImportIDs(s)
692	return all, err
693}
694