浏览代码

Fix ELF base calculation for exec mapping with offset != 0. (#425)

I was looking at a report where pprof wouldn't symbolize the data
collected for a Chrome binary using Linux perf (b/112303003). The mmap
information is:

start: 0000000002, limit: 0000000006, offset: 0000000002

The ELF file header:

elf.FileHeader{Class:elf.ELFCLASS64, Data:elf.ELFDATA2LSB, Version:elf.EV_CURRENT, OSABI:elf.ELFOSABI_NONE, ABIVersion:0x0, ByteOrder:binary.LittleEndian, Type:elf.ET_EXEC, Machine:elf.EM_X86_64, Entry:0x272e000}

The code segment:

elf.ProgHeader{Type:elf.PT_LOAD, Flags:elf.PF_X+elf.PF_R, Off:0x252f000,
Vaddr:0x272e000, Paddr:0x272e000, Filesz:0x43da610, Memsz:0x43da610,
Align:0x1000}

The dynamic loader here mapped 0x6b09000-0x272e000 = 0x43db000 bytes
starting 0x252f000 file offset into 0x272e000 virtual address, exactly
as instructed by the program header (so, no ASLR). Thus, the base
adjustment should be zero. Yet, the current GetBase produced the base of
0x252f000 which is wrong. The reason for that is that the ET_EXEC branch
of GetBase doesn't handle the general case of non-zero mmap file offset,
but rather only supports a couple of special cases. This change makes
handling the case of user-mode ET_EXEC more generic.
Alexey Alexandrov 6 年前
父节点
当前提交
5d8e3eb860
没有帐户链接到提交者的电子邮件
共有 2 个文件被更改,包括 17 次插入12 次删除
  1. 15
    10
      internal/elfexec/elfexec.go
  2. 2
    2
      internal/elfexec/elfexec_test.go

+ 15
- 10
internal/elfexec/elfexec.go 查看文件

178
 		pageOffsetPpc64 = 0xc000000000000000
178
 		pageOffsetPpc64 = 0xc000000000000000
179
 	)
179
 	)
180
 
180
 
181
-	if start == 0 && offset == 0 &&
182
-		(limit == ^uint64(0) || limit == 0) {
181
+	if start == 0 && offset == 0 && (limit == ^uint64(0) || limit == 0) {
183
 		// Some tools may introduce a fake mapping that spans the entire
182
 		// Some tools may introduce a fake mapping that spans the entire
184
 		// address space. Assume that the address has already been
183
 		// address space. Assume that the address has already been
185
 		// adjusted, so no additional base adjustment is necessary.
184
 		// adjusted, so no additional base adjustment is necessary.
189
 	switch fh.Type {
188
 	switch fh.Type {
190
 	case elf.ET_EXEC:
189
 	case elf.ET_EXEC:
191
 		if loadSegment == nil {
190
 		if loadSegment == nil {
192
-			// Fixed-address executable, no adjustment.
191
+			// Assume fixed-address executable and so no adjustment.
193
 			return 0, nil
192
 			return 0, nil
194
 		}
193
 		}
194
+		if stextOffset == nil && start > 0 && start < 0x8000000000000000 {
195
+			// A regular user-mode executable. Compute the base offset using same
196
+			// arithmetics as in ET_DYN case below, see the explanation there.
197
+			// Ideally, the condition would just be "stextOffset == nil" as that
198
+			// represents the address of _stext symbol in the vmlinux image. Alas,
199
+			// the caller may skip reading it from the binary (it's expensive to scan
200
+			// all the symbols) and so it may be nil even for the kernel executable.
201
+			// So additionally check that the start is within the user-mode half of
202
+			// the 64-bit address space.
203
+			return start - offset + loadSegment.Off - loadSegment.Vaddr, nil
204
+		}
205
+		// Various kernel heuristics and cases follow.
195
 		if start == 0 && limit != 0 {
206
 		if start == 0 && limit != 0 {
196
 			// ChromeOS remaps its kernel to 0. Nothing else should come
207
 			// ChromeOS remaps its kernel to 0. Nothing else should come
197
 			// down this path. Empirical values:
208
 			// down this path. Empirical values:
202
 			}
213
 			}
203
 			return -loadSegment.Vaddr, nil
214
 			return -loadSegment.Vaddr, nil
204
 		}
215
 		}
205
-		if loadSegment.Vaddr-loadSegment.Off == start-offset {
206
-			return offset, nil
207
-		}
208
-		if loadSegment.Vaddr == start-offset {
209
-			return offset, nil
210
-		}
211
 		if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
216
 		if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
212
 			// Some kernels look like:
217
 			// Some kernels look like:
213
 			//       VADDR=0xffffffff80200000
218
 			//       VADDR=0xffffffff80200000
230
 			//       start=0x198 limit=0x2f9fffff offset=0
235
 			//       start=0x198 limit=0x2f9fffff offset=0
231
 			//       VADDR=0xffffffff81000000
236
 			//       VADDR=0xffffffff81000000
232
 			// stextOffset=0xffffffff81000198
237
 			// stextOffset=0xffffffff81000198
233
-			return -(*stextOffset - start), nil
238
+			return start - *stextOffset, nil
234
 		}
239
 		}
235
 
240
 
236
 		return 0, fmt.Errorf("Don't know how to handle EXEC segment: %v start=0x%x limit=0x%x offset=0x%x", *loadSegment, start, limit, offset)
241
 		return 0, fmt.Errorf("Don't know how to handle EXEC segment: %v start=0x%x limit=0x%x offset=0x%x", *loadSegment, start, limit, offset)

+ 2
- 2
internal/elfexec/elfexec_test.go 查看文件

51
 		wanterr              bool
51
 		wanterr              bool
52
 	}{
52
 	}{
53
 		{"exec", fhExec, nil, nil, 0x400000, 0, 0, 0, false},
53
 		{"exec", fhExec, nil, nil, 0x400000, 0, 0, 0, false},
54
-		{"exec offset", fhExec, lsOffset, nil, 0x400000, 0x800000, 0, 0, false},
54
+		{"exec offset", fhExec, lsOffset, nil, 0x400000, 0x800000, 0, 0x200000, false},
55
 		{"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},
55
 		{"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},
56
 		{"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false},
56
 		{"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false},
57
 		{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},
57
 		{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},
85
 			continue
85
 			continue
86
 		}
86
 		}
87
 		if base != tc.want {
87
 		if base != tc.want {
88
-			t.Errorf("%s: want %x, got %x", tc.label, tc.want, base)
88
+			t.Errorf("%s: want 0x%x, got 0x%x", tc.label, tc.want, base)
89
 		}
89
 		}
90
 	}
90
 	}
91
 }
91
 }