diff --git a/base/client.jl b/base/client.jl index 86e8ba25e77ae..8f5a89fbb8666 100644 --- a/base/client.jl +++ b/base/client.jl @@ -81,8 +81,7 @@ end # deprecated function--preserved for DocTests.jl function ip_matches_func(ip, func::Symbol) - ip isa InterpreterIP || (ip -= 1) - for fr in StackTraces.lookupat(ip) + for fr in StackTraces.lookup(ip) if fr === StackTraces.UNKNOWN || fr.from_c return false end diff --git a/base/deprecated.jl b/base/deprecated.jl index a6dca85091e55..21131cf857632 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -102,8 +102,7 @@ function firstcaller(bt::Vector, funcsyms) # Identify the calling line found = false for ip in bt - ip isa Base.InterpreterIP || (ip -= 1) # convert from return stack to call stack (for inlining info) - lkups = StackTraces.lookupat(ip) + lkups = StackTraces.lookup(ip) for lkup in lkups if lkup == StackTraces.UNKNOWN || lkup.from_c continue diff --git a/base/error.jl b/base/error.jl index dc669ecfafaf5..c0a37ed66f899 100644 --- a/base/error.jl +++ b/base/error.jl @@ -85,7 +85,19 @@ function _reformat_bt(bt, bt2) ret end -function backtrace end +""" + backtrace() + +Get a backtrace object for the current program point. +""" +function backtrace() + @_noinline_meta + # skip frame for backtrace(). Note that for this to work properly, + # backtrace() itself must not be interpreted nor inlined. + skip = 1 + bt1, bt2 = ccall(:jl_backtrace_from_here, Any, (Cint,Cint), false, skip) + _reformat_bt(bt1, bt2) +end """ catch_backtrace() diff --git a/base/errorshow.jl b/base/errorshow.jl index 8bc8ba1c2a4ae..8108346d71a59 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -615,10 +615,8 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) lkups = t[i] if lkups isa StackFrame lkups = [lkups] - elseif lkups isa Base.InterpreterIP - lkups = StackTraces.lookupat(lkups) else - lkups = StackTraces.lookupat(lkups - 1) + lkups = StackTraces.lookup(lkups) end for lkup in lkups if lkup === StackTraces.UNKNOWN @@ -665,3 +663,14 @@ function show_exception_stack(io::IO, stack::Vector) println(io) end end + +# Defined here rather than error.jl for bootstrap ordering +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))") + else + print(io, " in $(ip.code) at statement $(Int(ip.stmt))") + end +end + diff --git a/base/stacktraces.jl b/base/stacktraces.jl index 42e2769f1454b..e04512905bf13 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -96,13 +96,13 @@ end """ - lookupat(pointer::Union{Ptr{Cvoid}, UInt}) -> Vector{StackFrame} + lookup(pointer::Ptr{Cvoid}) -> Vector{StackFrame} Given a pointer to an execution context (usually generated by a call to `backtrace`), looks up stack frame context information. Returns an array of frame information for all functions inlined at that point, innermost function first. """ -function lookupat(pointer::Ptr{Cvoid}) +function lookup(pointer::Ptr{Cvoid}) infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false) pointer = convert(UInt64, pointer) isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN @@ -117,7 +117,7 @@ end const top_level_scope_sym = Symbol("top-level scope") -function lookupat(ip::Base.InterpreterIP) +function lookup(ip::Base.InterpreterIP) if ip.code isa Core.MethodInstance && ip.code.def isa Method codeinfo = ip.code.uninferred func = ip.code.def.name @@ -149,35 +149,6 @@ function lookupat(ip::Base.InterpreterIP) return scopes end -""" - backtrace() - -Get a backtrace object for the current program point. -""" -function Base.backtrace() - bt, bt2 = ccall(:jl_backtrace_from_here, Any, (Int32,), false) - if length(bt) > 2 - # remove frames for jl_backtrace_from_here and backtrace() - if bt[2] == Ptr{Cvoid}(-1%UInt) - # backtrace() is interpreted - # Note: win32 is missing the top frame (see https://bugs.chromium.org/p/crashpad/issues/detail?id=53) - @static if Base.Sys.iswindows() && Int === Int32 - deleteat!(bt, 1:2) - else - deleteat!(bt, 1:3) - end - pushfirst!(bt2) - else - @static if Base.Sys.iswindows() && Int === Int32 - deleteat!(bt, 1) - else - deleteat!(bt, 1:2) - end - end - end - return Base._reformat_bt(bt, bt2) -end - """ stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false]) -> StackTrace @@ -188,8 +159,7 @@ trace, `stacktrace` first calls `backtrace`. function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false) stack = StackTrace() for ip in trace - ip isa Base.InterpreterIP || (ip -= 1) # convert from return stack to call stack (for inlining info) - for frame in lookupat(ip) + for frame in lookup(ip) # Skip frames that come from C calls. if c_funcs || !frame.from_c push!(stack, frame) diff --git a/doc/src/base/stacktraces.md b/doc/src/base/stacktraces.md index ce3931e9b0512..3286ee204dc74 100644 --- a/doc/src/base/stacktraces.md +++ b/doc/src/base/stacktraces.md @@ -7,9 +7,9 @@ Base.StackTraces.stacktrace ``` The following methods and types in `Base.StackTraces` are not exported and need to be called e.g. -as `StackTraces.lookupat(ptr)`. +as `StackTraces.lookup(ptr)`. ```@docs -Base.StackTraces.lookupat +Base.StackTraces.lookup Base.StackTraces.remove_frames! ``` diff --git a/doc/src/manual/stacktraces.md b/doc/src/manual/stacktraces.md index bfbe1ceb58c50..23cad109182df 100644 --- a/doc/src/manual/stacktraces.md +++ b/doc/src/manual/stacktraces.md @@ -300,12 +300,12 @@ julia> stacktrace(trace, true) ``` Individual pointers returned by [`backtrace`](@ref) can be translated into [`StackTraces.StackFrame`](@ref) -s by passing them into [`StackTraces.lookupat`](@ref): +s by passing them into [`StackTraces.lookup`](@ref): ```julia-repl julia> pointer = backtrace()[1]; -julia> frame = StackTraces.lookupat(pointer - 1) +julia> frame = StackTraces.lookup(pointer) 1-element Array{Base.StackTraces.StackFrame,1}: jl_apply_generic at gf.c:2167 diff --git a/src/gf.c b/src/gf.c index da3dadc5c418c..7168522de2933 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1714,7 +1714,7 @@ static void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args, jl_static_show((JL_STREAM*)STDERR_FILENO,(jl_value_t*)f); jl_printf((JL_STREAM*)STDERR_FILENO," world %u\n", (unsigned)world); jl_static_show((JL_STREAM*)STDERR_FILENO,args); jl_printf((JL_STREAM*)STDERR_FILENO,"\n"); jl_ptls_t ptls = jl_get_ptls_states(); - ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE); + ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE, 0); jl_critical_error(0, NULL, ptls->bt_data, &ptls->bt_size); abort(); } diff --git a/src/interpreter-stacktrace.c b/src/interpreter-stacktrace.c index c15fbbc6f4ef9..18cd5f4243fc1 100644 --- a/src/interpreter-stacktrace.c +++ b/src/interpreter-stacktrace.c @@ -397,13 +397,14 @@ JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintp #else interpreter_state *s = (interpreter_state *)(sp+TOTAL_STACK_PADDING); #endif - if (space_remaining <= 1) + int required_space = 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 2; + return required_space; } extern void * CALLBACK_ABI enter_interpreter_frame(void * CALLBACK_ABI (*callback)(interpreter_state *, void *), void *arg); @@ -420,6 +421,7 @@ JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip) JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining) { + // Leave bt_entry[0] as the native instruction ptr return 0; } #define CALLBACK_ABI diff --git a/src/julia_internal.h b/src/julia_internal.h index b98937e9c6d41..d408257150e00 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -636,10 +636,15 @@ typedef int bt_cursor_t; #endif // Special marker in backtrace data for encoding interpreter frames #define JL_BT_INTERP_FRAME (((uintptr_t)0)-1) -size_t rec_backtrace(uintptr_t *data, size_t maxsize) JL_NOTSAFEPOINT; -size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; +// 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; +// 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, + int add_interp_frames) JL_NOTSAFEPOINT; #ifdef LIBOSXUNWIND -size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; +size_t rec_backtrace_ctx_dwarf(uintptr_t *bt_data, size_t maxsize, bt_context_t *ctx, int add_interp_frames) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT void jl_get_backtrace(jl_array_t **bt, jl_array_t **bt2); void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size); diff --git a/src/stackwalk.c b/src/stackwalk.c index 7dad4e0436a6c..b403a5e46e1a8 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -27,15 +27,42 @@ extern "C" { static int jl_unw_init(bt_cursor_t *cursor, bt_context_t *context) JL_NOTSAFEPOINT; static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintptr_t *fp) JL_NOTSAFEPOINT; -size_t jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, size_t maxsize, int add_interp_frames) JL_NOTSAFEPOINT +// Record backtrace entries into bt_data by stepping cursor with jl_unw_step +// until the outermost frame is encountered or the buffer bt_data is (close to) +// full. Returned instruction pointers are adjusted to point to the address of +// the call instruction. The first `skip` frames are not included in `bt_data`. +// +// `maxsize` is the size of the buffer `bt_data` (and `sp` if non-NULL). It +// must be at least JL_BT_MAX_ENTRY_SIZE to accommodate extended backtrace +// entries. If `sp != NULL`, the stack pointer corresponding `bt_data[i]` is +// stored in `sp[i]`. +// +// Flag `add_interp_frames==1` should be set to record an extended backtrace +// entries in `bt_data` for each julia interpreter frame. +// +// Flag `from_signal_handler==1` should be set if the cursor was obtained by +// asynchronously interrupting the code. +// +// 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, + uintptr_t *sp, size_t maxsize, int skip, int add_interp_frames, + int from_signal_handler) JL_NOTSAFEPOINT { jl_ptls_t ptls = jl_get_ptls_states(); volatile size_t n = 0; - uintptr_t thesp; - uintptr_t thefp; + volatile int need_more_space = 0; + uintptr_t return_ip = 0; + uintptr_t thesp = 0; + uintptr_t thefp = 0; #if defined(_OS_WINDOWS_) && !defined(_CPU_X86_64_) assert(!jl_in_stackwalk); jl_in_stackwalk = 1; + if (!from_signal_handler) { + // Workaround 32-bit windows bug missing top frame + // See for example https://bugs.chromium.org/p/crashpad/issues/detail?id=53 + skip--; + } #endif #if !defined(_OS_WINDOWS_) jl_jmp_buf *old_buf = ptls->safe_restore; @@ -43,22 +70,67 @@ size_t jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, size_t ma if (!jl_setjmp(buf, 0)) { ptls->safe_restore = &buf; #endif - while (1) { - if (n >= maxsize) { - n = maxsize; // return maxsize + 1 if ran out of space - break; - } - if (!jl_unw_step(cursor, &ip[n], &thesp, &thefp)) - break; - if (sp) - sp[n] = thesp; - if (add_interp_frames && jl_is_enter_interpreter_frame(ip[n])) { - n += jl_capture_interp_frame(&ip[n], thesp, thefp, maxsize-n-1) + 1; - } else { - n++; - } + int have_more_frames = 1; + while (have_more_frames) { + if (n + JL_BT_MAX_ENTRY_SIZE > maxsize) { + // Postpone advancing the cursor: may need more space + need_more_space = 1; + break; + } + have_more_frames = jl_unw_step(cursor, &return_ip, &thesp, &thefp); + if (skip > 0) { + skip--; + continue; + } + if (sp) + sp[n] = thesp; + // For the purposes of looking up debug info for functions, we want + // to harvest addresses for the *call* instruction `call_ip` during + // stack walking. However, this information isn't directly + // available. Instead, the stack walk discovers the address + // `return_ip` which would be *returned to* as the stack is + // unwound. + // + // To infer `call_ip` in full generality we need to understand each + // platform ABI instruction pointer encoding and calling + // conventions, noting that the latter may vary per stack frame. + // + // See also: + // * The LLVM unwinder functions step() and setInfoBasedOnIPRegister() + // https://github.com/llvm/llvm-project/blob/master/libunwind/src/UnwindCursor.hpp + // * The way that libunwind handles it in `unw_get_proc_name`: + // https://lists.nongnu.org/archive/html/libunwind-devel/2014-06/msg00025.html + uintptr_t call_ip = return_ip; + // ARM instruction pointer encoding uses the low bit as a flag for + // thumb mode, which must be cleared before further use. (Note not + // needed for ARM AArch64.) See + // https://github.com/libunwind/libunwind/pull/131 + #ifdef _CPU_ARM_ + call_ip &= ~(uintptr_t)0x1; + #endif + // Now there's two main cases to adjust for: + // * Normal stack frames where compilers emit a `call` instruction + // which we can get from the return address via `call_ip = return_ip - 1`. + // * Code which was interrupted asynchronously (eg, via a signal) + // is expected to have `call_ip == return_ip`. + if (n != 0 || !from_signal_handler) { + // normal frame + call_ip -= 1; + } + if (call_ip == JL_BT_INTERP_FRAME) { + // Never leave special marker in the bt data as it can corrupt the GC. + call_ip = 0; + } + uintptr_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; + n++; + } } - n++; #if !defined(_OS_WINDOWS_) } else { @@ -73,30 +145,49 @@ size_t jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, size_t ma #if defined(_OS_WINDOWS_) && !defined(_CPU_X86_64_) jl_in_stackwalk = 0; #endif - return n; + *bt_size = n; + return need_more_space; } -size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, - bt_context_t *context, int add_interp_frames) +NOINLINE size_t rec_backtrace_ctx(uintptr_t *bt_data, size_t maxsize, + bt_context_t *context, int add_interp_frames) JL_NOTSAFEPOINT { - size_t n = 0; bt_cursor_t cursor; if (!jl_unw_init(&cursor, context)) return 0; - n = jl_unw_stepn(&cursor, data, NULL, maxsize, add_interp_frames); - return n > maxsize ? maxsize : n; + size_t bt_size = 0; + jl_unw_stepn(&cursor, bt_data, &bt_size, NULL, maxsize, 0, add_interp_frames, 1); + return bt_size; } -size_t rec_backtrace(uintptr_t *data, size_t maxsize) +// Record backtrace into buffer `bt_data`, using a maximum of `maxsize` +// elements, and returning the number of elements written. +// +// 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) { bt_context_t context; memset(&context, 0, sizeof(context)); jl_unw_get(&context); - return rec_backtrace_ctx(data, maxsize, &context, 1); + bt_cursor_t cursor; + if (!jl_unw_init(&cursor, &context)) + return 0; + size_t bt_size = 0; + jl_unw_stepn(&cursor, bt_data, &bt_size, NULL, maxsize, skip + 1, 1, 0); + return bt_size; } static jl_value_t *array_ptr_void_type JL_ALWAYS_LEAFTYPE = NULL; -JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp) +// Return backtrace information as an svec of (bt1, bt2, [sp]) +// +// The stack pointers `sp` are returned only when `returnsp` evaluates to true. +// bt1 contains raw backtrace entries, while bt2 exists to root any julia +// objects associated with the entries in bt1. +// +// The frame from jl_backtrace_from_here will be skipped; set `skip > 0` to +// skip additional native frames from the start of the backtrace. +JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp, int skip) { jl_array_t *ip = NULL; jl_array_t *sp = NULL; @@ -114,18 +205,28 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp) memset(&context, 0, sizeof(context)); jl_unw_get(&context); if (jl_unw_init(&cursor, &context)) { - size_t n = 0, offset = 0; - do { + // Skip frame for jl_backtrace_from_here itself + skip += 1; + size_t offset = 0; + int have_more_frames = 1; + while (have_more_frames) { jl_array_grow_end(ip, maxincr); - if (returnsp) jl_array_grow_end(sp, maxincr); - n = jl_unw_stepn(&cursor, (uintptr_t*)jl_array_data(ip) + offset, - returnsp ? (uintptr_t*)jl_array_data(sp) + offset : NULL, maxincr, 1); - offset += maxincr; - } while (n > maxincr); - jl_array_del_end(ip, maxincr - n); - if (returnsp) jl_array_del_end(sp, maxincr - n); + uintptr_t *sp_ptr = NULL; + if (returnsp) { + sp_ptr = (uintptr_t*)jl_array_data(sp) + offset; + 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, + &size_incr, sp_ptr, maxincr, skip, 1, 0); + skip = 0; + offset += size_incr; + } + jl_array_del_end(ip, jl_array_len(ip) - offset); + if (returnsp) + jl_array_del_end(sp, jl_array_len(sp) - offset); - n = 0; + size_t n = 0; 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)); @@ -331,9 +432,7 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt *sp = (uintptr_t)cursor->stackframe.AddrStack.Offset; if (fp) *fp = (uintptr_t)cursor->stackframe.AddrFrame.Offset; - if (*ip == 0 || *ip == JL_BT_INTERP_FRAME) { - // don't leave special marker in the bt data as it can corrupt the GC. - *ip = 0; + if (*ip == 0) { if (!readable_pointer((LPCVOID)*sp)) return 0; cursor->stackframe.AddrPC.Offset = *(DWORD32*)*sp; // POP EIP (aka RET) @@ -349,9 +448,7 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt *sp = (uintptr_t)cursor->Rsp; if (fp) *fp = (uintptr_t)cursor->Rbp; - if (*ip == 0 || *ip == JL_BT_INTERP_FRAME) { - // don't leave special marker in the bt data as it can corrupt the GC. - *ip = 0; + if (*ip == 0) { if (!readable_pointer((LPCVOID)*sp)) return 0; cursor->Rip = *(DWORD64*)*sp; // POP RIP (aka RET) @@ -404,8 +501,7 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt unw_word_t reg; if (unw_get_reg(cursor, UNW_REG_IP, ®) < 0) return 0; - // don't leave special marker in the bt data as it can corrupt the GC. - *ip = reg == JL_BT_INTERP_FRAME ? 0 : reg; + *ip = reg; if (unw_get_reg(cursor, UNW_REG_SP, ®) < 0) return 0; *sp = reg; @@ -422,19 +518,15 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt } #ifdef LIBOSXUNWIND -int jl_unw_init_dwarf(bt_cursor_t *cursor, bt_context_t *uc) -{ - return unw_init_local_dwarf(cursor, uc) != 0; -} -size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, - bt_context_t *context, int add_interp_frames) +NOINLINE size_t rec_backtrace_ctx_dwarf(uintptr_t *bt_data, size_t maxsize, + bt_context_t *context, int add_interp_frames) { - size_t n; + size_t bt_size = 0; bt_cursor_t cursor; - if (!jl_unw_init_dwarf(&cursor, context)) + if (unw_init_local_dwarf(&cursor, context) != UNW_ESUCCESS) return 0; - n = jl_unw_stepn(&cursor, data, NULL, maxsize, add_interp_frames); - return n > maxsize ? maxsize : n; + jl_unw_stepn(&cursor, bt_data, &bt_size, NULL, maxsize, 0, add_interp_frames, 1); + return bt_size; } #endif diff --git a/src/task.c b/src/task.c index 2d566b700f418..aeaa8af71e8ed 100644 --- a/src/task.c +++ b/src/task.c @@ -234,10 +234,12 @@ JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *tid) return (void *)((char *)task->stkbuf + off); } -static void record_backtrace(jl_ptls_t ptls) JL_NOTSAFEPOINT +// Marked noinline so we can consistently skip the associated frame. +// `skip` is number of additional frames to skip. +NOINLINE static void record_backtrace(jl_ptls_t ptls, int skip) JL_NOTSAFEPOINT { // storing bt_size in ptls ensures roots in bt_data will be found - ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE); + ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE, skip + 1); } JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) @@ -484,7 +486,7 @@ JL_DLLEXPORT void jl_throw(jl_value_t *e JL_MAYBE_UNROOTED) assert(e != NULL); if (ptls->safe_restore) throw_internal(NULL); - record_backtrace(ptls); + record_backtrace(ptls, 1); throw_internal(e); } @@ -669,7 +671,7 @@ STATIC_OR_JS void NOINLINE JL_NORETURN start_task(void) t->started = 1; if (t->exception != jl_nothing) { - record_backtrace(ptls); + record_backtrace(ptls, 0); jl_push_excstack(&t->excstack, t->exception, ptls->bt_data, ptls->bt_size); res = t->exception; diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 2c59cd2d60cd4..429bb80fe4f60 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -5,11 +5,10 @@ Profiling support, main entry point is the [`@profile`](@ref) macro. """ module Profile -import Base.StackTraces: lookupat, UNKNOWN, show_spec_linfo, StackFrame +import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo, StackFrame # deprecated functions: use `getdict` instead -lookup(ip::UInt) = lookupat(convert(Ptr{Cvoid}, ip) - 1) -lookup(ip::Ptr{Cvoid}) = lookupat(ip - 1) +lookup(ip::UInt) = lookup(convert(Ptr{Cvoid}, ip)) export @profile @@ -199,7 +198,7 @@ end function getdict(data::Vector{UInt}) dict = LineInfoDict() for ip in data - get!(() -> lookupat(convert(Ptr{Cvoid}, ip)), dict, UInt64(ip)) + get!(() -> lookup(convert(Ptr{Cvoid}, ip)), dict, UInt64(ip)) end return dict end @@ -375,17 +374,6 @@ function fetch() end data = Vector{UInt}(undef, len) GC.@preserve data unsafe_copyto!(pointer(data), get_data_pointer(), len) - # post-process the data to convert from a return-stack to a call-stack - first = true - for i = 1:length(data) - if data[i] == 0 - first = true - elseif first - first = false - else - data[i] -= 1 - end - end return data end diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index ce8097cbe5c64..98c0e8c983e8d 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -45,8 +45,7 @@ const DISPLAY_FAILED = ( # Backtrace utility functions function ip_has_file_and_func(ip, file, funcs) - ip isa Base.InterpreterIP || (ip -= 1) # convert from return stack to call stack (for inlining info) - return any(fr -> (string(fr.file) == file && fr.func in funcs), StackTraces.lookupat(ip)) + return any(fr -> (string(fr.file) == file && fr.func in funcs), StackTraces.lookup(ip)) end function scrub_backtrace(bt) diff --git a/test/backtrace.jl b/test/backtrace.jl index 41dd7f4b04a36..715f5d2913a9a 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -1,7 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -lookup(ip) = StackTraces.lookupat(ip - 1) -lookup(ip::Base.InterpreterIP) = StackTraces.lookupat(ip) # TODO: Base.InterpreterIP should not need a special-case +import Base.StackTraces: lookup # Test location information for inlined code (ref issues #1334 #12544) module test_inline_bt @@ -225,3 +224,37 @@ let trace = try @test trace[1].line == 2 end +# issue #29695 (see also test for #28442) +let code = """ + f29695(c) = g29695(c) + g29695(c) = c >= 1000 ? (return backtrace()) : f29695(c + 1) + bt = f29695(1) + meth_names = [ip.code.def.name for ip in bt + if ip isa Base.InterpreterIP && ip.code isa Core.MethodInstance] + num_fs = sum(meth_names .== :f29695) + num_gs = sum(meth_names .== :g29695) + print(num_fs, ' ', num_gs) + """ + + @test read(`$(Base.julia_cmd()) --startup-file=no --compile=min -e $code`, String) == "1000 1000" +end + +# Test that modules make it into InterpreterIP for top-level code +let code = """ + module A + foo() = error("Expected") + try + foo() + catch + global bt = catch_backtrace() + end + end + + foreach(println, A.bt) + """ + + 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) +end + diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 555cf5e8af6a2..d82d4d96b7065 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -894,7 +894,7 @@ function break_21369() i = 1 local fr while true - fr = Base.StackTraces.lookupat(bt[i] - 1)[end] + fr = Base.StackTraces.lookup(bt[i])[end] if !fr.from_c && fr.func !== :error break end diff --git a/test/meta.jl b/test/meta.jl index 9f67a98252f02..eafa1dd04b162 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -32,7 +32,7 @@ h_noinlined() = g_noinlined() function foundfunc(bt, funcname) for b in bt - for lkup in StackTraces.lookupat(b - 1) + for lkup in StackTraces.lookup(b) if lkup.func == funcname return true end diff --git a/test/stacktraces.jl b/test/stacktraces.jl index 2312d0873ce2a..b3ec4db44a3ef 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -48,7 +48,7 @@ let (default, with_c, without_c) = (stacktrace(), stacktrace(true), stacktrace(f @test isempty(filter(frame -> frame.from_c, without_c)) end -@test StackTraces.lookupat(C_NULL) == [StackTraces.UNKNOWN] == StackTraces.lookupat(C_NULL + 1) == StackTraces.lookupat(C_NULL - 1) +@test StackTraces.lookup(C_NULL) == [StackTraces.UNKNOWN] == StackTraces.lookup(C_NULL + 1) == StackTraces.lookup(C_NULL - 1) let ct = current_task() # After a task switch, there should be nothing in catch_backtrace @@ -118,7 +118,7 @@ let li = typeof(fieldtype).name.mt.cache.func::Core.MethodInstance, end let ctestptr = cglobal((:ctest, "libccalltest")), - ctest = StackTraces.lookupat(ctestptr) + ctest = StackTraces.lookup(ctestptr) @test length(ctest) == 1 @test ctest[1].func === :ctest