Skip to content

Commit 1a52c42

Browse files
authored
Always use assert-free APIs when profiling and crashing
rb_profile_frames() is used by profilers in a way such that it can run on any instruction in the binary, and it crashed previously in the following situation in `RUBY_DEBUG` builds: ``` * thread #1, queue = 'com.apple.main-thread', stop reason = step over frame #0: 0x00000001002827f0 miniruby`vm_make_env_each(ec=0x0000000101866b00, cfp=0x000000080c91bee8) at vm.c:992:74 989 } 990 991 vm_make_env_each(ec, prev_cfp); -> 992 VM_FORCE_WRITE_SPECIAL_CONST(&ep[VM_ENV_DATA_INDEX_SPECVAL], VM_GUARDED_PREV_EP(prev_cfp->ep)); 993 } 994 } 995 else { (lldb) call rb_profile_frames(0, 100, $2, $3) /Users/alan/ruby/vm_core.h:1448: Assertion Failed: VM_ENV_FLAGS:FIXNUM_P(flags) ruby 3.5.0dev (2025-09-23T20:20:04Z master 06b7a70) +PRISM [arm64-darwin25] -- Crash Report log information -------------------------------------------- See Crash Report log file in one of the following locations: * ~/Library/Logs/DiagnosticReports * /Library/Logs/DiagnosticReports for more details. Don't forget to include the above Crash Report log file in bug reports. -- Control frame information ----------------------------------------------- c:0008 p:---- s:0029 e:000028 CFUNC :lambda /Users/alan/ruby/vm_core.h:1448: Assertion Failed: VM_ENV_FLAGS:FIXNUM_P(flags) ruby 3.5.0dev (2025-09-23T20:20:04Z master 06b7a70) +PRISM [arm64-darwin25] -- Crash Report log information -------------------------------------------- <snip> ``` There is a small window where the control frame is invalid and fails the assert. The double crash also shows that in `RUBY_DEBUG` builds, the crash reporter was previously not resilient to corrupt frame state. In release builds, it prints more info. Add unchecked APIs for the crash reporter and profilers so they work as well in `RUBY_DEBUG` builds as non-debug builds.
1 parent 62430c1 commit 1a52c42

File tree

4 files changed

+90
-9
lines changed

4 files changed

+90
-9
lines changed

vm_backtrace.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,14 +1745,14 @@ thread_profile_frames(rb_execution_context_t *ec, int start, int limit, VALUE *b
17451745
end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
17461746

17471747
for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
1748-
if (VM_FRAME_RUBYFRAME_P(cfp) && cfp->pc != 0) {
1748+
if (VM_FRAME_RUBYFRAME_P_UNCHECKED(cfp) && cfp->pc != 0) {
17491749
if (start > 0) {
17501750
start--;
17511751
continue;
17521752
}
17531753

17541754
/* record frame info */
1755-
cme = rb_vm_frame_method_entry(cfp);
1755+
cme = rb_vm_frame_method_entry_unchecked(cfp);
17561756
if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ) {
17571757
buff[i] = (VALUE)cme;
17581758
}
@@ -1770,6 +1770,8 @@ thread_profile_frames(rb_execution_context_t *ec, int start, int limit, VALUE *b
17701770
// before entering a non-leaf method (so that `caller` will work),
17711771
// so only the topmost frame could possibly have an out-of-date PC.
17721772
// ZJIT doesn't set `cfp->jit_return`, so it's not a reliable signal.
1773+
// TODO(zjit): lightweight frames potentially makes more than
1774+
// the top most frame invalid.
17731775
//
17741776
// Avoid passing invalid PC to calc_lineno() to avoid crashing.
17751777
if (cfp == top && (pc < iseq_encoded || pc > pc_end)) {
@@ -1783,7 +1785,7 @@ thread_profile_frames(rb_execution_context_t *ec, int start, int limit, VALUE *b
17831785
i++;
17841786
}
17851787
else {
1786-
cme = rb_vm_frame_method_entry(cfp);
1788+
cme = rb_vm_frame_method_entry_unchecked(cfp);
17871789
if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) {
17881790
if (start > 0) {
17891791
start--;

vm_core.h

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1449,12 +1449,25 @@ VM_ENV_FLAGS(const VALUE *ep, long flag)
14491449
return flags & flag;
14501450
}
14511451

1452+
static inline unsigned long
1453+
VM_ENV_FLAGS_UNCHECKED(const VALUE *ep, long flag)
1454+
{
1455+
VALUE flags = ep[VM_ENV_DATA_INDEX_FLAGS];
1456+
return flags & flag;
1457+
}
1458+
14521459
static inline unsigned long
14531460
VM_FRAME_TYPE(const rb_control_frame_t *cfp)
14541461
{
14551462
return VM_ENV_FLAGS(cfp->ep, VM_FRAME_MAGIC_MASK);
14561463
}
14571464

1465+
static inline unsigned long
1466+
VM_FRAME_TYPE_UNCHECKED(const rb_control_frame_t *cfp)
1467+
{
1468+
return VM_ENV_FLAGS_UNCHECKED(cfp->ep, VM_FRAME_MAGIC_MASK);
1469+
}
1470+
14581471
static inline int
14591472
VM_FRAME_LAMBDA_P(const rb_control_frame_t *cfp)
14601473
{
@@ -1473,6 +1486,12 @@ VM_FRAME_FINISHED_P(const rb_control_frame_t *cfp)
14731486
return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_FINISH) != 0;
14741487
}
14751488

1489+
static inline int
1490+
VM_FRAME_FINISHED_P_UNCHECKED(const rb_control_frame_t *cfp)
1491+
{
1492+
return VM_ENV_FLAGS_UNCHECKED(cfp->ep, VM_FRAME_FLAG_FINISH) != 0;
1493+
}
1494+
14761495
static inline int
14771496
VM_FRAME_BMETHOD_P(const rb_control_frame_t *cfp)
14781497
{
@@ -1498,12 +1517,24 @@ VM_FRAME_CFRAME_P(const rb_control_frame_t *cfp)
14981517
return cframe_p;
14991518
}
15001519

1520+
static inline int
1521+
VM_FRAME_CFRAME_P_UNCHECKED(const rb_control_frame_t *cfp)
1522+
{
1523+
return VM_ENV_FLAGS_UNCHECKED(cfp->ep, VM_FRAME_FLAG_CFRAME) != 0;
1524+
}
1525+
15011526
static inline int
15021527
VM_FRAME_RUBYFRAME_P(const rb_control_frame_t *cfp)
15031528
{
15041529
return !VM_FRAME_CFRAME_P(cfp);
15051530
}
15061531

1532+
static inline int
1533+
VM_FRAME_RUBYFRAME_P_UNCHECKED(const rb_control_frame_t *cfp)
1534+
{
1535+
return !VM_FRAME_CFRAME_P_UNCHECKED(cfp);
1536+
}
1537+
15071538
static inline int
15081539
VM_FRAME_NS_SWITCH_P(const rb_control_frame_t *cfp)
15091540
{
@@ -1522,11 +1553,23 @@ VM_ENV_LOCAL_P(const VALUE *ep)
15221553
return VM_ENV_FLAGS(ep, VM_ENV_FLAG_LOCAL) ? 1 : 0;
15231554
}
15241555

1556+
static inline int
1557+
VM_ENV_LOCAL_P_UNCHECKED(const VALUE *ep)
1558+
{
1559+
return VM_ENV_FLAGS_UNCHECKED(ep, VM_ENV_FLAG_LOCAL) ? 1 : 0;
1560+
}
1561+
1562+
static inline const VALUE *
1563+
VM_ENV_PREV_EP_UNCHECKED(const VALUE *ep)
1564+
{
1565+
return GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]);
1566+
}
1567+
15251568
static inline const VALUE *
15261569
VM_ENV_PREV_EP(const VALUE *ep)
15271570
{
15281571
VM_ASSERT(VM_ENV_LOCAL_P(ep) == 0);
1529-
return GC_GUARDED_PTR_REF(ep[VM_ENV_DATA_INDEX_SPECVAL]);
1572+
return VM_ENV_PREV_EP_UNCHECKED(ep);
15301573
}
15311574

15321575
static inline VALUE
@@ -1934,6 +1977,7 @@ void rb_gc_mark_machine_context(const rb_execution_context_t *ec);
19341977
rb_cref_t *rb_vm_rewrite_cref(rb_cref_t *node, VALUE old_klass, VALUE new_klass);
19351978

19361979
const rb_callable_method_entry_t *rb_vm_frame_method_entry(const rb_control_frame_t *cfp);
1980+
const rb_callable_method_entry_t *rb_vm_frame_method_entry_unchecked(const rb_control_frame_t *cfp);
19371981

19381982
#define sysstack_error GET_VM()->special_exceptions[ruby_error_sysstack]
19391983

vm_dump.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c
6161
const char *magic, *iseq_name = "-", *selfstr = "-", *biseq_name = "-";
6262
VALUE tmp;
6363
const rb_iseq_t *iseq = NULL;
64-
const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp);
64+
const rb_callable_method_entry_t *me = rb_vm_frame_method_entry_unchecked(cfp);
6565

6666
if (ep < 0 || (size_t)ep > ec->vm_stack_size) {
6767
ep = (ptrdiff_t)cfp->ep;
6868
ep_in_heap = 'p';
6969
}
7070

71-
switch (VM_FRAME_TYPE(cfp)) {
71+
switch (VM_FRAME_TYPE_UNCHECKED(cfp)) {
7272
case VM_FRAME_MAGIC_TOP:
7373
magic = "TOP";
7474
break;
@@ -128,7 +128,9 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c
128128
iseq = cfp->iseq;
129129
pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded;
130130
iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label);
131-
line = rb_vm_get_sourceline(cfp);
131+
if (pc >= 0 && pc <= ISEQ_BODY(iseq)->iseq_size) {
132+
line = rb_vm_get_sourceline(cfp);
133+
}
132134
if (line) {
133135
snprintf(posbuf, MAX_POSBUF, "%s:%d", RSTRING_PTR(rb_iseq_path(iseq)), line);
134136
}
@@ -138,7 +140,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c
138140
}
139141
}
140142
}
141-
else if (me != NULL) {
143+
else if (me != NULL && IMEMO_TYPE_P(me, imemo_ment)) {
142144
iseq_name = rb_id2name(me->def->original_id);
143145
snprintf(posbuf, MAX_POSBUF, ":%s", iseq_name);
144146
line = -1;
@@ -158,7 +160,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c
158160
if (line) {
159161
kprintf(" %s", posbuf);
160162
}
161-
if (VM_FRAME_FINISHED_P(cfp)) {
163+
if (VM_FRAME_FINISHED_P_UNCHECKED(cfp)) {
162164
kprintf(" [FINISH]");
163165
}
164166
if (0) {

vm_insnhelper.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,25 @@ check_method_entry(VALUE obj, int can_be_svar)
761761
}
762762
}
763763

764+
static rb_callable_method_entry_t *
765+
env_method_entry_unchecked(VALUE obj, int can_be_svar)
766+
{
767+
if (obj == Qfalse) return NULL;
768+
769+
switch (imemo_type(obj)) {
770+
case imemo_ment:
771+
return (rb_callable_method_entry_t *)obj;
772+
case imemo_cref:
773+
return NULL;
774+
case imemo_svar:
775+
if (can_be_svar) {
776+
return env_method_entry_unchecked(((struct vm_svar *)obj)->cref_or_me, FALSE);
777+
}
778+
default:
779+
return NULL;
780+
}
781+
}
782+
764783
const rb_callable_method_entry_t *
765784
rb_vm_frame_method_entry(const rb_control_frame_t *cfp)
766785
{
@@ -775,6 +794,20 @@ rb_vm_frame_method_entry(const rb_control_frame_t *cfp)
775794
return check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
776795
}
777796

797+
const rb_callable_method_entry_t *
798+
rb_vm_frame_method_entry_unchecked(const rb_control_frame_t *cfp)
799+
{
800+
const VALUE *ep = cfp->ep;
801+
rb_callable_method_entry_t *me;
802+
803+
while (!VM_ENV_LOCAL_P_UNCHECKED(ep)) {
804+
if ((me = env_method_entry_unchecked(ep[VM_ENV_DATA_INDEX_ME_CREF], FALSE)) != NULL) return me;
805+
ep = VM_ENV_PREV_EP_UNCHECKED(ep);
806+
}
807+
808+
return env_method_entry_unchecked(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
809+
}
810+
778811
static const rb_iseq_t *
779812
method_entry_iseqptr(const rb_callable_method_entry_t *me)
780813
{

0 commit comments

Comments
 (0)