Skip to content

Commit 6c916db

Browse files
committed
Fix leaf lineno
1 parent 972cfde commit 6c916db

15 files changed

Lines changed: 81 additions & 48 deletions

host/host.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Frame struct {
3838
File FileID
3939
Lineno libpf.AddressOrLineno
4040
Type libpf.FrameType
41+
Extra libpf.AddressOrLineno
4142
ReturnAddress bool
4243
}
4344

interpreter/ruby/ruby.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,6 @@ type rubyIseq struct {
318318
// methodName is the optional method name for this iseq
319319
// only present on CME-based iseq
320320
methodName libpf.String
321-
322-
// line of code in source file for this instruction sequence
323-
line libpf.SourceLineno
324321
}
325322

326323
type rubyInstance struct {
@@ -862,11 +859,6 @@ func unpackEnvFlags(packed uint16) uint32 {
862859

863860
func (r *rubyInstance) readIseqBody(iseqBody, pc libpf.Address, frameAddrType uint8, frameFlags uint32) (*rubyIseq, error) {
864861
vms := &r.r.vmStructs
865-
lineNo, err := r.getRubyLineNo(iseqBody, uint64(pc))
866-
if err != nil {
867-
lineNo = 0
868-
log.Warnf("RubySymbolizer: Failed to get line number (%d) %v", frameAddrType, err)
869-
}
870862

871863
// Read contiguous pointer values into a buffer to be more efficient
872864
dataBytes := make([]byte, 3*vms.size_of_value)
@@ -919,7 +911,6 @@ func (r *rubyInstance) readIseqBody(iseqBody, pc libpf.Address, frameAddrType ui
919911
baseLabel: iseqBaseLabel,
920912
methodName: methodName,
921913
sourceFileName: sourceFileName,
922-
line: libpf.SourceLineno(lineNo),
923914
}, nil
924915
}
925916

@@ -1009,14 +1000,22 @@ func (r *rubyInstance) Symbolize(frame *host.Frame, frames *libpf.Frames) error
10091000
fullLabel = qualifiedMethodName(classPath, methodName, singleton)
10101001
sourceFile = cfuncDummyFile
10111002
} else {
1003+
10121004
// The Ruby VM program counter that was extracted from the current call frame is embedded in
1013-
// the Linenos field.
1005+
// the Linenos field, and we use the iseq address passed in frame.Extra to decode it
1006+
1007+
lineNo, err := r.getRubyLineNo(libpf.Address(frame.Extra), uint64(pc))
1008+
if err != nil {
1009+
lineNo = 0
1010+
log.Warnf("RubySymbolizer: Failed to get line number (%d) %v", frameAddrType, err)
1011+
}
1012+
10141013
iseq, err := r.readIseqBody(iseqBody, pc, frameAddrType, frameFlags)
10151014
if err != nil {
10161015
return err
10171016
}
10181017
sourceFile = iseq.sourceFileName
1019-
sourceLine = iseq.line
1018+
sourceLine = libpf.SourceLineno(lineNo)
10201019

10211020
fullLabel = profileFrameFullLabel(classPath, iseq.label, iseq.baseLabel, iseq.methodName, singleton, cframe)
10221021

support/ebpf/ruby_tracer.ebpf.c

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ typedef struct vm_env_struct {
6666
// frame_type is encoded into the "file" attribute of frame in the spare bits
6767
// frame flags are encoded in the upper bits of "line" for debugging purposes, but this
6868
// is may change in the future.
69-
static EBPF_INLINE ErrorCode push_ruby(Trace *trace, u16 flags, u8 frame_type, u64 file, u64 line)
69+
static EBPF_INLINE ErrorCode
70+
push_ruby(Trace *trace, u16 flags, u8 frame_type, u64 file, u64 line, u64 iseq)
7071
{
7172
if (frame_type != FRAME_TYPE_NONE) {
7273
// Ensure address is actually no more than 48-bits
@@ -90,7 +91,7 @@ static EBPF_INLINE ErrorCode push_ruby(Trace *trace, u16 flags, u8 frame_type, u
9091
line = packed;
9192
}
9293
}
93-
return _push(trace, file, line, FRAME_MARKER_RUBY);
94+
return _push_with_extra(trace, file, line, iseq, FRAME_MARKER_RUBY);
9495
}
9596

9697
// Read a single Ruby frame
@@ -107,6 +108,8 @@ static EBPF_INLINE ErrorCode read_ruby_frame(
107108
u8 frame_type;
108109
// Actual frame address of the given type
109110
u64 frame_addr;
111+
// Address of the cfp->iseq, used to get the line number using the pc
112+
u64 iseq_addr = 0;
110113
u64 pc;
111114

112115
Trace *trace = &record->trace;
@@ -268,27 +271,30 @@ static EBPF_INLINE ErrorCode read_ruby_frame(
268271
}
269272
}
270273

271-
// Fallback to just reading the iseq if we couldn't detect a supported CME type
272-
if (frame_type == FRAME_TYPE_NONE) {
274+
if (!cfunc) {
275+
// Read the control frame iseq so we can get the line number
273276
if (control_frame.iseq == NULL) {
274277
increment_metric(metricID_UnwindRubyErrInvalidIseq);
275278
return ERR_RUBY_INVALID_ISEQ;
276-
}
277-
278-
if (control_frame.iseq != NULL) {
279+
} else {
279280
if (bpf_probe_read_user(
280-
&frame_addr, sizeof(frame_addr), (void *)(control_frame.iseq + rubyinfo->body))) {
281+
&iseq_addr, sizeof(iseq_addr), (void *)(control_frame.iseq + rubyinfo->body))) {
281282
increment_metric(metricID_UnwindRubyErrReadIseqBody);
282283
return ERR_RUBY_READ_ISEQ_BODY;
283284
}
284-
frame_type = FRAME_TYPE_ISEQ;
285285
}
286286
}
287287

288+
// Fallback to just reading the iseq if we couldn't detect a supported CME type
289+
if (frame_type == FRAME_TYPE_NONE) {
290+
frame_addr = iseq_addr;
291+
frame_type = FRAME_TYPE_ISEQ;
292+
}
293+
288294
// For symbolization of the frame we forward the information about the CME,
289295
// or plain iseq to userspace, along with the pc so we can get line information.
290296
// From this we can then extract information like file or function name and line number.
291-
ErrorCode error = push_ruby(trace, packed_flags, frame_type, frame_addr, pc);
297+
ErrorCode error = push_ruby(trace, packed_flags, frame_type, frame_addr, pc, iseq_addr);
292298
if (error) {
293299
DEBUG_PRINT("ruby: failed to push frame");
294300
return error;
@@ -398,7 +404,8 @@ static EBPF_INLINE ErrorCode walk_ruby_stack(
398404
// If we entered native unwinding because we saw a cfunc frame, lets push that
399405
// frame now so it can take "ownership" of the native code that was unwound
400406
if (record->rubyUnwindState.cfunc_saved_frame != 0) {
401-
error = push_ruby(trace, 0, FRAME_TYPE_CME_CFUNC, record->rubyUnwindState.cfunc_saved_frame, 0);
407+
error =
408+
push_ruby(trace, 0, FRAME_TYPE_CME_CFUNC, record->rubyUnwindState.cfunc_saved_frame, 0, 0);
402409
if (error) {
403410
DEBUG_PRINT("ruby: failed to push cframe");
404411
return error;

support/ebpf/tracemgmt.h

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -329,27 +329,32 @@ static inline EBPF_INLINE bool unwinder_unwind_frame_pointer(UnwindState *state)
329329
// calc_line). This should probably be renamed to something like "frame type
330330
// specific data".
331331
static inline EBPF_INLINE ErrorCode _push_with_max_frames(
332-
Trace *trace, u64 file, u64 line, u8 frame_type, u8 return_address, u32 max_frames)
332+
Trace *trace, u64 file, u64 line, u64 extra, u8 frame_type, u8 return_address, u32 max_frames)
333333
{
334334
if (trace->stack_len >= max_frames) {
335335
DEBUG_PRINT("unable to push frame: stack is full");
336336
increment_metric(metricID_UnwindErrStackLengthExceeded);
337337
return ERR_STACK_LENGTH_EXCEEDED;
338338
}
339339

340+
u64 extra_addr = (u64)extra & 0x0000FFFFFFFFFFFFULL;
340341
#ifdef TESTING_COREDUMP
341342
// tools/coredump uses CGO to build the eBPF code. This dispatches
342343
// the frame information directly to helper implemented in ebpfhelpers.go.
343-
int __push_frame(u64, u64, u64, u8, u8);
344+
int __push_frame(u64, u64, u64, u64, u8, u8);
344345
trace->stack_len++;
345-
return __push_frame(__cgo_ctx->id, file, line, frame_type, return_address);
346+
return __push_frame(__cgo_ctx->id, file, line, extra_addr, frame_type, return_address);
346347
#else
347-
trace->frames[trace->stack_len++] = (Frame){
348+
Frame frame = {
348349
.file_id = file,
349350
.addr_or_line = line,
350351
.kind = frame_type,
351352
.return_address = return_address,
352353
};
354+
if (extra_addr != 0) {
355+
__builtin_memcpy(frame.pad, &extra_addr, 6);
356+
}
357+
trace->frames[trace->stack_len++] = frame;
353358

354359
return ERR_OK;
355360
#endif
@@ -360,19 +365,27 @@ static inline EBPF_INLINE ErrorCode
360365
_push_with_return_address(Trace *trace, u64 file, u64 line, u8 frame_type, bool return_address)
361366
{
362367
return _push_with_max_frames(
363-
trace, file, line, frame_type, return_address, MAX_NON_ERROR_FRAME_UNWINDS);
368+
trace, file, line, 0, frame_type, return_address, MAX_NON_ERROR_FRAME_UNWINDS);
364369
}
365370

366371
// Push the file ID, line number and frame type into FrameList
367372
static inline EBPF_INLINE ErrorCode _push(Trace *trace, u64 file, u64 line, u8 frame_type)
368373
{
369-
return _push_with_max_frames(trace, file, line, frame_type, 0, MAX_NON_ERROR_FRAME_UNWINDS);
374+
return _push_with_max_frames(trace, file, line, 0, frame_type, 0, MAX_NON_ERROR_FRAME_UNWINDS);
375+
}
376+
377+
// Push the file ID, line number and frame type with an extra address into FrameList
378+
static inline EBPF_INLINE ErrorCode
379+
_push_with_extra(Trace *trace, u64 file, u64 line, u64 extra, u8 frame_type)
380+
{
381+
return _push_with_max_frames(
382+
trace, file, line, extra, frame_type, 0, MAX_NON_ERROR_FRAME_UNWINDS);
370383
}
371384

372385
// Push a critical error frame.
373386
static inline EBPF_INLINE ErrorCode push_error(Trace *trace, ErrorCode error)
374387
{
375-
return _push_with_max_frames(trace, 0, error, FRAME_MARKER_ABORT, 0, MAX_FRAME_UNWINDS);
388+
return _push_with_max_frames(trace, 0, error, 0, FRAME_MARKER_ABORT, 0, MAX_FRAME_UNWINDS);
376389
}
377390

378391
// Send a trace to user-land via the `trace_events` perf event buffer.

support/ebpf/tracer.ebpf.amd64

35.4 KB
Binary file not shown.

support/ebpf/tracer.ebpf.arm64

35.4 KB
Binary file not shown.

tools/coredump/ebpfhelpers.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ func __bpf_log(buf unsafe.Pointer, sz C.int) {
3636
}
3737

3838
//export __push_frame
39-
func __push_frame(id, file, line C.u64, frameType, returnAddress C.uchar) C.int {
39+
func __push_frame(id, file, line, extra C.u64, frameType, returnAddress C.uchar) C.int {
4040
ctx := ebpfContextMap[id]
4141

4242
ctx.trace.Frames = append(ctx.trace.Frames, host.Frame{
4343
File: host.FileID(file),
4444
Lineno: libpf.AddressOrLineno(line),
4545
Type: libpf.FrameType(frameType),
46+
Extra: libpf.AddressOrLineno(extra),
4647
ReturnAddress: returnAddress != 0,
4748
})
4849

tools/coredump/testdata/amd64/ruby-3.3.9-loop.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"lwp": 1849675,
66
"frames": [
7-
"Object#is_prime+0 in /app/loop.rb:45481984",
7+
"Object#is_prime+0 in /app/loop.rb:10",
88
"libruby.so.3.3.9+0x31ba80",
99
"libruby.so.3.3.9+0x321007",
1010
"libruby.so.3.3.9+0x22912d",

tools/coredump/testdata/amd64/ruby-3.4.5-loop.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"lwp": 1177439,
66
"frames": [
7-
"Object#is_prime+0 in /app/loop.rb:0",
7+
"Object#is_prime+0 in /app/loop.rb:10",
88
"libruby.so.3.4.5+0x346a40",
99
"libruby.so.3.4.5+0x34bb17",
1010
"libruby.so.3.4.5+0x24a095",

tools/coredump/testdata/amd64/ruby-3.5.0-loop.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"lwp": 1844728,
66
"frames": [
7-
"Object#is_prime+0 in /app/loop.rb:0",
7+
"Object#is_prime+0 in /app/loop.rb:10",
88
"libruby.so.3.5.0+0x3399b0",
99
"libruby.so.3.5.0+0x33e9d7",
1010
"libruby.so.3.5.0+0x23e175",

0 commit comments

Comments
 (0)