-
Notifications
You must be signed in to change notification settings - Fork 399
Initial no-op plumbing for BEAM (Erlang/Elixir) support #958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
b4a4d8c
Initial plumbing for no-op BEAM tracer
GregMefford 9f42665
Remove unnecessary info logs
GregMefford 67e69c8
Properly define BEAM_PC_INVALID error
GregMefford add11d3
Add a simple but nontrivial erlang testsource
GregMefford 6a65cfe
Format C code
GregMefford e1645b4
More specific Regex match on beam.smp
GregMefford bb697b3
Alphabetize lists
GregMefford acd3836
Debug log on attach
GregMefford 8f243e7
Implement String decorator
GregMefford 5f7b040
Standardize missing ProcInfo convention
GregMefford File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package beam // import "go.opentelemetry.io/ebpf-profiler/interpreter/beam" | ||
|
|
||
| // BEAM VM Unwinder support code | ||
|
|
||
| // The BEAM VM is an interpreter for Erlang, as well as several other languages | ||
| // that share the same bytecode, such as Elixir and Gleam. | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "regexp" | ||
| "unsafe" | ||
|
|
||
| "go.opentelemetry.io/ebpf-profiler/internal/log" | ||
|
|
||
| "go.opentelemetry.io/ebpf-profiler/host" | ||
| "go.opentelemetry.io/ebpf-profiler/interpreter" | ||
| "go.opentelemetry.io/ebpf-profiler/libpf" | ||
| "go.opentelemetry.io/ebpf-profiler/lpm" | ||
| "go.opentelemetry.io/ebpf-profiler/process" | ||
| "go.opentelemetry.io/ebpf-profiler/remotememory" | ||
| "go.opentelemetry.io/ebpf-profiler/reporter" | ||
| "go.opentelemetry.io/ebpf-profiler/support" | ||
| ) | ||
|
|
||
| var ( | ||
| // regex for matching the process name | ||
| beamRegex = regexp.MustCompile(`(^|\/)beam\.smp`) | ||
| _ interpreter.Data = &beamData{} | ||
| _ interpreter.Instance = &beamInstance{} | ||
| ) | ||
|
|
||
| type beamData struct { | ||
| otpRelease string | ||
| ertsVersion string | ||
| } | ||
|
|
||
| type beamInstance struct { | ||
| interpreter.InstanceStubs | ||
|
|
||
| pid libpf.PID | ||
| data *beamData | ||
| rm remotememory.RemoteMemory | ||
| bias libpf.Address | ||
|
|
||
| // prefixes is indexed by the prefix added to ebpf maps (to be cleaned up) to its generation | ||
| prefixes map[lpm.Prefix]uint32 | ||
| // mappingGeneration is the current generation (so old entries can be pruned) | ||
| mappingGeneration uint32 | ||
| } | ||
|
|
||
| func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) { | ||
| matches := beamRegex.FindStringSubmatch(info.FileName()) | ||
| if matches == nil { | ||
| return nil, nil | ||
| } | ||
|
|
||
| ef, err := info.GetELF() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| _, otpRelease, err := ef.SymbolData("etp_otp_release", 4) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to read OTP release: %v", err) | ||
| } | ||
|
|
||
| _, ertsVersion, err := ef.SymbolData("etp_erts_version", 64) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to read ERTS version: %v", err) | ||
| } | ||
|
|
||
| d := &beamData{ | ||
| otpRelease: string(otpRelease[:len(otpRelease)-1]), | ||
| ertsVersion: string(ertsVersion[:len(ertsVersion)-1]), | ||
| } | ||
|
|
||
| return d, nil | ||
| } | ||
|
|
||
| func (d *beamData) String() string { | ||
| return fmt.Sprintf("BEAM OTP %s, ERTS %s", d.otpRelease, d.ertsVersion) | ||
| } | ||
|
|
||
| func (d *beamData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libpf.Address, rm remotememory.RemoteMemory) (interpreter.Instance, error) { | ||
| log.Debugf("BEAM attaching, OTP %s, ERTS %s, bias: 0x%x", d.otpRelease, d.ertsVersion, bias) | ||
|
|
||
| data := support.BEAMProcInfo{ | ||
| Bias: uint64(bias), | ||
| } | ||
|
|
||
| if err := ebpf.UpdateProcData(libpf.BEAM, pid, unsafe.Pointer(&data)); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &beamInstance{ | ||
| pid: pid, | ||
| data: d, | ||
| rm: rm, | ||
| bias: bias, | ||
| prefixes: make(map[lpm.Prefix]uint32), | ||
| }, nil | ||
| } | ||
|
|
||
| func (d *beamData) Unload(_ interpreter.EbpfHandler) { | ||
| } | ||
|
|
||
| func (i *beamInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler, _ reporter.ExecutableReporter, pr process.Process, mappings []process.Mapping) error { | ||
| pid := pr.PID() | ||
| i.mappingGeneration++ | ||
| for idx := range mappings { | ||
| m := &mappings[idx] | ||
| if !m.IsExecutable() || !m.IsAnonymous() { | ||
| continue | ||
| } | ||
|
|
||
| // Just assume all anonymous and executable mappings are BEAM for now | ||
| log.Debugf("Enabling BEAM for %#x/%#x", m.Vaddr, m.Length) | ||
|
|
||
| prefixes, err := lpm.CalculatePrefixList(m.Vaddr, m.Vaddr+m.Length) | ||
| if err != nil { | ||
| return fmt.Errorf("new anonymous mapping lpm failure %#x/%#x", m.Vaddr, m.Length) | ||
| } | ||
|
|
||
| for _, prefix := range prefixes { | ||
| _, exists := i.prefixes[prefix] | ||
| if !exists { | ||
| err := ebpf.UpdatePidInterpreterMapping(pid, prefix, support.ProgUnwindBEAM, 0, 0) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| i.prefixes[prefix] = i.mappingGeneration | ||
| } | ||
| } | ||
|
|
||
| // Remove prefixes not seen | ||
| for prefix, generation := range i.prefixes { | ||
| if generation == i.mappingGeneration { | ||
| continue | ||
| } | ||
| log.Debugf("Delete BEAM prefix %#v", prefix) | ||
| _ = ebpf.DeletePidInterpreterMapping(pid, prefix) | ||
| delete(i.prefixes, prefix) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func (i *beamInstance) Detach(interpreter.EbpfHandler, libpf.PID) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (i *beamInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error { | ||
| if !frame.Type.IsInterpType(libpf.BEAM) { | ||
| return interpreter.ErrMismatchInterpreterType | ||
| } | ||
|
|
||
| frames.Append(&libpf.Frame{ | ||
| Type: libpf.BEAMFrame, | ||
| SourceFile: libpf.Intern("Unknown File"), | ||
| SourceLine: libpf.SourceLineno(frame.Lineno), | ||
| }) | ||
|
|
||
| return nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #include "bpfdefs.h" | ||
| #include "tracemgmt.h" | ||
| #include "types.h" | ||
|
|
||
| struct beam_procs_t { | ||
| __uint(type, BPF_MAP_TYPE_HASH); | ||
| __type(key, pid_t); | ||
| __type(value, BEAMProcInfo); | ||
| __uint(max_entries, 256); | ||
| } beam_procs SEC(".maps"); | ||
|
|
||
| // unwind_beam is the entry point for tracing when invoked from the native tracer | ||
| // or interpreter dispatcher. It does not reset the trace object and will append the | ||
| // BEAM stack frames to the trace object for the current CPU. | ||
| static EBPF_INLINE int unwind_beam(struct pt_regs *ctx) | ||
| { | ||
|
|
||
| int unwinder = PROG_UNWIND_STOP; | ||
| ErrorCode error = ERR_OK; | ||
|
|
||
| PerCPURecord *record = get_per_cpu_record(); | ||
| if (!record) { | ||
| DEBUG_PRINT("beam: no PerCPURecord found"); | ||
| return -1; | ||
| } | ||
|
|
||
| Trace *trace = &record->trace; | ||
| UnwindState *state = &record->state; | ||
| u32 pid = trace->pid; | ||
|
|
||
| BEAMProcInfo *info = bpf_map_lookup_elem(&beam_procs, &pid); | ||
|
|
||
| if (!info) { | ||
|
GregMefford marked this conversation as resolved.
|
||
| DEBUG_PRINT("beam: no BEAMProcInfo for this pid"); | ||
| error = ERR_BEAM_NO_PROC_INFO; | ||
| goto exit; | ||
| } | ||
|
|
||
| unwinder_mark_nonleaf_frame(state); | ||
| _push_with_return_address(trace, 0LL, state->pc, FRAME_MARKER_BEAM, state->return_address); | ||
| // Pretend that there was an error unwinding for now, | ||
| // so that we don't have an infinite loop, | ||
| // since we're not actually unwinding / updating the state. | ||
| error = ERR_BEAM_PC_INVALID; | ||
|
|
||
| exit: | ||
| record->state.unwind_error = error; | ||
| tail_call(ctx, unwinder); | ||
| DEBUG_PRINT("beam: tail call for next frame unwinder (%d) failed", unwinder); | ||
| return -1; | ||
| } | ||
|
|
||
| MULTI_USE_FUNC(unwind_beam) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.