Browse Source

Pass text segment info to GetBase to handle Linux kernel ASLR case. (#299)

When pprof symbolizes kernel addresses in vmlinux binary for a profile
converted using https://github.com/google/perf_data_converter, the
addresses need to be adjusted if kernel ASLR is in effect. So far the
call to GetBase did not pass text segment info to GetBase which
shortcircuited the code to merely return zero adjustment. This change
fixes the call to GetBase to address that.

The added test case is a simulation of what happens with vmlinux, but it
should be pretty close. Including a vmlinux file into the test data does
not appear practical due to the binary size. I verified that the test
failed before the fix and passes after.

Note that the fixed issue is specific to the kernel ASLR as user-mode
ASRL-enabled binaries (i.e. built with -pie / -fpie) have ET_DYN type
which takes a different code path in GetBase which did not have issues
before this fix in practice.
Alexey Alexandrov 7 years ago
parent
commit
62c86caf17
No account linked to committer's email address
3 changed files with 65 additions and 32 deletions
  1. 1
    1
      internal/binutils/binutils.go
  2. 48
    31
      internal/binutils/binutils_test.go
  3. 16
    0
      internal/elfexec/elfexec.go

+ 1
- 1
internal/binutils/binutils.go View File

215
 		}
215
 		}
216
 	}
216
 	}
217
 
217
 
218
-	base, err := elfexec.GetBase(&ef.FileHeader, nil, stextOffset, start, limit, offset)
218
+	base, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), stextOffset, start, limit, offset)
219
 	if err != nil {
219
 	if err != nil {
220
 		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
220
 		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
221
 	}
221
 	}

+ 48
- 31
internal/binutils/binutils_test.go View File

201
 
201
 
202
 func TestObjFile(t *testing.T) {
202
 func TestObjFile(t *testing.T) {
203
 	skipUnlessLinuxAmd64(t)
203
 	skipUnlessLinuxAmd64(t)
204
-	bu := &Binutils{}
205
-	f, err := bu.Open(filepath.Join("testdata", "hello"), 0, math.MaxUint64, 0)
206
-	if err != nil {
207
-		t.Fatalf("Open: unexpected error %v", err)
208
-	}
209
-	defer f.Close()
210
-	syms, err := f.Symbols(regexp.MustCompile("main"), 0)
211
-	if err != nil {
212
-		t.Fatalf("Symbols: unexpected error %v", err)
213
-	}
204
+	for _, tc := range []struct {
205
+		desc                 string
206
+		start, limit, offset uint64
207
+		addr                 uint64
208
+	}{
209
+		{"fake mapping", 0, math.MaxUint64, 0, 0x40052d},
210
+		{"fixed load address", 0x400000, 0x4006fc, 0, 0x40052d},
211
+		// True user-mode ASLR binaries are ET_DYN rather than ET_EXEC so this case
212
+		// is a bit artificial except that it approximates the
213
+		// vmlinux-with-kernel-ASLR case where the binary *is* ET_EXEC.
214
+		{"simulated ASLR address", 0x500000, 0x5006fc, 0, 0x50052d},
215
+	} {
216
+		t.Run(tc.desc, func(t *testing.T) {
217
+			bu := &Binutils{}
218
+			f, err := bu.Open(filepath.Join("testdata", "hello"), tc.start, tc.limit, tc.offset)
219
+			if err != nil {
220
+				t.Fatalf("Open: unexpected error %v", err)
221
+			}
222
+			defer f.Close()
223
+			syms, err := f.Symbols(regexp.MustCompile("main"), 0)
224
+			if err != nil {
225
+				t.Fatalf("Symbols: unexpected error %v", err)
226
+			}
214
 
227
 
215
-	find := func(name string) *plugin.Sym {
216
-		for _, s := range syms {
217
-			for _, n := range s.Name {
218
-				if n == name {
219
-					return s
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
+					}
220
 				}
235
 				}
236
+				return nil
221
 			}
237
 			}
222
-		}
223
-		return nil
224
-	}
225
-	m := find("main")
226
-	if m == nil {
227
-		t.Fatalf("Symbols: did not find main")
228
-	}
229
-	frames, err := f.SourceLine(m.Start)
230
-	if err != nil {
231
-		t.Fatalf("SourceLine: unexpected error %v", err)
232
-	}
233
-	expect := []plugin.Frame{
234
-		{Func: "main", File: "/tmp/hello.c", Line: 3},
235
-	}
236
-	if !reflect.DeepEqual(frames, expect) {
237
-		t.Fatalf("SourceLine for main: expect %v; got %v\n", expect, frames)
238
+			m := find("main")
239
+			if m == nil {
240
+				t.Fatalf("Symbols: did not find main")
241
+			}
242
+			for _, addr := range []uint64{m.Start + f.Base(), tc.addr} {
243
+				gotFrames, err := f.SourceLine(addr)
244
+				if err != nil {
245
+					t.Fatalf("SourceLine: unexpected error %v", err)
246
+				}
247
+				wantFrames := []plugin.Frame{
248
+					{Func: "main", File: "/tmp/hello.c", Line: 3},
249
+				}
250
+				if !reflect.DeepEqual(gotFrames, wantFrames) {
251
+					t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, wantFrames)
252
+				}
253
+			}
254
+		})
238
 	}
255
 	}
239
 }
256
 }
240
 
257
 

+ 16
- 0
internal/elfexec/elfexec.go View File

259
 	}
259
 	}
260
 	return 0, fmt.Errorf("Don't know how to handle FileHeader.Type %v", fh.Type)
260
 	return 0, fmt.Errorf("Don't know how to handle FileHeader.Type %v", fh.Type)
261
 }
261
 }
262
+
263
+// FindTextProgHeader finds the program segment header containing the .text
264
+// section or nil if the segment cannot be found.
265
+func FindTextProgHeader(f *elf.File) *elf.ProgHeader {
266
+	for _, s := range f.Sections {
267
+		if s.Name == ".text" {
268
+			// Find the LOAD segment containing the .text section.
269
+			for _, p := range f.Progs {
270
+				if p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 && s.Addr >= p.Vaddr && s.Addr < p.Vaddr+p.Memsz {
271
+					return &p.ProgHeader
272
+				}
273
+			}
274
+		}
275
+	}
276
+	return nil
277
+}