-
Notifications
You must be signed in to change notification settings - Fork 3
/
dwarf.go
206 lines (189 loc) · 6.11 KB
/
dwarf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
This file is based on a copy of https://github.com/FiloSottile/gorebuild/blob/master/dwarf.go.
The file is (c) by Filippo Valsorda under the MIT license. See LICENSE.dwarf.go.txt.
*/
package main
import (
"debug/elf"
"debug/gosym"
"debug/macho"
"debug/pe"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
func getTableElf(f *os.File) (textStart uint64, symtab, pclntab []byte, err error) {
obj, err := elf.NewFile(f)
if err != nil {
return 0, nil, nil, errors.Wrap(err, "file is not an ELF binary")
}
if sect := obj.Section(".text"); sect == nil {
return 0, nil, nil, errors.New("empty .text")
} else {
textStart = sect.Addr
}
if sect := obj.Section(".gosymtab"); sect != nil {
if symtab, err = sect.Data(); err != nil {
return 0, nil, nil, errors.Wrap(err, "error reading .gosymtab")
}
}
if sect := obj.Section(".gopclntab"); sect != nil {
if pclntab, err = sect.Data(); err != nil {
return 0, nil, nil, errors.Wrap(err, "error reading .gopclntab")
}
} else {
return 0, nil, nil, errors.New("empty .gopclntab")
}
return textStart, symtab, pclntab, nil
}
func getTableMachO(f *os.File) (textStart uint64, symtab, pclntab []byte, err error) {
obj, err := macho.NewFile(f)
if err != nil {
return 0, nil, nil, errors.Wrap(err, "file has neither elf nor macho format")
}
if sect := obj.Section("__text"); sect == nil {
return 0, nil, nil, errors.New("empty __text")
} else {
textStart = sect.Addr
}
if sect := obj.Section("__gosymtab"); sect != nil {
if symtab, err = sect.Data(); err != nil {
return 0, nil, nil, errors.Wrap(err, "error reading __gosymtab")
}
}
if sect := obj.Section("__gopclntab"); sect != nil {
if pclntab, err = sect.Data(); err != nil {
return 0, nil, nil, errors.Wrap(err, "error reading __gopclntab")
}
} else {
return 0, nil, nil, errors.New("empty __gopclntab")
}
return textStart, symtab, pclntab, nil
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
// With hat tip to the Delve authors https://github.com/derekparker/delve/blob/master/pkg/proc/bininfo.go#L427
func getTablePe(f *os.File) (textStart uint64, symtab, pclntab []byte, err error) {
obj, err := pe.NewFile(f)
if err != nil {
return 0, nil, nil, errors.Wrap(err, "file is not a PE binary")
}
var imageBase uint64
switch oh := obj.OptionalHeader.(type) {
case *pe.OptionalHeader32:
imageBase = uint64(oh.ImageBase)
case *pe.OptionalHeader64:
imageBase = oh.ImageBase
default:
return 0, nil, nil, fmt.Errorf("pe file format not recognized")
}
if sect := obj.Section(".text"); sect != nil {
textStart = imageBase + uint64(sect.VirtualAddress)
}
if pclntab, err = loadPETable(obj, "runtime.pclntab", "runtime.epclntab"); err != nil {
// We didn't find the symbols, so look for the names used in 1.3 and earlier.
// TODO: Remove code looking for the old symbols when we no longer care about 1.3.
if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil {
return 0, nil, nil, errors.Wrap(err, "(e)pclntab not found")
}
}
if symtab, err = loadPETable(obj, "runtime.symtab", "runtime.esymtab"); err != nil {
// Same as above.
if symtab, err = loadPETable(obj, "symtab", "esymtab"); err != nil {
return 0, nil, nil, errors.Wrap(err, "(e)symtab not found")
}
}
return textStart, symtab, pclntab, nil
}
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
for _, s := range f.Symbols {
if s.Name != name {
continue
}
if s.SectionNumber <= 0 {
return nil, fmt.Errorf("symbol %s: invalid section number %d", name, s.SectionNumber)
}
if len(f.Sections) < int(s.SectionNumber) {
return nil, fmt.Errorf("symbol %s: section number %d is larger than max %d", name, s.SectionNumber, len(f.Sections))
}
return s, nil
}
return nil, fmt.Errorf("no %s symbol found", name)
}
func loadPETable(f *pe.File, sname, ename string) ([]byte, error) {
ssym, err := findPESymbol(f, sname)
if err != nil {
return nil, err
}
esym, err := findPESymbol(f, ename)
if err != nil {
return nil, err
}
if ssym.SectionNumber != esym.SectionNumber {
return nil, fmt.Errorf("%s and %s symbols must be in the same section", sname, ename)
}
sect := f.Sections[ssym.SectionNumber-1]
data, err := sect.Data()
if err != nil {
return nil, err
}
return data[ssym.Value:esym.Value], nil
}
func getTable(file string) (*gosym.Table, error) {
f, err := os.Open(file)
if err != nil {
return nil, errors.Wrap(err, "Error opening file "+file)
}
textStart, symtab, pclntab, err := getTableElf(f)
if err != nil {
if textStart, symtab, pclntab, err = getTableMachO(f); err != nil {
if textStart, symtab, pclntab, err = getTablePe(f); err != nil {
return nil, errors.Wrap(err, "file format is neither of ELF, Mach-O, or PE")
}
}
}
pcln := gosym.NewLineTable(pclntab, textStart)
t, err := gosym.NewTable(symtab, pcln)
if err != nil {
return nil, errors.Wrap(err, "cannot create symbol table")
}
return t, nil
}
func getMainPathDwarf(file string) (string, string, error) {
table, err := getTable(file)
if err != nil {
return "", "", errors.Wrap(err, "main path not found (getTable)")
}
gosymFunc := table.LookupFunc("main.main")
if gosymFunc == nil {
return "", "", errors.Wrap(err, "main path not found (LookupFunc)")
}
path, _, _ := table.PCToLine(gosymFunc.Entry)
return stripPath(filepath.Dir(path)), "", nil
}
// strip path strips the GOPATH prefix from the raw source code path
// as returned by getMainPath.
// If the path is absolute, stripPath first assumes a GOPATH prefix and
// searches for the first occurrence of "/src/". It returns the part
// after "/src/".
// If the absolute path contains no "/src/", stripPath searches for "/pkg/mod/",
// which is where Go modules are stored.
// If the absolute path contains neither "/src/" nor "/pkg/mod/",
// stripPath returns the full path.
// If the path is relative, stripPath does not touch the path at all.
func stripPath(path string) string {
path = filepath.ToSlash(path)
if !filepath.IsAbs(path) {
return path
}
n := strings.Index(path, "/src/")
if n != -1 {
return path[n+5:]
}
n = strings.Index(path, "/pkg/mod/")
if n != -1 {
return path[n+9:]
}
return path
}