diff --git a/base/error.jl b/base/error.jl index 0dc356723ab5e..d77fb650732ae 100644 --- a/base/error.jl +++ b/base/error.jl @@ -62,25 +62,39 @@ rethrow(e) = ccall(:jl_rethrow_other, Bottom, (Any,), e) struct InterpreterIP code::Union{CodeInfo,Core.MethodInstance,Nothing} stmt::Csize_t + mod::Union{Module,Nothing} end -# convert dual arrays (ips, interpreter_frames) to a single array of locations +# convert dual arrays (raw bt buffer, array of GC managed values) to a single +# array of locations function _reformat_bt(bt, bt2) ret = Vector{Union{InterpreterIP,Ptr{Cvoid}}}() i, j = 1, 1 while i <= length(bt) ip = bt[i]::Ptr{Cvoid} - if UInt(ip) == (-1 % UInt) - # The next one is really a CodeInfo - push!(ret, InterpreterIP( - bt2[j], - bt[i+2])) - j += 1 - i += 3 - else - push!(ret, Ptr{Cvoid}(ip)) + if UInt(ip) != (-1 % UInt) # See also jl_bt_is_native + # native frame + push!(ret, ip) i += 1 + continue + end + # Extended backtrace entry + entry_metadata = reinterpret(UInt, bt[i+1]) + njlvalues = entry_metadata & 0x7 + nuintvals = (entry_metadata >> 3) & 0x7 + tag = (entry_metadata >> 6) & 0xf + header = entry_metadata >> 10 + if tag == 1 # JL_BT_INTERP_FRAME_TAG + code = bt2[j] + mod = njlvalues == 2 ? bt2[j+1] : nothing + push!(ret, InterpreterIP(code, header, mod)) + else + # Tags we don't know about are an error + throw(ArgumentError("Unexpected extended backtrace entry tag $tag at bt[$i]")) end + # See jl_bt_entry_size + j += njlvalues + i += Int(2 + njlvalues + nuintvals) end ret end diff --git a/base/errorshow.jl b/base/errorshow.jl index 3b6495a2a3c9f..ce14fa16071c5 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -705,7 +705,7 @@ end function show(io::IO, ip::InterpreterIP) print(io, typeof(ip)) if ip.code isa Core.CodeInfo - print(io, " in top-level CodeInfo at statement $(Int(ip.stmt))") + print(io, " in top-level CodeInfo for $(ip.mod) at statement $(Int(ip.stmt))") else print(io, " in $(ip.code) at statement $(Int(ip.stmt))") end diff --git a/base/stacktraces.jl b/base/stacktraces.jl index e04512905bf13..e63449d1ec23a 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -7,6 +7,7 @@ module StackTraces import Base: hash, ==, show +import Core: CodeInfo, MethodInstance export StackTrace, StackFrame, stacktrace @@ -52,7 +53,7 @@ struct StackFrame # this type should be kept platform-agnostic so that profiles "the line number in the file containing the execution context" line::Int "the MethodInstance or CodeInfo containing the execution context (if it could be found)" - linfo::Union{Core.MethodInstance, Core.CodeInfo, Nothing} + linfo::Union{MethodInstance, CodeInfo, Nothing} "true if the code is from C" from_c::Bool "true if the code is from an inlined frame" @@ -118,7 +119,7 @@ end const top_level_scope_sym = Symbol("top-level scope") function lookup(ip::Base.InterpreterIP) - if ip.code isa Core.MethodInstance && ip.code.def isa Method + if ip.code isa MethodInstance && ip.code.def isa Method codeinfo = ip.code.uninferred func = ip.code.def.name file = ip.code.def.file @@ -127,7 +128,7 @@ function lookup(ip::Base.InterpreterIP) # interpreted top-level expression with no CodeInfo return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)] else - @assert ip.code isa Core.CodeInfo + @assert ip.code isa CodeInfo codeinfo = ip.code func = top_level_scope_sym file = empty_sym @@ -206,7 +207,7 @@ function remove_frames!(stack::StackTrace, m::Module) return stack end -is_top_level_frame(f::StackFrame) = f.linfo isa Core.CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym) +is_top_level_frame(f::StackFrame) = f.linfo isa CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym) function show_spec_linfo(io::IO, frame::StackFrame) if frame.linfo === nothing @@ -220,7 +221,7 @@ function show_spec_linfo(io::IO, frame::StackFrame) :nothing printstyled(io, Base.demangle_function_name(string(frame.func)), color=color) end - elseif frame.linfo isa Core.MethodInstance + elseif frame.linfo isa MethodInstance def = frame.linfo.def if isa(def, Method) sig = frame.linfo.specTypes @@ -243,7 +244,7 @@ function show_spec_linfo(io::IO, frame::StackFrame) else Base.show(io, frame.linfo) end - elseif frame.linfo isa Core.CodeInfo + elseif frame.linfo isa CodeInfo print(io, "top-level scope") end end @@ -276,7 +277,7 @@ function from(frame::StackFrame, m::Module) finfo = frame.linfo result = false - if finfo isa Core.MethodInstance + if finfo isa MethodInstance frame_m = finfo.def isa(frame_m, Method) && (frame_m = frame_m.module) result = nameof(frame_m) === nameof(m) diff --git a/src/gc.c b/src/gc.c index 831dc14545348..5c5e3bd8d8de8 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2059,34 +2059,41 @@ excstack: { gc_mark_excstack_t *stackitr = gc_pop_markdata(&sp, gc_mark_excstack_t); jl_excstack_t *excstack = stackitr->s; size_t itr = stackitr->itr; - size_t i = stackitr->i; + size_t bt_index = stackitr->bt_index; + size_t jlval_index = stackitr->jlval_index; while (itr > 0) { size_t bt_size = jl_excstack_bt_size(excstack, itr); - uintptr_t *bt_data = jl_excstack_bt_data(excstack, itr); - while (i+2 < bt_size) { - if (bt_data[i] != JL_BT_INTERP_FRAME) { - i++; + jl_bt_element_t *bt_data = jl_excstack_bt_data(excstack, itr); + for (; bt_index < bt_size; bt_index += jl_bt_entry_size(bt_data + bt_index)) { + jl_bt_element_t *bt_entry = bt_data + bt_index; + if (jl_bt_is_native(bt_entry)) continue; - } - // found an interpreter frame to mark - new_obj = (jl_value_t*)bt_data[i+1]; - uintptr_t nptr = 0; - i += 3; - if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->i = i; - stackitr->itr = itr; - gc_repush_markdata(&sp, gc_mark_excstack_t); - goto mark; + // Found an extended backtrace entry: iterate over any + // GC-managed values inside. + size_t njlvals = jl_bt_num_jlvals(bt_entry); + while (jlval_index < njlvals) { + new_obj = jl_bt_entry_jlvalue(bt_entry, jlval_index); + uintptr_t nptr = 0; + jlval_index += 1; + if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { + stackitr->itr = itr; + stackitr->bt_index = bt_index; + stackitr->jlval_index = jlval_index; + gc_repush_markdata(&sp, gc_mark_excstack_t); + goto mark; + } } } - // mark the exception + // The exception comes last - mark it new_obj = jl_excstack_exception(excstack, itr); itr = jl_excstack_next(excstack, itr); - i = 0; + bt_index = 0; + jlval_index = 0; uintptr_t nptr = 0; if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->i = i; stackitr->itr = itr; + stackitr->bt_index = bt_index; + stackitr->jlval_index = jlval_index; gc_repush_markdata(&sp, gc_mark_excstack_t); goto mark; } @@ -2359,7 +2366,7 @@ mark: { if (ta->excstack) { gc_setmark_buf_(ptls, ta->excstack, bits, sizeof(jl_excstack_t) + sizeof(uintptr_t)*ta->excstack->reserved_size); - gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0}; + gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0, 0}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack), &stackdata, sizeof(stackdata), 1); } @@ -2654,13 +2661,15 @@ static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) { - size_t n = 0; - while (n+2 < ptls2->bt_size) { - if (ptls2->bt_data[n] == JL_BT_INTERP_FRAME) { - gc_mark_queue_obj(gc_cache, sp, (jl_value_t*)ptls2->bt_data[n+1]); - n += 2; - } - n++; + jl_bt_element_t *bt_data = ptls2->bt_data; + size_t bt_size = ptls2->bt_size; + for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { + jl_bt_element_t *bt_entry = bt_data + i; + if (jl_bt_is_native(bt_entry)) + continue; + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t j = 0; j < njlvals; j++) + gc_mark_queue_obj(gc_cache, sp, jl_bt_entry_jlvalue(bt_entry, j)); } } diff --git a/src/gc.h b/src/gc.h index a9c360bff3820..1dea38863a2fe 100644 --- a/src/gc.h +++ b/src/gc.h @@ -153,9 +153,10 @@ typedef struct { // Exception stack data typedef struct { - jl_excstack_t *s; // Stack of exceptions - size_t itr; // Iterator into exception stack - size_t i; // Iterator into backtrace data for exception + jl_excstack_t *s; // Stack of exceptions + size_t itr; // Iterator into exception stack + size_t bt_index; // Current backtrace buffer entry index + size_t jlval_index; // Index into GC managed values for current bt entry } gc_mark_excstack_t; // Module bindings. This is also the beginning of module scanning. diff --git a/src/interpreter-stacktrace.c b/src/interpreter-stacktrace.c index 18cd5f4243fc1..dacccb5e12e77 100644 --- a/src/interpreter-stacktrace.c +++ b/src/interpreter-stacktrace.c @@ -390,20 +390,29 @@ JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip) return enter_interpreter_frame_start <= ip && ip <= enter_interpreter_frame_end; } -JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining) +JL_DLLEXPORT size_t jl_capture_interp_frame(jl_bt_element_t *bt_entry, uintptr_t sp, + uintptr_t fp, size_t space_remaining) { #ifdef FP_CAPTURE_OFFSET interpreter_state *s = (interpreter_state *)(fp-FP_CAPTURE_OFFSET); #else interpreter_state *s = (interpreter_state *)(sp+TOTAL_STACK_PADDING); #endif - int required_space = 3; + int need_module = !s->mi; + int required_space = need_module ? 4 : 3; if (space_remaining < required_space) - return 0; - // Sentinel value to indicate an interpreter frame - data[0] = JL_BT_INTERP_FRAME; - data[1] = s->mi ? (uintptr_t)s->mi : s->src ? (uintptr_t)s->src : (uintptr_t)jl_nothing; - data[2] = (uintptr_t)s->ip; + return 0; // Should not happen + size_t njlvalues = need_module ? 2 : 1; + uintptr_t entry_tags = jl_bt_entry_descriptor(njlvalues, 0, JL_BT_INTERP_FRAME_TAG, s->ip); + bt_entry[0].uintptr = JL_BT_NON_PTR_ENTRY; + bt_entry[1].uintptr = entry_tags; + bt_entry[2].jlvalue = s->mi ? (jl_value_t*)s->mi : + s->src ? (jl_value_t*)s->src : (jl_value_t*)jl_nothing; + if (need_module) { + // If we only have a CodeInfo (s->src), we are in a top level thunk and + // need to record the module separately. + bt_entry[3].jlvalue = (jl_value_t*)s->module; + } return required_space; } @@ -419,7 +428,8 @@ JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip) return 0; } -JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining) +JL_DLLEXPORT size_t jl_capture_interp_frame(jl_bt_element_t *bt_entry, uintptr_t sp, + uintptr_t fp, size_t space_remaining) { // Leave bt_entry[0] as the native instruction ptr return 0; diff --git a/src/julia.h b/src/julia.h index 3ab36075d1299..64c2d5456a668 100644 --- a/src/julia.h +++ b/src/julia.h @@ -236,6 +236,13 @@ typedef struct _jl_llvm_functions_t { typedef struct _jl_method_instance_t jl_method_instance_t; +typedef struct _jl_line_info_node_t { + jl_value_t *method; + jl_sym_t *file; + intptr_t line; + intptr_t inlined_at; +} jl_line_info_node_t; + // This type describes a single function body typedef struct _jl_code_info_t { // ssavalue-indexed arrays of properties: diff --git a/src/julia_internal.h b/src/julia_internal.h index de2b060419d98..58f9b093f49c7 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -590,7 +590,109 @@ jl_tupletype_t *arg_type_tuple(jl_value_t *arg1, jl_value_t **args, size_t nargs int jl_has_meta(jl_array_t *body, jl_sym_t *sym); -// backtraces +//-------------------------------------------------- +// Backtraces + +// Backtrace buffers: +// +// A backtrace buffer conceptually contains a stack of instruction pointers +// ordered from the inner-most frame to the outermost. We store them in a +// special raw format for two reasons: +// +// * Efficiency: Every `throw()` must populate the trace so it must be as +// efficient as possible. +// * Signal safety: For signal-based exceptions such as StackOverflowError +// the trace buffer needs to be filled from a signal handler where most +// operations are not allowed (including malloc) so we choose a flat +// preallocated buffer. +// +// The raw buffer layout contains "frame entries" composed of one or several +// values of type `jl_bt_element_t`. From the point of view of the GC, an entry +// is either: +// +// 1. A single instruction pointer to native code, not GC-managed. +// 2. An "extended entry": a mixture of raw data and pointers to julia objects +// which must be treated as GC roots. +// +// A single extended entry is seralized using multiple elements from the raw +// buffer; if `e` is the pointer to the first slot we have: +// +// e[0] JL_BT_NON_PTR_ENTRY - Special marker to distinguish extended entries +// e[1] descriptor - A bit packed uintptr_t containing a tag and +// the number of GC- managed and non-managed values +// e[2+j] - GC managed data +// e[2+ngc+i] - Non-GC-managed data +// +// The format of `descriptor` is, from LSB to MSB: +// 0:2 ngc Number of GC-managed pointers for this frame entry +// 3:5 nptr Number of non-GC-managed buffer elements +// 6:9 tag Entry type +// 10:... header Entry-specific header data +typedef struct _jl_bt_element_t { + union { + uintptr_t uintptr; // Metadata or native instruction ptr + jl_value_t* jlvalue; // Pointer to GC-managed value + }; +} jl_bt_element_t; + +#define JL_BT_NON_PTR_ENTRY (((uintptr_t)0)-1) +// Maximum size for an extended backtrace entry (likely significantly larger +// than the actual size of 3-4 for an interpreter frame) +#define JL_BT_MAX_ENTRY_SIZE 16 + +STATIC_INLINE int jl_bt_is_native(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + return bt_entry[0].uintptr != JL_BT_NON_PTR_ENTRY; +} + +// Extended backtrace entry header packing; the bit packing is done manually +// for precise layout control for interop with julia side. +STATIC_INLINE uintptr_t jl_bt_entry_descriptor(int ngc, int nptr, + int tag, uintptr_t header) JL_NOTSAFEPOINT +{ + assert(((ngc & 0x7) == ngc) && ((nptr & 0x7) == nptr) && ((tag & 0xf) == tag)); + return (ngc & 0x7) | (nptr & 0x7) << 3 | (tag & 0xf) << 6 | header << 10; +} + +// Unpacking of extended backtrace entry data +STATIC_INLINE size_t jl_bt_num_jlvals(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + assert(!jl_bt_is_native(bt_entry)); + return bt_entry[1].uintptr & 0x7; +} +STATIC_INLINE size_t jl_bt_num_uintvals(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + assert(!jl_bt_is_native(bt_entry)); + return (bt_entry[1].uintptr >> 3) & 0x7; +} +STATIC_INLINE int jl_bt_entry_tag(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + assert(!jl_bt_is_native(bt_entry)); + return (bt_entry[1].uintptr >> 6) & 0xf; +} +STATIC_INLINE uintptr_t jl_bt_entry_header(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + assert(!jl_bt_is_native(bt_entry)); + return bt_entry[1].uintptr >> 10; +} + +// Return `i`th GC-managed pointer for extended backtrace entry +// The returned value is rooted for the lifetime of the parent exception stack. +STATIC_INLINE jl_value_t *jl_bt_entry_jlvalue(jl_bt_element_t *bt_entry, size_t i) JL_NOTSAFEPOINT +{ + return bt_entry[2 + i].jlvalue; +} + +#define JL_BT_INTERP_FRAME_TAG 1 // An interpreter frame + +// Number of bt elements in frame. +STATIC_INLINE size_t jl_bt_entry_size(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + return jl_bt_is_native(bt_entry) ? + 1 : 2 + jl_bt_num_jlvals(bt_entry) + jl_bt_num_uintvals(bt_entry); +} + +// Function metadata arising from debug info lookup of instruction pointer typedef struct { char *func_name; char *file_name; @@ -634,23 +736,21 @@ typedef unw_cursor_t bt_cursor_t; typedef int bt_context_t; typedef int bt_cursor_t; #endif -// Special marker in backtrace data for encoding interpreter frames -#define JL_BT_INTERP_FRAME (((uintptr_t)0)-1) -// Maximum number of elements of bt_data taken up by interpreter frame -#define JL_BT_MAX_ENTRY_SIZE 3 -size_t rec_backtrace(uintptr_t *bt_data, size_t maxsize, int skip) JL_NOTSAFEPOINT; +size_t rec_backtrace(jl_bt_element_t *bt_data, size_t maxsize, int skip) JL_NOTSAFEPOINT; // Record backtrace from a signal handler. `ctx` is the context of the code // which was asynchronously interrupted. -size_t rec_backtrace_ctx(uintptr_t *bt_data, size_t maxsize, bt_context_t *ctx, +size_t rec_backtrace_ctx(jl_bt_element_t *bt_data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; #ifdef LIBOSXUNWIND -size_t rec_backtrace_ctx_dwarf(uintptr_t *bt_data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; +size_t rec_backtrace_ctx_dwarf(jl_bt_element_t *bt_data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT jl_value_t *jl_get_backtrace(void); -void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size); +void jl_critical_error(int sig, bt_context_t *context, jl_bt_element_t *bt_data, size_t *bt_size); JL_DLLEXPORT void jl_raise_debugger(void); int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gdblookup(void* ip) JL_NOTSAFEPOINT; +void jl_print_native_codeloc(uintptr_t ip) JL_NOTSAFEPOINT; +void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_data) JL_NOTSAFEPOINT; // *to is NULL or malloc'd pointer, from is allowed to be NULL STATIC_INLINE char *jl_copy_str(char **to, const char *from) { @@ -666,7 +766,8 @@ STATIC_INLINE char *jl_copy_str(char **to, const char *from) } JL_DLLEXPORT int jl_is_interpreter_frame(uintptr_t ip) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip) JL_NOTSAFEPOINT; -JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining) JL_NOTSAFEPOINT; +JL_DLLEXPORT size_t jl_capture_interp_frame(jl_bt_element_t *bt_data, uintptr_t sp, + uintptr_t fp, size_t space_remaining) JL_NOTSAFEPOINT; // Exception stack: a stack of pairs of (exception,raw_backtrace). // The stack may be traversed and accessed with the functions below. @@ -676,25 +777,25 @@ typedef struct _jl_excstack_t { // Pack all stack entries into a growable buffer to amortize allocation // across repeated exception handling. // Layout: [bt_data1... bt_size1 exc1 bt_data2... bt_size2 exc2 ..] - // uintptr_t data[]; // Access with jl_excstack_raw + // jl_bt_element_t data[]; // Access with jl_excstack_raw } jl_excstack_t; -STATIC_INLINE uintptr_t *jl_excstack_raw(jl_excstack_t *stack) JL_NOTSAFEPOINT +STATIC_INLINE jl_bt_element_t *jl_excstack_raw(jl_excstack_t *stack) JL_NOTSAFEPOINT { - return (uintptr_t*)(stack + 1); + return (jl_bt_element_t*)(stack + 1); } // Exception stack access STATIC_INLINE jl_value_t *jl_excstack_exception(jl_excstack_t *stack JL_PROPAGATES_ROOT, size_t itr) JL_NOTSAFEPOINT { - return (jl_value_t*)(jl_excstack_raw(stack)[itr-1]); + return jl_excstack_raw(stack)[itr-1].jlvalue; } STATIC_INLINE size_t jl_excstack_bt_size(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT { - return jl_excstack_raw(stack)[itr-2]; + return jl_excstack_raw(stack)[itr-2].uintptr; } -STATIC_INLINE uintptr_t *jl_excstack_bt_data(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT +STATIC_INLINE jl_bt_element_t *jl_excstack_bt_data(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT { return jl_excstack_raw(stack) + itr-2 - jl_excstack_bt_size(stack, itr); } @@ -708,9 +809,10 @@ void jl_reserve_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, size_t reserved_size); void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, jl_value_t *exception JL_ROOTED_ARGUMENT, - uintptr_t *bt_data, size_t bt_size); + jl_bt_element_t *bt_data, size_t bt_size); void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFEPOINT; +//-------------------------------------------------- // timers // Returns time in nanosec JL_DLLEXPORT uint64_t jl_hrtime(void); diff --git a/src/julia_threads.h b/src/julia_threads.h index 7dea19af4f7c6..7d47bd1ec716c 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -156,6 +156,7 @@ typedef struct { } jl_gc_mark_cache_t; typedef struct _jl_excstack_t jl_excstack_t; +struct _jl_bt_element_t; // This includes all the thread local states we care about for a thread. // Changes to TLS field types must be reflected in codegen. #define JL_MAX_BT_SIZE 80000 @@ -193,7 +194,7 @@ struct _jl_tls_states_t { // Temp storage for exception thrown in signal handler. Not rooted. struct _jl_value_t *sig_exception; // Temporary backtrace buffer. Scanned for gc roots when bt_size > 0. - uintptr_t *bt_data; // JL_MAX_BT_SIZE + 1 elements long + struct _jl_bt_element_t *bt_data; // JL_MAX_BT_SIZE + 1 elements long size_t bt_size; // Size for backtrace in transit in bt_data // Atomically set by the sender, reset by the handler. volatile sig_atomic_t signal_request; diff --git a/src/rtutils.c b/src/rtutils.c index c8670b942e172..d00aac0a38789 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -297,7 +297,7 @@ JL_DLLEXPORT void jl_restore_excstack(size_t state) void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFEPOINT { assert(dest->reserved_size >= src->top); - memcpy(jl_excstack_raw(dest), jl_excstack_raw(src), sizeof(uintptr_t)*src->top); + memcpy(jl_excstack_raw(dest), jl_excstack_raw(src), sizeof(jl_bt_element_t)*src->top); dest->top = src->top; } @@ -317,15 +317,16 @@ void jl_reserve_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, } void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, - jl_value_t *exception JL_ROOTED_ARGUMENT, - uintptr_t *bt_data, size_t bt_size) + jl_value_t *exception JL_ROOTED_ARGUMENT, + jl_bt_element_t *bt_data, size_t bt_size) { jl_reserve_excstack(stack, (*stack ? (*stack)->top : 0) + bt_size + 2); jl_excstack_t *s = *stack; - memcpy(jl_excstack_raw(s) + s->top, bt_data, sizeof(uintptr_t)*bt_size); + jl_bt_element_t *rawstack = jl_excstack_raw(s); + memcpy(rawstack + s->top, bt_data, sizeof(jl_bt_element_t)*bt_size); s->top += bt_size + 2; - jl_excstack_raw(s)[s->top-2] = bt_size; - jl_excstack_raw(s)[s->top-1] = (uintptr_t)exception; + rawstack[s->top-2].uintptr = bt_size; + rawstack[s->top-1].jlvalue = exception; } // conversion ----------------------------------------------------------------- diff --git a/src/signal-handling.c b/src/signal-handling.c index 73ca73e22d708..43d133f6e394c 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -18,7 +18,7 @@ extern "C" { #include // Profiler control variables // -static volatile intptr_t *bt_data_prof = NULL; +static volatile jl_bt_element_t *bt_data_prof = NULL; static volatile size_t bt_size_max = 0; static volatile size_t bt_size_cur = 0; static volatile uint64_t nsecprof = 0; @@ -221,7 +221,7 @@ void jl_show_sigill(void *_ctx) } // what to do on a critical error -void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size) +void jl_critical_error(int sig, bt_context_t *context, jl_bt_element_t *bt_data, size_t *bt_size) { // This function is not allowed to reference any TLS variables. // We need to explicitly pass in the TLS buffer pointer when @@ -230,10 +230,14 @@ void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_ if (sig) jl_safe_printf("\nsignal (%d): %s\n", sig, strsignal(sig)); jl_safe_printf("in expression starting at %s:%d\n", jl_filename, jl_lineno); - if (context) + if (context) { + // Must avoid extended backtrace frames here unless we're sure bt_data + // is properly rooted. *bt_size = n = rec_backtrace_ctx(bt_data, JL_MAX_BT_SIZE, context, 0); - for (i = 0; i < n; i++) - jl_gdblookup(bt_data[i] - 1); + } + for (i = 0; i < n; i += jl_bt_entry_size(bt_data + i)) { + jl_print_bt_entry_codeloc(bt_data + i); + } gc_debug_print_status(); gc_debug_critical_error(); } @@ -247,7 +251,7 @@ JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec) nsecprof = delay_nsec; if (bt_data_prof != NULL) free((void*)bt_data_prof); - bt_data_prof = (intptr_t*) calloc(maxsize, sizeof(intptr_t)); + bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t)); if (bt_data_prof == NULL && maxsize > 0) return -1; bt_size_cur = 0; diff --git a/src/signals-mach.c b/src/signals-mach.c index 09caa0a874ec4..416c362dc76dd 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -448,10 +448,10 @@ void *mach_profile_listener(void *arg) if (forceDwarf == 0) { // Save the backtrace - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); + bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); } else if (forceDwarf == 1) { - bt_size_cur += rec_backtrace_ctx_dwarf((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); + bt_size_cur += rec_backtrace_ctx_dwarf((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); } else if (forceDwarf == -1) { jl_safe_printf("WARNING: profiler attempt to access an invalid memory location\n"); @@ -459,11 +459,11 @@ void *mach_profile_listener(void *arg) forceDwarf = -2; #else - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); + bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, 0); #endif // Mark the end of this block with 0 - bt_data_prof[bt_size_cur++] = 0; + bt_data_prof[bt_size_cur++].uintptr = 0; // Reset the alarm kern_return_t ret = clock_alarm(clk, TIME_RELATIVE, timerprof, profile_port); diff --git a/src/signals-unix.c b/src/signals-unix.c index fa234c337714c..5737d64248be9 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -551,7 +551,7 @@ static void kqueue_signal(int *sigqueue, struct kevent *ev, int sig) static void *signal_listener(void *arg) { - static uintptr_t bt_data[JL_MAX_BT_SIZE + 1]; + static jl_bt_element_t bt_data[JL_MAX_BT_SIZE + 1]; static size_t bt_size = 0; sigset_t sset; int sig, critical, profile; @@ -669,7 +669,7 @@ static void *signal_listener(void *arg) bt_size += rec_backtrace_ctx(bt_data + bt_size, JL_MAX_BT_SIZE / jl_n_threads - 1, signal_context, 0); - bt_data[bt_size++] = 0; + bt_data[bt_size++].uintptr = 0; } // do backtrace for profiler @@ -686,13 +686,13 @@ static void *signal_listener(void *arg) jl_safe_printf("WARNING: profiler attempt to access an invalid memory location\n"); } else { // Get backtrace data - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, + bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, signal_context, 0); } ptls->safe_restore = old_buf; // Mark the end of this block with 0 - bt_data_prof[bt_size_cur++] = 0; + bt_data_prof[bt_size_cur++].uintptr = 0; } if (bt_size_cur >= bt_size_max - 1) { // Buffer full: Delete the timer diff --git a/src/signals-win.c b/src/signals-win.c index 49ea19780c0b5..fefa3daf29622 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -297,7 +297,7 @@ LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo) jl_safe_printf("UNKNOWN"); break; } jl_safe_printf(" at 0x%Ix -- ", (size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress); - jl_gdblookup((uintptr_t)ExceptionInfo->ExceptionRecord->ExceptionAddress); + jl_print_native_codeloc(ExceptionInfo->ExceptionRecord->ExceptionAddress); jl_critical_error(0, ExceptionInfo->ContextRecord, ptls->bt_data, &ptls->bt_size); @@ -344,10 +344,10 @@ static DWORD WINAPI profile_bt( LPVOID lparam ) break; } // Get backtrace data - bt_size_cur += rec_backtrace_ctx((uintptr_t*)bt_data_prof + bt_size_cur, + bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, &ctxThread, 0); // Mark the end of this block with 0 - bt_data_prof[bt_size_cur] = 0; + bt_data_prof[bt_size_cur].uintptr = 0; bt_size_cur++; } if ((DWORD)-1 == ResumeThread(hMainThread)) { diff --git a/src/stackwalk.c b/src/stackwalk.c index d4615a19b2c54..4304757a5cb05 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -45,7 +45,7 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt // // jl_unw_stepn will return 1 if there are more frames to come. The number of // elements written to bt_data (and sp if non-NULL) are returned in bt_size. -int jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *bt_data, size_t *bt_size, +int jl_unw_stepn(bt_cursor_t *cursor, jl_bt_element_t *bt_data, size_t *bt_size, uintptr_t *sp, size_t maxsize, int skip, int add_interp_frames, int from_signal_handler) JL_NOTSAFEPOINT { @@ -117,17 +117,17 @@ int jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *bt_data, size_t *bt_size, // normal frame call_ip -= 1; } - if (call_ip == JL_BT_INTERP_FRAME) { + if (call_ip == JL_BT_NON_PTR_ENTRY) { // Never leave special marker in the bt data as it can corrupt the GC. call_ip = 0; } - uintptr_t *bt_entry = bt_data + n; + jl_bt_element_t *bt_entry = bt_data + n; size_t entry_sz = 0; if (add_interp_frames && jl_is_enter_interpreter_frame(call_ip) && (entry_sz = jl_capture_interp_frame(bt_entry, thesp, thefp, maxsize-n)) != 0) { n += entry_sz; } else { - *bt_entry = call_ip; + bt_entry->uintptr = call_ip; n++; } } @@ -149,7 +149,7 @@ int jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *bt_data, size_t *bt_size, return need_more_space; } -NOINLINE size_t rec_backtrace_ctx(uintptr_t *bt_data, size_t maxsize, +NOINLINE size_t rec_backtrace_ctx(jl_bt_element_t *bt_data, size_t maxsize, bt_context_t *context, int add_interp_frames) JL_NOTSAFEPOINT { bt_cursor_t cursor; @@ -165,7 +165,7 @@ NOINLINE size_t rec_backtrace_ctx(uintptr_t *bt_data, size_t maxsize, // // The first `skip` frames are omitted, in addition to omitting the frame from // `rec_backtrace` itself. -NOINLINE size_t rec_backtrace(uintptr_t *bt_data, size_t maxsize, int skip) +NOINLINE size_t rec_backtrace(jl_bt_element_t *bt_data, size_t maxsize, int skip) { bt_context_t context; memset(&context, 0, sizeof(context)); @@ -217,7 +217,7 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp, int skip) jl_array_grow_end(sp, maxincr); } size_t size_incr = 0; - have_more_frames = jl_unw_stepn(&cursor, (uintptr_t*)jl_array_data(ip) + offset, + have_more_frames = jl_unw_stepn(&cursor, (jl_bt_element_t*)jl_array_data(ip) + offset, &size_incr, sp_ptr, maxincr, skip, 1, 0); skip = 0; offset += size_incr; @@ -227,12 +227,18 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp, int skip) jl_array_del_end(sp, jl_array_len(sp) - offset); size_t n = 0; + jl_bt_element_t *bt_data = (jl_bt_element_t*)jl_array_data(ip); while (n < jl_array_len(ip)) { - if ((uintptr_t)jl_array_ptr_ref(ip, n) == JL_BT_INTERP_FRAME) { - jl_array_ptr_1d_push(bt2, jl_array_ptr_ref(ip, n+1)); - n += 2; + jl_bt_element_t *bt_entry = bt_data + n; + if (!jl_bt_is_native(bt_entry)) { + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t j = 0; j < njlvals; j++) { + jl_value_t *v = jl_bt_entry_jlvalue(bt_entry, j); + JL_GC_PROMISE_ROOTED(v); + jl_array_ptr_1d_push(bt2, v); + } } - n++; + n += jl_bt_entry_size(bt_entry); } } jl_value_t *bt = returnsp ? (jl_value_t*)jl_svec(3, ip, bt2, sp) : (jl_value_t*)jl_svec(2, ip, bt2); @@ -240,32 +246,37 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp, int skip) return bt; } -// note: btout and bt2out must be GC roots -void decode_backtrace(uintptr_t *bt_data, size_t bt_size, - jl_array_t **btout, jl_array_t **bt2out) +void decode_backtrace(jl_bt_element_t *bt_data, size_t bt_size, + jl_array_t **btout JL_REQUIRE_ROOTED_SLOT, + jl_array_t **bt2out JL_REQUIRE_ROOTED_SLOT) { jl_array_t *bt, *bt2; if (array_ptr_void_type == NULL) { array_ptr_void_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_voidpointer_type, jl_box_long(1)); } bt = *btout = jl_alloc_array_1d(array_ptr_void_type, bt_size); - memcpy(bt->data, bt_data, bt_size * sizeof(void*)); + static_assert(sizeof(jl_bt_element_t) == sizeof(void*), + "jl_bt_element_t is presented as Ptr{Cvoid} on julia side"); + memcpy(bt->data, bt_data, bt_size * sizeof(jl_bt_element_t)); bt2 = *bt2out = jl_alloc_array_1d(jl_array_any_type, 0); - // Scan the stack for any interpreter frames - size_t n = 0; - while (n < bt_size) { - if (bt_data[n] == JL_BT_INTERP_FRAME) { - jl_array_ptr_1d_push(bt2, (jl_value_t*)bt_data[n+1]); - n += 2; + // Scan the backtrace buffer for any gc-managed values + for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { + jl_bt_element_t* bt_entry = bt_data + i; + if (jl_bt_is_native(bt_entry)) + continue; + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t j = 0; j < njlvals; j++) { + jl_value_t *v = jl_bt_entry_jlvalue(bt_entry, j); + JL_GC_PROMISE_ROOTED(v); + jl_array_ptr_1d_push(bt2, v); } - n++; } } JL_DLLEXPORT jl_value_t *jl_get_backtrace(void) { jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; - uintptr_t *bt_data = NULL; + jl_bt_element_t *bt_data = NULL; size_t bt_size = 0; if (s && s->top) { bt_data = jl_excstack_bt_data(s, s->top); @@ -519,7 +530,7 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt } #ifdef LIBOSXUNWIND -NOINLINE size_t rec_backtrace_ctx_dwarf(uintptr_t *bt_data, size_t maxsize, +NOINLINE size_t rec_backtrace_ctx_dwarf(jl_bt_element_t *bt_data, size_t maxsize, bt_context_t *context, int add_interp_frames) { size_t bt_size = 0; @@ -577,8 +588,22 @@ JL_DLLEXPORT jl_value_t *jl_lookup_code_address(void *ip, int skipC) return rs; } -//for looking up functions from gdb: -JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) +void jl_safe_print_codeloc(const char* func_name, const char* file_name, + int line, int inlined) JL_NOTSAFEPOINT +{ + const char *inlined_str = inlined ? " [inlined]" : ""; + if (line != -1) { + jl_safe_printf("%s at %s:%d%s\n", func_name, file_name, line, inlined_str); + } + else { + jl_safe_printf("%s at %s (unknown line)%s\n", func_name, file_name, inlined_str); + } +} + +// Print function, file and line containing native instruction pointer `ip` by +// looking up debug info. Prints multiple such frames when `ip` points to +// inlined code. +void jl_print_native_codeloc(uintptr_t ip) JL_NOTSAFEPOINT { // This function is not allowed to reference any TLS variables since // it can be called from an unmanaged thread on OSX. @@ -593,15 +618,7 @@ JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) jl_safe_printf("unknown function (ip: %p)\n", (void*)ip); } else { - const char *inlined = frame.inlined ? " [inlined]" : ""; - if (frame.line != -1) { - jl_safe_printf("%s at %s:%" PRIuPTR "%s\n", frame.func_name, - frame.file_name, (uintptr_t)frame.line, inlined); - } - else { - jl_safe_printf("%s at %s (unknown line)%s\n", frame.func_name, - frame.file_name, inlined); - } + jl_safe_print_codeloc(frame.func_name, frame.file_name, frame.line, frame.inlined); free(frame.func_name); free(frame.file_name); } @@ -609,22 +626,70 @@ JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) free(frames); } +// Print code location for backtrace buffer entry at *bt_entry +void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT +{ + if (jl_bt_is_native(bt_entry)) { + jl_print_native_codeloc(bt_entry[0].uintptr); + } + else if (jl_bt_entry_tag(bt_entry) == JL_BT_INTERP_FRAME_TAG) { + size_t ip = jl_bt_entry_header(bt_entry); + jl_value_t *code = jl_bt_entry_jlvalue(bt_entry, 0); + if (jl_is_method_instance(code)) { + // When interpreting a method instance, need to unwrap to find the code info + code = ((jl_method_instance_t*)code)->uninferred; + } + if (jl_is_code_info(code)) { + jl_code_info_t *src = (jl_code_info_t*)code; + // See also the debug info handling in codegen.cpp. + // NB: debuginfoloc is 1-based! + intptr_t debuginfoloc = ((int32_t*)jl_array_data(src->codelocs))[ip]; + while (debuginfoloc != 0) { + jl_line_info_node_t *locinfo = (jl_line_info_node_t*) + jl_array_ptr_ref(src->linetable, debuginfoloc - 1); + assert(jl_typeis(locinfo, jl_lineinfonode_type)); + jl_value_t *method = locinfo->method; + if (jl_is_method_instance(method)) { + method = ((jl_method_instance_t*)method)->def.value; + if (jl_is_method(method)) + method = (jl_value_t*)((jl_method_t*)method)->name; + } + const char *func_name = jl_is_symbol(method) ? + jl_symbol_name((jl_sym_t*)method) : "Unknown"; + jl_safe_print_codeloc(func_name, jl_symbol_name(locinfo->file), + locinfo->line, locinfo->inlined_at); + debuginfoloc = locinfo->inlined_at; + } + } + else { + // If we're using this function something bad has already happened; + // be a bit defensive to avoid crashing while reporting the crash. + jl_safe_printf("No code info - unknown interpreter state!\n"); + } + } + else { + jl_safe_printf("Non-native bt entry with tag and header bits 0x%" PRIxPTR "\n", + bt_entry[1].uintptr); + } +} + +//-------------------------------------------------- +// Tools for interactive debugging in gdb +JL_DLLEXPORT void jl_gdblookup(void* ip) +{ + jl_print_native_codeloc((uintptr_t)ip); +} + +// Print backtrace for current exception in catch block JL_DLLEXPORT void jlbacktrace(void) JL_NOTSAFEPOINT { jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; if (!s) return; size_t bt_size = jl_excstack_bt_size(s, s->top); - uintptr_t *bt_data = jl_excstack_bt_data(s, s->top); - for (size_t i = 0; i < bt_size; ) { - if (bt_data[i] == JL_BT_INTERP_FRAME) { - jl_safe_printf("Interpreter frame (ip: %d)\n", (int)bt_data[i+2]); - jl_static_show(JL_STDERR, (jl_value_t*)bt_data[i+1]); - i += 3; - } else { - jl_gdblookup(bt_data[i] - 1); - i += 1; - } + jl_bt_element_t *bt_data = jl_excstack_bt_data(s, s->top); + for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { + jl_print_bt_entry_codeloc(bt_data + i); } } diff --git a/src/task.c b/src/task.c index aeaa8af71e8ed..e8a21e90884d7 100644 --- a/src/task.c +++ b/src/task.c @@ -518,7 +518,7 @@ JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) if (!excstack || excstack->top == 0) jl_error("rethrow(exc) not allowed outside a catch block"); // overwrite exception on top of stack. see jl_excstack_exception - jl_excstack_raw(excstack)[excstack->top-1] = (uintptr_t)e; + jl_excstack_raw(excstack)[excstack->top-1].jlvalue = e; JL_GC_PROMISE_ROOTED(e); throw_internal(NULL); } @@ -673,7 +673,7 @@ STATIC_OR_JS void NOINLINE JL_NORETURN start_task(void) if (t->exception != jl_nothing) { record_backtrace(ptls, 0); jl_push_excstack(&t->excstack, t->exception, - ptls->bt_data, ptls->bt_size); + ptls->bt_data, ptls->bt_size); res = t->exception; } else { diff --git a/src/threading.c b/src/threading.c index b92ce7776be71..8bae4948ac421 100644 --- a/src/threading.c +++ b/src/threading.c @@ -262,14 +262,15 @@ void jl_init_threadtls(int16_t tid) sizeof(size_t)); } ptls->defer_signal = 0; - void *bt_data = malloc(sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); + jl_bt_element_t *bt_data = (jl_bt_element_t*) + malloc(sizeof(jl_bt_element_t) * (JL_MAX_BT_SIZE + 1)); if (bt_data == NULL) { jl_printf(JL_STDERR, "could not allocate backtrace buffer\n"); gc_debug_critical_error(); abort(); } - memset(bt_data, 0, sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); - ptls->bt_data = (uintptr_t*)bt_data; + memset(bt_data, 0, sizeof(jl_bt_element_t) * (JL_MAX_BT_SIZE + 1)); + ptls->bt_data = bt_data; ptls->sig_exception = NULL; ptls->previous_exception = NULL; #ifdef _OS_WINDOWS_ diff --git a/test/backtrace.jl b/test/backtrace.jl index 715f5d2913a9a..8b6ca94c77970 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -255,6 +255,6 @@ let code = """ bt_str = read(`$(Base.julia_cmd()) --startup-file=no --compile=min -e $code`, String) @test occursin("InterpreterIP in MethodInstance for foo", bt_str) - @test occursin("InterpreterIP in top-level CodeInfo", bt_str) + @test occursin("InterpreterIP in top-level CodeInfo for Main.A", bt_str) end