@@ -184,16 +184,95 @@ static void ruby_runtime_stack_callback(
184184 // Only using emit_frame - ignore emit_stacktrace_string parameter
185185 (void )emit_stacktrace_string ;
186186
187- // Use the simplest possible implementation that we know works
188- // This avoids any risky Ruby VM API calls that might cause hangs
189- ddog_crasht_RuntimeStackFrame frame = {
190- .function_name = "ruby_runtime_callback" ,
187+ // Try to safely walk Ruby stack using direct VM structure access
188+ // Avoid Ruby API calls that might hang in crash context
189+
190+ // First emit a marker frame to show callback is working
191+ ddog_crasht_RuntimeStackFrame marker_frame = {
192+ .function_name = "ruby_stack_walker_start" ,
191193 .file_name = "crashtracker.c" ,
192- .line_number = 42 ,
194+ .line_number = 1 ,
193195 .column_number = 0
194196 };
197+ emit_frame (& marker_frame );
198+
199+ // Try to get current thread and execution context safely
200+ VALUE current_thread = rb_thread_current ();
201+ if (current_thread == Qnil ) return ;
195202
196- emit_frame (& frame );
203+ // Get thread struct carefully
204+ static const rb_data_type_t * thread_data_type = NULL ;
205+ if (thread_data_type == NULL ) {
206+ thread_data_type = RTYPEDDATA_TYPE (current_thread );
207+ if (!thread_data_type ) return ;
208+ }
209+
210+ rb_thread_t * th = (rb_thread_t * ) rb_check_typeddata (current_thread , thread_data_type );
211+ if (!th ) return ;
212+
213+ const rb_execution_context_t * ec = th -> ec ;
214+ if (!ec ) return ;
215+
216+ // Safety checks
217+ if (th -> status == THREAD_KILLED ) return ;
218+ if (!ec -> vm_stack || ec -> vm_stack_size == 0 ) return ;
219+
220+ const rb_control_frame_t * cfp = ec -> cfp ;
221+ const rb_control_frame_t * end_cfp = RUBY_VM_END_CONTROL_FRAME (ec );
222+
223+ if (!cfp || !end_cfp ) return ;
224+
225+ // Skip dummy frames
226+ end_cfp = RUBY_VM_NEXT_CONTROL_FRAME (end_cfp );
227+ if (end_cfp <= cfp ) return ;
228+
229+ // Walk stack frames with extreme caution
230+ int frame_count = 0 ;
231+ const int MAX_FRAMES = 10 ; // Keep very low to minimize crash risk
232+
233+ for (; cfp != RUBY_VM_NEXT_CONTROL_FRAME (end_cfp ) && frame_count < MAX_FRAMES ;
234+ cfp = RUBY_VM_NEXT_CONTROL_FRAME (cfp )) {
235+
236+ if (VM_FRAME_RUBYFRAME_P (cfp ) && cfp -> iseq ) {
237+ // Instead of calling rb_iseq_* functions, work directly with iseq struct
238+ const rb_iseq_t * iseq = cfp -> iseq ;
239+
240+ // Basic frame info without risky API calls
241+ char frame_desc [64 ];
242+ snprintf (frame_desc , sizeof (frame_desc ), "ruby_frame_%d" , frame_count );
243+
244+ // Try to get basic line info safely using direct struct access
245+ int line_no = 0 ;
246+ if (iseq && cfp -> pc ) {
247+ // Use direct access to iseq body instead of helper functions
248+ if (iseq -> body && iseq -> body -> iseq_encoded && iseq -> body -> iseq_size > 0 ) {
249+ ptrdiff_t pc_offset = cfp -> pc - iseq -> body -> iseq_encoded ;
250+ if (pc_offset >= 0 && pc_offset < iseq -> body -> iseq_size ) {
251+ line_no = frame_count + 1 ; // Use frame position as approximation
252+ }
253+ }
254+ }
255+
256+ ddog_crasht_RuntimeStackFrame frame = {
257+ .function_name = frame_desc ,
258+ .file_name = "ruby_source.rb" ,
259+ .line_number = line_no ,
260+ .column_number = 0
261+ };
262+
263+ emit_frame (& frame );
264+ frame_count ++ ;
265+ }
266+ }
267+
268+ // Emit end marker
269+ ddog_crasht_RuntimeStackFrame end_frame = {
270+ .function_name = "ruby_stack_walker_end" ,
271+ .file_name = "crashtracker.c" ,
272+ .line_number = frame_count ,
273+ .column_number = 0
274+ };
275+ emit_frame (& end_frame );
197276}
198277
199278static VALUE _native_register_runtime_stack_callback (DDTRACE_UNUSED VALUE _self , VALUE callback_type ) {
0 commit comments