Spec-Zone.ru › OCaml
☰Инструменты OCaml
  • Пакетная компиляция (ocamlc)
  • Интерактивная система или REPL (ocaml)
  • Система времени выполнения (ocamlrun)
  • Компиляция в машинный код (ocamlopt)
  • Генераторы лексических анализаторов и синтаксических анализаторов (ocamllex, ocamlyacc)
  • Генератор зависимостей (ocamldep)
  • Генератор документации (ocamldoc)
  • Отладчик (ocamldebug)
  • Нативная отладка (gdb, lldb)
  • Профилирование (ocamlprof, perf)
  • Взаимодействие C с OCaml
  • Оптимизация с помощью Flambda
  • Фаззинг с помощью afl-fuzz
  • Трассировка времени выполнения с помощью событий времени выполнения
  • Преобразование программы «Tail Modulo Constructor»
  • Обнаружение гонок данных во время выполнения с помощью ThreadSanitizer

Глава 21. Нативная отладка (gdb, lldb)

1 Обзор

В этой главе описана поддержка отладки исполняемых файлов OCaml, собранных компилятором в машинный код ocamlopt, с помощью стандартных нативных отладчиков, таких как GDB и LLDB. Мы будем называть это нативной отладкой, в отличие от отладки байткода, поддерживаемой с помощью ocamldebug (глава 20).

Нативная отладка поддерживается на платформах Linux, macOS и FreeBSD. Поддержка Windows в настоящее время недоступна.

1.1 DWARF

OCaml использует формат отладочной информации DWARF для описания генерируемых отладочных данных. DWARF — это формат отладочной информации, используемый многими компиляторами и отладчиками для поддержки отладки на уровне исходного кода. Он применяется в исполняемых форматах ELF и Mach-O.

Отладочная информация включает два ключевых компонента:

Информация о кадрах вызовов (CFI): описывает, как раскрутить стек вызовов для создания трассировок стека. Информация CFI в OCaml охватывает границы языков — от кода OCaml до функций среды выполнения C и через вызовы Foreign Function Interface (FFI), если иностранный язык также предоставляет данные CFI.

Сопоставление строк исходного кода: сопоставляет каждую машинную инструкцию с соответствующим исходным местоположением, позволяя отладчикам отображать исходный код OCaml и поддерживать пошаговое выполнение на уровне исходного кода. Например, инструкция по адресу памяти 0xdeadbeef может соответствовать myprogram.ml:42.

В OCaml определено собственное соглашение о вызовах, описывающее передачу аргументов функциям, возврат значений из функций и использование регистров. Эта информация зависит от архитектуры и документирована в исходных файлах asmcomp/<ARCH>/proc.ml для каждой архитектуры.

1.2 Искажение имён

При компиляции исходного кода OCaml преобразует языковые конструкции, такие как функции и имена модулей, в искажённые имена, которые появляются в конечном исполняемом файле. Эти искажённые имена служат нескольким целям:

  • Они обеспечивают уникальность символов в скомпилированном двоичном файле
  • Они кодируют структуру модулей и информацию о пространствах имён
  • Они отображаются в выводе отладчика, например в трассировках стека и списках символов
  • Их можно использовать для установки точек останова, если информация об исходных файлах недоступна

Текущая схема искажения имён для OCaml 5.3 и более поздних версий:

  • Linux: caml<MODULE_NAME>.<FUNCTION_NAME>_<NNN>
  • macOS и Windows MSVC: caml<MODULE_NAME>$<FUNCTION_NAME>_<NNN>

где NNN — уникальный сгенерированный номер.

Пример: Функция fib в модуле MyMath может получить имя camlMyMath.fib_271 в Linux или camlMyMath$fib_271 в macOS.

Примечание: В версиях OCaml до 5.1.1 использовались двойные подчёркивания: caml<MODULE_NAME>__<FUNCTION_NAME>_<NNN>.

1.3 Указатели кадров

Указатели кадров предоставляют отладчикам альтернативный способ обхода стека вызовов. OCaml поддерживает указатели кадров на платформах AMD64 и ARM64.

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

Указатели кадров необязательны (для отладки они не нужны) и должны быть явно включены при настройке компилятора (подробности см. в разделе «Профилирование», 22.2.2).

2 Компиляция для отладки

Перед отладкой программ OCaml необходимо установить нативный компилятор ocamlopt с поддержкой CFI, которая включена по умолчанию. Эту настройку также можно явно задать с помощью флага конфигурации --enable-cfi при сборке компилятора.

