Stack unwinding (#18163)
This MR enables GHC to provide backtraces/stacktraces by using the Info Table Provenance Entry Table (IPE table). For this neither DWARF nor profiling support is used.
The basic idea is:
- The IPE table provides a map from Info Table pointers (addresses) to a descriptive source locations
- GHC emits an IPE for every return frame at compile time
- To get a stacktrace:
- Clone the stack (to avoid concurrent modification issues)
- Traverse the cloned stack and lookup the IPE for every return frame's info table (pointer)
The best entry to this MR is the user facing API: libraries/base/GHC/Stack/CloneStack.hs
A Quick Summary of the API
libraries/base/GHC/Stack/CloneStack.hs
reduced to the most important type signatures:
-- | A frozen snapshot of the state of an execution stack.
--
-- @since 2.16.0.0
data StackSnapshot = StackSnapshot !StackSnapshot#
foreign import prim "stg_decodeStackzh" decodeStack# :: StackSnapshot# -> State# RealWorld -> (# State# RealWorld, Array# (Ptr InfoProvEnt) #)
foreign import prim "stg_cloneMyStackzh" cloneMyStack# :: State# RealWorld -> (# State# RealWorld, StackSnapshot# #)
foreign import prim "stg_sendCloneStackMessagezh" sendCloneStackMessage# :: ThreadId# -> StablePtr# PrimMVar -> State# RealWorld -> (# State# RealWorld, (# #) #)
-- | Clone the stack of the executing thread
--
-- @since 2.16.0.0
cloneMyStack :: IO StackSnapshot
-- | Clone the stack of a thread identified by its 'ThreadId'
--
-- @since 2.16.0.0
cloneThreadStack :: ThreadId -> IO StackSnapshot
-- | Represetation for the source location where a return frame was pushed on the stack.
-- This happens every time when a @case ... of@ scrutinee is evaluated.
data StackEntry = StackEntry
{ functionName :: String,
moduleName :: String,
srcLoc :: String,
closureType :: Word
}
deriving (Show, Eq)
-- | Decode a 'StackSnapshot' to a stacktrace (a list of 'StackEntry').
-- The stacktrace is created from return frames with according 'InfoProv'
-- entries. To generate them, use the GHC flag @-finfo-table-map@. If there are
-- no 'InfoProv' entries, an empty list is returned.
--
-- @since 2.16.0.0
decode :: StackSnapshot -> IO [StackEntry]
Source Notes
The approach is documented in several source notes. The reviewer might want to read them after understanding the API described before.
- Stack Cloning
- Stack Decoding
- Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)
Tests
The tests provide working examples of the API usage. Most significant is testsuite/tests/rts/decodeMyStack.hs
.
The initial design was described by @bgamari in this comment: #18741 (comment 342188)
To get meaningful source locations from GHC's libraries, you need to use -finfo-table-map
for them in Hadrian.
For source locations in user code, it's sufficient to compile it with -finfo-table-map
(no special build of GHC needed).
ToDo
-
(Not possible)cloneStack#
should be an ordinary C function (no primop) -
Update Notes to describe the C interface -
Decide if cloning and decoding should be two separate operations or not -
Use Array#
instead ofMutableArray#
to return decoding results -
Return an array of Ptr InfoProvEnt
on decoding (dolookupIPE
directly in the RTS while decoding)