Преглед изворни кода

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
No account linked to committer's email address
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,8 +178,7 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
178 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 182
 		// Some tools may introduce a fake mapping that spans the entire
184 183
 		// address space. Assume that the address has already been
185 184
 		// adjusted, so no additional base adjustment is necessary.
@@ -189,9 +188,21 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
189 188
 	switch fh.Type {
190 189
 	case elf.ET_EXEC:
191 190
 		if loadSegment == nil {
192
-			// Fixed-address executable, no adjustment.
191
+			// Assume fixed-address executable and so no adjustment.
193 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 206
 		if start == 0 && limit != 0 {
196 207
 			// ChromeOS remaps its kernel to 0. Nothing else should come
197 208
 			// down this path. Empirical values:
@@ -202,12 +213,6 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
202 213
 			}
203 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 216
 		if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
212 217
 			// Some kernels look like:
213 218
 			//       VADDR=0xffffffff80200000
@@ -230,7 +235,7 @@ func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint6
230 235
 			//       start=0x198 limit=0x2f9fffff offset=0
231 236
 			//       VADDR=0xffffffff81000000
232 237
 			// stextOffset=0xffffffff81000198
233
-			return -(*stextOffset - start), nil
238
+			return start - *stextOffset, nil
234 239
 		}
235 240
 
236 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,7 +51,7 @@ func TestGetBase(t *testing.T) {
51 51
 		wanterr              bool
52 52
 	}{
53 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 55
 		{"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},
56 56
 		{"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false},
57 57
 		{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},
@@ -85,7 +85,7 @@ func TestGetBase(t *testing.T) {
85 85
 			continue
86 86
 		}
87 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
 }