Для отладки на уровне исходного кода скомпилируйте весь код с флагом -g: он записывает информацию DWARF для трассировок исключений и генерирует информацию о строках для сопоставления ассемблерного кода с расположением в исходном коде OCaml. Компиляция с флагом -g не снижает производительность во время выполнения, но увеличивает размер исполняемых файлов, поскольку в них включаются разделы с отладочной информацией. Обратите внимание, что библиотеки OCaml и другие зависимости необходимо компилировать с отладочной информацией DWARF; в противном случае возможности отладки на уровне исходного кода будут недоступны для этих фрагментов кода.

Отладчикам необходим доступ к исходным файлам, указанным в информации DWARF. Для зависимостей можно использовать сохранение каталога сборки opam:

# First tell opam to keep the source code
$ export OPAMKEEPBUILDDIR=1

# Then, reinstall the packages to force redownloading the sources
$ opam switch reinstall

# Source code for packages will appear inside the opam switch
# in a build directory. e.g. _opam/.opam-switch/build/ for a
# local switch or ~/.opam/<switch_name>/.opam-switch/build

В следующих разделах показана отладка программ OCaml с помощью GDB и LLDB, а также распространённые сценарии работы и ожидаемые результаты.

3 Использование GDB

Рассмотрим отладку простой программы OCaml с помощью GDB в Linux, включая используемые команды и ожидаемые результаты. Обратите внимание: в этом примере используется Ubuntu 24.04 LTS на AMD64 с OCaml 5.4.

Рассмотрим следующую программу:

(* fib.ml *)
let rec fib n =
  if n = 0 then 0
  else if n = 1 then 1
  else fib (n-1) + fib (n-2)

let main () =
  let r = fib 20 in
  Printf.printf "fib(20) = %d" r

let _ = main ()

Скомпилируйте эту программу с помощью ocamlopt следующим образом:

$ ocamlopt -g -o fib.exe fib.ml
$ ./fib.exe 20
fib(20) = 6765

При запуске программа выводит 20-е число Фибоначчи. Рекурсия здесь нужна для того, чтобы изучить стек вызовов. Запустите сеанс GDB для этой программы:

$ gdb ./fib.exe

Точки останова можно задавать как с помощью искажённых имён, созданных компилятором, так и с помощью сочетания имени файла и номера строки. Например:

(gdb) break camlFib.fib_     # press tab
(gdb) break camlFib.fib_271  # 271 happens to be the unique number generated
Breakpoint 1 at 0x3cd50: file fib.ml, line 2.

(gdb) break fib.ml:7         # breakpoint for main function
Breakpoint 2 at 0x3cdc0: file fib.ml, line 7.

Теперь можно запустить программу и вывести трассировку стека.

(gdb) run
Starting program: fib.exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 2, camlFib.main_273 () at fib.ml:7
7	let main () =
(gdb) continue
Continuing.

Breakpoint 1, camlFib.fib_271 () at fib.ml:2
2	let rec fib n =
(gdb) backtrace
#0  camlFib.fib_270 () at fib.ml:2
#1  0x0000555555590de1 in camlFib.main_273 () at fib.ml:7
#2  0x0000555555590e86 in camlFib.entry () at fib.ml:11
#3  0x000055555558eaa7 in caml_program ()
#4  <signal handler called>
#5  0x00005555555de126 in caml_startup_common (pooling=<optimised out>, argv=0x7fffffffe3f8)
    at runtime/startup_nat.c:132
#6  caml_startup_common (argv=0x7fffffffe3f8, pooling=<optimised out>) at runtime/startup_nat.c:88
#7  0x00005555555de19f in caml_startup_exn (argv=<optimised out>) at runtime/startup_nat.c:139
#8  caml_startup (argv=<optimised out>) at runtime/startup_nat.c:144
#9  caml_main (argv=<optimised out>) at runtime/startup_nat.c:151
#10 0x000055555558e892 in main (argc=<optimised out>, argv=<optimised out>) at runtime/main.c:37

Также поддерживается базовый вывод значений OCaml с помощью встроенных средств написания скриптов на Python в GDB и файла tools/gdb.py. Найдите этот файл в вашем переключателе opam, например по пути /.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/tools/gdb.py, или скачайте его с GitHub. Затем загрузите его в GDB с помощью команды ‘source‘.

(gdb) source ~/.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/tools/gdb.py
OCaml support module loaded. Values of type 'value' will now
print as OCaml values, there is a $Array() convenience function,
and an 'ocaml' command is available for heap exploration
(see 'help ocaml' for more information).

(gdb) p (value)$rax
$1 = caml:14

Можно также выводить другие типы значений OCaml. Чтобы показать это, рассмотрим следующую программу:

