Przeglądaj źródła

Add symbolization support for Mach-O

This will enable symbolization support for Go on Mac OS
It re-enables symbolization using debug/pprof/symbol on
Go profiles in the legacy format, and implements basic
mach-O support on the binutils package.
Raul Silvera 8 lat temu
rodzic
commit
0c91ef751d
2 zmienionych plików z 57 dodań i 24 usunięć
  1. 47
    20
      internal/binutils/binutils.go
  2. 10
    4
      internal/driver/fetch.go

+ 47
- 20
internal/binutils/binutils.go Wyświetl plik

@@ -17,6 +17,7 @@ package binutils
17 17
 
18 18
 import (
19 19
 	"debug/elf"
20
+	"debug/macho"
20 21
 	"fmt"
21 22
 	"os"
22 23
 	"os/exec"
@@ -32,10 +33,14 @@ import (
32 33
 // SetConfig must be called before any of the other methods.
33 34
 type Binutils struct {
34 35
 	// Commands to invoke.
35
-	llvmSymbolizer string
36
-	addr2line      string
37
-	nm             string
38
-	objdump        string
36
+	llvmSymbolizer      string
37
+	llvmSymbolizerFound bool
38
+	addr2line           string
39
+	addr2lineFound      bool
40
+	nm                  string
41
+	nmFound             bool
42
+	objdump             string
43
+	objdumpFound        bool
39 44
 
40 45
 	// if fast, perform symbolization using nm (symbol names only),
41 46
 	// instead of file-line detail from the slower addr2line.
@@ -66,22 +71,22 @@ func (b *Binutils) SetTools(config string) {
66 71
 	}
67 72
 
68 73
 	defaultPath := paths[""]
69
-	b.llvmSymbolizer = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...))
70
-	b.addr2line = findExe("addr2line", append(paths["addr2line"], defaultPath...))
71
-	b.nm = findExe("nm", append(paths["nm"], defaultPath...))
72
-	b.objdump = findExe("objdump", append(paths["objdump"], defaultPath...))
74
+	b.llvmSymbolizer, b.llvmSymbolizerFound = findExe("llvm-symbolizer", append(paths["llvm-symbolizer"], defaultPath...))
75
+	b.addr2line, b.addr2lineFound = findExe("addr2line", append(paths["addr2line"], defaultPath...))
76
+	b.nm, b.nmFound = findExe("nm", append(paths["nm"], defaultPath...))
77
+	b.objdump, b.objdumpFound = findExe("objdump", append(paths["objdump"], defaultPath...))
73 78
 }
74 79
 
75 80
 // findExe looks for an executable command on a set of paths.
76 81
 // If it cannot find it, returns cmd.
77
-func findExe(cmd string, paths []string) string {
82
+func findExe(cmd string, paths []string) (string, bool) {
78 83
 	for _, p := range paths {
79 84
 		cp := filepath.Join(p, cmd)
80 85
 		if c, err := exec.LookPath(cp); err == nil {
81
-			return c
86
+			return c, true
82 87
 		}
83 88
 	}
84
-	return cmd
89
+	return cmd, false
85 90
 }
86 91
 
87 92
 // Disasm returns the assembly instructions for the specified address range
@@ -118,21 +123,42 @@ func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFil
118 123
 	// use a table of prefixes if we need to support other
119 124
 	// systems at some point.
120 125
 
121
-	f, err := os.Open(name)
122
-	if err != nil {
126
+	if _, err := os.Stat(name); err != nil {
123 127
 		// For testing, do not require file name to exist.
124 128
 		if strings.Contains(b.addr2line, "testdata/") {
125 129
 			return &fileAddr2Line{file: file{b: b, name: name}}, nil
126 130
 		}
127
-
128 131
 		return nil, err
129 132
 	}
130
-	defer f.Close()
131 133
 
132
-	ef, err := elf.NewFile(f)
134
+	if f, err := b.openELF(name, start, limit, offset); err == nil {
135
+		return f, nil
136
+	}
137
+	if f, err := b.openMachO(name, start, limit, offset); err == nil {
138
+		return f, nil
139
+	}
140
+	return nil, fmt.Errorf("unrecognized binary: %s", name)
141
+}
142
+
143
+func (b *Binutils) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
144
+	of, err := macho.Open(name)
145
+	if err != nil {
146
+		return nil, fmt.Errorf("Parsing %s: %v", name, err)
147
+	}
148
+	defer of.Close()
149
+
150
+	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
151
+		return &fileNM{file: file{b: b, name: name}}, nil
152
+	}
153
+	return &fileAddr2Line{file: file{b: b, name: name}}, nil
154
+}
155
+
156
+func (b *Binutils) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
157
+	ef, err := elf.Open(name)
133 158
 	if err != nil {
134 159
 		return nil, fmt.Errorf("Parsing %s: %v", name, err)
135 160
 	}
161
+	defer ef.Close()
136 162
 
137 163
 	var stextOffset *uint64
138 164
 	var pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
@@ -162,12 +188,13 @@ func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFil
162 188
 		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
163 189
 	}
164 190
 
165
-	// Find build ID, while we have the file open.
166 191
 	buildID := ""
167
-	if id, err := elfexec.GetBuildID(f); err == nil {
168
-		buildID = fmt.Sprintf("%x", id)
192
+	if f, err := os.Open(name); err == nil {
193
+		if id, err := elfexec.GetBuildID(f); err == nil {
194
+			buildID = fmt.Sprintf("%x", id)
195
+		}
169 196
 	}
170
-	if b.fast {
197
+	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
171 198
 		return &fileNM{file: file{b, name, base, buildID}}, nil
172 199
 	}
173 200
 	return &fileAddr2Line{file: file{b, name, base, buildID}}, nil

+ 10
- 4
internal/driver/fetch.go Wyświetl plik

@@ -273,12 +273,18 @@ func collectMappingSources(p *profile.Profile, source string) plugin.MappingSour
273 273
 		}{
274 274
 			source, m.Start,
275 275
 		}
276
-		if key := m.BuildID; key != "" {
277
-			ms[key] = append(ms[key], src)
276
+		key := m.BuildID
277
+		if key == "" {
278
+			key = m.File
278 279
 		}
279
-		if key := m.File; key != "" {
280
-			ms[key] = append(ms[key], src)
280
+		if key == "" {
281
+			// If there is no build id or source file, use the source as the
282
+			// mapping file. This will enable remote symbolization for this
283
+			// mapping, in particular for Go profiles on the legacy format.
284
+			m.File = source
285
+			key = source
281 286
 		}
287
+		ms[key] = append(ms[key], src)
282 288
 	}
283 289
 	return ms
284 290
 }