瀏覽代碼

Set base address on OSX (#313)

Set base address on OSX.

This fixes issue #311. The mapped address of libraries was never considered, nor was the load address of the segments. It also fixes a couple of issues in binutils.go, such as when the tail of the data wasn't handled right when grouping symbols at the same address.
Tim 7 年之前
父節點
當前提交
3a72daef6d
共有 4 個檔案被更改,包括 129 行新增24 行删除
  1. 20
    5
      internal/binutils/binutils.go
  2. 80
    14
      internal/binutils/binutils_test.go
  3. 29
    5
      internal/binutils/disasm.go
  4. 0
    0
      internal/binutils/testdata/exe_linux_64

+ 20
- 5
internal/binutils/binutils.go 查看文件

175
 func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
175
 func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
176
 	of, err := macho.Open(name)
176
 	of, err := macho.Open(name)
177
 	if err != nil {
177
 	if err != nil {
178
-		return nil, fmt.Errorf("Parsing %s: %v", name, err)
178
+		return nil, fmt.Errorf("error parsing %s: %v", name, err)
179
 	}
179
 	}
180
 	defer of.Close()
180
 	defer of.Close()
181
 
181
 
182
+	// Subtract the load address of the __TEXT section. Usually 0 for shared
183
+	// libraries or 0x100000000 for executables. You can check this value by
184
+	// running `objdump -private-headers <file>`.
185
+
186
+	textSegment := of.Segment("__TEXT")
187
+	if textSegment == nil {
188
+		return nil, fmt.Errorf("could not identify base for %s: no __TEXT segment", name)
189
+	}
190
+	if textSegment.Addr > start {
191
+		return nil, fmt.Errorf("could not identify base for %s: __TEXT segment address (0x%x) > mapping start address (0x%x)",
192
+			name, textSegment.Addr, start)
193
+	}
194
+
195
+	base := start - textSegment.Addr
196
+
182
 	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
197
 	if b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {
183
-		return &fileNM{file: file{b: b, name: name}}, nil
198
+		return &fileNM{file: file{b: b, name: name, base: base}}, nil
184
 	}
199
 	}
185
-	return &fileAddr2Line{file: file{b: b, name: name}}, nil
200
+	return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
186
 }
201
 }
187
 
202
 
188
 func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
203
 func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
189
 	ef, err := elf.Open(name)
204
 	ef, err := elf.Open(name)
190
 	if err != nil {
205
 	if err != nil {
191
-		return nil, fmt.Errorf("Parsing %s: %v", name, err)
206
+		return nil, fmt.Errorf("error parsing %s: %v", name, err)
192
 	}
207
 	}
193
 	defer ef.Close()
208
 	defer ef.Close()
194
 
209
 
217
 
232
 
218
 	base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset)
233
 	base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset)
219
 	if err != nil {
234
 	if err != nil {
220
-		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
235
+		return nil, fmt.Errorf("could not identify base for %s: %v", name, err)
221
 	}
236
 	}
222
 
237
 
223
 	buildID := ""
238
 	buildID := ""

+ 80
- 14
internal/binutils/binutils_test.go 查看文件

177
 
177
 
178
 func skipUnlessLinuxAmd64(t *testing.T) {
178
 func skipUnlessLinuxAmd64(t *testing.T) {
179
 	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
179
 	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
180
-		t.Skip("Disasm only tested on x86-64 linux")
180
+		t.Skip("This test only works on x86-64 Linux")
181
+	}
182
+}
183
+
184
+func skipUnlessDarwinAmd64(t *testing.T) {
185
+	if runtime.GOOS != "darwin" || runtime.GOARCH != "amd64" {
186
+		t.Skip("This test only works on x86-64 Mac")
181
 	}
187
 	}
182
 }
188
 }
183
 
189
 
184
 func TestDisasm(t *testing.T) {
190
 func TestDisasm(t *testing.T) {
185
 	skipUnlessLinuxAmd64(t)
191
 	skipUnlessLinuxAmd64(t)
186
 	bu := &Binutils{}
192
 	bu := &Binutils{}
187
-	insts, err := bu.Disasm(filepath.Join("testdata", "hello"), 0, math.MaxUint64)
193
+	insts, err := bu.Disasm(filepath.Join("testdata", "exe_linux_64"), 0, math.MaxUint64)
188
 	if err != nil {
194
 	if err != nil {
189
 		t.Fatalf("Disasm: unexpected error %v", err)
195
 		t.Fatalf("Disasm: unexpected error %v", err)
190
 	}
196
 	}
199
 	}
205
 	}
