Pārlūkot izejas kodu

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 gadus atpakaļ
vecāks
revīzija
3a72daef6d

+ 20
- 5
internal/binutils/binutils.go Parādīt failu

@@ -175,20 +175,35 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
175 175
 func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
176 176
 	of, err := macho.Open(name)
177 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 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 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 203
 func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
189 204
 	ef, err := elf.Open(name)
190 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 208
 	defer ef.Close()
194 209
 
@@ -217,7 +232,7 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi
217 232
 
218 233
 	base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset)
219 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 238
 	buildID := ""

+ 80
- 14
internal/binutils/binutils_test.go Parādīt failu

@@ -177,14 +177,20 @@ func TestSetFastSymbolization(t *testing.T) {
177 177
 
178 178
 func skipUnlessLinuxAmd64(t *testing.T) {
179 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 190
 func TestDisasm(t *testing.T) {
185 191
 	skipUnlessLinuxAmd64(t)
186 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 194
 	if err != nil {
189 195
 		t.Fatalf("Disasm: unexpected error %v", err)
190 196
 	}
@@ -199,6 +205,17 @@ func TestDisasm(t *testing.T) {
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 219
 func TestObjFile(t *testing.T) {
203 220
 	skipUnlessLinuxAmd64(t)
204 221
 	for _, tc := range []struct {
@@ -215,7 +232,7 @@ func TestObjFile(t *testing.T) {
215 232
 	} {
216 233
 		t.Run(tc.desc, func(t *testing.T) {
217 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 236
 			if err != nil {
220 237
 				t.Fatalf("Open: unexpected error %v", err)
221 238
 			}
@@ -225,17 +242,7 @@ func TestObjFile(t *testing.T) {
225 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 246
 			if m == nil {
240 247
 				t.Fatalf("Symbols: did not find main")
241 248
 			}
@@ -255,6 +262,65 @@ func TestObjFile(t *testing.T) {
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 324
 func TestLLVMSymbolizer(t *testing.T) {
259 325
 	if runtime.GOOS != "linux" {
260 326
 		t.Skip("testtdata/llvm-symbolizer has only been tested on linux")

+ 29
- 5
internal/binutils/disasm.go Parādīt failu

@@ -34,24 +34,48 @@ var (
34 34
 func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
35 35
 	// Collect all symbols from the nm output, grouping names mapped to
36 36
 	// the same address into a single symbol.
37
+
38
+	// The symbols to return.
37 39
 	var symbols []*plugin.Sym
40
+
41
+	// The current group of symbol names, and the address they are all at.
38 42
 	names, start := []string{}, uint64(0)
43
+
39 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 60
 		if err != nil {
61
+			// There was some kind of serious error reading nm's output.
42 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 67
 			names = append(names, name)
46 68
 			continue
47 69
 		}
70
+
71
+		// Otherwise append the current group to the list of symbols.
48 72
 		if match := matchSymbol(names, start, symAddr-1, r, address); match != nil {
49 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 77
 		names, start = []string{name}, symAddr
52 78
 	}
53
-
54
-	return symbols, nil
55 79
 }
56 80
 
57 81
 // matchSymbol checks if a symbol is to be selected by checking its
@@ -62,7 +86,7 @@ func matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address ui
62 86
 		return names
63 87
 	}
64 88
 	for _, name := range names {
65
-		if r.MatchString(name) {
89
+		if r == nil || r.MatchString(name) {
66 90
 			return []string{name}
67 91
 		}
68 92
 

internal/binutils/testdata/hello → internal/binutils/testdata/exe_linux_64 Parādīt failu