Skip to content

Commit fc28b11

Browse files
committed
runtime: scan all writable program segments
Previously we used to scan between _edata and _end. This is not correct: the .data section starts *before* _edata. Fixing this would mean changing _edata to _etext, but that didn't quite work either. It appears that there are inaccessible pages between _etext and _end on ARM. Therefore, a different solution was needed. What I've implemented is similar to Windows and MacOS: namely, finding writable segments by parsing the program header of the currently running program. It's a lot more verbose, but it should be correct on all architectures. It probably also reduces the globals to scan to those that _really_ need to be scanned. This bug didn't result in issues in CI, but did result in a bug in the recover branch: #2331. This patch fixes this bug.
1 parent 8754f64 commit fc28b11

File tree

1 file changed

+84
-11
lines changed

1 file changed

+84
-11
lines changed

src/runtime/os_linux.go

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,94 @@ const (
2424
clock_MONOTONIC_RAW = 4
2525
)
2626

27-
//go:extern _edata
28-
var globalsStartSymbol [0]byte
27+
// For the definition of the various header structs, see:
28+
// https://refspecs.linuxfoundation.org/elf/elf.pdf
29+
// Also useful:
30+
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
31+
type elfHeader struct {
32+
ident_magic uint32
33+
ident_class uint8
34+
ident_data uint8
35+
ident_version uint8
36+
ident_osabi uint8
37+
ident_abiversion uint8
38+
_ [7]byte // reserved
39+
filetype uint16
40+
machine uint16
41+
version uint32
42+
entry uintptr
43+
phoff uintptr
44+
shoff uintptr
45+
flags uint32
46+
ehsize uint16
47+
phentsize uint16
48+
phnum uint16
49+
shentsize uint16
50+
shnum uint16
51+
shstrndx uint16
52+
}
2953

30-
//go:extern _end
31-
var globalsEndSymbol [0]byte
54+
type elfProgramHeader64 struct {
55+
_type uint32
56+
flags uint32
57+
offset uintptr
58+
vaddr uintptr
59+
paddr uintptr
60+
filesz uintptr
61+
memsz uintptr
62+
align uintptr
63+
}
3264

33-
// markGlobals marks all globals, which are reachable by definition.
65+
type elfProgramHeader32 struct {
66+
_type uint32
67+
offset uintptr
68+
vaddr uintptr
69+
paddr uintptr
70+
filesz uintptr
71+
memsz uintptr
72+
flags uint32
73+
align uintptr
74+
}
75+
76+
// ELF header of the currently running process.
3477
//
35-
// This implementation marks all globals conservatively and assumes it can use
36-
// linker-defined symbols for the start and end of the .data section.
78+
//go:extern __ehdr_start
79+
var ehdr_start elfHeader
80+
81+
// markGlobals marks all globals, which are reachable by definition.
82+
// It parses the ELF program header to find writable segments.
3783
func markGlobals() {
38-
start := uintptr(unsafe.Pointer(&globalsStartSymbol))
39-
end := uintptr(unsafe.Pointer(&globalsEndSymbol))
40-
start = (start + unsafe.Alignof(uintptr(0)) - 1) &^ (unsafe.Alignof(uintptr(0)) - 1) // align on word boundary
41-
markRoots(start, end)
84+
// Relevant constants from the ELF specification.
85+
// See: https://refspecs.linuxfoundation.org/elf/elf.pdf
86+
const (
87+
PT_LOAD = 1
88+
PF_W = 0x2 // program flag: write access
89+
)
90+
91+
headerPtr := unsafe.Pointer(uintptr(unsafe.Pointer(&ehdr_start)) + ehdr_start.phoff)
92+
for i := 0; i < int(ehdr_start.phnum); i++ {
93+
// Look for a writable segment and scan its contents.
94+
// There is a little bit of duplication here, which is unfortunate. But
95+
// the alternative would be to put elfProgramHeader in separate files
96+
// which is IMHO a lot uglier. If only the ELF spec was consistent
97+
// between 32-bit and 64-bit...
98+
if TargetBits == 64 {
99+
header := (*elfProgramHeader64)(headerPtr)
100+
if header._type == PT_LOAD && header.flags&PF_W != 0 {
101+
start := header.vaddr
102+
end := start + header.memsz
103+
markRoots(start, end)
104+
}
105+
} else {
106+
header := (*elfProgramHeader32)(headerPtr)
107+
if header._type == PT_LOAD && header.flags&PF_W != 0 {
108+
start := header.vaddr
109+
end := start + header.memsz
110+
markRoots(start, end)
111+
}
112+
}
113+
headerPtr = unsafe.Pointer(uintptr(headerPtr) + uintptr(ehdr_start.phentsize))
114+
}
42115
}
43116

44117
//export getpagesize

0 commit comments

Comments
 (0)