使用GHC编译Haskell时如何禁用<< loop >>异常?

时间:2018-07-17 07:31:05

标签: haskell ghc ghci

如果我的程序到达无限循环,我希望它实际上被卡住:永远运行,耗尽内存或由于堆栈溢出而崩溃。

我不希望它因<<loop>>错误消息而立即退出。如何禁用运行时无限循环检测?

2 个答案:

答案 0 :(得分:3)

这是一个可能有效的骇客破解。您可以为检测到黑洞循环并使其循环的非终止异常创建一个伪造的信息表。

假设您有一个Haskell程序:

-- Loop.hs
foo :: Int
foo = foo
main = print $ foo

如果使用以下命令进行编译:

$ ghc -O2 Loop.hs

并运行它,它将生成一个Loop: <<loop>>错误。

但是,如果您创建程序集文件:

# halt_and_catch_fire.s
# version for Linux x86_64 only
.globl base_ControlziExceptionziBase_nonTermination_closure
.section .data
.align 8
base_ControlziExceptionziBase_nonTermination_closure:
    .quad loop
.section .text
.align 8
    .quad 0
    .quad 14            # FUN_STATIC
loop:   jmp loop

并使用Haskell程序对其进行编译(带有适当的链接器标志,以忽略重复的定义):

$ ghc -O2 Loop.hs halt_and_catch_fire.s -optl -zmuldefs

运行它,它将锁定。

请注意,以上程序集可在x86_64 Linux上运行。在其他任何体系结构(包括32位Linux)上,都需要对其进行修改,因为闭包布局非常依赖于体系结构。

答案 1 :(得分:2)

这不是真正的答案,但不适合评论。在这种情况下,应该问什么是无限循环检测及其工作原理。我将通过从Haskell转换为具有严格(非惰性)评估的Haskell外观类似的语言(例如使用Haskell语法的JavaScript)来解释它,然后对其进行解释。

Haskell:

let f () = f () in f ()

转换为严格:

let f () = make_thunk (\() -> eval_thunk f ()) in eval_thunk (make_thunk (\() -> f ()))

现在让我们进行优化:

let f () = eval_thunk f_unit
    f_unit = make_thunk (\() -> f ())
in
eval_thunk f_unit

现在让我们写下一个关于暴徒的假定义:

data ThunkInner a = Done a | Fresh (() -> a) | Working
data Thunk a = Thunk { mutable inner :: ThunkInner a }
make_thunk f = Thunk (Fresh f)
eval_thunk t = case t.inner of
  | Done a -> a
  | Fresh f -> (t.inner <- Working; let result = f () in t.inner <- Done result; result)
  | Working -> error "<<loop>>"

因此,当我们尝试评估某物的价值时,我们看到了一个无限循环。您可以手动跟踪上述程序,以查看此错误如何发生。

但是,如果没有进行优化怎么办?好吧(假设您没有严格地进行优化),将会导致堆栈溢出,并可能将其报告为<<loop>>错误。如果将其优化为真正严格的标准,则可能会遇到堆栈溢出错误,也可能会遇到无限循环。

一个人可能会问如何避免这种错误,答案可能是:

  1. 不要写无限循环
  2. 也许没有优化就编译

但是您说有时无限循环很有用,因为例如事件循环或寿命长的服务器。答复是,在Haskell中,无限循环没有用,因为要使用有用的循环,您需要IO。考虑一个函数be_useful :: IO (),现在可以编写:

f = be_useful >>= \() -> f

现在执行f有两个步骤:

  1. 评估重击
  2. 以该值执行IO操作
  3. 执行下一个IO操作(再次计算重击),依此类推。

不需要<<loop>>