Toady I updated my developing machine from Ubuntu 10.04 LTS to Ubuntu 12.04 LTS (or ghc 6.12.1
to ghc 7.4.1
) and I run into a very strange behavior at my currenct project.
After some hours, I reduced it to the following code:
{-# LANGUAGE ForeignFunctionInterface #-}
module Main where
import Data.Word
import Text.Printf
import Foreign
foreign import ccall "dynamic"
code_void :: FunPtr (IO ()) -> (IO ())
main :: IO ()
main = do
entryPtr <- (mallocBytes 2)
poke entryPtr (0xc390 :: Word16) -- nop (0x90); ret(0xc3) (little endian order)
_ <- printf "entry point: 0x%08x\n" ((fromIntegral $ ptrToIntPtr entryPtr) :: Int)
_ <- getLine -- for debugging
code_void $ castPtrToFunPtr entryPtr
putStrLn "welcome back"
I'm trying to generate some code at run-time, jump to it, and come back again. Using a Makefile, everything is fine:
$ make
ghc --make -Wall -O2 Main.hs -o stackoverflow_segv
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking stackoverflow_segv ...
./stackoverflow_segv
entry point: 0x098d77e0
welcome back
However, if I call the binary directly from the shell:
$ ./stackoverflow_segv
entry point: 0x092547e0
Segmentation fault (core dumped)
This behavior is reproducible (luckily?).
Using gdb
, objdump
and /proc
I figured out:
$ gdb -q stackoverflow_segv
Reading symbols from /home/lewurm/stackoverflow/stackoverflow_segv...(no debugging symbols found)...done.
(gdb) run
Starting program: /home/lewurm/stackoverflow/stackoverflow_segv
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
entry point: 0x080fc810
before pressing enter, I switch to a second terminal:
$ cat /proc/`pgrep stackoverflow`/maps
[...]
08048000-080ea000 r-xp 00000000 08:01 2492678 /home/lewurm/stackoverflow/stackoverflow_segv
080ea000-080eb000 r--p 000a2000 08:01 2492678 /home/lewurm/stackoverflow/stackoverflow_segv
080eb000-080f1000 rw-p 000a3000 08:01 2492678 /home/lewurm/stackoverflow/stackoverflow_segv
080f1000-08115000 rw-p 00000000 00:00 0 [heap]
[...]
and back again:
<enter>
Program received signal SIGSEGV, Segmentation fault.
0x0804ce3c in s2aV_info ()
Boo. Let's see what this code does:
$ objdump -D stackoverflow_segv | grep -C 3 804ce3c
804ce31: 89 44 24 4c mov %eax,0x4c(%esp)
804ce35: 83 ec 0c sub $0xc,%esp
804ce38: 8b 44 24 4c mov 0x4c(%esp),%eax
804ce3c: ff d0 call *%eax
804ce3e: 83 c4 0c add $0xc,%esp
804ce41: 83 ec 08 sub $0x8,%esp
804ce44: 8b 44 24 54 mov 0x54(%esp),%eax
uhm, jumping to *%eax
. What was %eax
again?
(gdb) info reg eax
eax 0x80fc810 135251984
Well, actually it's just the code buffer. Looking up /proc/*/maps
tells us, that this page isn't executeable (rw-p
, right?). But, it's the same situation when executing it within make
.
What is wrong here?
btw, the code is also available via gist
edit: ghc bug report
A temporary solution is to use mprotect(3)
and set the memory region explicitly as executable. mprotect(3)
requires a aligned memory block, therefore memalign(3)
is required.
{-# LANGUAGE ForeignFunctionInterface #-}
module Main where
import Data.Word
import Text.Printf
import Foreign
import Foreign.C.Types
foreign import ccall "dynamic"
code_void :: FunPtr (IO ()) -> (IO ())
foreign import ccall "static sys/mman.h"
mprotect :: CUInt -> CUInt -> Int -> IO ()
foreign import ccall "static stdlib.h"
memalign :: CUInt -> CUInt -> IO (Ptr a)
main :: IO ()
main = do
entryPtr <- memalign 0x1000 0x2
poke entryPtr (0xc390 :: Word16) -- nop (0x90); ret(0xc3) (little endian order)
let i_entry = (fromIntegral $ ptrToIntPtr entryPtr) :: Int
-- 0x7 = PROT_{READ,WRITE,EXEC}
mprotect (fromIntegral i_entry) 2 0x7
_ <- printf "entry point: 0x%08x\n" i_entry
_ <- getLine -- for debugging
code_void $ castPtrToFunPtr entryPtr
putStrLn "welcome back"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With