Skip to content

Commit a1679cb

Browse files
committed
WIP: gosym v2 implementation
1 parent fbac94a commit a1679cb

File tree

11 files changed

+1858
-0
lines changed

11 files changed

+1858
-0
lines changed

src/debug/gosym/v2/cli/main.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
package main
7+
8+
import (
9+
"debug/gosym/v2"
10+
"flag"
11+
"fmt"
12+
"os"
13+
"strconv"
14+
)
15+
16+
var (
17+
inlines = flag.Bool("inlines", false, "List inline functions")
18+
lines = flag.Bool("lines", false, "List function lines")
19+
)
20+
21+
func functions(table *gosym.Table) error {
22+
for f := range table.Functions() {
23+
name, err := f.Name()
24+
if err != nil {
25+
return err
26+
}
27+
entry, err := f.Entry()
28+
if err != nil {
29+
return err
30+
}
31+
end, err := f.End()
32+
if err != nil {
33+
return err
34+
}
35+
deferreturn, err := f.DeferReturn()
36+
if err != nil {
37+
return err
38+
}
39+
file, err := f.File()
40+
if err != nil {
41+
return err
42+
}
43+
startLine, err := f.StartLine()
44+
if err != nil {
45+
return err
46+
}
47+
fmt.Printf("%s@%s:%d pc=[0x%x, 0x%x) deferreturn=0x%x\n", name.Value(), file.Value(), startLine, entry, end, deferreturn)
48+
}
49+
return nil
50+
}
51+
52+
func describe(table *gosym.Table, pc uint64, inlines, lines bool) error {
53+
f, err := table.ResolveFunction(pc)
54+
name, err := f.Name()
55+
if err != nil {
56+
return err
57+
}
58+
entry, err := f.Entry()
59+
if err != nil {
60+
return err
61+
}
62+
end, err := f.End()
63+
if err != nil {
64+
return err
65+
}
66+
deferreturn, err := f.DeferReturn()
67+
if err != nil {
68+
return err
69+
}
70+
file, err := f.File()
71+
if err != nil {
72+
return err
73+
}
74+
startLine, err := f.StartLine()
75+
if err != nil {
76+
return err
77+
}
78+
fmt.Printf("%s@%s:%d pc=[0x%x, 0x%x) deferreturn=0x%x\n",
79+
name.Value(), file.Value(), startLine, entry, end, deferreturn)
80+
81+
if inlines {
82+
inlines, err := f.InlineFunctions(nil)
83+
if err != nil {
84+
return err
85+
}
86+
if len(inlines) == 0 {
87+
fmt.Println(" (no inlines)")
88+
}
89+
for _, inline := range inlines {
90+
fmt.Printf(" %s@%s:%d\n",
91+
inline.Name.Value(), inline.File.Value(), inline.StartLine)
92+
}
93+
}
94+
if lines {
95+
r, err := f.Lines(gosym.LinesResult{})
96+
if err != nil {
97+
return err
98+
}
99+
if len(r.FunctionLines) == 0 {
100+
fmt.Println(" (no lines)")
101+
}
102+
for _, line := range r.FunctionLines {
103+
fmt.Printf(" [0x%x, 0x%x) %s@%s:%d parentPC=0x%x\n",
104+
line.PCLo, line.PCHi, line.Name.Value(), line.File.Value(), line.Line, line.ParentPC)
105+
}
106+
}
107+
return nil
108+
}
109+
110+
func locate(table *gosym.Table, pc uint64) error {
111+
locs, err := table.ResolveLocations(pc, nil)
112+
if err != nil {
113+
return err
114+
}
115+
for _, loc := range locs {
116+
fmt.Printf("%s@%s:%d\n", loc.Function.Value(), loc.File.Value(), loc.Line)
117+
}
118+
return nil
119+
}
120+
121+
func main() {
122+
usage := func() {
123+
fmt.Printf(`Usage:
124+
$ %s functions <binary> # List all function symbols in this binary
125+
$ %s describe <binary> <pc> (--inlines)? (--lines)? # Describe a function at a specific pc
126+
$ %s locate <binary> <pc> # Locate a pc
127+
`, os.Args[0], os.Args[0], os.Args[0])
128+
os.Exit(1)
129+
}
130+
if len(os.Args) < 3 {
131+
fmt.Printf("missing subcommand or binary argument\n")
132+
usage()
133+
os.Exit(1)
134+
}
135+
136+
binary := os.Args[2]
137+
reader, err := os.Open(binary)
138+
if err != nil {
139+
fmt.Printf("failed to open binary: %v\n", err)
140+
os.Exit(1)
141+
}
142+
defer reader.Close()
143+
table, err := gosym.NewMagic(reader)
144+
if err != nil {
145+
fmt.Printf("failed to parse binary: %v\n", err)
146+
os.Exit(1)
147+
}
148+
149+
var pc uint64
150+
if len(os.Args) > 3 {
151+
pc, err = strconv.ParseUint(os.Args[3], 0, 64)
152+
if err != nil {
153+
fmt.Printf("failed to parse pc: %v\n", err)
154+
os.Exit(1)
155+
}
156+
}
157+
158+
subcommand := os.Args[1]
159+
switch subcommand {
160+
case "functions":
161+
if len(os.Args) != 3 {
162+
fmt.Printf("functions expects 1 argument, got %d\n", len(os.Args)-2)
163+
usage()
164+
os.Exit(1)
165+
}
166+
err := functions(table)
167+
if err != nil {
168+
fmt.Println(err)
169+
os.Exit(1)
170+
}
171+
case "describe":
172+
if len(os.Args) < 4 {
173+
fmt.Printf("describe expects 2 arguments, got %d\n", len(os.Args)-2)
174+
usage()
175+
os.Exit(1)
176+
}
177+
flag.CommandLine.Parse(os.Args[4:])
178+
err := describe(table, pc, *inlines, *lines)
179+
if err != nil {
180+
fmt.Println(err)
181+
os.Exit(1)
182+
}
183+
case "locate":
184+
if len(os.Args) < 4 {
185+
fmt.Printf("locate expects 2 arguments, got %d\n", len(os.Args)-2)
186+
usage()
187+
os.Exit(1)
188+
}
189+
err := locate(table, pc)
190+
if err != nil {
191+
fmt.Println(err)
192+
os.Exit(1)
193+
}
194+
default:
195+
fmt.Printf("unknown subcommand: %s\n", subcommand)
196+
usage()
197+
os.Exit(1)
198+
}
199+
}