(* test_blocks.ml *)
type t = {s : string; i : int}

let main a b =
  print_endline "Hello, world!";
  print_endline a;
  print_endline b.s

let _ = main "foo" {s = "bar"; i = 42}

Скомпилируйте эту программу с помощью ocamlopt и загрузите её в GDB:

$ ocamlopt -g -o test_blocks.exe test_blocks.ml
$ gdb ./test_blocks.exe
(gdb) source ~/.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/tools/gdb.py
...
(gdb) break camlTest_blocks.main_273
Breakpoint 1 at 0x16db0: file test_blocks.ml, line 4.
(gdb) run
...
Breakpoint 1, camlTest_blocks.main_273 () at test_blocks.ml:4
4	let main a b =
(gdb) p (value)$rax      # Print out the first argument to main
$1 = caml(-):'foo'<3>
(gdb) p (value)$rbx      # Then print the second argument
$2 = caml(-):('bar', 42) = {caml(-):'bar'<3>, caml:42}

Обратите внимание на имена регистров AMD64: $rax и $rbx, используемые для доступа к первому и второму аргументам функции. Это соответствует соглашению о вызовах OCaml на AMD64, где в регистрах от $rax до $r13 хранятся аргументы функций OCaml, а в $rax — результаты функций. Дополнительные сведения о соглашениях о вызовах OCaml для конкретной архитектуры см. в файле asmcomp/<ARCH>/proc.ml.

По разным причинам исполняемые файлы могут не содержать точной информации о расположении исходного кода, использованного для их сборки. В GDB это проявляется в виде отсутствия исходного кода и предупреждения ‘No such file or directory‘. В этом случае GDB предоставляет разные способы указать расположение исходных файлов. Подробности см. в документации GDB Путь к исходным файлам.

Вернёмся к упомянутому ранее файлу fib.exe: исходный файл был скопирован в /tmp/fib.ml, где GDB его не найдёт. Здесь мы используем команду GDB directory, чтобы указать GDB, где находятся исходные файлы для fib.ml и стандартной библиотеки OCaml.

$ gdb ./fib.exe
...
(gdb) break camlFib.main_276
(gdb) break camlStdlib__Printf.fprintf_431
(gdb) run
...
Breakpoint 1, 0x00005555555921b0 in camlFib.main () at fib.ml:7
warning: 9	fib.ml: No such file or directory
# Update directories to search for source files for fib.ml and OCaml
(gdb) directory /tmp ~/.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/
Source directories searched: /tmp:/home/user/.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0:$cdir:$cwd
(gdb) list
4	  else if n = 1 then 1
5	  else fib (n-1) + fib (n-2)
6
7	let main () =
8	  let r = fib 20 in
9	  Printf.printf "fib(20) = %d" r
10
11	let _ = main ()

3.1 Команды GDB

Список полезных команд GDB, специфичных для OCaml:

break locspec
Устанавливает точки останова во всех местах кода, соответствующих locspec, например с помощью искажённых имён OCaml или указания номера строки в исходном файле в формате filename:linenum.
backtrace
Выводит трассировку всего стека. Она включает ссылки на исходный код OCaml, указывающие, какой кадр стека соответствует местоположению в исходном файле, например fib.ml:4.
disassemble addresses
Отображает диапазон addresses в виде машинных инструкций. Обычно используется с искажёнными именами OCaml для отображения ассемблерного кода функции.
info frame
Эта команда выводит подробное описание выбранного кадра стека.
list linenum
Выводит строки вокруг номера строки linenum в текущем исходном файле. Будет выведен исходный код OCaml и среды выполнения OCaml, написанной на C.
directory dirname
Добавляет каталог dirname в начало пути поиска исходных файлов; можно указать несколько имён каталогов, разделив их символом :. Полезно, если каталоги изменились между компиляцией и сеансом отладки.

Дополнительные сведения см. в документации «Отладка с помощью GDB». В целом описанные выше возможности работают с OCaml; если это не так, GDB переключается на отладку на языке ассемблера. Ожидается, что GDB будет работать на всех поддерживаемых архитектурах Linux.

4 Использование LLDB

Рассмотрим отладку упомянутого ранее примера fib с помощью LLDB в Linux. Запустите сеанс LLDB, используя ранее созданный файл fib.exe. Обратите внимание: в этом примере используется Ubuntu 24.04 LTS на ARM64 с OCaml 5.4.

$ lldb ./fib.exe
Current executable set to 'fib.exe' (aarch64).
(lldb)

Точки останова можно задавать с помощью искажённых имён OCaml или сочетания имени файла и номера строки. Например:

