Skip to content

Commit caa65c3

Browse files
committed
SynchronizeMappings like v8 does, or using JITDump
1 parent 0a99c70 commit caa65c3

File tree

2 files changed

+239
-2
lines changed

2 files changed

+239
-2
lines changed

interpreter/beam/beam.go

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package beam // import "go.opentelemetry.io/ebpf-profiler/interpreter/beam"
1010

1111
import (
1212
"fmt"
13+
"os"
1314
"regexp"
1415
"strconv"
1516

@@ -19,6 +20,8 @@ import (
1920
"go.opentelemetry.io/ebpf-profiler/interpreter"
2021
"go.opentelemetry.io/ebpf-profiler/libpf"
2122
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
23+
"go.opentelemetry.io/ebpf-profiler/lpm"
24+
"go.opentelemetry.io/ebpf-profiler/process"
2225
"go.opentelemetry.io/ebpf-profiler/remotememory"
2326
"go.opentelemetry.io/ebpf-profiler/reporter"
2427
"go.opentelemetry.io/ebpf-profiler/support"
@@ -40,6 +43,12 @@ type beamInstance struct {
4043

4144
data *beamData
4245
rm remotememory.RemoteMemory
46+
// mappings is indexed by the Mapping to its generation
47+
mappings map[process.Mapping]*uint32
48+
// prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation
49+
prefixes map[lpm.Prefix]*uint32
50+
// mappingGeneration is the current generation (so old entries can be pruned)
51+
mappingGeneration uint32
4352
}
4453

4554
func readSymbolValue(ef *pfelf.File, name libpf.SymbolName) ([]byte, error) {
@@ -116,11 +125,126 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr
116125
func (d *beamData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libpf.Address, rm remotememory.RemoteMemory) (interpreter.Instance, error) {
117126
log.Infof("BEAM interpreter attaching")
118127
return &beamInstance{
119-
data: d,
120-
rm: rm,
128+
data: d,
129+
rm: rm,
130+
mappings: make(map[process.Mapping]*uint32),
131+
prefixes: make(map[lpm.Prefix]*uint32),
121132
}, nil
122133
}
123134

135+
func (i *beamInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler,
136+
_ reporter.SymbolReporter, pr process.Process, mappings []process.Mapping) error {
137+
pid := pr.PID()
138+
i.mappingGeneration++
139+
for idx := range mappings {
140+
m := &mappings[idx]
141+
if !m.IsExecutable() || !m.IsAnonymous() {
142+
continue
143+
}
144+
145+
if _, exists := i.mappings[*m]; exists {
146+
*i.mappings[*m] = i.mappingGeneration
147+
continue
148+
}
149+
150+
// Generate a new uint32 pointer which is shared for mapping and the prefixes it owns
151+
// so updating the mapping above will reflect to prefixes also.
152+
mappingGeneration := i.mappingGeneration
153+
i.mappings[*m] = &mappingGeneration
154+
155+
// Just assume all anonymous and executable mappings are BEAM for now
156+
log.Infof("Enabling BEAM for %#x/%#x", m.Vaddr, m.Length)
157+
158+
prefixes, err := lpm.CalculatePrefixList(m.Vaddr, m.Vaddr+m.Length)
159+
if err != nil {
160+
return fmt.Errorf("new anonymous mapping lpm failure %#x/%#x", m.Vaddr, m.Length)
161+
}
162+
163+
for _, prefix := range prefixes {
164+
_, exists := i.prefixes[prefix]
165+
if !exists {
166+
err := ebpf.UpdatePidInterpreterMapping(pid, prefix, support.ProgUnwindBEAM, 0, 0)
167+
if err != nil {
168+
return err
169+
}
170+
}
171+
i.prefixes[prefix] = &mappingGeneration
172+
}
173+
}
174+
175+
// Remove prefixes not seen
176+
for prefix, generationPtr := range i.prefixes {
177+
if *generationPtr == i.mappingGeneration {
178+
continue
179+
}
180+
log.Infof("Delete BEAM prefix %#v", prefix)
181+
_ = ebpf.DeletePidInterpreterMapping(pid, prefix)
182+
delete(i.prefixes, prefix)
183+
}
184+
for m, generationPtr := range i.mappings {
185+
if *generationPtr == i.mappingGeneration {
186+
continue
187+
}
188+
log.Infof("Disabling BEAM for %#x/%#x", m.Vaddr, m.Length)
189+
delete(i.mappings, m)
190+
}
191+
192+
return nil
193+
}
194+
195+
func (i *beamInstance) SynchronizeMappingsFromJITDump(ebpf interpreter.EbpfHandler,
196+
_ reporter.SymbolReporter, pr process.Process, mappings []process.Mapping) error {
197+
pid := pr.PID()
198+
file, err := os.Open(fmt.Sprintf("/tmp/jit-%d.dump", uint32(pid)))
199+
if err != nil {
200+
return err
201+
}
202+
defer file.Close()
203+
204+
header, err := ReadJITDumpHeader(file)
205+
if err != nil {
206+
return err
207+
}
208+
log.Infof("Parsed header: %v", *header)
209+
210+
for recordHeader, err := ReadJITDumpRecordHeader(file); err == nil; recordHeader, err = ReadJITDumpRecordHeader(file) {
211+
switch recordHeader.ID {
212+
case JITCodeLoad:
213+
record, name, err := ReadJITDumpRecordCodeLoad(file, recordHeader)
214+
if err != nil {
215+
return err
216+
}
217+
218+
log.Infof("JITDump Code Load %s @ 0x%x (%d bytes)", name, record.CodeAddr, record.CodeSize)
219+
220+
prefixes, err := lpm.CalculatePrefixList(record.CodeAddr, record.CodeAddr+record.CodeSize)
221+
if err != nil {
222+
return fmt.Errorf("lpm failure %#x/%#x", record.CodeAddr, record.CodeSize)
223+
}
224+
225+
for _, prefix := range prefixes {
226+
// TODO: Include FileID
227+
err := ebpf.UpdatePidInterpreterMapping(pid, prefix, support.ProgUnwindBEAM, 0, 0)
228+
if err != nil {
229+
return err
230+
}
231+
}
232+
233+
// TODO: remove mappings that have been moved/unloaded
234+
235+
default:
236+
log.Warnf("Ignoring JITDump record type %d", recordHeader.ID)
237+
SkipJITDumpRecord(file, recordHeader)
238+
}
239+
}
240+
241+
if err != nil {
242+
return err
243+
}
244+
245+
return nil
246+
}
247+
124248
func (i *beamInstance) Detach(interpreter.EbpfHandler, libpf.PID) error {
125249
log.Infof("BEAM interpreter detaching")
126250
return nil

interpreter/beam/jitdumpreader.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package beam // import "go.opentelemetry.io/ebpf-profiler/interpreter/beam"
5+
6+
// Minimal JITDUMP file reader for BEAM
7+
8+
// This has the minimal code we need to read the JITDUMP files that the BEAM
9+
// writes to `/tmp/jit-<pid>.dump`. It isn't BEAM-specific, so it could probably
10+
// be used more generally. The spec for this file format is at:
11+
// https://raw.githubusercontent.com/torvalds/linux/refs/heads/master/tools/perf/Documentation/jitdump-specification.txt
12+
13+
import (
14+
"encoding/binary"
15+
"fmt"
16+
"io"
17+
)
18+
19+
type JITDumpHeader struct {
20+
Magic uint32 // the ASCII string "JiTD", written is as 0x4A695444. The reader will detect an endian mismatch when it reads 0x4454694a
21+
Version uint32 // a 4-byte value representing the format version. It is currently set to 1
22+
TotalSize uint32 // size in bytes of file header
23+
ELFMach uint32 // ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h)
24+
Pad1 uint32 // padding. Reserved for future use
25+
Pid uint32 // JIT runtime process identification (OS specific)
26+
Timestamp uint64 // timestamp of when the file was created
27+
Flags uint64 // a bitmask of flags
28+
}
29+
30+
type JITDumpRecordHeader struct {
31+
ID uint32 // a value identifying the record type (e.g. beam.JITCodeLoad)
32+
TotalSize uint32 // the size in bytes of the record including this header
33+
Timestamp uint64 // a timestamp of when the record was created
34+
}
35+
36+
const (
37+
JITCodeLoad = 0 // record describing a jitted function
38+
JITCodeMove = 1 // record describing an already jitted function which is moved
39+
JITCodeDebugInfo = 2 // record describing the debug information for a jitted function
40+
JITCodeClose = 3 // record marking the end of the jit runtime (optional)
41+
JITCodeUnwindingInfo = 4 // record describing a function unwinding information
42+
)
43+
44+
type JITDumpRecordCodeLoad struct {
45+
PID uint32 // OS process id of the runtime generating the jitted code
46+
TID uint32 // OS thread identification of the runtime thread generating the jitted code
47+
VMA uint64 // virtual address of jitted code start
48+
CodeAddr uint64 // code start address for the jitted code. By default vma = code_addr
49+
CodeSize uint64 // size in bytes of the generated jitted code
50+
CodeIndex uint64 // unique identifier for the jitted code
51+
}
52+
53+
func ReadJITDumpHeader(file io.ReadSeeker) (*JITDumpHeader, error) {
54+
header := JITDumpHeader{}
55+
err := binary.Read(file, binary.LittleEndian, &header)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
if header.Magic != 0x4A695444 {
61+
return nil, fmt.Errorf("File malformed, or maybe wrong endianness. Found magic number: %x", header.Magic)
62+
}
63+
64+
return &header, nil
65+
}
66+
67+
func ReadJITDumpRecordHeader(file io.ReadSeeker) (*JITDumpRecordHeader, error) {
68+
header := JITDumpRecordHeader{}
69+
err := binary.Read(file, binary.LittleEndian, &header)
70+
if err != nil {
71+
return nil, err
72+
}
73+
return &header, nil
74+
}
75+
76+
func ReadJITDumpRecordCodeLoad(file io.ReadSeeker, header *JITDumpRecordHeader) (*JITDumpRecordCodeLoad, string, error) {
77+
record := JITDumpRecordCodeLoad{}
78+
err := binary.Read(file, binary.LittleEndian, &record)
79+
if err != nil {
80+
return nil, "", err
81+
}
82+
83+
recordHeaderSize := uint32(16)
84+
codeLoadRecordHeaderSize := uint32(40)
85+
nameSize := header.TotalSize - uint32(record.CodeSize) - recordHeaderSize - codeLoadRecordHeaderSize
86+
name := make([]byte, nameSize)
87+
err = binary.Read(file, binary.LittleEndian, &name)
88+
if err != nil {
89+
return nil, "", err
90+
}
91+
92+
if name[nameSize-1] != '\x00' {
93+
return nil, "", fmt.Errorf("Expected null terminated string, found %c", name[nameSize-1])
94+
}
95+
96+
// Skip over the actual native code because we don't need it but we
97+
// probably do want to read the next record.
98+
_, err = file.Seek(int64(record.CodeSize), io.SeekCurrent)
99+
if err != nil {
100+
return nil, "", err
101+
}
102+
103+
return &record, string(name), nil
104+
}
105+
106+
func SkipJITDumpRecord(file io.ReadSeeker, header *JITDumpRecordHeader) error {
107+
recordHeaderSize := uint64(16)
108+
_, err := file.Seek(int64(header.TotalSize)-int64(recordHeaderSize), io.SeekCurrent)
109+
if err != nil {
110+
return err
111+
}
112+
return nil
113+
}

0 commit comments

Comments
 (0)