Skip to content

Commit 3a72dae

Browse files
Timmmmaalexand
authored andcommitted
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.
1 parent aed8473 commit 3a72dae

File tree

4 files changed

+129
-24
lines changed

4 files changed

+129
-24
lines changed

internal/binutils/binutils.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,20 +175,35 @@ func (bu *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFi
175175
func (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
176176
of, err := macho.Open(name)
177177
if err != nil {
178-
return nil, fmt.Errorf("Parsing %s: %v", name, err)
178+
return nil, fmt.Errorf("error parsing %s: %v", name, err)
179179
}
180180
defer of.Close()
181181

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+
182197
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
184199
}
185-
return &fileAddr2Line{file: file{b: b, name: name}}, nil
200+
return &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil
186201
}
187202

188203
func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
189204
ef, err := elf.Open(name)
190205
if err != nil {
191-
return nil, fmt.Errorf("Parsing %s: %v", name, err)
206+
return nil, fmt.Errorf("error parsing %s: %v", name, err)
192207
}
193208
defer ef.Close()
194209

@@ -217,7 +232,7 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi
217232

218233
base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset)
219234
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)
221236
}
222237

223238
buildID := ""

internal/binutils/binutils_test.go

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,20 @@ func TestSetFastSymbolization(t *testing.T) {
177177

178178
func skipUnlessLinuxAmd64(t *testing.T) {
179179
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")
181187
}
182188
}
183189

184190
func TestDisasm(t *testing.T) {
185191
skipUnlessLinuxAmd64(t)
186192
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)
188194
if err != nil {
189195
t.Fatalf("Disasm: unexpected error %v", err)
190196
}
@@ -199,6 +205,17 @@ func TestDisasm(t *testing.T) {
199205
}
200206
}
201207

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+
202219
func TestObjFile(t *testing.T) {
203220
skipUnlessLinuxAmd64(t)
204221
for _, tc := range []struct {
@@ -215,7 +232,7 @@ func TestObjFile(t *testing.T) {
215232
} {
216233
t.Run(tc.desc, func(t *testing.T) {
217234
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)
219236
if err != nil {
220237
t.Fatalf("Open: unexpected error %v", err)
221238
}
@@ -225,17 +242,7 @@ func TestObjFile(t *testing.T) {
225242
t.Fatalf("Symbols: unexpected error %v", err)
226243
}
227244

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")
239246
if m == nil {
240247
t.Fatalf("Symbols: did not find main")
241248
}
@@ -255,6 +262,65 @@ func TestObjFile(t *testing.T) {
255262
}
256263
}
257264

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+
258324
func TestLLVMSymbolizer(t *testing.T) {
259325
if runtime.GOOS != "linux" {
260326
t.Skip("testtdata/llvm-symbolizer has only been tested on linux")

internal/binutils/disasm.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,48 @@ var (
3434
func findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
3535
// Collect all symbols from the nm output, grouping names mapped to
3636
// the same address into a single symbol.
37+
38+
// The symbols to return.
3739
var symbols []*plugin.Sym
40+
41+
// The current group of symbol names, and the address they are all at.
3842
names, start := []string{}, uint64(0)
43+
3944
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+
4160
if err != nil {
61+
// There was some kind of serious error reading nm's output.
4262
return nil, err
4363
}
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 {
4567
names = append(names, name)
4668
continue
4769
}
70+
71+
// Otherwise append the current group to the list of symbols.
4872
if match := matchSymbol(names, start, symAddr-1, r, address); match != nil {
4973
symbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1})
5074
}
75+
76+
// And start a new group.
5177
names, start = []string{name}, symAddr
5278
}
53-
54-
return symbols, nil
5579
}
5680

5781
// 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
6286
return names
6387
}
6488
for _, name := range names {
65-
if r.MatchString(name) {
89+
if r == nil || r.MatchString(name) {
6690
return []string{name}
6791
}
6892

File renamed without changes.

0 commit comments

Comments
 (0)