(lldb) breakpoint set -n camlFib.fib        # press tab for autocomplete
(lldb) breakpoint set -n camlFib.fib_271
Breakpoint 2: where = fib.exe`camlFib.fib_271 + 80, address = 0x0000000000052360
(lldb) breakpoint set -f fib.ml -l 7         # breakpoint for line 7 in fib.ml
Breakpoint 2: where = fib.exe`camlFib.main_272, address = 0x0000000000051088
(lldb)

Теперь можно запустить программу.

(lldb) run
...
Process 11391 stopped
* thread #1, name = 'fib.exe', stop reason = breakpoint 2.1
    frame #0: 0x0000aaaaaaaf1088 fib.exe`camlFib.main_272 at fib.ml:7
   4   	  else if n = 1 then 1
   5   	  else fib (n-1) + fib (n-2)
   6
-> 7   	let main () =
   8   	  let r = fib 20 in
   9   	  Printf.printf "fib(20) = %d" r
   10
...
(lldb) continue
Process 28032 resuming
Process 28032 stopped
* thread #1, name = 'fib.exe', stop reason = breakpoint 2.1
    frame #0: 0x0000aaaaaaaf2360 fib.exe`camlFib.fib_271 at fib.ml:5
   2   	let rec fib n =
   3   	  if n = 0 then 0
   4   	  else if n = 1 then 1
-> 5   	  else fib (n-1) + fib (n-2)
   6
   7   	let main () =
   8   	  let r = fib 20 in

(lldb) bt               # Print a backtrace
* thread #1, name = 'fib.exe', stop reason = breakpoint 2.1
  * frame #0: 0x0000aaaaaaaf2360 fib.exe`camlFib.fib_271 at fib.ml:5
    frame #1: 0x0000aaaaaaaf23d0 fib.exe`camlFib.main_273 at fib.ml:8
    frame #2: 0x0000aaaaaaaf2490 fib.exe`camlFib.entry at fib.ml:11
    frame #3: 0x0000aaaaaaaef748 fib.exe`caml_program + 480
    frame #4: 0x0000aaaaaab4ab90 fib.exe`caml_start_program + 132
    frame #5: 0x0000aaaaaab4a5f8 fib.exe`caml_startup_common [inlined] caml_startup_common(pooling=-1430712272, argv=0x0000000000000010) at startup_nat.c:127:9
    frame #6: 0x0000aaaaaab4a528 fib.exe`caml_startup_common(argv=0x0000000000000010, pooling=-1430712272) at startup_nat.c:86:7
    frame #7: 0x0000aaaaaab4a670 fib.exe`caml_main [inlined] caml_startup_exn(argv=<unavailable>) at startup_nat.c:134:10
    frame #8: 0x0000aaaaaab4a66c fib.exe`caml_main [inlined] caml_startup(argv=<unavailable>) at startup_nat.c:139:15
    frame #9: 0x0000aaaaaab4a66c fib.exe`caml_main(argv=<unavailable>) at startup_nat.c:146:3
    frame #10: 0x0000aaaaaaaef3d0 fib.exe`main(argc=<unavailable>, argv=<unavailable>) at main.c:37:3
    frame #11: 0x0000fffff7d784c4 libc.so.6`__libc_start_call_main(main=(fib.exe`main at main.c:31:1), argc=1, argv=0x0000fffffffffc98) at libc_start_call_main.h:58:16
    frame #12: 0x0000fffff7d78598 libc.so.6`__libc_start_main_impl(main=0x0000aaaaaaba0e68, argc=1, argv=0x0000fffffffffc98, init=<unavailable>, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=<unavailable>) at libc-start.c:360:3
    frame #13: 0x0000aaaaaaaef470 fib.exe`_start + 48

Поддерживается базовый вывод значений OCaml с помощью встроенных средств написания скриптов на Python в LLDB и файла tools/lldb.py. Найдите этот файл в вашем переключателе opam, например по пути /.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/tools/lldb.py, или скачайте его с GitHub. Затем загрузите его в LLDB с помощью команды command.

(lldb) command script import ~/.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/tools/lldb.py
OCaml support module loaded. Values of type 'value' will now
print as OCaml values, and an 'ocaml' command is available for
heap exploration (see 'help ocaml' for more information).
(lldb)  p (value)$x0
(value) 41 caml:20
(lldb)

Примечание: выше используется компьютер с Linux на ARM64, поэтому первый аргумент передаётся в первый регистр x0.

Можно также выводить значения OCaml разных типов. Повторно используя программу test_blocks.exe, запустите новый сеанс LLDB:

