diff --git a/client/ghc-debug-client.cabal b/client/ghc-debug-client.cabal index 7d509cd..a39b8bf 100644 --- a/client/ghc-debug-client.cabal +++ b/client/ghc-debug-client.cabal @@ -19,6 +19,7 @@ library network >=2.6 && <2.7, unordered-containers, ghc-debug-common, cpu, - dwarfadt, dwarf-el, text, process, filepath, directory + dwarfadt, dwarf-el, text, process, filepath, directory, + mtl hs-source-dirs: src - default-language: Haskell2010 \ No newline at end of file + default-language: Haskell2010 diff --git a/client/src/GHC/Debug/Client.hs b/client/src/GHC/Debug/Client.hs index c1b4d7e..ae64a5c 100644 --- a/client/src/GHC/Debug/Client.hs +++ b/client/src/GHC/Debug/Client.hs @@ -1,10 +1,15 @@ +{-# LANGUAGE GADTs #-} + module GHC.Debug.Client ( Debuggee + , DebuggeeAction + , applyDebuggeeAction , withDebuggee , withDebuggeeSocket , pauseDebuggee , request , Request(..) + , getCurrentFrame , getInfoTblPtr , decodeClosure , decodeStack @@ -26,6 +31,7 @@ module GHC.Debug.Client import Control.Concurrent import Control.Exception import Control.Monad +import Control.Monad.State.Lazy import GHC.Debug.Types import GHC.Debug.Decode import GHC.Debug.Decode.Stack @@ -38,6 +44,7 @@ import System.Endian import Data.Foldable import Data.Coerce import Data.Bitraversable +import Data.Word (Word32) import qualified Data.Dwarf as Dwarf @@ -55,11 +62,18 @@ import System.Directory import Text.Printf data Debuggee = Debuggee { debuggeeHdl :: Handle - , debuggeeInfoTblEnv :: MVar (HM.HashMap InfoTablePtr RawInfoTable) + , debuggeeInfoTblEnv :: HM.HashMap InfoTablePtr RawInfoTable , debuggeeDwarf :: Maybe Dwarf , debuggeeFilename :: FilePath + , debuggeeFrame :: Word32 } +type DebuggeeAction a = StateT Debuggee IO a + + +applyDebuggeeAction :: Debuggee -> DebuggeeAction a -> IO a +applyDebuggeeAction = flip evalStateT + debuggeeProcess :: FilePath -> FilePath -> IO CreateProcess debuggeeProcess exe sockName = do @@ -69,7 +83,7 @@ debuggeeProcess exe sockName = do -- | Open a debuggee, this will also read the DWARF information withDebuggee :: FilePath -- ^ path to executable - -> (Debuggee -> IO a) + -> DebuggeeAction a -> IO a withDebuggee exeName action = do let sockName = "/tmp/ghc-debug2" @@ -86,33 +100,45 @@ withDebuggee exeName action = do withDebuggeeSocket :: FilePath -- ^ executable name of the debuggee -> FilePath -- ^ debuggee's socket location -> Maybe Dwarf - -> (Debuggee -> IO a) + -> DebuggeeAction a -> IO a withDebuggeeSocket exeName sockName mdwarf action = do s <- socket AF_UNIX Stream defaultProtocol connect s (SockAddrUnix sockName) hdl <- socketToHandle s ReadWriteMode - infoTableEnv <- newMVar mempty - action (Debuggee hdl infoTableEnv mdwarf exeName) + evalStateT action (Debuggee hdl mempty mdwarf exeName 0) -- | Send a request to a 'Debuggee' paused with 'pauseDebuggee'. -request :: Debuggee -> Request resp -> IO resp -request d req = doRequest (debuggeeHdl d) req +request :: Request resp -> DebuggeeAction resp +request req = do + hdl <- gets debuggeeHdl + payload <- liftIO $ doRequest hdl req + -- if we did a successful pause, the payload contains the current frame + -- number + case req of + RequestPause -> modify' $ \d -> d { debuggeeFrame = payload } + _ -> return () + return payload -lookupInfoTable :: Debuggee -> RawClosure -> IO (RawInfoTable, RawClosure) -lookupInfoTable d rc = do +lookupInfoTable :: RawClosure -> DebuggeeAction (RawInfoTable, RawClosure) +lookupInfoTable rc = do let ptr = getInfoTblPtr rc - itblEnv <- readMVar (debuggeeInfoTblEnv d) + itblEnv <- gets debuggeeInfoTblEnv case HM.lookup ptr itblEnv of Nothing -> do - [itbl] <- request d (RequestInfoTables [ptr]) - modifyMVar_ (debuggeeInfoTblEnv d) $ return . HM.insert ptr itbl + [itbl] <- request (RequestInfoTables [ptr]) + infoTblEnv <- gets debuggeeInfoTblEnv + modify' $ \s -> s { debuggeeInfoTblEnv = HM.insert ptr itbl infoTblEnv } return (itbl, rc) Just itbl -> return (itbl, rc) -pauseDebuggee :: Debuggee -> IO a -> IO a -pauseDebuggee d = - bracket_ (void $ request d RequestPause) (void $ request d RequestResume) +pauseDebuggee :: DebuggeeAction a -> DebuggeeAction a +pauseDebuggee action = do + -- TODO: replace poor-mans bracket_ with proper implementation for StateT + request RequestPause + rc <- action + request RequestResume + return rc getDwarfInfo :: FilePath -> IO Dwarf getDwarfInfo fn = do @@ -121,10 +147,12 @@ getDwarfInfo fn = do -- print $ DwarfPretty.dwarf dwarf return dwarf -lookupDwarf :: Debuggee -> InfoTablePtr -> Maybe ([FilePath], Int, Int) -lookupDwarf d (InfoTablePtr w) = do - (Dwarf units) <- debuggeeDwarf d - asum (map (lookupDwarfUnit (fromBE64 w)) units) +lookupDwarf :: InfoTablePtr -> DebuggeeAction (Maybe ([FilePath], Int, Int)) +lookupDwarf (InfoTablePtr w) = do + mDwarf <- gets debuggeeDwarf + case mDwarf of + Nothing -> return Nothing + Just (Dwarf units) -> return $ asum (map (lookupDwarfUnit (fromBE64 w)) units) lookupDwarfUnit :: Word64 -> Boxed CompilationUnit -> Maybe ([FilePath], Int, Int) lookupDwarfUnit w (Boxed _ cu) = do @@ -158,17 +186,21 @@ lookupDwarfLine w Nothing (d, nd) = do else Nothing lookupDwarfLine _ (Just r) _ = Just r -showFileSnippet :: Debuggee -> ([FilePath], Int, Int) -> IO () -showFileSnippet d (fps, l, c) = go fps +showFileSnippet :: ([FilePath], Int, Int) -> DebuggeeAction () +showFileSnippet (fps, l, c) = do + dbgFilename <- gets debuggeeFilename + liftIO $ go dbgFilename fps where - go [] = putStrLn ("No files could be found: " ++ show fps) - go (fp: fps) = do - exists <- doesFileExist fp + go :: FilePath -> [FilePath] -> IO () + go _ [] = putStrLn ("No files could be found: " ++ show fps) + go dbgFilename (fp:fps) = do + exists <- liftIO $ doesFileExist $ fp -- get file modtime if not exists - then go fps + then go dbgFilename fps else do - fp `warnIfNewer` (debuggeeFilename d) + -- TODO: get the modtime of debuggee above + fp `warnIfNewer` dbgFilename src <- zip [1..] . lines <$> readFile fp let ctx = take 10 (drop (max (l - 5) 0) src) putStrLn (fp <> ":" <> show l <> ":" <> show c) @@ -176,45 +208,44 @@ showFileSnippet d (fps, l, c) = go fps let sn = show n in putStrLn (sn <> replicate (5 - length sn) ' ' <> l)) ctx -dereferenceClosure :: Debuggee -> ClosurePtr -> IO Closure -dereferenceClosure d c = head <$> dereferenceClosures d [c] +dereferenceClosure :: ClosurePtr -> DebuggeeAction Closure +dereferenceClosure c = head <$> dereferenceClosures [c] -dereferenceClosures :: Debuggee -> [ClosurePtr] -> IO [Closure] -dereferenceClosures d cs = do - raw_cs <- request d (RequestClosures cs) +dereferenceClosures :: [ClosurePtr] -> DebuggeeAction [Closure] +dereferenceClosures cs = do + raw_cs <- request (RequestClosures cs) let its = map getInfoTblPtr raw_cs --print $ map (lookupDwarf d) its - raw_its <- request d (RequestInfoTables its) + raw_its <- request (RequestInfoTables its) return $ map (uncurry decodeClosure) (zip raw_its (zip cs raw_cs)) -dereferenceStack :: Debuggee -> StackCont -> IO Stack -dereferenceStack d (StackCont stack) = do - print stack - i <- lookupInfoTable d (coerce stack) +dereferenceStack :: StackCont -> DebuggeeAction Stack +dereferenceStack (StackCont stack) = do + liftIO $ print stack + i <- lookupInfoTable (coerce stack) let st_it = decodeInfoTable . fst $ i - print i - print st_it - bt <- request d (RequestBitmap (getInfoTblPtr (coerce stack))) + liftIO $ print i + liftIO $ print st_it + bt <- request (RequestBitmap (getInfoTblPtr (coerce stack))) let decoded_stack = decodeStack stack st_it bt - print decoded_stack + liftIO $ print decoded_stack return decoded_stack -dereferenceConDesc :: Debuggee -> ClosurePtr -> IO ConstrDesc -dereferenceConDesc d i = do - request d (RequestConstrDesc i) +dereferenceConDesc :: ClosurePtr -> DebuggeeAction ConstrDesc +dereferenceConDesc i = request (RequestConstrDesc i) -fullTraversal :: Debuggee -> ClosurePtr -> IO UClosure -fullTraversal d c = do - dc <- dereferenceClosure d c - print dc - MkFix1 <$> tritraverse (dereferenceConDesc d) (fullStackTraversal d) (fullTraversal d) dc +fullTraversal :: ClosurePtr -> DebuggeeAction UClosure +fullTraversal c = do + dc <- dereferenceClosure c + liftIO $ print dc + MkFix1 <$> tritraverse dereferenceConDesc fullStackTraversal fullTraversal dc -fullStackTraversal :: Debuggee -> StackCont -> IO UStack -fullStackTraversal d sc = do - ds <- dereferenceStack d sc - print ds - MkFix2 <$> traverse (fullTraversal d) ds +fullStackTraversal :: StackCont -> DebuggeeAction UStack +fullStackTraversal sc = do + ds <- dereferenceStack sc + liftIO $ print ds + MkFix2 <$> traverse fullTraversal ds -- | Print a warning if source file (first argument) is newer than the binary (second argument) warnIfNewer :: FilePath -> FilePath -> IO () @@ -228,3 +259,7 @@ warnIfNewer fpSrc fpBin = do fpSrc fpBin else return () + +-- | Return the current frame number +getCurrentFrame :: DebuggeeAction Word32 +getCurrentFrame = gets debuggeeFrame diff --git a/common/src/GHC/Debug/Types.hs b/common/src/GHC/Debug/Types.hs index 183d451..40e480f 100644 --- a/common/src/GHC/Debug/Types.hs +++ b/common/src/GHC/Debug/Types.hs @@ -35,8 +35,8 @@ import GHC.Debug.Types.Ptr as T data Request a where -- | Request protocol version RequestVersion :: Request Word32 - -- | Pause the debuggee. - RequestPause :: Request () + -- | Pause the debuggee, get number of current pause frame. + RequestPause :: Request Word32 -- | Resume the debuggee. RequestResume :: Request () -- | Request the debuggee's root pointers. @@ -136,7 +136,7 @@ putRequest (RequestFindPtr c) = getResponse :: Request a -> Get a getResponse RequestVersion = getWord32be -getResponse RequestPause = get +getResponse RequestPause = getWord32be getResponse RequestResume = get getResponse RequestRoots = many get getResponse (RequestClosures _) = many getRawClosure diff --git a/ghc-vis/src/GHC/Vis.hs b/ghc-vis/src/GHC/Vis.hs index 1df12fe..5da6ab8 100644 --- a/ghc-vis/src/GHC/Vis.hs +++ b/ghc-vis/src/GHC/Vis.hs @@ -475,8 +475,9 @@ react dbg window canvas = do derefBox :: Debuggee -> DerefFunction derefBox dbg cp = do - c <- dereferenceClosure dbg cp - tritraverse (dereferenceConDesc dbg) pure pure c + let apply = applyDebuggeeAction dbg + c <- apply $ dereferenceClosure cp + tritraverse (\x -> apply $ dereferenceConDesc x) pure pure c runCorrect :: MonadIO m => (View -> f) -> m f runCorrect f = do diff --git a/stub/Test.hs b/stub/Test.hs index 18cf478..d0a6616 100644 --- a/stub/Test.hs +++ b/stub/Test.hs @@ -27,7 +27,7 @@ main :: IO () main = do start let !y = Data.Sequence.fromList [1..5] - -- let !y = [1..5] + let !y = [1..5] performGC saveClosures [Box y] print "start" diff --git a/stub/cbits/stub.cpp b/stub/cbits/stub.cpp index e99691d..93bfa0b 100644 --- a/stub/cbits/stub.cpp +++ b/stub/cbits/stub.cpp @@ -126,12 +126,12 @@ class Response { // Then status this->sock.write((char *) &status_payload, sizeof(uint16_t)); // then the body, usually empty - trace("FLUSHING(%lu)( ", len); - for (int i = 0; i < len; i++) - { - trace("%02X", buf[i]); - } - trace("\n"); + trace("FLUSHING(%lu)( ", len); + for (int i = 0; i < len; i++) + { + trace("%02X", buf[i]); + } + trace("\n"); this->sock.write(this->buf, len); this->tail = this->buf; } @@ -187,6 +187,8 @@ class Response { }; static bool paused = false; +// track how often the target was paused +static uint32_t num_pause_frame = 0; static RtsPaused r_paused; static Response * r_poll_pause_resp = NULL; @@ -199,6 +201,7 @@ void pause_mutator() { r_poll_pause_resp->finish(RESP_OKAY); } paused = true; + ++num_pause_frame; } extern "C" @@ -282,9 +285,13 @@ static int handle_command(Socket& sock, const char *buf, uint32_t cmd_len) { trace("PAUSE: %d", paused); if (paused) { trace("ALREADY"); + // even though we are already paused we tell the callee what pause + // frame we are in + resp.write(htonl(num_pause_frame)); resp.finish(RESP_ALREADY_PAUSED); } else { pause_mutator(); + resp.write(htonl(num_pause_frame)); resp.finish(RESP_OKAY); } break; diff --git a/test/Test.hs b/test/Test.hs index f0fd3f9..8325af6 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -4,11 +4,13 @@ import GHC.Debug.Client import GHC.Debug.Types.Graph import Control.Monad +import Control.Monad.State.Lazy (liftIO, get, gets) import Debug.Trace import Control.Exception import Control.Concurrent import Data.Bitraversable import GHC.Vis +import Text.Printf prog = "/home/matt/ghc-debug/dist-newstyle/build/x86_64-linux/ghc-8.9.0.20190806/ghc-debug-stub-0.1.0.0/x/debug-test/build/debug-test/debug-test" @@ -16,48 +18,54 @@ prog2 = "/home/matt/ghc-debug/dist-newstyle/build/x86_64-linux/ghc-8.9.0.2019080 --main = withDebuggeeSocket "/tmp/ghc-debug" Nothing p14 main = withDebuggee prog2 p12 ---main = withDebuggee prog p15 +-- main = withDebuggee prog p15 -- Test pause/resume -p1 d = pauseDebuggee d (void $ getChar) - +p1 :: DebuggeeAction () +p1 = do + pauseDebuggee (liftIO getChar) + return () -- Testing error codes -p2 d = do - request d RequestPause - print "req1" - request d RequestPause - request d RequestPause - request d RequestPause +p2 = do + request RequestPause + liftIO $ print "req1" + request RequestPause + request RequestPause + request RequestPause -- Testing get version -p3 d = do - request d RequestVersion >>= print - request d RequestPause - request d RequestResume +p3 :: DebuggeeAction () +p3 = do + version <- request RequestVersion + liftIO $ print version + request RequestPause + request RequestResume + return () -- Testing get roots -p4 d = do - request d RequestPause - request d RequestRoots >>= print +p4 = do + request RequestPause + roots <- request RequestRoots + liftIO $ print roots -- request closures -p5 d = do - request d RequestPause - r <- request d RequestRoots - print (length r) +p5 = do + request RequestPause + r <- request RequestRoots + liftIO $ print (length r) forM_ [0..length r - 1] $ \i -> do let cs = [r !! i] - print cs - dereferenceClosures d cs + liftIO $ print cs + dereferenceClosures cs -- request all closures -p5a d = do - request d RequestPause - rs <- request d RequestRoots - print rs - cs <- request d (RequestClosures rs) - print cs +p5a = do + request RequestPause + rs <- request RequestRoots + liftIO $ print rs + cs <- request (RequestClosures rs) + liftIO $ print cs {- let it = getInfoTblPtr c print it @@ -68,117 +76,141 @@ p5a d = do -} -- request all closures -p5b d = do - request d RequestPause - rs <- request d RequestRoots - dereferenceClosures d rs - +p5b = do + request RequestPause + rs <- request RequestRoots + dereferenceClosures rs -p6 d = do +p6 = do -- This blocks until a pause - request d RequestPoll - print "POLL" + request RequestPoll + liftIO $ print "POLL" -- Should return already paused - request d RequestPause - print "PAUSE" + request RequestPause + liftIO $ print "PAUSE" -- Now unpause - request d RequestResume - print "RESUME" + request RequestResume + liftIO $ print "RESUME" -- Request saved objects -p7 d = do - request d RequestPause - request d RequestSavedObjects >>= print +p7 = do + request RequestPause + objs <- request RequestSavedObjects + liftIO $ print objs -- request saved objects -p8 d = do - request d RequestPause - sos <- request d RequestSavedObjects - dereferenceClosures d sos +p8 = do + request RequestPause + sos <- request RequestSavedObjects + dereferenceClosures sos -- Using findPtr -p9 d = do - request d RequestPause - (s:_) <- request d RequestSavedObjects - print s - sos <- request d (RequestFindPtr s) - print ("FIND_PTR_RES", sos) - dereferenceClosures d sos +p9 = do + request RequestPause + (s:_) <- request RequestSavedObjects + liftIO $ print s + sos <- request (RequestFindPtr s) + liftIO $ print ("FIND_PTR_RES", sos) + dereferenceClosures sos p10 d = do - request d RequestPause - (s:_) <- request d RequestRoots - request d (RequestFindPtr s) >>= print - -p11 d = do - threadDelay 10000000 - request d RequestPause - ss <- request d RequestSavedObjects - [c] <- request d (RequestClosures ss) + request RequestPause + (s:_) <- request RequestRoots + ptr <- request (RequestFindPtr s) + liftIO $ print ptr + +p11 = do + liftIO $ threadDelay 10000000 + request RequestPause + ss <- request RequestSavedObjects + [c] <- request (RequestClosures ss) let itb = getInfoTblPtr c - case lookupDwarf d itb of - Just r -> showFileSnippet d r + mDwarf <- lookupDwarf itb + case mDwarf of + Just r -> showFileSnippet r Nothing -> return () -p12 d = do - request d RequestPoll - [ss] <- request d RequestSavedObjects - r <- request d (RequestFindPtr ss) - print ss - putStrLn "Retaining closures" - dcs <- dereferenceClosures d r - mapM print dcs - putStrLn "" - cs <- request d (RequestClosures r) +p12 = do + liftIO $ putStrLn "Polling.." + request RequestPoll + liftIO $ putStrLn "Requesting saved objects.." + [ss] <- request RequestSavedObjects + liftIO $ putStrLn "Requesting pointer to saved objects.." + r <- request (RequestFindPtr ss) + liftIO $ print ss + liftIO $ putStrLn "Retaining closures" + dcs <- dereferenceClosures r + liftIO $ mapM print dcs + liftIO $ putStrLn "" + liftIO $ putStrLn "Requesting closures.." + cs <- request (RequestClosures r) forM_ cs $ \c -> do let itb = getInfoTblPtr c - case lookupDwarf d itb of - Just r -> showFileSnippet d r + mDwarf <- lookupDwarf itb + case mDwarf of + Just r -> showFileSnippet r Nothing -> return () - print "Following thunk" + liftIO $ print "Following thunk" let thunk = r !! 2 - r <- request d (RequestFindPtr thunk) - putStrLn "Retaining closures 2" - dereferenceClosures d r >>= mapM print - putStrLn "" - cs <- request d (RequestClosures r) + r <- request (RequestFindPtr thunk) + liftIO $ putStrLn "Retaining closures 2" + closures <- dereferenceClosures r + liftIO $ mapM_ print closures + liftIO $ putStrLn "" + cs <- request (RequestClosures r) forM_ cs $ \c -> do let itb = getInfoTblPtr c - case lookupDwarf d itb of - Just r -> showFileSnippet d r + mDwarf <- lookupDwarf itb + case mDwarf of + Just r -> showFileSnippet r Nothing -> return () -- testing stack decoding -p13 d = do - request d RequestPause - rs <- request d RequestRoots +p13 = do + request RequestPause + rs <- request RequestRoots forM_ rs $ \r -> do - print r - res <- fullTraversal d r - print res + liftIO $ print r + res <- fullTraversal r + liftIO $ print res -p14 d = do - request d RequestPause - rs <- request d RequestSavedObjects +p14 = do + request RequestPause + rs <- request RequestSavedObjects forM_ rs $ \r -> do - print r - res <- fullTraversal d r - print res + liftIO $ print r + res <- fullTraversal r + liftIO $ print res -- Testing ghc-vis -p15 d = do - request d RequestPause - (r:_) <- request d RequestSavedObjects - vis d - view r "saved" - getChar +p15 :: DebuggeeAction Char +p15 = do + request RequestPause + (r:_) <- request RequestSavedObjects + d <- get + liftIO $ vis d + liftIO $ view r "saved" + liftIO $ getChar -- pretty-print graph -p16 d = do - request d RequestPause - [so] <- request d RequestSavedObjects - hg <- buildHeapGraph (derefBox d) 20 () so - putStrLn $ ppHeapGraph hg +p16 = do + request RequestPause + [so] <- request RequestSavedObjects + dbg <- get + hg <- liftIO $ buildHeapGraph (derefBox dbg) 20 () so + liftIO $ putStrLn $ ppHeapGraph hg + +p17 = do + printFrame + replicateM_ 20 $ do + printFrame + request RequestPause + request RequestResume + where + printFrame = do + frame <- getCurrentFrame + liftIO $ putStrLn $ + printf "Current frame number: %d" frame diff --git a/test/ghc-debugger.cabal b/test/ghc-debugger.cabal index ec7d232..ca67470 100644 --- a/test/ghc-debugger.cabal +++ b/test/ghc-debugger.cabal @@ -16,6 +16,5 @@ cabal-version: >=1.10 executable debugger main-is: Test.hs ghc-options: -threaded -debug -g3 - build-depends: base, ghc-debug-client, ghc-debug-common, ghc-heap, ghc-vis, containers + build-depends: base, ghc-debug-client, ghc-debug-common, ghc-heap, ghc-vis, containers, mtl default-language: Haskell2010 -