【问题标题】:Is there a needed GHC option for an executable using the FFI?使用 FFI 的可执行文件是否需要 GHC 选项?
【发布时间】:2018-04-12 12:01:49
【问题描述】:

我在Main.hs 中有一个模块Main。该程序使用 FFI(特别是 FunPtr)。

当我运行 stack exec ghci 并加载模块 (:l src/Main.hs) 时,它运行良好。

但是,当我将模块编译为可执行文件并运行该可执行文件时,我遇到了崩溃,即分段错误。

因此我想知道是否需要使用某个选项进行编译。处理 FFI 时是否有特定的选项可供使用?我试过-O0-fllvm,没办法。也许stack exec ghci 包含一个可以与 GHC 一起使用的选项?

另外,是否有一个调试选项可以设置为 GHC 以便使用 gdb 运行可执行文件?我尝试了-g 选项,但gdb 没有找到调试符号。 EDIT这点解决了:gdb在我用stack exec -- ghc -g -rtsopts src/Main.hs编译的时候找到调试符号。

我在 Linux Ubuntu 上使用 GHC 8.2.2。

编辑

这是一个反映我真实程序结构的最小程序。这个工作正常(在 GHCI 中或作为可执行文件),但我仍然包含它。

helloffi.c

#include <stdlib.h>

double** evalf(double (*f)(double), double x){
    double** out = malloc(2 * sizeof(double*));
    for(unsigned i=0; i<2; i++){
        out[i] = malloc(2 * sizeof(double));
        out[i][0] = (*f)(x);
        out[i][1] = (*f)(x+1);
    }
    return out;
}

double sumpointer(double** pptr){
    double x=0;
    for(unsigned i=0; i<2; i++){
        for(unsigned j=0; j<2; j++){
            x += pptr[i][j];
        }
    }
    return x;
}

库的主模块,Lib.hs

