Просмотр исходного кода

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 лет назад
Родитель
Сommit
62c86caf17
Аккаунт пользователя с таким Email не найден
3 измененных файлов: 65 добавлений и 32 удалений
  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 Просмотреть файл

@@ -215,7 +215,7 @@ func (b *binrep) openELF(name string, start, limit, offset uint64) (plugin.ObjFi
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 219
 	if err != nil {
220 220
 		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
221 221
 	}

+ 48
- 31
internal/binutils/binutils_test.go Просмотреть файл

@@ -201,40 +201,57 @@ func TestDisasm(t *testing.T) {
201 201
 
202 202
 func TestObjFile(t *testing.T) {
203 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 Просмотреть файл

@@ -259,3 +259,19 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
259 259
 	}
260 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
+}