Return line 0 for JIT frames

Frames pushed by YJIT have an unreliable PC.  The PC could be garbage,
and if we try to read the line number with a garbage PC, then the
program can crash.

This commit returns line 0 for programs where there is a `jit_return`
function.  If `jit_return` has been set then this frame was pushed by
the JIT, and we cannot trust the PC.

Here is a debugger session for a program that crashed due to a broken
PC:

```
(lldb) p ruby_current_vm_ptr->ractor.main_thread->ec->cfp->iseq->body->iseq_encoded
(VALUE *) $0 = 0x0000000118a30e00
(lldb) p/x ruby_current_vm_ptr->ractor.main_thread->ec->cfp->pc
(const VALUE *) $1 = 0x0000600000b02d00
(lldb) p/x ruby_current_vm_ptr->ractor.main_thread->ec->cfp->jit_return
(void *) $2 = 0x000000010622942c
```

You can see the PC is completely out of range, but there is a
`jit_return` pointer so we can avoid this crash.
This commit is contained in:
Aaron Patterson 2023-09-11 14:11:46 -07:00 committed by Aaron Patterson
parent efe2822708
commit a1dc1a3de9

View File

@ -1583,6 +1583,7 @@ rb_profile_frames(int start, int limit, VALUE *buff, int *lines)
int i;
const rb_execution_context_t *ec = GET_EC();
const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
const rb_control_frame_t *top = cfp;
const rb_callable_method_entry_t *cme;
// If this function is called inside a thread after thread creation, but
@ -1613,7 +1614,18 @@ rb_profile_frames(int start, int limit, VALUE *buff, int *lines)
buff[i] = (VALUE)cfp->iseq;
}
if (lines) lines[i] = calc_lineno(cfp->iseq, cfp->pc);
if (lines) {
// The topmost frame may not have an updated PC because the JIT
// may not have set one. The JIT compiler will update the PC
// before entering a new function (so that `caller` will work),
// so only the topmost frame could possibly have an out of date PC
if (cfp == top && cfp->jit_return) {
lines[i] = 0;
}
else {
lines[i] = calc_lineno(cfp->iseq, cfp->pc);
}
}
i++;
}