1// Copyright 2019 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
5package version
6
7import (
8	"bytes"
9	"debug/elf"
10	"debug/macho"
11	"debug/pe"
12	"fmt"
13	"internal/xcoff"
14	"io"
15	"os"
16)
17
18// An exe is a generic interface to an OS executable (ELF, Mach-O, PE, XCOFF).
19type exe interface {
20	// Close closes the underlying file.
21	Close() error
22
23	// ReadData reads and returns up to size byte starting at virtual address addr.
24	ReadData(addr, size uint64) ([]byte, error)
25
26	// DataStart returns the writable data segment start address.
27	DataStart() uint64
28}
29
30// openExe opens file and returns it as an exe.
31func openExe(file string) (exe, error) {
32	f, err := os.Open(file)
33	if err != nil {
34		return nil, err
35	}
36	data := make([]byte, 16)
37	if _, err := io.ReadFull(f, data); err != nil {
38		return nil, err
39	}
40	f.Seek(0, 0)
41	if bytes.HasPrefix(data, []byte("\x7FELF")) {
42		e, err := elf.NewFile(f)
43		if err != nil {
44			f.Close()
45			return nil, err
46		}
47		return &elfExe{f, e}, nil
48	}
49	if bytes.HasPrefix(data, []byte("MZ")) {
50		e, err := pe.NewFile(f)
51		if err != nil {
52			f.Close()
53			return nil, err
54		}
55		return &peExe{f, e}, nil
56	}
57	if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) {
58		e, err := macho.NewFile(f)
59		if err != nil {
60			f.Close()
61			return nil, err
62		}
63		return &machoExe{f, e}, nil
64	}
65	if bytes.HasPrefix(data, []byte{0x01, 0xDF}) || bytes.HasPrefix(data, []byte{0x01, 0xF7}) {
66		e, err := xcoff.NewFile(f)
67		if err != nil {
68			f.Close()
69			return nil, err
70		}
71		return &xcoffExe{f, e}, nil
72
73	}
74	return nil, fmt.Errorf("unrecognized executable format")
75}
76
77// elfExe is the ELF implementation of the exe interface.
78type elfExe struct {
79	os *os.File
80	f  *elf.File
81}
82
83func (x *elfExe) Close() error {
84	return x.os.Close()
85}
86
87func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
88	for _, prog := range x.f.Progs {
89		if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
90			n := prog.Vaddr + prog.Filesz - addr
91			if n > size {
92				n = size
93			}
94			data := make([]byte, n)
95			_, err := prog.ReadAt(data, int64(addr-prog.Vaddr))
96			if err != nil {
97				return nil, err
98			}
99			return data, nil
100		}
101	}
102	return nil, fmt.Errorf("address not mapped")
103}
104
105func (x *elfExe) DataStart() uint64 {
106	for _, s := range x.f.Sections {
107		if s.Name == ".go.buildinfo" {
108			return s.Addr
109		}
110	}
111	for _, p := range x.f.Progs {
112		if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
113			return p.Vaddr
114		}
115	}
116	return 0
117}
118
119// peExe is the PE (Windows Portable Executable) implementation of the exe interface.
120type peExe struct {
121	os *os.File
122	f  *pe.File
123}
124
125func (x *peExe) Close() error {
126	return x.os.Close()
127}
128
129func (x *peExe) imageBase() uint64 {
130	switch oh := x.f.OptionalHeader.(type) {
131	case *pe.OptionalHeader32:
132		return uint64(oh.ImageBase)
133	case *pe.OptionalHeader64:
134		return oh.ImageBase
135	}
136	return 0
137}
138
139func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
140	addr -= x.imageBase()
141	for _, sect := range x.f.Sections {
142		if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
143			n := uint64(sect.VirtualAddress+sect.Size) - addr
144			if n > size {
145				n = size
146			}
147			data := make([]byte, n)
148			_, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
149			if err != nil {
150				return nil, err
151			}
152			return data, nil
153		}
154	}
155	return nil, fmt.Errorf("address not mapped")
156}
157
158func (x *peExe) DataStart() uint64 {
159	// Assume data is first writable section.
160	const (
161		IMAGE_SCN_CNT_CODE               = 0x00000020
162		IMAGE_SCN_CNT_INITIALIZED_DATA   = 0x00000040
163		IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
164		IMAGE_SCN_MEM_EXECUTE            = 0x20000000
165		IMAGE_SCN_MEM_READ               = 0x40000000
166		IMAGE_SCN_MEM_WRITE              = 0x80000000
167		IMAGE_SCN_MEM_DISCARDABLE        = 0x2000000
168		IMAGE_SCN_LNK_NRELOC_OVFL        = 0x1000000
169		IMAGE_SCN_ALIGN_32BYTES          = 0x600000
170	)
171	for _, sect := range x.f.Sections {
172		if sect.VirtualAddress != 0 && sect.Size != 0 &&
173			sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
174			return uint64(sect.VirtualAddress) + x.imageBase()
175		}
176	}
177	return 0
178}
179
180// machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
181type machoExe struct {
182	os *os.File
183	f  *macho.File
184}
185
186func (x *machoExe) Close() error {
187	return x.os.Close()
188}
189
190func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
191	for _, load := range x.f.Loads {
192		seg, ok := load.(*macho.Segment)
193		if !ok {
194			continue
195		}
196		if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
197			if seg.Name == "__PAGEZERO" {
198				continue
199			}
200			n := seg.Addr + seg.Filesz - addr
201			if n > size {
202				n = size
203			}
204			data := make([]byte, n)
205			_, err := seg.ReadAt(data, int64(addr-seg.Addr))
206			if err != nil {
207				return nil, err
208			}
209			return data, nil
210		}
211	}
212	return nil, fmt.Errorf("address not mapped")
213}
214
215func (x *machoExe) DataStart() uint64 {
216	// Look for section named "__go_buildinfo".
217	for _, sec := range x.f.Sections {
218		if sec.Name == "__go_buildinfo" {
219			return sec.Addr
220		}
221	}
222	// Try the first non-empty writable segment.
223	const RW = 3
224	for _, load := range x.f.Loads {
225		seg, ok := load.(*macho.Segment)
226		if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
227			return seg.Addr
228		}
229	}
230	return 0
231}
232
233// xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface.
234type xcoffExe struct {
235	os *os.File
236	f  *xcoff.File
237}
238
239func (x *xcoffExe) Close() error {
240	return x.os.Close()
241}
242
243func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) {
244	for _, sect := range x.f.Sections {
245		if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
246			n := uint64(sect.VirtualAddress+sect.Size) - addr
247			if n > size {
248				n = size
249			}
250			data := make([]byte, n)
251			_, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
252			if err != nil {
253				return nil, err
254			}
255			return data, nil
256		}
257	}
258	return nil, fmt.Errorf("address not mapped")
259}
260
261func (x *xcoffExe) DataStart() uint64 {
262	return x.f.SectionByType(xcoff.STYP_DATA).VirtualAddress
263}
264