对Dynlink的OCaml调用导致seg故障

对Dynlink的OCaml调用导致seg故障,ocaml,dynamic-linking,ocamlbuild,Ocaml,Dynamic Linking,Ocamlbuild,我有一个OCaml程序,它编写另一个OCaml程序,编译它,然后尝试动态加载它。不幸的是,这会在我的OSX 10.14机器OCaml 4.07.1上导致分段错误 我的课程结构如下: 文件A静态加载一组模块(称它们为Helper模块以供参考),并定义:1。描述将动态加载的模块的模块签名,2。此模块选项的参考,由加载的插件3设置。使用此引用的另一个模块 文件B是加载程序,简而言之,它运行Dynlink.loadfile 文件C是生成的OCaml文件,它还使用Helper模块,定义PLUGIN_

我有一个OCaml程序,它编写另一个OCaml程序,编译它,然后尝试动态加载它。不幸的是,这会在我的OSX 10.14机器OCaml 4.07.1上导致分段错误

我的课程结构如下:

  • 文件A静态加载一组模块(称它们为Helper模块以供参考),并定义:1。描述将动态加载的模块的模块签名,2。此模块选项的参考,由加载的插件3设置。使用此引用的另一个模块
  • 文件B是加载程序,简而言之,它运行
    Dynlink.loadfile

  • 文件C是生成的OCaml文件,它还使用Helper模块,定义PLUGIN_类型的模块,并设置插件引用

我使用ocamlbuild构建主程序,然后再次使用ocamlbuild构建插件(它需要与主程序相同的帮助器模块/文件)

当我尝试运行这个程序时,我得到一个segfault,大概是在执行Dynlink.loadfile时。我不确定我做错了什么,我将助手模块与主程序和插件链接的事实让我感到不舒服,但我不确定如何解决它

附加LLDB跟踪:

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x00000001002624da Main.native`caml_oldify_local_roots at roots.c:286 [opt]
    frame #1: 0x00000001002664fb Main.native`caml_empty_minor_heap at minor_gc.c:352 [opt]
    frame #2: 0x0000000100266cc5 Main.native`caml_gc_dispatch at minor_gc.c:446 [opt]
    frame #3: 0x000000010026dca6 Main.native`caml_make_vect(len=<unavailable>, init=<unavailable>) at array.c:335 [opt]
    frame #4: 0x0000000100114eb9 Main.native`camlLru_cache__init_inner_2624 + 89
    frame #5: 0x0000000100087ea6 Main.native`camlSyntax__memoize_7621 + 38
    frame #6: 0x000000010312d317 Plugin.cmxs`camlInterp__entry + 311
    frame #7: 0x0000000100283424 Main.native`caml_start_program + 92
    frame #8: 0x000000010027ad19 Main.native`caml_callback(closure=<unavailable>, arg=<unavailable>) at callback.c:173 [opt]
    frame #9: 0x000000010027f6a0 Main.native`caml_natdynlink_run(handle_v=4345299456, symbol=72181230668639817) at natdynlink.c:141 [opt]
    frame #10: 0x000000010009d727 Main.native`camlDynlink__fun_2440 + 23
    frame #11: 0x0000000100183581 Main.native`camlStdlib__list__iter_1148 + 33
    frame #12: 0x000000010009d5bc Main.native`camlDynlink__loadunits_2288 + 332
    frame #13: 0x000000010009d788 Main.native`camlDynlink__load_2301 + 72
    frame #14: 0x000000010000552c Main.native`camlLoader__load_plugin_1002 + 268
    frame #15: 0x00000001000055d8 Main.native`camlLoader__simulate_1056 + 120
    frame #16: 0x00000001000052c8 Main.native`camlMain__entry + 280
    frame #17: 0x0000000100002489 Main.native`caml_program + 3481
    frame #18: 0x0000000100283424 Main.native`caml_start_program + 92
    frame #19: 0x00000001002617dc Main.native`caml_startup_common(argv=0x00007ffeefbff538, pooling=<unavailable>) at startup.c:157 [opt]
    frame #20: 0x000000010026184b Main.native`caml_main [inlined] caml_startup_exn(argv=<unavailable>) at startup.c:162 [opt]
    frame #21: 0x0000000100261844 Main.native`caml_main [inlined] caml_startup(argv=<unavailable>) at startup.c:167 [opt]
    frame #22: 0x0000000100261844 Main.native`caml_main(argv=<unavailable>) at startup.c:174 [opt]
    frame #23: 0x00000001002618bc Main.native`main(argc=<unavailable>, argv=<unavailable>) at main.c:44 [opt]
    frame #24: 0x00007fff6d4f1ed9 libdyld.dylib`start + 1
    frame #25: 0x00007fff6d4f1ed9 libdyld.dylib`start + 1

