【问题标题】:GHC: segmentation fault under strange conditionsGHC:奇怪条件下的分段错误
【发布时间】:2012-05-07 16:31:57
【问题描述】:

今天我将我的开发机器从 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

但是,如果我直接从 shell 调用二进制文件:

$ ./stackoverflow_segv 
entry point: 0x092547e0

Segmentation fault (core dumped)

这种行为是可重现的(幸运的是?)。

使用gdb、objdump 和/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

在回车之前,我切换到第二个终端:

$ 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 bug report

【问题讨论】:

  • 这里的命令行可以正常工作(openSUSE 11.4,x86_64)。可能是Ubuntu的错吗?如果有时间,请尝试使用自建的 GHC。
  • 感谢您的回复!我用haskell.org 的二进制包试了一下:(1) ghc-7.4.1,还是同样的问题。 (2) ghc-7.2.2 不再是\o/了! (3) ghc-6.12.1 同样。你认为我应该把它作为一个错误报告给 GHC 人员吗?
  • 原版二进制文件(unknown-linux)?不过,如果我使用 7.2.2 而不是 7.4.1,我会得到与您相同的行为。确实很奇怪。
  • 哦,如果我使用 7.2.2,现在它也会与 Makefile 发生段错误。陌生人和陌生人。
  • 是的,香草二进制。目前我在 7.4.1 和 7.2.2 之间区分 ghc -v 输出。后者在链接器阶段有一个额外的-lHSffi 标志,但 7.4.1 没有。

标签: linux haskell segmentation-fault ghc ffi


【解决方案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"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多