OCaml вызов Dynlink вызывает ошибку сегмента - PullRequest
1 голос
/ 11 июля 2019

У меня есть программа OCaml, которая пишет другую программу OCaml, компилирует ее и затем пытается динамически загрузить ее.К сожалению, это вызывает ошибку сегментации на моей машине с OSX 10.14, OCaml 4.07.1.

В частности, моя программа имеет следующую структуру:

  • Файл A статически загружает несколько модулей (называть их вспомогательными модулями для справки) и определяет: 1. сигнатуру модуля, которая описывает модуль, который будет динамически загружен, 2. ссылку на опцию этого модуля, которая будет установлена ​​загружаемым плагином 3. другой модуль, который используетэта ссылка.
open Helper
module type PLUGIN_TYPE = sig ... end

let plugin = ref None
let get_plugin () : (module PLUGIN_TYPE) =
  match !plugin with
  | Some x -> x
  | None -> failwith "No plugin loaded"

module Test
struct =
... get_plugin () ...
end
  • Файл B является загрузчиком, короче говоря, он запускается Dynlink.loadfile

  • Файл C являетсясгенерированный файл OCaml, который также использует модули Helper и определяет модуль типа PLUGIN_TYPE и устанавливает ссылку на плагин.

module Plugin : PLUGIN_TYPE =
...
end

let () = A.plugin := Some (module Plugin : PLUGIN_TYPE)

Я использую ocamlbuild для сборки основной программы, а затем снова ocamlbuildсоздать плагин (для которого требуются те же модули / файлы Helper, что и в основной программе).

Когда я пытаюсь запустить его, я получаю ошибку segfault, предположительно во время Dynlink.loadfИль выполнен.Я не уверен, что я делаю неправильно, тот факт, что я связываю модули Helper как с основной программой, так и с плагином, вызывает у меня неудобство, но я не уверен, как обойти это.

Присоединение LLDBtrace:

* 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

Для чего это стоит, это часть того, что я назвал модулями Helper:

    frame #4: 0x0000000100114eb9 Main.native`camlLru_cache__init_inner_2624 + 89
    frame #5: 0x0000000100087ea6 Main.native`camlSyntax__memoize_7621 + 38

Любые подсказки, что я делаю неправильно?

Ответы [ 2 ]

1 голос
/ 11 июля 2019

TL; DR; Известная ошибка. Используйте дюну, если это возможно. Если нет, используйте Findlib Dynlink вручную. Некоторая работа необходима, но выполнима. Вы не первый, кто решает эту проблему.

Проблема

Прежде всего, вы все делаете правильно, это относительно известная долгосрочная ошибка в OCaml. Несмотря на это, это было решено только недавно. Не волнуйтесь, есть несколько обходных путей (упомянутых ниже). Кроме того, к вашему сведению, если вы не касаетесь модуля Obj или не играете с внешними (C) заглушками, и не получаете segfault, то это определенно ошибка в системе OCaml, поэтому вы можете перейти непосредственно к средству отслеживания проблем OCaml. К счастью, это случается очень редко.

Теперь, что происходит? Проблема в том, что динамический компоновщик OCaml не проверяет, загружен ли модуль компиляции. Следовательно, когда вы загружаете новый блок, он может быть уже загружен или, в свою очередь, загружать другой блок, который уже был загружен. Когда модуль загружается в образ процесса OCaml, вызывается конструктор модуля (функция инициализации), который устанавливает начальные корни (глобальные переменные) и инициализирует кадры. Если модуль уже был инициализирован, это нарушает работу - переменные сбрасываются, значения перезаписываются. Если вам повезет, вы получите ошибку сегментации от сборщика мусора. И это то, что происходит в вашем случае.

