static void backtrace_each(rb_thread_t *th, void (*init)(void *arg, size_t size), void (*iter_iseq)(void *arg, const rb_control_frame_t *cfp), void (*iter_cfunc)(void *arg, const rb_control_frame_t *cfp, ID mid), void *arg) { rb_control_frame_t *last_cfp = th->cfp; rb_control_frame_t *start_cfp = RUBY_VM_END_CONTROL_FRAME(th); rb_control_frame_t *cfp; ptrdiff_t size, i; /* <- start_cfp (end control frame) * top frame (dummy) * top frame (dummy) * top frame <- start_cfp * top frame * ... * 2nd frame <- lev:0 * current frame <- th->cfp */ start_cfp = RUBY_VM_NEXT_CONTROL_FRAME( RUBY_VM_NEXT_CONTROL_FRAME(start_cfp)); /* skip top frames */ if (start_cfp < last_cfp) { size = 0; } else { size = start_cfp - last_cfp + 1; } init(arg, size); /* SDR(); */ for (i=0, cfp = start_cfp; i<size; i++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) { /* fprintf(stderr, "cfp: %d\n", (rb_control_frame_t *)(th->stack + th->stack_size) - cfp); */ if (cfp->iseq) { if (cfp->pc) { iter_iseq(arg, cfp); } } else if (RUBYVM_CFUNC_FRAME_P(cfp)) { ID mid = cfp->me->def ? cfp->me->def->original_id : cfp->me->called_id; iter_cfunc(arg, cfp, mid); } } }
static int vm_backtrace_each(rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_iter_func *iter, void *arg) { const rb_control_frame_t *limit_cfp = th->cfp; const rb_control_frame_t *cfp = (void *)(th->stack + th->stack_size); VALUE file = Qnil; int line_no = 0; cfp -= 2; while (lev-- >= 0) { if (++limit_cfp > cfp) { return FALSE; } } if (init) (*init)(arg); limit_cfp = RUBY_VM_NEXT_CONTROL_FRAME(limit_cfp); if (th->vm->progname) file = th->vm->progname; while (cfp > limit_cfp) { if (cfp->iseq != 0) { if (cfp->pc != 0) { rb_iseq_t *iseq = cfp->iseq; line_no = rb_vm_get_sourceline(cfp); file = iseq->filename; if ((*iter)(arg, file, line_no, iseq->name)) break; } } else if (RUBYVM_CFUNC_FRAME_P(cfp)) { ID id; extern VALUE ruby_engine_name; if (NIL_P(file)) file = ruby_engine_name; if (cfp->me->def) id = cfp->me->def->original_id; else id = cfp->me->called_id; if (id != ID_ALLOCATOR && (*iter)(arg, file, line_no, rb_id2str(id))) break; } cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp); } return TRUE; }
static void vm_stack_dump_each(rb_thread_t *th, rb_control_frame_t *cfp) { int i; VALUE rstr; VALUE *sp = cfp->sp; VALUE *lfp = cfp->lfp; VALUE *dfp = cfp->dfp; int argc = 0, local_size = 0; const char *name; rb_iseq_t *iseq = cfp->iseq; if (iseq == 0) { if (RUBYVM_CFUNC_FRAME_P(cfp)) { name = rb_id2name(cfp->me->original_id); } else { name = "?"; } } else if (RUBY_VM_IFUNC_P(iseq)) { name = "<ifunc>"; } else { argc = iseq->argc; local_size = iseq->local_size; name = RSTRING_PTR(iseq->name); } /* stack trace header */ if (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_METHOD || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_TOP || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_BLOCK || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_CLASS || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_PROC || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_LAMBDA || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_CFUNC || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_IFUNC || VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_EVAL) { VALUE *ptr = dfp - local_size; vm_stack_dump_each(th, cfp + 1); control_frame_dump(th, cfp); if (lfp != dfp) { local_size++; } for (i = 0; i < argc; i++) { rstr = rb_inspect(*ptr); fprintf(stderr, " arg %2d: %8s (%p)\n", i, StringValueCStr(rstr), (void *)ptr++); } for (; i < local_size - 1; i++) { rstr = rb_inspect(*ptr); fprintf(stderr, " local %2d: %8s (%p)\n", i, StringValueCStr(rstr), (void *)ptr++); } ptr = cfp->bp; for (; ptr < sp; ptr++, i++) { if (*ptr == Qundef) { rstr = rb_str_new2("undef"); } else { rstr = rb_inspect(*ptr); } fprintf(stderr, " stack %2d: %8s (%"PRIdPTRDIFF")\n", i, StringValueCStr(rstr), (ptr - th->stack)); } } else if (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_FINISH) { if ((th)->stack + (th)->stack_size > (VALUE *)(cfp + 2)) { vm_stack_dump_each(th, cfp + 1); } else { /* SDR(); */ } } else { rb_bug("unsupport frame type: %08lx", VM_FRAME_TYPE(cfp)); } }
static VALUE stacktrace(int argc, VALUE* argv, rb_thread_t *th) { VALUE ary = rb_ary_new(); int start = 0; int finish = -1; int stack_size = 0; const rb_control_frame_t *cfp = th->cfp; const rb_control_frame_t *tcfp; const rb_control_frame_t *limit_cfp = (void *)(th->stack + th->stack_size); VALUE file = Qnil; int line = 0; rb_iseq_t *iseq = 0; ID id; VALUE frame; extern VALUE ruby_engine_name; int flags = ST_F_ALL; if (argc > 0) { start = NUM2INT(argv[0]); } if (argc > 1) { finish = NUM2INT(argv[1]); } if (argc > 2) { flags = NUM2INT(argv[2]); } cfp += 1; limit_cfp -= 2; if (finish > 0) { finish--; } if (start < 0 || finish < 0) { tcfp = cfp; while (tcfp < limit_cfp) { if (tcfp->iseq != 0 && cfp->pc != 0) { stack_size++; } else if (RUBYVM_CFUNC_FRAME_P(tcfp)) { stack_size++; } tcfp++; } if (start < 0) { start = stack_size + start; } if (finish < 0) { finish = stack_size + finish; } } // rb_warn("flags: %i", flags & ST_F_KLASS); // rb_warn("test %i %i cfp: %i lcfp %i ss %i", start, finish, cfp, limit_cfp, stack_size); while (cfp < limit_cfp) { VALUE hash = 0; if (cfp->iseq != 0 && cfp->pc != 0) { if (start-- > 0) {cfp++; continue;} if (finish-- < 0) break; iseq = cfp->iseq; frame = rb_class_new_instance(0, 0, c_StackFrame); if (iseq->defined_method_id && ((flags & ST_F_KLASS) == ST_F_KLASS)) { rb_iv_set(frame, "@klass", iseq->klass); } if ((flags & ST_F_METHOD) == ST_F_METHOD) { rb_iv_set(frame, "@method", iseq->name); } if ((flags & ST_F_FILENAME) == ST_F_FILENAME) { rb_iv_set(frame, "@filename", iseq->filename); } if ((flags & ST_F_LINENUMBER) == ST_F_LINENUMBER) { line = rb_vm_get_sourceline(cfp); rb_iv_set(frame, "@line_number", INT2FIX(line)); } rb_ary_push(ary, frame); } else if (RUBYVM_CFUNC_FRAME_P(cfp)) { if (start-- > 0) {cfp++; continue;} if (finish-- < 0) break; if (NIL_P(file)) file = ruby_engine_name; if (cfp->me->def) id = cfp->me->def->original_id; else id = cfp->me->called_id; if (id != ID_ALLOCATOR) { frame = rb_class_new_instance(0, 0, c_StackFrame); if ((flags & ST_F_KLASS) == ST_F_KLASS) { rb_iv_set(frame, "@klass", cfp->me->klass); } if ((flags & ST_F_METHOD) == ST_F_METHOD) { rb_iv_set(frame, "@method", rb_id2str(id)); } rb_ary_push(ary,frame); } } cfp += 1; } return ary; }
VALUE rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( rb_control_frame_t** c_current_frame ) { const char* c_method_name = NULL; int c_sourcefile_line = 0; // create new hash for this frame VALUE rb_frame_hash = rb_hash_new(); VALUE rb_sourcefile_name = Qnil; VALUE rb_sourcefile_line = Qnil; VALUE rb_method_name = Qnil; VALUE rb_object_for_frame = Qnil; if ( ( *c_current_frame )->iseq != 0 ) { if ( ( *c_current_frame )->pc != 0 ) { rb_iseq_t *iseq = ( *c_current_frame )->iseq; // get sourcefile name and set in hash rb_sourcefile_name = iseq->filename; // get sourcefile line and set in hash c_sourcefile_line = rb_vm_get_sourceline( *c_current_frame ); rb_sourcefile_line = INT2FIX( c_sourcefile_line ); // get name of instruction sequence rb_method_name = ID2SYM( rb_intern( StringValuePtr( iseq->name ) ) ); rb_object_for_frame = ( *c_current_frame )->self; } } else if ( RUBYVM_CFUNC_FRAME_P( *c_current_frame ) ) { // get name of method #if RUBY_PATCHLEVEL >= -1 // For 1.9.2: const rb_method_entry_t* c_method_for_frame = ( *c_current_frame )->me; c_method_name = rb_id2name( c_method_for_frame->called_id ); #else // For 1.9.1: c_method_name = rb_id2name( ( *c_current_frame )->method_id ); #endif rb_method_name = ( c_method_name == NULL ? Qnil : ID2SYM( rb_intern( c_method_name ) ) ); rb_object_for_frame = ( *c_current_frame )->self; } // we have to test this case - it works for blocks but there may be other cases too else if ( ( *c_current_frame )->block_iseq != 0 && ( *c_current_frame )->pc == 0) { // If we got here we have a fiber // There doesn't seem to be much that we can tell about a fiber's context VALUE rb_current_fiber = rb_fiber_current(); rb_fiber_t* c_current_fiber = NULL; GetFiberPtr( rb_current_fiber, c_current_fiber); rb_context_t* c_context = & c_current_fiber->cont; // rb_block_t* c_blockptr = RUBY_VM_GET_BLOCK_PTR_IN_CFP( *c_current_frame ); rb_object_for_frame = ( *c_current_frame )->self; // get sourcefile name and set in hash rb_sourcefile_name = Qnil; // get sourcefile line and set in hash rb_sourcefile_line = Qnil; // get name of instruction sequence rb_method_name = rb_str_new2( "<Fiber>" ); // if we have a fiber we also include its ruby reference since we have so little other context rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "fiber" ) ), c_context->self ); // The one time that we know a fiber is in use in the Ruby base is with Enumerators // For now we will handle that with a special case VALUE rb_enumerator_class = rb_const_get( rb_cObject, rb_intern( "Enumerator" ) ); VALUE rb_object_for_frame_klass = ( ( TYPE( rb_object_for_frame ) == T_CLASS ) ? rb_object_for_frame : rb_funcall( rb_object_for_frame, rb_intern( "class" ), 0 ) ); VALUE rb_ancestors = rb_funcall( rb_object_for_frame_klass, rb_intern( "ancestors" ), 0 ); if ( rb_ary_includes( rb_ancestors, rb_enumerator_class ) ) { struct enumerator* c_enumerator = enumerator_ptr( rb_object_for_frame ); rb_object_for_frame = c_enumerator->obj; rb_method_name = ID2SYM( c_enumerator->meth ); } } else if ( ( *c_current_frame )->block_iseq == 0 && ( *c_current_frame )->pc == 0) { // this happens after we had a fiber and we try to go up - which doesn't make sense with a fiber // not sure what we want to do here, if anything return Qnil; } else { // The third possibility is that we have an iseq frame with nil params for what we want // In that case we can simply return the next frame *c_current_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( *c_current_frame ); // in theory this could crash because we are going forward a frame when we don't know what's there // in practice I think we are ok, since we are only jumping forward from nil frames which should never be at the end // at least - I don't think they should... we shall see. // // a fix would be to check the next frame, but that requires access to the thread or the limit cfp, // which requires passing more context; so for now, I'm leaving it there return rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( c_current_frame ); } // Push values to return hash rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "object" ) ), rb_object_for_frame ); rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "file" ) ), rb_sourcefile_name ); rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "line" ) ), rb_sourcefile_line ); rb_hash_aset( rb_frame_hash, ID2SYM( rb_intern( "method" ) ), rb_method_name ); return rb_frame_hash; }