200
 }
206
 }
201
 
207
 
208
+func findSymbol(syms []*plugin.Sym, name string) *plugin.Sym {
209
+	for _, s := range syms {
210
+		for _, n := range s.Name {
211
+			if n == name {
212
+				return s
213
+			}
214
+		}
215
+	}
216
+	return nil
217
+}
218
+
202
 func TestObjFile(t *testing.T) {
219
 func TestObjFile(t *testing.T) {
203
 	skipUnlessLinuxAmd64(t)
220
 	skipUnlessLinuxAmd64(t)
204
 	for _, tc := range []struct {
221
 	for _, tc := range []struct {
215
 	} {
232
 	} {
216
 		t.Run(tc.desc, func(t *testing.T) {
233
 		t.Run(tc.desc, func(t *testing.T) {
217
 			bu := &Binutils{}
234
 			bu := &Binutils{}
218
-			f, err := bu.Open(filepath.Join("testdata", "hello"), tc.start, tc.limit, tc.offset)
235
+			f, err := bu.Open(filepath.Join("testdata", "exe_linux_64"), tc.start, tc.limit, tc.offset)
219
 			if err != nil {
236
 			if err != nil {
220
 				t.Fatalf("Open: unexpected error %v", err)
237
 				t.Fatalf("Open: unexpected error %v", err)
221
 			}
238
 			}
225
 				t.Fatalf("Symbols: unexpected error %v", err)
242
 				t.Fatalf("Symbols: unexpected error %v", err)
226
 			}
243
 			}
227
 
244
 
228
-			find := func(name string) *plugin.Sym {
229
-				for _, s := range syms {
230
-					for _, n := range s.Name {
231
-						if n == name {
232
-							return s
233
-						}
234
-					}
235
-				}
236
-				return nil
237
-			}
238
-			m := find("main")
245
+			m := findSymbol(syms, "main")
239
 			if m == nil {
246
 			if m == nil {
240
 				t.Fatalf("Symbols: did not find main")
247
 				t.Fatalf("Symbols: did not find main")
241
 			}
248
 			}
255
 	}
262
 	}
256
 }
263
 }
257
 
264
 
265
+func TestMachoFiles(t *testing.T) {
266
+	skipUnlessDarwinAmd64(t)
267
+
268
+	t.Skip("Disabled because of issues with addr2line (see https://github.com/google/pprof/pull/313#issuecomment-364073010)")
269
+
270
+	// Load `file`, pretending it was mapped at `start`. Then get the symbol
271
+	// table. Check that it contains the symbol `sym` and that the address
272
+	// `addr` gives the `expected` stack trace.
273
+	for _, tc := range []struct {
274
+		desc                 string
275
+		file                 string
276
+		start, limit, offset uint64
277
+		addr                 uint64
278
+		sym                  string
279
+		expected             []plugin.Frame
280
+	}{
281
+		{"normal mapping", "exe_mac_64", 0x100000000, math.MaxUint64, 0,
282
+			0x100000f50, "_main",
283
+			[]plugin.Frame{
284
+				{Func: "main", File: "/tmp/hello.c", Line: 3},
285
+			}},
286
+		{"other mapping", "exe_mac_64", 0x200000000, math.MaxUint64, 0,
287
+			0x200000f50, "_main",
288
+			[]plugin.Frame{
289
+				{Func: "main", File: "/tmp/hello.c", Line: 3},
290
+			}},
291
+		{"lib normal mapping", "lib_mac_64", 0, math.MaxUint64, 0,
292
+			0xfa0, "_bar",
293
+			[]plugin.Frame{
294
+				{Func: "bar", File: "/tmp/lib.c", Line: 6},
295
+			}},
296
+	} {
297
+		t.Run(tc.desc, func(t *testing.T) {
298
+			bu := &Binutils{}
299
+			f, err := bu.Open(filepath.Join("testdata", tc.file), tc.start, tc.limit, tc.offset)
300
+			if err != nil {
301
+				t.Fatalf("Open: unexpected error %v", err)
302
+			}
303
+			defer f.Close()
304
+			syms, err := f.Symbols(nil, 0)
305
+			if err != nil {
306
+				t.Fatalf("Symbols: unexpected error %v", err)
307
+			}
308
+
309
+			m := findSymbol(syms, tc.sym)
310
+			if m == nil {
311
+				t.Fatalf("Symbols: could not find symbol %v", tc.sym)
312
+			}
313
+			gotFrames, err := f.SourceLine(tc.addr)
314
+			if err != nil {
315
+				t.Fatalf("SourceLine: unexpected error %v", err)
316
+			}
317
+			if !reflect.DeepEqual(gotFrames, tc.expected) {
318
+				t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, tc.expected)
319
+			}
320
+		})
321
+	}
322
+}
323
+
258
 func TestLLVMSymbolizer(t *testing.T) {
324
 func TestLLVMSymbolizer(t *testing.T) {
259
 	if runtime.GOOS != "linux" {
325
 	if runtime.GOOS != "linux" {
260
 		t.Skip("testtdata/llvm-symbolizer has only been tested on linux")
326
 		t.Skip("testtdata/llvm-symbolizer has only been tested on linux")

+ 29
- 5
internal/binutils/disasm.go 查看文件

34
 func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
34
 func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
35
 	// Collect all symbols from the nm output, grouping names mapped to
35
 	// Collect all symbols from the nm output, grouping names mapped to
36
 	// the same address into a single symbol.
36
 	// the same address into a single symbol.
37
+
38
+	// The symbols to return.
37
 	var symbols []*plugin.Sym
39
 	var symbols []*plugin.Sym
40
+
41
+	// The current group of symbol names, and the address they are all at.
38
 	names, start := []string{}, uint64(0)
42
 	names, start := []string{}, uint64(0)
43
+
39
 	buf := bytes.NewBuffer(syms)
44
 	buf := bytes.NewBuffer(syms)
40
-	for symAddr, name, err := nextSymbol(buf); err == nil; symAddr, name, err = nextSymbol(buf) {
45
+
46
+	for {
47
+		symAddr, name, err := nextSymbol(buf)
48
+		if err == io.EOF {
49
+			// Done. If there was an unfinished group, append it.
50
+			if len(names) != 0 {
51
+				if match := matchSymbol(names, start, symAddr-1, r, address); match != nil {
52
+					symbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1})
53
+				}
54
+			}
55
+
56
+			// And return the symbols.
57
+			return symbols, nil
58
+		}
59
+
41
 		if err != nil {
60
 		if err != nil {
61
+			// There was some kind of serious error reading nm's output.
42
 			return nil, err
62
 			return nil, err
43
 		}
63
 		}
44
-		if start == symAddr {
64
+
65
+		// If this symbol is at the same address as the current group, add it to the group.
66
+		if symAddr == start {
45
 			names = append(names, name)
67
 			names = append(names, name)
46
 			continue
68
 			continue
47
 		}
69
 		}
70
+
71
+		// Otherwise append the current group to the list of symbols.
48
 		if match := matchSymbol(names, start, symAddr-1, r, address); match != nil {
72
 		if match := matchSymbol(names, start, symAddr-1, r, address); match != nil {
49
 			symbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1})
73
 			symbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1})
50
 		}
74
 		}
75
+
76
+		// And start a new group.
51
 		names, start = []string{name}, symAddr
77
 		names, start = []string{name}, symAddr
52
 	}
78
 	}
53
-
54
-	return symbols, nil
55
 }
79
 }
56
 
80
 
57
 // matchSymbol checks if a symbol is to be selected by checking its
81
 // matchSymbol checks if a symbol is to be selected by checking its
62
 		return names
86
 		return names
63
 	}
87
 	}
64
 	for _, name := range names {
88
 	for _, name := range names {
65
-		if r.MatchString(name) {
89
+		if r == nil || r.MatchString(name) {
66
 			return []string{name}
90
 			return []string{name}
67
 		}
91
 		}
68
 
92
 

internal/binutils/testdata/hello → internal/binutils/testdata/exe_linux_64 查看文件