{-# LANGUAGE ForeignFunctionInterface #-}
module Lib
  where
import           Foreign.C.Types       
import           Foreign.Ptr           (Ptr, FunPtr, freeHaskellFunPtr)

type CFunction = CDouble -> IO CDouble

foreign import ccall "wrapper" functionPtr 
    :: CFunction -> IO (FunPtr CFunction)

foreign import ccall "evalf" c_evalf
    :: FunPtr CFunction
    -> CDouble
    -> IO (Ptr (Ptr CDouble))

fun2cfun :: (Double -> Double) -> CFunction
fun2cfun f x = 
    return $ realToFrac (f (realToFrac x))

evalFun :: (Double -> Double) -> Double -> IO (Ptr (Ptr CDouble))
evalFun f x = do
    fPtr <- functionPtr (fun2cfun f)
    result <- c_evalf fPtr (realToFrac x)
    freeHaskellFunPtr fPtr
    return result

foreign import ccall "sumpointer" c_sumpointer
    :: Ptr (Ptr CDouble) -> IO CDouble

要编译的模块 Main.hs

module Main
  where
import           Lib

main :: IO ()
main = do
    x <- evalFun (\x -> x*x) 2
    y <- c_sumpointer x
    print y

【问题讨论】:

  • 我明白了,这使得查找特定问题变得非常困难。我不确定一般结构会有所帮助。
  • 尝试使用 FFI 制作“Hello World”类型的程序,看看它是否有效。如果问题在于编译过程而不是代码,我们不需要 1000 行程序。
  • 很明显,问题不在于您如何编译程序,而在于您如何使用 FFI(或者可能在您的 C 或 Haskell 代码中完全不同)。不看代码是不可能回答这个问题的。您可能想尝试printf/trace 调试、GDB 等。
  • 看起来好像您在 C 端导致 未定义的行为,例如通过使用一些指针来访问未分配(或释放)的内存。在 C 领域,UB 并不意味着“肯定会崩溃”。程序可能会崩溃,也可能不会崩溃,具体取决于分配了多少内存,或者分配在哪里等。在 GHCi 下,分配可能是以程序偶然工作的方式完成的,而在 GHC 下,不会发生。这是一个常见的“C 指针地狱”问题,因为通常很难理解哪里出了问题:-(
  • @Euge 我已经解决了一半的问题。 C/marchingcubes.c 中的代码无法正确读取C/tables.c 中定义的数组的条目。所以我把这些数组移到了C/marchingcubes.c,这样更好,但还有一个问题。

标签: haskell ghc ffi ghci haskell-stack


【解决方案1】:

嗯,这并不是问题的真正答案,但我已经痛苦地解决了这个问题,我认为它可能对其他人有所帮助。值得分享。所以我发布了一个显示问题的最小示例。一个非常简单的例子。

helloffi/
├── C
│   ├── array.c
│   ├── helloffi.c
│   └── helloffi.h
├── helloffi.cabal
├── Setup.hs
├── src
│   ├── Lib.hs
│   └── Main.hs
└── stack.yaml

array.c:在C文件中定义一个数组,例如:

int array[2][3] =  
    {{1, 24, 1},
     {2, 19, 1}};

helloffi.c:定义一个使用这个数组的函数,例如:

#include "helloffi.h"

int getCoef(unsigned i, unsigned j){
    return array[i][j];
}

helloffi.h,头文件:

int array[2][3];
int getCoef(unsigned, unsigned);

这就是 C 部分的全部内容。现在是 Haskell 部分。制作一个库模块,导入 C 函数。

Lib.hs

{-# LANGUAGE ForeignFunctionInterface #-}
module Lib
  where
import           Foreign.C.Types       

foreign import ccall "getCoef" c_getCoef
    :: CUInt -> CUInt -> IO CInt

Main.hs,编译成可执行文件:

module Main
  where
import           Lib

main :: IO ()
main = do
    x <- c_getCoef 0 1
    print x

就是这样。而现在,谜团。编译库(获取模块LibMain.hs生成的可执行文件,假设这个叫做test)。

stack exec ghci,给出正确的结果(24):

Prelude> :l src/Main.hs 
[1 of 1] Compiling Main             ( src/Main.hs, interpreted )
Ok, one module loaded.
*Main> main
24

运行可执行文件,给出错误结果(总是0):

$ .stack-work/install/x86_64-linux/lts-11.4/8.2.2/bin/test 
0

test 可执行文件是编译后的Main.hs。但是它没有正确读取array.c 中的数组条目,而:l src/Main.hsstack exec ghci 之后给出了正确的结果。

是不是很奇怪?有没有人解释一下?

解决方案

我还不知道为什么stack exec ghci 和可执行文件之间的行为会有所不同,但现在有了@Alec 的以下cmets 的解决方案:将头文件中的int array[2][3] 替换为extern int array[2][3] 就足够了。看起来可执行文件将int array[2][3] 视为array 的定义,其条目初始化为0,而stack exec ghci 将其视为array 的声明。

【讨论】:

  • 如果将头文件中的int array[2][3]; 替换为extern int array[2][3]; 会发生什么?我怀疑 GHC/Stack 可能会以这样的顺序链接/编译东西,以至于标题中的int array[2][3];(这是一个暂定定义)最终被视为array 的定义(而不是声明)。跨度>
  • @Alec 你是对的!!如果我这样做,它会起作用。谢谢!
  • @Alec 你想写这个作为答案吗?否则我可以在我的帖子中包含这个(当然要归功于你)。
  • 哈哈,把它包含在你的帖子中 - 问题是array的暂定定义。这仅基于您刚刚发布的内容才有意义,因此更新您的答案可能更简单。很高兴能帮上忙。
猜你喜欢
  • 1970-01-01
  • 2011-09-17
  • 2011-05-02
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 2020-07-20
  • 1970-01-01
  • 2020-07-28
相关资源
最近更新 更多