diff --git a/Makefile b/Makefile index 88f3fd0..970a485 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/madvise EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/chdir EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/mkdir EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/rmdir +EXAMPLE_PROGRAMS += $(EXAMPLE_DST)/writev .PHONY: example-programs example-programs: $(EXAMPLE_PROGRAMS) diff --git a/example-programs/writev.c b/example-programs/writev.c new file mode 100644 index 0000000..480027a --- /dev/null +++ b/example-programs/writev.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +int main(int argc, char *const argv[]) +{ + // example taken from man for writev(2): + char *str0 = "hello "; + char *str1 = "world\n"; + struct iovec iov[2]; + ssize_t nwritten; + + iov[0].iov_base = str0; + iov[0].iov_len = strlen(str0); + iov[1].iov_base = str1; + iov[1].iov_len = strlen(str1); + + nwritten = writev(STDOUT_FILENO, iov, 2); + printf("%ld\n", nwritten); + printf("%p\n", iov[0].iov_base); + printf("%p\n", iov[1].iov_base); + + if (nwritten == -1) { + perror("writev"); + exit(1); + } + exit(0); +} diff --git a/src/System/Hatrace.hs b/src/System/Hatrace.hs index 749184d..eb19ffc 100644 --- a/src/System/Hatrace.hs +++ b/src/System/Hatrace.hs @@ -40,6 +40,8 @@ module System.Hatrace , SyscallExitDetails_faccessat(..) , SyscallEnterDetails_write(..) , SyscallExitDetails_write(..) + , SyscallEnterDetails_writev(..) + , SyscallExitDetails_writev(..) , SyscallEnterDetails_read(..) , SyscallExitDetails_read(..) , SyscallEnterDetails_close(..) @@ -1813,6 +1815,26 @@ instance SyscallExitFormatting SyscallExitDetails_rmdir where syscallExitToFormatted SyscallExitDetails_rmdir{ enterDetail } = (syscallEnterToFormatted enterDetail, NoReturn) +data SyscallEnterDetails_writev = SyscallEnterDetails_writev + { fd :: CInt + , count :: CSize + -- Peeked details + , iovs :: [IovecStruct] + , bufContents :: [ByteString] + } deriving (Eq, Ord, Show) + +instance SyscallEnterFormatting SyscallEnterDetails_writev where + syscallEnterToFormatted SyscallEnterDetails_writev{ fd, iovs, bufContents, count } = + FormattedSyscall "writev" [formatArg fd, argPlaceholder "*iovec", formatArg iovs, formatArg bufContents, formatArg count] + +data SyscallExitDetails_writev = SyscallExitDetails_writev + { enterDetail :: SyscallEnterDetails_writev + , writtenCount :: CSize + } deriving (Eq, Ord, Show) + +instance SyscallExitFormatting SyscallExitDetails_writev where + syscallExitToFormatted SyscallExitDetails_writev{ enterDetail, writtenCount } = + (syscallEnterToFormatted enterDetail, formatReturn writtenCount) data SyscallEnterDetails_lseek = SyscallEnterDetails_lseek { fd :: CInt @@ -1845,6 +1867,7 @@ data DetailedSyscallEnter | DetailedSyscallEnter_access SyscallEnterDetails_access | DetailedSyscallEnter_faccessat SyscallEnterDetails_faccessat | DetailedSyscallEnter_write SyscallEnterDetails_write + | DetailedSyscallEnter_writev SyscallEnterDetails_writev | DetailedSyscallEnter_read SyscallEnterDetails_read | DetailedSyscallEnter_execve SyscallEnterDetails_execve | DetailedSyscallEnter_close SyscallEnterDetails_close @@ -1908,6 +1931,7 @@ data DetailedSyscallExit | DetailedSyscallExit_access SyscallExitDetails_access | DetailedSyscallExit_faccessat SyscallExitDetails_faccessat | DetailedSyscallExit_write SyscallExitDetails_write + | DetailedSyscallExit_writev SyscallExitDetails_writev | DetailedSyscallExit_read SyscallExitDetails_read | DetailedSyscallExit_execve SyscallExitDetails_execve | DetailedSyscallExit_close SyscallExitDetails_close @@ -2067,6 +2091,17 @@ getSyscallEnterDetails syscall syscallArgs pid = let proc = TracedProcess pid in , count = fromIntegral count , bufContents } + Syscall_writev -> do + let SyscallArgs{ arg0 = fd, arg1 = iovPtr, arg2 = iovcnt } = syscallArgs + n = fromIntegral $ min iovcnt $ fromIntegral (maxBound :: Int) + iovs <- peekArray (TracedProcess pid) n (word64ToPtr iovPtr :: Ptr IovecStruct) + contents <- mapM (\IovecStruct{ iov_base = base, iov_len = len } -> peekBytes proc base (fromIntegral len)) iovs + pure $ DetailedSyscallEnter_writev $ SyscallEnterDetails_writev + { fd = fromIntegral fd + , iovs = iovs + , count = fromIntegral iovcnt + , bufContents = contents + } Syscall_read -> do let SyscallArgs{ arg0 = fd, arg1 = bufAddr, arg2 = count } = syscallArgs let bufPtr = word64ToPtr bufAddr @@ -2609,6 +2644,11 @@ getSyscallExitDetails detailedSyscallEnter result pid = pure $ DetailedSyscallExit_write $ SyscallExitDetails_write{ enterDetail, writtenCount = fromIntegral result } + DetailedSyscallEnter_writev + enterDetail@SyscallEnterDetails_writev{} -> do + pure $ DetailedSyscallExit_writev $ + SyscallExitDetails_writev{ enterDetail, writtenCount = fromIntegral result } + DetailedSyscallEnter_access enterDetail@SyscallEnterDetails_access{} -> do pure $ DetailedSyscallExit_access $ @@ -2917,7 +2957,9 @@ peekArray pid size ptr let (tmpPtr, _, _) = BSI.toForeignPtr arrayBytes withForeignPtr tmpPtr (\p -> Foreign.Marshal.Array.peekArray size (castPtr p)) where - elemSize = sizeOf ptr + elemSize = sizeOf (valueOf ptr) + valueOf :: Ptr a -> a + valueOf = undefined readPipeFds :: CPid -> Ptr CInt -> IO (CInt, CInt) readPipeFds pid pipefd = do @@ -3314,6 +3356,8 @@ formatSyscallEnter enterDetails = DetailedSyscallEnter_write details -> syscallEnterToFormatted details + DetailedSyscallEnter_writev details -> syscallEnterToFormatted details + DetailedSyscallEnter_read details -> syscallEnterToFormatted details DetailedSyscallEnter_close details -> syscallEnterToFormatted details @@ -3469,6 +3513,8 @@ formatDetailedSyscallExit detailedExit handleUnimplemented = DetailedSyscallExit_write details -> formatDetails details + DetailedSyscallExit_writev details -> formatDetails details + DetailedSyscallExit_read details -> formatDetails details DetailedSyscallExit_close details -> formatDetails details diff --git a/src/System/Hatrace/Types.hsc b/src/System/Hatrace/Types.hsc index 4bd1908..d646e6e 100644 --- a/src/System/Hatrace/Types.hsc +++ b/src/System/Hatrace/Types.hsc @@ -61,6 +61,7 @@ module System.Hatrace.Types , RLimitStruct(..) , SeekWhence(..) , SeekWhenceKnown(..) + , IovecStruct(..) ) where import Control.Monad (filterM) @@ -68,10 +69,11 @@ import Data.Bits import Data.List (intercalate) import Data.Map (lookup) import Data.Maybe (catMaybes) +import Data.Void (Void) import Foreign.C.Types (CShort(..), CUShort(..), CInt(..), CUInt(..), CLong(..), CULong(..)) import Foreign.Marshal (copyBytes) import Foreign.Marshal.Array (peekArray, pokeArray) -import Foreign.Ptr (plusPtr, castPtr) +import Foreign.Ptr (plusPtr, castPtr, Ptr) import Foreign.ForeignPtr (withForeignPtr, newForeignPtr_) import Foreign.Storable (Storable(..)) import qualified System.Posix.Signals as Signals hiding (inSignalSet) @@ -1771,3 +1773,23 @@ $(deriveEnumTypeClasses ''SeekWhence , ('SeekHole, (#const SEEK_HOLE), "SEEK_HOLE") #endif ]) + +data IovecStruct = IovecStruct + { iov_base :: Ptr Void -- ^ Starting address + , iov_len :: CULong -- ^ Number of bytes to transfer + } deriving (Eq, Ord, Show) + +instance ArgFormatting IovecStruct where + formatArg (IovecStruct {..}) = + StructArg [("iov_base", formatArg iov_base), ("iov_len", formatArg iov_len)] + +instance Storable IovecStruct where + sizeOf _ = #{size struct iovec} + alignment _ = #{alignment struct iovec} + peek p = do + iov_base <- #{peek struct iovec, iov_base} p + iov_len <- #{peek struct iovec, iov_len} p + return IovecStruct{..} + poke p IovecStruct{..} = do + #{poke struct iovec, iov_base} p iov_base + #{poke struct iovec, iov_len} p iov_len diff --git a/test/HatraceSpec.hs b/test/HatraceSpec.hs index 8634926..29747a9 100644 --- a/test/HatraceSpec.hs +++ b/test/HatraceSpec.hs @@ -12,6 +12,7 @@ import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Unlift (MonadUnliftIO) import Data.ByteString (ByteString) import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as B8 import Data.Conduit import qualified Data.Conduit.Combinators as CC import qualified Data.Conduit.List as CL @@ -24,7 +25,7 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as T import Foreign.C.Error (Errno(..), eBADF, eCONNRESET) import Foreign.Marshal.Alloc (alloca) -import Foreign.Ptr (nullPtr, plusPtr) +import Foreign.Ptr (nullPtr, plusPtr, ptrToWordPtr) import Foreign.Storable (sizeOf, peek, poke) import System.FilePath (takeFileName, takeDirectory) import System.Directory (doesFileExist, removeFile) @@ -116,6 +117,41 @@ spec = before_ assertNoChildren $ do exitCode `shouldSatisfy` \x -> x `elem` [ExitFailure 11, ExitFailure (128+11)] + describe "writev" $ do + it "cant peek correct values of struct iov and number of byte written" $ do + callProcess "make" ["--quiet", "example-programs-build/writev"] + + argv <- procToArgv "example-programs-build/writev" [] + (exitCode, events) <- + sourceTraceForkExecvFullPathWithSink argv $ + syscallExitDetailsOnlyConduit .| CL.consume + exitCode `shouldBe` ExitSuccess + + let [(stdinReads, (iov1:iov2:_), writtenCount_)] = + [ (bufContents, iovs, writtenCount) + | (_pid + , Right ( + DetailedSyscallExit_writev + SyscallExitDetails_writev + { enterDetail = SyscallEnterDetails_writev{ fd = 1, bufContents, iovs } + , writtenCount + } + ) + ) <- events + ] + -- The example program prints out some expected values, we retrieve them relying on the write syscall + let (expectedCount:expectedVect1:expectedVect2:_) = [ bufContents | + (_pid, Right ( + DetailedSyscallExit_write + SyscallExitDetails_write{ enterDetail = SyscallEnterDetails_write { bufContents } } + ) + ) <- events + ] + stdinReads `shouldBe` ["hello ", "world\n"] + fromIntegral writtenCount_ `shouldBe` (read (B8.unpack expectedCount) :: Int) + fromIntegral (ptrToWordPtr $ iov_base iov1) `shouldBe` (read (B8.unpack expectedVect1) :: Int) + fromIntegral (ptrToWordPtr $ iov_base iov2) `shouldBe` (read (B8.unpack expectedVect2) :: Int) + describe "sourceRawTraceForkExecvFullPathWithSink" $ do it "lets the process finish if the sink exits early" $ do