src/debug/gosym/v2/elf.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package gosym
2+
3+
import (
4+
"debug/elf"
5+
"encoding/binary"
6+
"fmt"
7+
)
8+
9+
type elfObject struct {
10+
*elf.File
11+
}
12+
13+
func (o elfObject) Endian() binary.ByteOrder {
14+
return o.ByteOrder
15+
}
16+
17+
func (o elfObject) Sections() (headers []SectionHeader, err error) {
18+
defer func() {
19+
if r := recover(); r != nil {
20+
err = fmt.Errorf("%w: failed to parse section headers: %v", ErrCorrupted, r)
21+
}
22+
}()
23+
headers = make([]SectionHeader, 0, len(o.File.Sections))
24+
for _, s := range o.File.Sections {
25+
headers = append(headers, SectionHeader{
26+
Name: s.Name,
27+
Addr: s.Addr,
28+
Size: s.Size,
29+
})
30+
}
31+
return
32+
}
33+
34+
func (o elfObject) SectionData(i int8) (data []byte, err error) {
35+
defer func() {
36+
if r := recover(); r != nil {
37+
err = fmt.Errorf("%w: failed to read section: %d: %v", ErrCorrupted, i, r)
38+
}
39+
}()
40+
data, err = o.File.Sections[i].Data()
41+
return
42+
}

