ghc-internal-9.1002.0: src/GHC/Internal/Exception/Backtrace.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
module GHC.Internal.Exception.Backtrace
( -- * Backtrace mechanisms
BacktraceMechanism(..)
, getBacktraceMechanismState
, setBacktraceMechanismState
-- * Collecting backtraces
, Backtraces
, displayBacktraces
, collectBacktraces
) where
import GHC.Internal.Base
import GHC.Internal.Data.OldList
import GHC.Internal.IORef
import GHC.Internal.IO.Unsafe (unsafePerformIO)
import GHC.Internal.Exception.Context
import GHC.Internal.Ptr
import GHC.Internal.Stack.Types as GHC.Stack (CallStack)
import qualified GHC.Internal.Stack as HCS
import qualified GHC.Internal.ExecutionStack as ExecStack
import qualified GHC.Internal.ExecutionStack.Internal as ExecStack
import qualified GHC.Internal.Stack.CloneStack as CloneStack
import qualified GHC.Internal.Stack.CCS as CCS
-- | How to collect a backtrace when an exception is thrown.
data BacktraceMechanism
-- | collect cost-centre stack backtraces (only available when built with profiling)
= CostCentreBacktrace
-- | collect 'HasCallStack' backtraces
| HasCallStackBacktrace
-- | collect backtraces via native execution stack unwinding (e.g. using DWARF debug information)
| ExecutionBacktrace
-- | collect backtraces from Info Table Provenance Entries
| IPEBacktrace
data EnabledBacktraceMechanisms =
EnabledBacktraceMechanisms
{ costCentreBacktraceEnabled :: !Bool
, hasCallStackBacktraceEnabled :: !Bool
, executionBacktraceEnabled :: !Bool
, ipeBacktraceEnabled :: !Bool
}
defaultEnabledBacktraceMechanisms :: EnabledBacktraceMechanisms
defaultEnabledBacktraceMechanisms = EnabledBacktraceMechanisms
{ costCentreBacktraceEnabled = False
, hasCallStackBacktraceEnabled = True
, executionBacktraceEnabled = False
, ipeBacktraceEnabled = False
}
backtraceMechanismEnabled :: BacktraceMechanism -> EnabledBacktraceMechanisms -> Bool
backtraceMechanismEnabled bm =
case bm of
CostCentreBacktrace -> costCentreBacktraceEnabled
HasCallStackBacktrace -> hasCallStackBacktraceEnabled
ExecutionBacktrace -> executionBacktraceEnabled
IPEBacktrace -> ipeBacktraceEnabled
setBacktraceMechanismEnabled
:: BacktraceMechanism -> Bool
-> EnabledBacktraceMechanisms
-> EnabledBacktraceMechanisms
setBacktraceMechanismEnabled bm enabled en =
case bm of
CostCentreBacktrace -> en { costCentreBacktraceEnabled = enabled }
HasCallStackBacktrace -> en { hasCallStackBacktraceEnabled = enabled }
ExecutionBacktrace -> en { executionBacktraceEnabled = enabled }
IPEBacktrace -> en { ipeBacktraceEnabled = enabled }
enabledBacktraceMechanismsRef :: IORef EnabledBacktraceMechanisms
enabledBacktraceMechanismsRef =
unsafePerformIO $ newIORef defaultEnabledBacktraceMechanisms
{-# NOINLINE enabledBacktraceMechanismsRef #-}
-- | Returns the currently enabled 'BacktraceMechanism's.
getEnabledBacktraceMechanisms :: IO EnabledBacktraceMechanisms
getEnabledBacktraceMechanisms = readIORef enabledBacktraceMechanismsRef
-- | Will the given 'BacktraceMechanism' be used when collecting
-- backtraces?
getBacktraceMechanismState :: BacktraceMechanism -> IO Bool
getBacktraceMechanismState bm =
backtraceMechanismEnabled bm `fmap` getEnabledBacktraceMechanisms
-- | Set whether the given 'BacktraceMechanism' will be used when collecting
-- backtraces?
setBacktraceMechanismState :: BacktraceMechanism -> Bool -> IO ()
setBacktraceMechanismState bm enabled = do
_ <- atomicModifyIORef'_ enabledBacktraceMechanismsRef (setBacktraceMechanismEnabled bm enabled)
return ()
-- | A collection of backtraces.
data Backtraces =
Backtraces {
btrCostCentre :: Maybe (Ptr CCS.CostCentreStack),
btrHasCallStack :: Maybe HCS.CallStack,
btrExecutionStack :: Maybe [ExecStack.Location],
btrIpe :: Maybe [CloneStack.StackEntry]
}
-- | Render a set of backtraces to a human-readable string.
displayBacktraces :: Backtraces -> String
displayBacktraces bts = concat
[ displayOne "Cost-centre stack backtrace" btrCostCentre displayCc
, displayOne "Native stack backtrace" btrExecutionStack displayExec
, displayOne "IPE backtrace" btrIpe displayIpe
, displayOne "HasCallStack backtrace" btrHasCallStack displayHsc
]
where
indent :: Int -> String -> String
indent n s = replicate n ' ' ++ s
-- The unsafePerformIO here is safe as we don't currently unload cost-centres.
displayCc = unlines . map (indent 2) . unsafePerformIO . CCS.ccsToStrings
displayExec = unlines . map (indent 2 . flip ExecStack.showLocation "")
displayIpe = unlines . map (indent 2 . CloneStack.prettyStackEntry)
displayHsc = unlines . map (indent 2 . prettyCallSite) . HCS.getCallStack
where prettyCallSite (f, loc) = f ++ ", called at " ++ HCS.prettySrcLoc loc
displayOne :: String -> (Backtraces -> Maybe rep) -> (rep -> String) -> String
displayOne label getBt displ
| Just bt <- getBt bts = concat [label, ":\n", displ bt]
| otherwise = ""
instance ExceptionAnnotation Backtraces where
displayExceptionAnnotation = displayBacktraces
-- | Collect a set of 'Backtraces'.
collectBacktraces :: (?callStack :: CallStack) => IO Backtraces
collectBacktraces = HCS.withFrozenCallStack $ do
getEnabledBacktraceMechanisms >>= collectBacktraces'
collectBacktraces'
:: (?callStack :: CallStack)
=> EnabledBacktraceMechanisms -> IO Backtraces
collectBacktraces' enabled = HCS.withFrozenCallStack $ do
let collect :: BacktraceMechanism -> IO (Maybe a) -> IO (Maybe a)
collect mech f
| backtraceMechanismEnabled mech enabled = f
| otherwise = return Nothing
ccs <- collect CostCentreBacktrace $ do
Just `fmap` CCS.getCurrentCCS ()
exec <- collect ExecutionBacktrace $ do
ExecStack.getStackTrace
ipe <- collect IPEBacktrace $ do
stack <- CloneStack.cloneMyStack
stackEntries <- CloneStack.decode stack
return (Just stackEntries)
hcs <- collect HasCallStackBacktrace $ do
return (Just ?callStack)
return (Backtraces { btrCostCentre = ccs
, btrHasCallStack = hcs
, btrExecutionStack = exec
, btrIpe = ipe
})