有没有关于我做错了什么的线索?

我不确定这是否是您的SEGFULT的原因,但您的线路:

let A.plugin = Some (module Plugin : PLUGIN_TYPE)
这是错误的。你想写的是:

let () = A.plugin := Some (module Plugin : PLUGIN_TYPE)
理想情况下,我建议您在
a
中创建一个函数
register\u plugin
,以避免此类错误


此外,您可能还想知道插件的构建是否失败并正确处理;博士已知的错误。如果可能,使用沙丘。如果没有,请手动使用Findlib Dynlink。有些工作是需要的,但是可行的。你不是第一个碰到这个问题的人

问题 首先,您做的一切都是正确的,这是OCaml中一个相对著名的长期错误。尽管如此,这只是最近的事。别担心,有几个变通方法(如下所述)。此外,仅供参考,如果您没有接触Obj模块或玩外部(C)存根,并且出现segfault,那么这肯定是OCaml系统中的一个错误,因此您可以直接转到OCaml问题跟踪器。幸运的是,这种情况很少发生

现在,发生了什么?问题是OCaml动态链接器没有检查编译单元是否已经加载。因此,当您加载一个新单元时,它可能已经加载,或者反过来加载另一个已经加载的单元。将单元加载到OCaml进程映像中时,将调用单元构造函数(初始化函数),它设置初始根(全局变量)并初始化帧。如果单元已经初始化,它会破坏破坏-变量被重置,值被重写。如果幸运的话,您将从垃圾收集器中得到一个分段错误。这就是你的情况

解决 修复程序已合并到OCAML4.08版本中,但您可能不会真正满意它。是的,您不会得到segfault,但相反,您的程序将正常失败,并出现一个错误,表明您正在尝试加载一个已在进程映像中的编译单元(Dynlink.error(Module_ready_loaded“Module name”)异常)。因此,插件系统开发人员有责任维护已加载模块的列表

很可能,您不想开发新系统。好消息是,这样的系统已经开发出来了(它们甚至适用于旧版本的OCaml,所以它们很健壮,可以防止OCaml出现故障)

我将在下面提供两种解决方案。两者都依赖于设施。当一个程序(或共享对象)被编译时,它会记录在程序内部构成它的编译单元列表,以便以后可以查阅它并做出决定,是否应该加载该单元,以及它是否与已经加载的单元一致(例如,我们不希望在流程映像中有同一库的多个版本)

沙丘 第一个解决方案是使用Dune。至少因为它只需要最少的工作量。Dune从零开始使用Findlib实现,所以一切都应该开箱即用。您只需将项目移植到Dune,将
Findlib.dynload
指定为主程序的依赖项(加载插件的程序)并使用
Fl\u dynload.load\u包
加载插件

OCamlbuild/绿洲 如果由于某些原因您无法将项目移动到Dune,那么您必须自己做一些工作。我们已经实现了我们自己的插件加载系统作为的一部分,因此您可以基于它构建自己的系统。它是在MIT许可下的,所以请随意获取您喜欢的任何代码并根据您的喜好进行修改。我们的系统提供了一点帮助比您可能需要的更多(我们使我们的插件独立,将它们打包为zip文件等),但想法是一样的-使用
Fl\u dynload
,并跟踪您正在加载的内容。一如既往,魔鬼在于细节。如果您使用OASIS或ocamlbuild构建非平凡的项目(如果您的项目很平凡,那么只需将其移植到Dune即可),那么需要注意的是,当ocamlbuild链接内部libraru(即源代码树中的库)时,它不会使用OCamlFind,因此链接的模块不会报告给Dynload工具。因此,我们必须编写一个ocamlbuild插件来实现这一点

基本上,您的加载程序必须跟踪已加载的编译单元,并且您的插件必须包含元信息,告知加载程序需要哪些编译单元以及它提供了哪些编译单元。这需要所有部分的配合。以下是它在BAP中的工作方式:

1) 我们也有
bapbuild
    frame #4: 0x0000000100114eb9 Main.native`camlLru_cache__init_inner_2624 + 89
    frame #5: 0x0000000100087ea6 Main.native`camlSyntax__memoize_7621 + 38
let A.plugin = Some (module Plugin : PLUGIN_TYPE)
let () = A.plugin := Some (module Plugin : PLUGIN_TYPE)