src/debug/gosym/v2/funcdata.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package gosym
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"math"
7+
)
8+
9+
func funcdataEntryOffsetSize(version version, ptrSize uint8) uint64 {
10+
switch version {
11+
case ver12, ver116:
12+
return uint64(ptrSize)
13+
default:
14+
return 4
15+
}
16+
}
17+
18+
func (t *Table) funcdataFieldOffset(field uint64) uint64 {
19+
return funcdataEntryOffsetSize(t.version, t.ptrSize) + (field-1)*4
20+
}
21+
22+
func (t *Table) funcdataField(funcOff uint64, field uint64) (uint32, error) {
23+
offset := t.funcdata[0] + funcOff + t.funcdataFieldOffset(field)
24+
if offset+4 > t.funcdata[1] {
25+
return 0, fmt.Errorf("%w: function data out of bounds: off=%d field=%d", ErrCorrupted, offset, field)
26+
}
27+
return binary.LittleEndian.Uint32(t.pclntab[offset:]), nil
28+
}
29+
30+
func (t *Table) funcdataNameOff(funcOff uint64) (uint32, error) {
31+
return t.funcdataField(funcOff, 1)
32+
}
33+
34+
func (t *Table) funcdataDeferReturn(funcOff uint64) (uint32, error) {
35+
return t.funcdataField(funcOff, 3)
36+
}
37+
38+
func (t *Table) funcdataPcfile(funcOff uint64) (uint32, error) {
39+
return t.funcdataField(funcOff, 5)
40+
}
41+
42+
func (t *Table) funcdataPcln(funcOff uint64) (uint32, error) {
43+
return t.funcdataField(funcOff, 6)
44+
}
45+
46+
func (t *Table) funcdataNpcdata(funcOff uint64) (uint32, error) {
47+
return t.funcdataField(funcOff, 7)
48+
}
49+
50+
func (t *Table) funcdataCuOffset(funcOff uint64) (uint32, error) {
51+
return t.funcdataField(funcOff, 8)
52+
}
53+
54+
func (t *Table) funcdataStartLine(funcOff uint64) (uint32, error) {
55+
line, err := t.funcdataField(funcOff, 9)
56+
if err != nil {
57+
return 0, err
58+
}
59+
return line, nil
60+
}
61+
62+
func (t *Table) funcdataFuncID(funcOff uint64) (uint8, error) {
63+
funcIDOff := t.funcdata[0] + funcOff + t.funcdataFieldOffset(10)
64+
if funcIDOff >= t.funcdata[1] {
65+
return 0, fmt.Errorf("%w: function data out of bounds, off=%d", ErrCorrupted, funcIDOff)
66+
}
67+
return t.pclntab[funcIDOff], nil
68+
}
69+
70+
func (t *Table) funcdataFileNoToOff(funcOff uint64, fno uint32) (uint32, error) {
71+
if t.version == ver12 {
72+
return uint32(fno * 4), nil
73+
}
74+
cuOff, err := t.funcdataCuOffset(funcOff)
75+
if err != nil {
76+
return 0, err
77+
}
78+
offOff := t.cutab[0] + uint64(cuOff)*4 + uint64(fno)*4
79+
if offOff+4 > t.cutab[1] {
80+
return 0, fmt.Errorf("%w: file offset out of bounds", ErrCorrupted)
81+
}
82+
off := binary.LittleEndian.Uint32(t.pclntab[offOff:])
83+
if off == math.MaxUint32 {
84+
// Valid for non-function entries. We skip them at higher levels,
85+
// so here we can return an error.
86+
return 0, fmt.Errorf("%w: no file entry", ErrCorrupted)
87+
}
88+
return off, nil
89+
}
90+
91+
func (t *Table) funcdataFileNo(funcOff uint64) (uint32, error) {
92+
pcfile, err := t.funcdataPcfile(funcOff)
93+
if err != nil {
94+
return 0, err
95+
}
96+
fno, err := t.firstPcValue(pcfile)
97+
if err != nil {
98+
return 0, err
99+
}
100+
return uint32(fno), nil
101+
}
102+
103+
func (t *Table) funcdataFileOff(funcOff uint64) (uint32, error) {
104+
fno, err := t.funcdataFileNo(funcOff)
105+
if err != nil {
106+
return 0, err
107+
}
108+
return t.funcdataFileNoToOff(funcOff, uint32(fno))
109+
}
110+
111+
func (t *Table) funcdataInlTreeIndex(funcOff uint64) (uint32, error) {
112+
npcdata, err := t.funcdataNpcdata(funcOff)
113+
if err != nil {
114+
return 0, err
115+
}
116+
const pcdataInlTreeIndex = 2
117+
if pcdataInlTreeIndex >= npcdata {
118+
return 0, fmt.Errorf("%w: inl tree index out of bounds, off=%d", ErrCorrupted, funcOff)
119+
}
120+
return t.funcdataField(funcOff, uint64(11+pcdataInlTreeIndex))
121+
}
122+
123+
func (t *Table) funcdataInlTree(funcOff uint64) ([]byte, error) {
124+
npcdata, err := t.funcdataNpcdata(funcOff)
125+
if err != nil {
126+
return nil, err
127+
}
128+
nfuncdataOff := t.funcdata[0] + funcOff + t.funcdataFieldOffset(11)
129+
if nfuncdataOff >= t.funcdata[1] {
130+
return nil, fmt.Errorf("%w: function data out of bounds, off=%d", ErrCorrupted, nfuncdataOff)
131+
}
132+
nfuncdata := t.pclntab[nfuncdataOff]
133+
const funcdataInlTree = 3
134+
if funcdataInlTree >= nfuncdata {
135+
return nil, fmt.Errorf("%w: inl tree out of bounds, off=%d", ErrCorrupted, funcOff)
136+
}
137+
inlTreeOff, err := t.funcdataField(funcOff, uint64(11+npcdata+funcdataInlTree))
138+
if err != nil {
139+
return nil, err
140+
}
141+
if inlTreeOff == math.MaxUint32 {
142+
// Valid case - this function has no inline functions
143+
return nil, nil
144+
}
145+
return t.gofunc[inlTreeOff:], nil
146+
}

0 commit comments

Comments
 (0)