2012-04-26 20 views
7

Toady Actualicé mi máquina en desarrollo de Ubuntu 10.04 LTS a Ubuntu 12.04 LTS (o ghc 6.12.1 a ghc 7.4.1) y me encontré con un comportamiento muy extraño en mi proyecto actual.GHC: falla de segmentación en condiciones extrañas

Después de algunas horas, lo que reduce al siguiente código:

{-# 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" 

estoy tratando de generar algo de código en tiempo de ejecución, saltar a él, y volver de nuevo. El uso de un Makefile, todo está bien:

$ 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 

Sin embargo, si llamo el binario directamente de la cáscara: (¿por suerte)

$ ./stackoverflow_segv 
entry point: 0x092547e0 

Segmentation fault (core dumped) 

Este comportamiento es reproducible.

Usando gdb, objdump y /proc me di cuenta de:

$ 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 

antes de pulsar ENTER, me cambio a un segundo 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] 
[...] 

y volver de nuevo:

<enter> 
Program received signal SIGSEGV, Segmentation fault. 
0x0804ce3c in s2aV_info() 

Abucheo. Vamos a ver lo que hace este código:

$ 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, saltando a *%eax. ¿Qué fue %eax nuevamente?

(gdb) info reg eax 
eax   0x80fc810  135251984 

Bueno, en realidad es solo el buffer del código. Buscando /proc/*/maps nos dice que esta página no es ejecutable (rw-p, ¿verdad?). Pero, es la misma situación al ejecutarlo dentro de make.

¿Qué pasa aquí?

por cierto, el código también está disponible a través de gist

edición: ghc bug report

+1

Funciona bien desde la línea de comandos aquí (openSUSE 11.4, x86_64). Podría ser culpa de Ubuntu? Pruebe con un GHC autoconstruido si tiene tiempo. –

+0

¡Gracias por su respuesta! Lo probé con el paquete binario de [haskell.org] (http://www.haskell.org/ghc): (1) ghc-7.4.1, sigue siendo el mismo problema. (2) ghc-7.2.2 ya no más \ o/nice! (3) ghc-6.12.1 del mismo modo. ¿Crees que debería informarlo como un error en la gente de GHC? – lewurm

+0

El binario de vainilla (unknown-linux)? Sin embargo, tengo el mismo comportamiento que yo si uso 7.2.2 en lugar de 7.4.1. Extraño de hecho. –

Respuesta

1

Una solución temporal es utilizar mprotect(3) y establecer la región de memoria de forma explícita como ejecutables. mprotect(3) requiere un bloque de memoria alineado, por lo tanto, se requiere memalign(3).

{-# 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" 
Cuestiones relacionadas