-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathscan_enroll.go
More file actions
131 lines (126 loc) · 3.84 KB
/
Copy pathscan_enroll.go
File metadata and controls
131 lines (126 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package ehmaps
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"strconv"
"strings"
)
// ScanAndEnroll walks /proc/* and populates pid_mappings entries for
// every executable mapping of every PID, WITHOUT compiling CFI. Returns
// (pidCount, distinctBinaryCount, err).
//
// Used by --unwind auto -a (Option A2 lazy mode). The deferred compile
// happens via AttachCompileOnly when the BPF walker emits a miss event
// for a sampled (pid, table_id) pair.
//
// Build-id reads are cached so each unique binary's build-id is parsed
// exactly once across all PIDs. ~30,000× cheaper than AttachAllProcesses
// on typical desktops (100s of µs vs tens of seconds).
func ScanAndEnroll(t *PIDTracker) (pids, tables int, err error) {
return ScanAndEnrollFromTree("/proc", t)
}
// ScanAndEnrollFromTree is the testable variant of ScanAndEnroll: takes
// the proc-tree root as a parameter so unit tests can run against a
// synthetic tree built in t.TempDir().
func ScanAndEnrollFromTree(procRoot string, t *PIDTracker) (pids, tables int, err error) {
entries, err := os.ReadDir(procRoot)
if err != nil {
return 0, 0, fmt.Errorf("read %s: %w", procRoot, err)
}
buildIDCache := map[string][]byte{}
self := os.Getpid()
for _, e := range entries {
if !e.IsDir() {
continue
}
pid, err := strconv.ParseUint(e.Name(), 10, 32)
if err != nil || pid == 0 || int(pid) == self {
continue
}
n, err := enrollPIDFromTree(procRoot, t, uint32(pid), buildIDCache)
if err != nil || n == 0 {
slog.Debug("ehmaps: ScanAndEnrollFromTree: skip", "pid", pid, "err", err)
continue
}
pids++
}
return pids, len(buildIDCache), nil
}
// enrollPIDFromTree reads procRoot/<pid>/maps and calls
// EnrollWithoutCompile once per unique binary path.
func enrollPIDFromTree(procRoot string, t *PIDTracker, pid uint32, cache map[string][]byte) (int, error) {
mapsPath := filepath.Join(procRoot, strconv.FormatUint(uint64(pid), 10), "maps")
data, err := os.ReadFile(mapsPath)
if err != nil {
return 0, fmt.Errorf("read %s: %w", mapsPath, err)
}
seen := map[string]struct{}{}
var firstErr error
n := 0
// Detect a synthetic test tree: when procRoot != "/proc" we must NOT
// fall back to /proc/<pid>/map_files (it points at a different PID
// namespace than the fake fixture). openableBinary only consults
// /proc, so for synthetic trees we restrict ourselves to the
// symbolic path. This matches the prior behavior of unit tests that
// build a fake proc tree in t.TempDir() and stage real ELFs under it.
syntheticTree := procRoot != "/proc"
for line := range strings.SplitSeq(string(data), "\n") {
fields := strings.Fields(line)
if len(fields) < 6 {
continue
}
if !strings.Contains(fields[1], "x") {
continue
}
path := fields[5]
if path == "" || strings.HasPrefix(path, "[") || strings.HasPrefix(path, "//anon") {
continue
}
dash := strings.IndexByte(fields[0], '-')
if dash < 0 {
continue
}
start, perr := strconv.ParseUint(fields[0][:dash], 16, 64)
if perr != nil {
continue
}
limit, perr := strconv.ParseUint(fields[0][dash+1:], 16, 64)
if perr != nil {
continue
}
if _, dup := seen[path]; dup {
continue
}
seen[path] = struct{}{}
var openPath string
if syntheticTree {
if info, err := os.Stat(path); err != nil || !info.Mode().IsRegular() {
continue
}
openPath = path
} else {
openPath = openableBinary(pid, start, limit, path)
if openPath == "" {
continue
}
if info, err := os.Stat(openPath); err != nil || !info.Mode().IsRegular() {
continue
}
}
if err := t.EnrollWithoutCompile(pid, path, openPath, cache); err != nil {
if firstErr == nil {
firstErr = fmt.Errorf("enroll %s: %w", path, err)
} else {
slog.Debug("ehmaps: enrollPIDFromTree: skip", "path", path, "err", err)
}
continue
}
n++
}
if n == 0 && firstErr != nil {
return 0, firstErr
}
return n, nil
}