7

Toady 開発中のマシンを Ubuntu 10.04 LTS から Ubuntu 12.04 LTS (または)ghc 6.12.1に更新ghc 7.4.1したところ、現在のプロジェクトで非常に奇妙な動作に遭遇しました。

数時間後、次のコードに減らしました。

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

実行時にいくつかのコードを生成し、それにジャンプして、もう一度戻ってきます。Makefile を使用すると、すべて問題ありません。

$ 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

ただし、シェルからバイナリを直接呼び出すと、次のようになります。

$ ./stackoverflow_segv 
entry point: 0x092547e0

Segmentation fault (core dumped)

この動作は再現可能です (幸いなことに?)。

を使用するgdbobjdump、次の/procことがわかりました。

$ 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

Enter キーを押す前に、2 番目のターミナルに切り替えます。

$ 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]
[...]

そして再び戻る:

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

ブー。このコードが何をするか見てみましょう:

$ 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

ええと、にジャンプし*%eaxます。また何だっ%eaxた?

 (gdb) info reg eax
 eax            0x80fc810        135251984

ええと、実際には単なるコード バッファです。調べてみる/proc/*/mapsと、このページは実行可能ではないことがわかります (rw-pですね?)。ただし、 内で実行する場合は同じ状況makeです。

ここで何が問題なのですか?

ところで、コードはgistからも入手できます

編集:ghcバグレポート

4

1 に答える 1

1

一時的な解決策はmprotect(3)、メモリ領域を使用して明示的に実行可能ファイルとして設定することです。mprotect(3)整列されたメモリブロックが必要なため、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"
于 2012-04-30T09:49:07.280 に答える