rb_vm_bugreport() silently skipped the control frame information, Ruby
level backtrace, and threading sections when vm or ec was NULL, so the
report jumped straight to the machine register context with no explanation
of why no Ruby level information was shown. ec can be NULL for various
reasons (e.g. a process-directed signal delivered to a non-Ruby thread, or
simply a window where the current thread has no execution context yet).
Emit an explicit note under the "Control frame information" header in that
case, without speculating about the cause. This makes the report
self-explanatory and keeps the section present, so test_crash_report no
longer fails intermittently when ec happens to be NULL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename for consistency with ISEQ_BODY naming convention. Remove the
separate rb_cfp_has_iseq/rb_cfp_has_pc wrappers — CFP_ISEQ and CFP_PC
return NULL for missing values, which works directly in if conditions.
Per tenderlove's review, every cfp->iseq read should go through an
accessor that accounts for JITFrame. Rename the field to _iseq so
direct access causes a compile error, and rename the accessors to
drop the zjit prefix (rb_cfp_iseq, rb_cfp_has_iseq, rb_cfp_pc,
rb_cfp_has_pc).
rb_ec_ractor_ptr(ec) can return NULL when ec->thread_ptr is NULL
(e.g. when crashing from a GC free function). Dereferencing the
NULL return value crashes the bug reporter itself, causing "Crashed
while printing bug report" and losing all diagnostic information.
Similarly, ec->thread_ptr can be NULL, so guard the scheduler check.
* To show environments stack when the current namespace is unexpected or
namespace detection is broken
* It is displayed only when RUBY_BUGREPORT_NAMESPACE_ENV=1 is specified
to fix inconsistent and wrong current namespace detections.
This includes:
* Moving load_path and related things from rb_vm_t to rb_namespace_t to simplify
accessing those values via namespace (instead of accessing either vm or ns)
* Initializing root_namespace earlier and consolidate builtin_namespace into root_namespace
* Adding VM_FRAME_FLAG_NS_REQUIRE for checkpoints to detect a namespace to load/require files
* Removing implicit refinements in the root namespace which was used to determine
the namespace to be loaded (replaced by VM_FRAME_FLAG_NS_REQUIRE)
* Removing namespaces from rb_proc_t because its namespace can be identified by lexical context
* Starting to use ep[VM_ENV_DATA_INDEX_SPECVAL] to store the current namespace when
the frame type is MAGIC_TOP or MAGIC_CLASS (block handlers don't exist in this case)
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 06b7a70837) +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 06b7a70837) +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.
rb_fiber_scheduler_current() may return nil depending on whether the
scheduler is being prevented for some reason, e.g., Fiber.blocking{}.
Co-Authored-By: Samuel Williams <samuel.williams@oriontransfer.co.nz>
* [win32] fix compilation for windows-arm64
Credits to MSYS2 Ruby package using this patch.
* [win32] nm use full options
Fix compilation error when using MSYS2 environment.
Credits to MSYS2 Ruby package using this patch.
* [win32] detect llvm-windres (used for windows-arm64)
When adding preprocessor option for llvm-windres (using clang as
parameter), it fails. Thus, do not add this.
It's needed to be able to compile windows-arm64 version, because MSYS2
toolchain is LLVM based (instead of GCC/binutils).
* [win32] pioinfo detection for windows-arm64
This fixes "unexpected ucrtbase.dll" for native windows-arm64 ruby
binary. It does not solve issue with x64 version emulated on this
platform.
Value of pioinfo pointer can be found in ucrtbase.dll at latest adrp/add
sequence before return of _isatty function. This works for both release
and debug ucrt.
Due to the nature of aarch64 ISA (vs x86 or x64), it's needed to
disassemble instructions to retrieve offset value, which is a bit more
complicated than matching specific string patterns.
Details about adrp/add usage can be found in this blog post:
https://devblogs.microsoft.com/oldnewthing/20220809-00/?p=106955
For instruction decoding, the Arm documentation was used as a reference.
Remove rb_control_frame_t::__bp__ and optimize bmethod calls
This commit removes the __bp__ field from rb_control_frame_t. It was
introduced to help MJIT, but since MJIT was replaced by RJIT, we can use
vm_base_ptr() to compute it from the SP of the previous control frame
instead. Removing the field avoids needing to set it up when pushing new
frames.
Simply removing __bp__ would cause crashes since RJIT and YJIT used a
slightly different stack layout for bmethod calls than the interpreter.
At the moment of the call, the two layouts looked as follows:
┌────────────┐ ┌────────────┐
│ frame_base │ │ frame_base │
├────────────┤ ├────────────┤
│ ... │ │ ... │
├────────────┤ ├────────────┤
│ args │ │ args │
├────────────┤ └────────────┘<─prev_frame_sp
│ receiver │
prev_frame_sp─>└────────────┘
RJIT & YJIT interpreter
Essentially, vm_base_ptr() needs to compute the address to frame_base
given prev_frame_sp in the diagrams. The presence of the receiver
created an off-by-one situation.
Make the interpreter use the layout the JITs use for iseq-to-iseq
bmethod calls. Doing so removes unnecessary argument shifting and
vm_exec_core() re-entry from the interpreter, yielding a speed
improvement visible through `benchmark/vm_defined_method.yml`:
patched: 7578743.1 i/s
master: 4796596.3 i/s - 1.58x slower
C-to-iseq bmethod calls now store one more VALUE than before, but that
should have negligible impact on overall performance.
Note that re-entering vm_exec_core() used to be necessary for firing
TracePoint events, but that's no longer the case since
9121e57a5f50bc91bae48b3b91edb283bf96cb6b.
Closesruby/ruby#6428
This is useful for crash triaging. It also helps to hint extension
developers about the misuse of `rb_thread_call_without_gvl()`.
Example:
$ ./miniruby -e 'Ractor.new{Ractor.receive};
Thread.new{sleep}; Process.kill:SEGV,Process.pid'
<snip>
-- Threading information ---------------------------------------------------
Total ractor count: 2
Ruby thread count for this ractor: 2
This patch pushes dummy frames when loading code for the
profiling purpose.
The following methods push a dummy frame:
* `Kernel#require`
* `Kernel#load`
* `RubyVM::InstructionSequence.compile_file`
* `RubyVM::InstructionSequence.load_from_binary`
https://bugs.ruby-lang.org/issues/18559
In the event that we are crashing due to a corrupt Ruby stack, we might
re-enter rb_vm_bugreport() due to failed assertions in
rb_backtrace_print_as_bugreport() or SDR(). In these cases we were
printing the bug report ad infinitum with unbounded recusion.
I seem to run into this every once in a while and the amount of log it
prints out is pretty distracting. On CI environments it makes the log
output unnecessarily big. Let's fix this.
`NON_SCALAR_THREAD_ID` shows `pthread_t` is non-scalar (non-pointer)
and only s390x is known platform. However, the supporting code is
very complex and it is only used for deubg print information.
So this patch removes the support of `NON_SCALAR_THREAD_ID`
and make the code simple.