$ lldb ./test_blocks.exe
...
(lldb) command script import ~/.opam/5.4.0/.opam-switch/sources/ocaml-compiler.5.4.0/tools/gdb.py
OCaml support module loaded. Values of type 'value' will now
print as OCaml values, and an 'ocaml' command is available for
heap exploration (see 'help ocaml' for more information).
(lldb) breakpoint set -n camlTest_blocks.main_274
Breakpoint 1: where = test_blocks.exe`camlTest_blocks.main_274 + 44, address = 0x000000000001a6fc
(lldb) run
...
Process 15536 stopped
* thread #1, name = 'test_blocks.exe', stop reason = breakpoint 1.1
    frame #0: 0x0000aaaaaaaba6fc test_blocks.exe`camlTest_blocks.main_274 at test_blocks.ml:5
   2   	type t = {s : string; i : int}
   3
   4   	let main a b =
-> 5   	  print_endline "Hello, world!";
   6   	  print_endline a;
   7   	  print_endline b.s
   8
...
(lldb) p (value)$x0
(value) 187649984957416 caml(-):'Hello, world!'<13>
(lldb) p (value)$x1
(value) 187649984957360 caml(-):('bar', 42)

Здесь используются регистры ARM64 $x0 и $x1 для доступа к первому и второму аргументам функции. Это соответствует соглашению о вызовах OCaml на ARM64, где в регистрах от $x0 до $x15 хранятся аргументы функций OCaml. Дополнительные сведения о соглашениях о вызовах OCaml для конкретной архитектуры см. в файле asmcomp/<ARCH>/proc.ml.

В LLDB есть возможность указать расположение исходных файлов. В сеансе LLDB команда settings set target.source-map /tmp/build /my/src/path сопоставляет каталог сборки с каталогом исходных файлов и поддерживает несколько пар from to.

Например, скопируйте исходный файл fib.ml в /tmp/fib.ml и используйте переключатель opam для OCaml 5.4.

$ lldb ./fib.exe
(lldb) target create "./fib.exe"
Current executable set to '/home/user/fib.exe' (x86_64).
(lldb) br s -n camlStdlib__Printf.fprintf_431
Breakpoint 1: where = fib.exe`camlStdlib__Printf.fprintf_431 + 16, address = 0x000000000007ddb0
(lldb) br s -f fib.ml -l 9
Breakpoint 2: where = fib.exe`camlFib.main_276 + 66, address = 0x00000000000482c2
(lldb) run
Process 95112 launched: '/home/user/fib.exe' (x86_64)
Process 95112 stopped
* thread #1, name = 'fib.exe', stop reason = breakpoint 1.1
    frame #0: 0x00005555555d1db0 fib.exe`camlStdlib__Printf.fprintf_431 at printf.ml:27:21
# No source listing displayed for printf.ml file.
$ settings set target.source-map /home/user/ /tmp /home/user/.opam/5.4.0/.opam-switch/build/ocaml-variants.5.4.0/ /home/user/.opam/5.4.0/.opam-switch/sources/ocaml-variants.5.4.0/

4.1 Команды LLDB

Список полезных команд LLDB, специфичных для OCaml:

breakpoint set -n symbol
Устанавливает точку останова в месте кода, соответствующем symbol, например с помощью искажённого имени OCaml.
breakpoint set -f filename -l linenum
Устанавливает точку останова на строке linenum в файле filename, например fib.ml:7
breakpoint set -a address
Устанавливает точку останова по адресу памяти address.
backtrace
Выводит трассировку всего стека. Она включает ссылки на исходный код OCaml, указывающие, какой кадр стека соответствует местоположению в исходном файле.
disassemble
Дизассемблирует указанные инструкции в текущей цели. Полезные параметры: -n вместе с искажённым именем OCaml для дизассемблирования конкретной функции и -a вместе с адресом для дизассемблирования функции, содержащей этот адрес.
frame info
Выводит информацию о текущем кадре стека в текущем потоке.
source
Команды для просмотра исходного кода, описанного отладочной информацией для текущего целевого процесса.
settings set target.source-mapfrom to
Сопоставляет исходные пути from с новым исходным путём to, который используется при поиске исходного кода для отображения рядом с отлаживаемой программой. Поддерживается несколько пар сопоставлений from to.

В целом описанные выше возможности работают с OCaml; если это не так, LLDB переключается на отладку на языке ассемблера. Ожидается, что LLDB будет работать на всех поддерживаемых архитектурах Linux.

« Отладчик (ocamldebug)Профилирование (ocamlprof, perf) »
Copyright © 2026 Национальный институт исследований в области информатики и автоматики

© 1995-2026 INRIA.
https://ocaml.org/manual/5.5/native-debugger.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API