Opened 3 years ago

Closed 3 years ago

#6059 closed bug (invalid)

FFI: segfault when jumping to code buffer (under certain conditions)

Reported by: guest Owned by:
Priority: normal Milestone: 7.6.1
Component: Compiler (FFI) Version: 7.4.1
Keywords: segfault ffi Cc: lewurm@…
Operating System: Linux Architecture: x86
Type of failure: Runtime crash Test Case:
Blocked By: Blocking:
Related Tickets: Differential Revisions:


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 ...
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/".
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:

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?

The code is also available via a gist

First, I posted this issue to stackoverflow, since I wasn't sure what causes this issue and I don't thought it would be a problem with GHC. But (read the comments there please), as it turns out, with older releases of GHC (e.g. 7.2.2) it works for me.

Some Information about the used system:

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.4.1
$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO

$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A

This program built for i686-pc-linux-gnu
$ uname -a
Linux matevm-dev 3.2.0-23-generic #36-Ubuntu SMP Tue Apr 10 20:41:14 UTC 2012 i686 athlon i386 GNU/Linux
$ cat /etc/issue
Ubuntu 12.04 LTS \n \l

Change History (3)

comment:1 Changed 3 years ago by simonmar

  • difficulty set to Unknown

If I understand correctly, you're asking why the program sometimes works, not why it sometimes fails? It is expected that jumping to ordinary memory might crash.

In the GHC RTS we have a function allocateExec() for allocating executable memory. Internally it calls into libffi which has all sorts of hacks to make this work.

If you want we could make allocateExec() visible from Haskell, though it needs an explicit freeExec() (you can't allocate garbage-collectable executable memory).

comment:2 Changed 3 years ago by simonmar

  • Milestone set to 7.6.1

comment:3 Changed 3 years ago by guest

  • Resolution set to invalid
  • Status changed from new to closed

Well, as I said, it's reproducible (at least on my system). Within make it works as desired, but calling it from shell, it doesn't.

I looked at the implementation of mallocBytes. It's just calling malloc, so a came up with this code:

#include <stdio.h>
#include <stdlib.h>

int main(void)
        unsigned short *ptr = (unsigned short*) malloc(2);
        *ptr = 0xc390;
        printf("entry point: 0x%08x\n", (unsigned short) ptr);
        ((void (*)()) ptr) ();
        printf("welcome back\n");

Same behaviour: Calling it from make, everything is fine. Calling it from shell, segfault.
So I guess it isn't GHC related at all. Sorry :-/ I think this ticket can be closed.

The solution for my project was, allocate memory and mark it as executable with mprotect.


Note: See TracTickets for help on using tickets.