forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstacktraces.jl
229 lines (181 loc) · 7.58 KB
/
stacktraces.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# This file is a part of Julia. License is MIT: http://julialang.org/license
module StackTraces
import Base: hash, ==, show
import Base.Serializer: serialize, deserialize
export StackTrace, StackFrame, stacktrace, catch_stacktrace
"""
StackFrame
Stack information representing execution context, with the following fields:
- `func::Symbol`
The name of the function containing the execution context.
- `linfo::Nullable{Core.MethodInstance}`
The MethodInstance containing the execution context (if it could be found).
- `file::Symbol`
The path to the file containing the execution context.
- `line::Int`
The line number in the file containing the execution context.
- `from_c::Bool`
True if the code is from C.
- `inlined::Bool`
True if the code is from an inlined frame.
- `pointer::Int64`
Representation of the pointer to the execution context as returned by `backtrace`.
"""
struct StackFrame # this type should be kept platform-agnostic so that profiles can be dumped on one machine and read on another
"the name of the function containing the execution context"
func::Symbol
"the path to the file containing the execution context"
file::Symbol
"the line number in the file containing the execution context"
line::Int
"the MethodInstance containing the execution context (if it could be found)"
linfo::Nullable{Core.MethodInstance}
"true if the code is from C"
from_c::Bool
"true if the code is from an inlined frame"
inlined::Bool
"representation of the pointer to the execution context as returned by `backtrace`"
pointer::UInt64 # Large enough to be read losslessly on 32- and 64-bit machines.
end
StackFrame(func, file, line) = StackFrame(func, file, line, Nullable{Core.MethodInstance}(), false, false, 0)
"""
StackTrace
An alias for `Vector{StackFrame}` provided for convenience; returned by calls to
`stacktrace` and `catch_stacktrace`.
"""
typealias StackTrace Vector{StackFrame}
const empty_sym = Symbol("")
const UNKNOWN = StackFrame(empty_sym, empty_sym, -1, Nullable{Core.MethodInstance}(), true, false, 0) # === lookup(C_NULL)
#=
If the StackFrame has function and line information, we consider two of them the same if
they share the same function/line information.
=#
function ==(a::StackFrame, b::StackFrame)
a.line == b.line && a.from_c == b.from_c && a.func == b.func && a.file == b.file && a.inlined == b.inlined
end
function hash(frame::StackFrame, h::UInt)
h += 0xf4fbda67fe20ce88 % UInt
h = hash(frame.line, h)
h = hash(frame.file, h)
h = hash(frame.func, h)
h = hash(frame.from_c, h)
h = hash(frame.inlined, h)
end
# provide a custom serializer that skips attempting to serialize the `outer_linfo`
# which is likely to contain complex references, types, and module references
# that may not exist on the receiver end
function serialize(s::AbstractSerializer, frame::StackFrame)
Serializer.serialize_type(s, typeof(frame))
serialize(s, frame.func)
serialize(s, frame.file)
write(s.io, frame.line)
write(s.io, frame.from_c)
write(s.io, frame.inlined)
write(s.io, frame.pointer)
end
function deserialize(s::AbstractSerializer, ::Type{StackFrame})
func = deserialize(s)
file = deserialize(s)
line = read(s.io, Int)
from_c = read(s.io, Bool)
inlined = read(s.io, Bool)
pointer = read(s.io, UInt64)
return StackFrame(func, file, line, Nullable{Core.MethodInstance}(), from_c, inlined, pointer)
end
"""
lookup(pointer::Union{Ptr{Void}, UInt}) -> 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 lookup(pointer::Ptr{Void})
infos = ccall(:jl_lookup_code_address, Any, (Ptr{Void}, Cint), pointer - 1, false)
isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, Nullable{Core.MethodInstance}(), true, false, convert(UInt64, pointer))]
res = Array{StackFrame}(length(infos))
for i in 1:length(infos)
info = infos[i]
@assert(length(info) == 7)
li = info[4] === nothing ? Nullable{Core.MethodInstance}() : Nullable{Core.MethodInstance}(info[4])
res[i] = StackFrame(info[1], info[2], info[3], li, info[5], info[6], info[7])
end
return res
end
lookup(pointer::UInt) = lookup(convert(Ptr{Void}, pointer))
"""
stacktrace([trace::Vector{Ptr{Void}},] [c_funcs::Bool=false]) -> StackTrace
Returns a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace
doesn't return C functions, but this can be enabled.) When called without specifying a
trace, `stacktrace` first calls `backtrace`.
"""
function stacktrace(trace::Vector{Ptr{Void}}, c_funcs::Bool=false)
stack = vcat(StackTrace(), map(lookup, trace)...)::StackTrace
# Remove frames that come from C calls.
if !c_funcs
filter!(frame -> !frame.from_c, stack)
end
# Remove frame for this function (and any functions called by this function).
remove_frames!(stack, :stacktrace)
# is there a better way? the func symbol has a number suffix which changes.
# it's possible that no test is needed and we could just shift! all the time.
# this line was added to PR #16213 because otherwise stacktrace() != stacktrace(false).
# not sure why. possibly b/c of re-ordering of base/sysimg.jl
!isempty(stack) && startswith(string(stack[1].func),"jlcall_stacktrace") && shift!(stack)
stack
end
stacktrace(c_funcs::Bool=false) = stacktrace(backtrace(), c_funcs)
"""
catch_stacktrace([c_funcs::Bool=false]) -> StackTrace
Returns the stack trace for the most recent error thrown, rather than the current execution
context.
"""
catch_stacktrace(c_funcs::Bool=false) = stacktrace(catch_backtrace(), c_funcs)
"""
remove_frames!(stack::StackTrace, name::Symbol)
Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and
removes the `StackFrame` specified by the function name from the `StackTrace` (also removing
all frames above the specified function). Primarily used to remove `StackTraces` functions
from the `StackTrace` prior to returning it.
"""
function remove_frames!(stack::StackTrace, name::Symbol)
splice!(stack, 1:findlast(frame -> frame.func == name, stack))
return stack
end
function remove_frames!(stack::StackTrace, names::Vector{Symbol})
splice!(stack, 1:findlast(frame -> frame.func in names, stack))
return stack
end
function show_spec_linfo(io::IO, frame::StackFrame)
if isnull(frame.linfo)
if frame.func === empty_sym
@printf(io, "ip:%#x", frame.pointer)
else
print_with_color(Base.have_color && get(io, :backtrace, false) ? Base.stackframe_function_color() : :nothing, io, string(frame.func))
end
else
linfo = get(frame.linfo)
if isdefined(linfo, :def)
Base.show_lambda_types(io, linfo)
else
Base.show(io, linfo)
end
end
end
function show(io::IO, frame::StackFrame; full_path::Bool=false)
show_spec_linfo(io, frame)
if frame.file !== empty_sym
file_info = full_path ? string(frame.file) : basename(string(frame.file))
print(io, " at ")
Base.with_output_color(Base.have_color && get(io, :backtrace, false) ? Base.stackframe_lineinfo_color() : :nothing, io) do io
print(io, file_info, ":")
if frame.line >= 0
print(io, frame.line)
else
print(io, "?")
end
end
end
if frame.inlined
print(io, " [inlined]")
end
end
end