* Решения 1011 * Исправление было объединено в версии OCaml 4.08, но вы, вероятно, не будете по-настоящему довольны им. Да, вы не получите segfault, но вместо этого ваша программа завершится неудачно из-за ошибки, указывающей, что вы пытаетесь загрузить модуль компиляции, который уже находится в образе процесса (исключение Dynlink.Error (Module_already_loaded "module name")). Поэтому ответственность за ведение списка уже загруженных модулей лежит на разработчике системы плагинов. Скорее всего, вы не хотите разрабатывать новую систему. И хорошая новость заключается в том, что такие системы уже были разработаны (и они даже работают для старых версий OCaml, поэтому они надежны, чтобы не допустить сегментации OCaml). Я приведу два решения ниже. Оба полагаются на Findlib Dynload . Который, когда программа (или совместно используемый объект) компилируется, записывает список модулей компиляции, которые составляют ее, внутри самой программы, чтобы впоследствии к ним можно было обратиться и принять решение, следует ли загружать модуль, и является ли он в соответствии с уже загруженными модулями (например, мы не хотим, чтобы в образе процесса было несколько версий одной и той же библиотеки). Дюна Решением номер один будет использование Dune. Ну хотя бы потому, что требует минимум работы. Дюна реализована с нуля до , корректно работает с Findlib, поэтому все должно работать из коробки. Вам просто нужно перенести ваш проект на Dune, указать findlib.dynload как зависимость вашей хост-программы (программы, которая загружает плагины) и использовать Fl_dynload.load_packages для загрузки ваших плагинов. OCamlbuild / OASIS Если по каким-то причинам вы не можете перенести свой проект в Dune, вам придется выполнить какую-то работу самостоятельно. Мы внедрили нашу собственную систему загрузки плагинов в рамках проекта BAP , поэтому вы можете создать свою собственную систему на ее основе. Он находится под лицензией MIT, поэтому не стесняйтесь захватывать любой код, который вам нравится, и изменять его по своему вкусу. Наша система предоставляет немного больше, чем вам может понадобиться (мы делаем наши плагины автономными, упаковываем их в zip-файлы и т. Д.), Но идея та же самая - используйте Fl_dynload и следите за тем, что вы загружаете , Как всегда, дьявол кроется в деталях. Если вы используете OASIS или ocamlbuild для создания нетривиального проекта (и если ваш проект тривиален, то просто перенесите его на Dune), тогда предостережение заключается в том, что когда ocamlbuild связывает внутренний libraru (то есть библиотеку из вашего исходного дерева) ) он не будет использовать OCamlFind и, следовательно, связанные модули не будут переданы в службу Dynload. Поэтому мы должны написать плагин OCamlBuild, который сделает это. По сути, ваш загрузчик должен отслеживать, какие модули компиляции уже загружены, а ваш плагин должен содержать мета-информацию, которая сообщает загрузчику, какие модули компиляции ему нужны и какие он предоставляет. Это требует сотрудничества со всех сторон. Вот как это работает в BAP: 1) У нас есть инструмент bapbuild, который ocamlbuild улучшен с помощью (ocamlbuild) плагина , который знает, как создавать файлы *.plugin. Файл .plugin - это zip-файл под капотом с фиксированной разметкой (на нашем языке он называется bundle). Он содержит файл MANIFEST, который включает в себя список необходимых библиотек и список предоставленных модулей, а также некоторую метаинформацию и, конечно, cmxs (и cma) для самого кода. При желании в комплект могут быть включены все зависимые библиотеки (чтобы плагин можно было загружать в средах, где необходимые библиотеки не предоставляются). Инструмент bapbuild по умолчанию будет упаковывать все зависимости, а поскольку некоторые библиотеки в юниверсе OPAM вообще не предоставляют cmxs, он также создаст для них cmxs и упакует их в плагин. Примечание 2) У нас есть bap_plugins библиотека времени выполнения , которая загружает плагины, выполняет их зависимости и гарантирует, что никакие модули не будут загружены дважды. 3) Поскольку хост-программа (которая загружает плагины) может (и будет) также содержать некоторые модули компиляции, так как она будет связана с некоторым набором модулей компиляции, которые либо локальны для дерева проекта, либо получены из внешних библиотеки. Поэтому нам необходимо некоторое сотрудничество со стороны системы сборки, которая сообщит нам, какие модули уже загружены (в качестве альтернативы мы можем проанализировать структуры ELF двоичного файла хоста, но это не звучит как очень переносимое и надежное решение). Мы используем библиотеку ocamlfind.dynlink, которая обеспечивает такое взаимодействие, сохраняя список библиотек и пакетов, которые использовались для создания двоичного файла во внутренней структуре данных. Мы написали небольшой плагин pocamlbuild] 6 , который позволяет это делать, а остальное делает ocamlfind (который фактически генерирует файл и связывает его в двоичный файл хоста). 

0 голосов
/ 11 июля 2019

Я не уверен, является ли это причиной вашего segfault, но ваша строка:

let A.plugin = Some (module Plugin : PLUGIN_TYPE)

неверна.Что вы хотите написать:

let () = A.plugin := Some (module Plugin : PLUGIN_TYPE)

В идеале, я бы посоветовал вам создать функцию register_plugin в A, чтобы избежать такой ошибки.

Кроме того, вы 'Возможно, вы захотите узнать, не удалась ли сборка вашего плагина, и правильно с этим справиться.

...