inject a live haskell application and do magic with both C and haskell
who would win? 2 GHC runtimes or 1 C B O I?
your application:
main :: IO ()
main = print $ 2 + 3
an aggressive bystander:
new_plus :: Integer -> Integer -> Integer
new_plus a b = unsafePerformIO $ do
let c = a + b
putStrLn $ show a ++ " + " ++ show b ++ " = " ++ show c
return c
regular output:
5
output on steroids:
2 + 3 = 5
5
here are things that appear to work
- jumping from haskell into C and back, without breaking the runtime
- replacing
instance Num Integer
's(+)
withnew_plus :: Integer -> Integer -> Integer
- replacing
instance Num Integer
's(+)
withnew_plus :: Integer -> Integer -> Integer
withunsafePerformIO (return (a + b))
- using
>>=
(monadic bind) withinunsafePerformIO
here are things that don't work
putStrLn
insideunsafePerformIO
- functions
new_plus :: (Num a) => a -> a -> a
and directly replacingNum.+
macOS
- the injector code is macOS only via mach_inject but if you port the injector code to your platform then everything should workx86_64
- the code is simply not written to handle normalx86
- some version of ghc
- stack
- a lot of free time
-
build your application (
$ stack build
inside./stack-calc
) -
checkout a relevant copy of ghc into
./src/ghc/
-
in particular, you want to make sure
SIZEOF_VOID_P == 8
andTABLES_NEXT_TO_CODE
is a thing -
wait 5 years for ghc to finish building
-
use CMake to generate build files of your own choice
-
$ make
- this may require
stack ghc -- -fobject-code unsafe_zp.hs
- this may require
- launch your haskell application (
$ stack exec stack-calc
) - inject your haskell application (
$ sudo ./injector <pid> libhaskell-hook.dylib
) - profit
-
laziness makes it very hard to actually pull out values
-
base_GHCziNum_zdfNumInteger_closure
is nowhere to be found when we are inbase_GHCziNum_zp_info
? -
gluing
safe_weird_zp
toNumInteger_closure
causes it to return the constant2199023255808 = 0x20000000100
which is very weird considering it expects aNum
instance (arity=3
) as well and it doesn't crash -
gluing
safe_weird_zp'
toNumInteger_closure
does what we expect. -
gluing the other functions
unsafe_zp'
andunsafe_zp''
andforeign_zp
cause the runtime to crash due to multiple statically linked runtimes.i postulate that
unsafePerformIO
can be made to work, but i do not know how yet. -
the application should be single threaded, yet sometimes in
lldb
i will get haskell output in between hooked output... R5: HEAP closure at 0x420007d380... R5: tag=0 R5: real addr=0x420007d380 R5: info ptr=0x100013548 R5: info tbl=0x100013538 R5: closure type: THUNK_1_0 (16) R5: closure payload pointers: 1 R5: closure payload non-pointers: 0 R5: payload[0]=0x420007d310 R6: null pointer 7 *Sp: STACK closure at 0x42001e7e70... *Sp: tag=0 *Sp: real addr=0x42001e7e70 *Sp: info ptr=0x1001de1d8 *Sp: info tbl=0x1001de1c8 *Sp: closure type: UPDATE_FRAME (33) *Sp: closure layout: 0x1 ...
-
with respect to
unsafePerformIO
, the issue is almost certainly the fact that there are two statically linked runtimes competing with each other here. in particular, withinmaybePerformBlockedException
, there are several checks againstEND_TSO_QUEUE
andEND_BLOCKED_EXCEPTIONS_QUEUE
, both of which are defined to bestg_END_TSO_QUEUE_closure
, but because there are two and everything is compiled withrip
relative addressing, dynamic patching is needed to ensure that the runtimes agree with each other. this is currently done withinmain.c
ofhaskell-hook
, but the goal is to put the logic elsewhere once we getunsafePerformIO
working. when these values are made to agree, the runtime will end up dying inevacuate
by hitting thedefault
case, probably because theARR_WORDS
it finds is allocated in the injected runtime.HEAP_ALLOCED_GC
is defined as a macro whenUSE_LARGE_ADDRESS_SPACE
is set so we cannot hook it to do what we expect. if we can get heap allocations to agree between the two runtimes then i suspect that we can finally getunsafePerformIO
to work.
- provide a better interface for closures
- extract the numbers that are added togehter in
base GHC.Num +