hgdbmi (empty) → 0.1
raw patch · 5 files changed
+338/−0 lines, 5 filesdep +basedep +parsecdep +processsetup-changed
Dependencies added: base, parsec, process, unix
Files
- GDBMI.hs +198/−0
- LICENSE +26/−0
- Setup.lhs +4/−0
- Test.hs +89/−0
- hgdbmi.cabal +21/−0
+ GDBMI.hs view
@@ -0,0 +1,198 @@+-- hgdbmi: a Haskell interface to GDB/MI.+-- Copyright (C) 2008 Evan Martin <martine@danga.com>++-- |GDB\/MI lets programs drive GDB. It can be used, for example, by GDB+-- frontends. This module wraps attaching GDB to a process and parsing the+-- (surprisingly complicated) GDB\/MI output.++module GDBMI (+ GDB,+ attach,+ detach,++ runCommand,+ GDBCommand(..),+ MIOutput(..),+ MIOOB(..),+ MIResult(..),+ MIKeyVal,+ MIValue(..),++ parse+) where++import System.IO+import System.Posix.IO (createPipe, fdToHandle)+import System.Posix.Types (ProcessID)+import System.Process (runProcess, ProcessHandle)+import Text.ParserCombinators.Parsec hiding (parse)+import qualified Text.ParserCombinators.Parsec as Parsec (parse)++-- |A connection to a GDB process.+data GDB = GDB {+ gdbPid :: ProcessHandle,+ gdbCommand :: Handle,+ gdbResponse :: Handle+}++-- |A GDB command. CLICommand is any command you'd normally type at the GDB+-- prompt. MICommand are more machine-parsing friendly commands; see the+-- GDB\/MI docs for details.+data GDBCommand = CLICommand String+ | MICommand String -- ^TODO: expand this to support arguments.++-- |The output of running a GDB command. Output is a collection of Out-Of-Band+-- messages (such as logging information) and an optional final result.+data MIOutput = MIOutput [MIOOB] (Maybe MIResult) deriving Show++-- |The type of OOB mesages. (TODO: many of these aren't properly parsed yet.)+data MIOOB =+ MIStatus String -- ^Contains on-going status information about the progress+ -- of a slow operation.+ | MIExec String -- ^Contains asynchronous state change on the target+ -- (stopped, started, disappeared).+ | MINotify String -- ^Contains supplementary information that the client+ -- should handle (e.g., a new breakpoint information).+ | MIConsole String -- ^Output that should be displayed as is in the console.+ -- It is the textual response to a CLI command.+ | MITarget String -- ^The output produced by the target program.+ | MILog String -- ^Output text coming from GDB's internals, for instance+ -- messages that should be displayed as part of an error+ -- log.+ deriving (Eq, Show)++-- |The type of the GDB result. (TODO: many result types aren't implemented+-- yet.)+data MIResult =+ MIDone [MIKeyVal] -- ^The synchronous operation was successful,+ -- along with potential key-value return data.+ | MIError String -- ^The operation failed. The string contains the+ -- corresponding error message. + deriving (Eq, Show)++-- |A key-value pair output from GDB.+type MIKeyVal = (String, MIValue)++-- |The type of a GDB "value", used in the output of structured data.+data MIValue =+ MIString String+ | MITuple [MIKeyVal]+ deriving (Eq, Show)++-- |Attach to a process, returning an error or the 'GDB' connection and its+-- initial output.+attach :: Maybe FilePath -- ^Working directory for GDB. (Important if the+ -- process has loaded libraries from relative paths.)+ -> ProcessID+ -> IO (Either String (GDB, MIOutput))+attach workdir pid = do+ (commandR, commandW) <- createPipe >>= asHandles+ (responseR, responseW) <- createPipe >>= asHandles+ phandle <- runProcess "gdb" ["--interpreter", "mi", "-p", show pid]+ workdir Nothing{-env-}+ (Just commandR) -- stdin+ (Just responseW) -- stdout+ Nothing -- stderr+ mapM_ (`hSetBuffering` LineBuffering) [commandW, responseR]+ let gdb = GDB phandle commandW responseR+ resp <- readResponse gdb+ case resp of+ Left err -> return $ Left err+ Right ok -> return $ Right (gdb, ok)+ where+ asHandles (f1, f2) = do+ h1 <- fdToHandle f1; h2 <- fdToHandle f2; return (h1, h2)++-- |Close a 'GDB' connection.+detach :: GDB -> IO ()+-- TODO: we don't examine the result code, because our parser wants each+-- response to be terminated by the "(gdb) " prompt, which this lacks.+detach gdb = hPutStrLn (gdbCommand gdb) "-gdb-exit"++-- |Run a GDB command.+runCommand :: GDBCommand -> GDB -> IO (Either String MIOutput)+runCommand cmd gdb = do+ hPutStrLn (gdbCommand gdb) (cmdStr cmd)+ readResponse gdb+ where+ cmdStr (CLICommand str) = str+ cmdStr (MICommand str) = '-' : str++readResponse :: GDB -> IO (Either String MIOutput)+readResponse gdb = do+ resp <- readResponseLines+ case parse "output" (unlines resp) of+ Left err -> return $ Left (show err)+ Right out -> return $ Right out+ where+ readResponseLines :: IO [String]+ readResponseLines = do+ line <- hGetLine (gdbResponse gdb)+ if line == "(gdb) "+ then return []+ else do rest <- readResponseLines+ return (line:rest)++-- Our Parsec parsers all start with p_.++-- Parse the main ouptut from GDB.+p_output = do+ oob <- p_oob `sepEndBy` newline+ res <- optionMaybe p_result+ eof+ return $ MIOutput oob res+-- Parse an "OOB" message from GDB.+p_oob = p_console <|> p_log+-- Parse a console OOB message from GDB.+p_console = do char '~'; str <- p_cstring; return $ MIConsole str+-- Parse a log OOB message from GDB.+p_log = do char '&'; str <- p_cstring; return $ MILog str++-- Parse a result message from GDB.+p_result = do+ char '^'+ res <- p_done <|> p_error+ newline; return res+ where+ -- Parse a done result message from GDB.+ p_done = do+ string "done"+ res <- (do char ','; p_keyval `sepBy` char ',') <|> return []+ return $ MIDone res+ -- Parse a error result message from GDB.+ p_error = do+ string "error"+ char ','+ -- XXX: The GDB/MI docs say this should be just a cstring, but my GDB+ -- doesn't agree.+ string "msg=" -- Hack here; perhaps it's really like the "done" output?+ err <- p_cstring+ return $ MIError err++-- Parse a key=val output ("result") from GDB.+p_keyval = do var <- p_var; char '='; val <- p_val; return $ (var, val) where+ p_var = many1 (letter <|> char '-') -- XXX: this is underspecified.+ p_val = p_const <|> p_tuple+ p_const = do str <- p_cstring; return $ MIString str+ p_tuple = do+ vals <- tuplewrap $ p_keyval `sepBy` char ','+ return $ MITuple vals+ -- It's unclear why they have []-style tuples and {}-style tuples...+ tuplewrap p = between (char '{') (char '}') p+ <|> between (char '[') (char ']') p++-- Parse a C-style string (underspecified by the GDB manual).+p_cstring = between (char '"') (char '"') (many p_cchar) where+ p_cchar = p_cbackslash+ <|> noneOf "\""+ p_cbackslash = do+ char '\\'+ c <- anyChar+ case c of+ '\\' -> return '\\'+ 'n' -> return '\n'+ '"' -> return '"'+ _ -> fail $ "unknown backslash escape: " ++ show c++-- |An interface to the output parser. Just used for testing.+parse = Parsec.parse p_output
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) 2008 Evan Martin <martine@danga.com>+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:+* Redistributions of source code must retain the above copyright notice,+ this list of conditions and the following disclaimer.+* Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+* Neither the name of the author nor the names of contributors may be+ used to endorse or promote products derived from this software without+ specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++> import Distribution.Simple+> main = defaultMain
+ Test.hs view
@@ -0,0 +1,89 @@+-- hgdbmi: a Haskell interface to GDB/MI.+-- Copyright (C) 2008 Evan Martin <martine@danga.com>++import GDBMI+import Test.HUnit++startupText =+ "~\"GNU gdb 6.7.1-debian\\n\"\n" +++ "~\"Copyright (C) 2007 Free Software Foundation, Inc.\\n\"\n" +++ "~\"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\\n\"\n" +++ "~\"This is free software: you are free to change and redistribute it.\\n\"\n" +++ "~\"There is NO WARRANTY, to the extent permitted by law. Type \\\"show copying\\\"\\n\"\n" +++ "~\"and \\\"show warranty\\\" for details.\\n\"\n" +++ "~\"This GDB was configured as \\\"i486-linux-gnu\\\".\\n\"\n" +++ "~\"Attaching to process 8902\\n\"\n" +++ "~\"Reading symbols from /home/martine/projects/c-repl/dist/build/child...\"\n" +++ "~\"done.\\n\"\n" +++ "~\"Using host libthread_db library \\\"/lib/tls/i686/cmov/libthread_db.so.1\\\".\\n\"\n" +++ "~\"Reading symbols from /lib/tls/i686/cmov/libdl.so.2...\"\n" +++ "~\"done.\\n\"\n" +++ "~\"Loaded symbols for /lib/tls/i686/cmov/libdl.so.2\\n\"\n" +++ "~\"Reading symbols from /lib/tls/i686/cmov/libc.so.6...\"\n" +++ "~\"done.\\n\"\n" +++ "~\"Loaded symbols for /lib/tls/i686/cmov/libc.so.6\\n\"\n" +++ "~\"Reading symbols from /lib/ld-linux.so.2...\"\n" +++ "~\"done.\\n\"\n" +++ "~\"Loaded symbols for /lib/ld-linux.so.2\\n\"\n" +++ "~\"Reading symbols from /home/martine/projects/c-repl/.c-repl/dl1.so...\"\n" +++ "~\"done.\\n\"\n" +++ "~\"Loaded symbols for ./.c-repl/dl1.so\\n\"\n"++testStartup = test $+ case GDBMI.parse "startup" startupText of+ Left err -> assertFailure $ show err+ Right (MIOutput info result) -> do+ assertEqual "oob line count" 23 (length info)+ assertEqual "oob first line"+ (MIConsole "GNU gdb 6.7.1-debian\n") (head info)++printText =+ "&\"p x\\n\"\n" +++ "~\"$1 = 3\"\n" +++ "~\"\n\"\n" +++ "^done,thread-id=\"0\",frame={addr=\"0xb7f0a410\",func=\"__kernel_vsyscall\",args=[]}\n"++testPrint = test $+ case GDBMI.parse "parse 'p x' output" printText of+ Left err -> assertFailure $ show err+ Right (MIOutput info result) -> do+ assertEqual "oob line count" 3 (length info)+ assertEqual "oob first line" (MILog "p x\n") (head info)+ case result of+ Just (MIDone tuples) -> do+ assertEqual "done tuple #1" ("thread-id", MIString "0") (head tuples)+ case lookup "frame" tuples of+ Just (MITuple frame) -> + assertEqual "done tuple #4" ("args", MITuple []) (last frame)+ _ -> assertFailure $ "couldn't find 'frame'"+ _ ->+ assertFailure $ "expected done result, got " ++ show result++errorPrintText =+ "&\"p y\\n\"\n" +++ "&\"No symbol \\\"y\\\" in current context.\\n\"\n" +++ "^error,msg=\"No symbol \\\"y\\\" in current context.\"\n"++testErrorPrint = test $+ case GDBMI.parse "parse 'p y' error output" errorPrintText of+ Left err -> assertFailure $ show err+ Right (MIOutput info result) -> do+ assertEqual "oob line count" 2 (length info)+ assertEqual "oob first line" (MILog "p y\n") (head info)+ assertEqual "result"+ (Just (MIError "No symbol \"y\" in current context.")) result++varCreateText = "^done,name=\"vx\",numchild=\"0\",value=\"4\",type=\"int\"\n"++testVarCreate = test $+ case GDBMI.parse "parse 'var-create' output" varCreateText of+ Left err -> assertFailure $ show err+ Right (MIOutput info result) -> do+ assertEqual "no oob info" 0 (length info)+ assertEqual "result"+ (Just (MIDone [("name", MIString "vx"), ("numchild", MIString "0"),+ ("value", MIString "4"), ("type", MIString "int")]))+ result++main =+ runTestTT $ TestList [testStartup, testPrint, testErrorPrint, testVarCreate]
+ hgdbmi.cabal view
@@ -0,0 +1,21 @@+Cabal-Version: >= 1.2+Build-Type: Simple+Name: hgdbmi+Version: 0.1+Synopsis: GDBMI interface (program-driven control of GDB)+Description:+ GDB\/MI lets programs drive GDB. It can be used, for example, by GDB+ frontends. This module wraps attaching GDB to a process and parsing the+ (surprisingly complicated) GDB\/MI output.+Category: Development+License: BSD3+License-File: LICENSE+Author: Evan Martin+Maintainer: martine@danga.com+Copyright: (c) 2008 Evan Martin <martine@danga.com>+Homepage: http://neugierig.org/software/darcs/browse/?r=hgdbmi;a=summary+Extra-Source-Files: Test.hs++Library+ Build-Depends: base, parsec, process, unix+ Exposed-Modules: GDBMI