Начиная с выпуска 7.10 GHC может генерировать отладочную информацию, которая помогает средствам отладки понимать код, создаваемый GHC. Эту отладочную информацию поддерживает большинство отладочных средств UNIX.
-
-g -
-g⟨n⟩ -
- Версия:
-
7.10; числовые уровни — начиная с 8.0
- Подразумевает:
-
-fexpose-internal-symbols, если ⟨n⟩ >= 2.
Добавляет отладочную информацию в объектный код. В настоящее время на платформах x86-64 и i386 поддерживается только отладочная информация DWARF. Сейчас принимаются уровни отладки от 0 до 3:
-
-g0: отладочная информация не создаётся -
-g1: создаются записи для раскрутки стека функций верхнего уровня (достаточно для простых трассировок стека) -
-g2: создаются записи для раскрутки стека функций верхнего уровня и внутренних блоков (это позволяет получать более точные трассировки стека, чем-g1). -
-g3: создаётся специфичная для GHC информация DWARF для более сложных средств отладки с поддержкой Haskell (подробности см. в разделе Сущности отладочной информации)
Если ⟨n⟩ не указан, используется уровень 2.
Обратите внимание: для надёжной раскрутки стека все библиотеки, включая внешние библиотеки и поставляемые вместе с GHC, например base, должны быть скомпилированы с информацией для раскрутки стека. Бинарные дистрибутивы GHC с соответствующей конфигурацией доступны для ряда платформ; для остальных платформ рекомендуется выполнить сборку с помощью преобразователя варианта Hadrian +debug_info. Кроме того, встроенная поддержка раскрутки стека, предоставляемая модулем GHC.ExecutionStack библиотеки base, требует, чтобы среда выполнения была собрана с включённой поддержкой libdw (для этого при сборке компилятора передайте --enable-dwarf-unwind флагу configure), а также чтобы платформа поддерживала libdw.
9.1. Учебное руководство
Рассмотрим простой пример:
1 -- fib.hs 2 fib :: Int -> Int 3 fib 0 = 0 4 fib 1 = 1 5 fib n = fib (n-1) + fib (n-2) 6 7 main :: IO () 8 main = print $ fib 50
Сначала посмотрим, как выполняется эта программа. Для начала сообщим GHC, что нам нужна отладочная информация:
$ ghc -g -rtsopts fib.hs
Здесь мы использовали параметр -g, чтобы сообщить GHC о необходимости добавить отладочную информацию в создаваемый бинарный файл. Есть три уровня отладочного вывода: -g0 (без отладочной информации, по умолчанию), -g1 (достаточно для простых трассировок стека), -g2 (или просто -g для краткости; выводится вся информация, известная GHC). Обратите внимание, что эта отладочная информация не влияет на оптимизации, выполняемые GHC.
Совет
В Mac OS X отладочная информация хранится отдельно от исполняемого файла. После компиляции исполняемого файла необходимо использовать утилиту dsymutil, чтобы извлечь отладочную информацию и поместить её в отладочный архив:
$ dsymutil fib
В результате должен появиться файл с именем fib.dSYM.
Теперь посмотрим на ход выполнения программы. Для этого можно просто запустить программу под управлением gdb (или аналогичного отладчика), как любую другую нативную исполняемую программу:
$ gdb --args ./Fib +RTS -V0 Reading symbols from Fib...done. (gdb) run Starting program: /opt/exp/ghc/ghc-dwarf/Fib [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". ^C Program received signal SIGINT, Interrupt. 0x000000000064fc7c in cfy4_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:424 424 minusInteger x y = inline plusInteger x (inline negateInteger y) (gdb)
Здесь мы использовали параметр -V0 среды выполнения, чтобы отключить периодический таймер RTS, который может мешать отладке. При остановке программы gdb показывает место в исходном коде, соответствующее текущей точке выполнения.
Кроме того, можно попросить gdb показать ход выполнения, который привёл программу к этой точке:
(gdb) bt #0 0x000000000064fc7c in cfy4_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:424 #1 0x00000000006eb0c0 in ?? () #2 0x000000000064301c in cbuV_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:323 #3 0x000000000064311b in integerzmgmp_GHCziIntegerziType_eqInteger_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:312 #4 0x0000000000406eca in roz_info () at Fib.hs:2 #5 0x00000000006eb0c0 in ?? () #6 0x000000000064f075 in cfru_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:412 #7 0x00000000006eb0c0 in ?? () #8 0x000000000064f075 in cfru_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:412 #9 0x00000000006eb0c0 in ?? () #10 0x000000000064eefe in integerzmgmp_GHCziIntegerziType_plusInteger_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:393 ... #64 0x0000000000643ac8 in integerzmgmp_GHCziIntegerziType_ltIntegerzh_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:343 #65 0x00000000004effcc in base_GHCziShow_zdwintegerToString_info () at libraries/base/GHC/Show.hs:443 #66 0x00000000004f0795 in base_GHCziShow_zdfShowIntegerzuzdcshow_info () at libraries/base/GHC/Show.hs:145 #67 0x000000000048892b in cdGW_info () at libraries/base/GHC/IO/Handle/Text.hs:595 #68 0x0000000000419cb2 in base_GHCziBase_thenIO1_info () at libraries/base/GHC/Base.hs:1072
Подсказка
Обратите внимание: в начале трассировки стека есть несколько неидентифицированных кадров с адресом 0x006eb0c0. Если запросить у gdb информацию об этом месте, выяснится, что это замыкающие обновления STG:
(gdb) print/a 0x006eb0c0 $1 = 0x6eb0c0 <stg_upd_frame_info>
Причина, по которой gdb не показывает это имя символа в трассировке стека, заключается в неточности интерпретации отладочной информации: предполагается инвариант, соблюдаемый в программах на C, но не в программах на Haskell. К сожалению, пока это поведение не будет исправлено в исходном проекте, проблему приходится обходить вручную.
Примечание
Из-за агрессивной оптимизации, выполняемой GHC для компилируемых программ, довольно сложно точно определить, к какой точке исходного кода следует отнести ту или иную машинную инструкцию. Фактически внутри GHC каждой инструкции сопоставляется набор мест в исходном коде. При создании стандартной отладочной информации, используемой gdb и другими независимыми от языка средствами отладки, GHC вынужден эвристически выбирать одно место из этого набора.
Поэтому при интерпретации мест в исходном коде, указанных GDB, следует соблюдать осторожность. Обычно эти места в некотором смысле «верны», но не всегда полезны. По этой причине средства профилирования Haskell должны дополнять стандартную информацию о местах в исходном коде специфичными для GHC аннотациями (создаваемыми с помощью -g2) при назначении затрат.
Можно даже устанавливать точки останова:
(gdb) break fib.hs:4 Breakpoint 1 at 0x406c60: fib.hs:4. (5 locations) (gdb) run Starting program: /opt/exp/ghc/ghc-dwarf/Fib Breakpoint 1, c1RV_info () at Fib.hs:4 4 fib n = fib (n-1) + fib (n-2) (gdb) bt #0 c1RV_info () at Fib.hs:4 #1 0x00000000006eb0c0 in ?? () #2 0x0000000000643ac8 in integerzmgmp_GHCziIntegerziType_ltIntegerzh_info () at libraries/integer-gmp/src/GHC/Integer/Type.hs:343 #3 0x00000000004effcc in base_GHCziShow_zdwintegerToString_info () at libraries/base/GHC/Show.hs:443 #4 0x00000000004f0795 in base_GHCziShow_zdfShowIntegerzuzdcshow_info () at libraries/base/GHC/Show.hs:145 #5 0x000000000048892b in cdGW_info () at libraries/base/GHC/IO/Handle/Text.hs:595 #6 0x0000000000419cb2 in base_GHCziBase_thenIO1_info () at libraries/base/GHC/Base.hs:1072 #7 0x00000000006ebcb0 in ?? () at rts/Exception.cmm:332 #8 0x00000000006e7320 in ?? () (gdb)
Из-за особенностей кучи GHC и интенсивной оптимизации, которую он выполняет, довольно сложно проверять значения привязок во время выполнения. Поэтому отладка программы на Haskell с поддержкой DWARF всё ещё несколько уступает возможностям типичных отладчиков для императивных языков.
9.2. Запрос трассировки стека из кода Haskell
Среда выполнения GHC имеет встроенную поддержку сбора информации о трассировке стека работающей программы на Haskell. Для этого сейчас требуется наличие библиотеки libdw из пакета elfutils. Разумеется, трассировка стека будет мало полезна, если в исполняемом файле и его зависимых библиотеках нет отладочной информации.
Функции трассировки стека доступны программам на Haskell в модуле GHC.ExecutionStack. Подробные сведения об использовании см. в документации Haddock к этому модулю.
9.3. Запрос трассировки стека с помощью SIGQUIT
На POSIX-совместимых платформах среда выполнения GHC (собранная с поддержкой libdw) выводит трассировку стека при получении сигнала SIGQUIT stderr (во многих системах этот сигнал можно отправить сочетанием клавиш Ctrl-\). Например, используя тот же fib.hs, что и выше:
$ ./fib & killall -SIGQUIT fib
Caught SIGQUIT; Backtrace:
0x7f3176b15dd8 dwfl_thread_getframes (/usr/lib/x86_64-linux-gnu/libdw-0.163.so)
0x7f3176b1582f (null) (/usr/lib/x86_64-linux-gnu/libdw-0.163.so)
0x7f3176b15b57 dwfl_getthreads (/usr/lib/x86_64-linux-gnu/libdw-0.163.so)
0x7f3176b16150 dwfl_getthread_frames (/usr/lib/x86_64-linux-gnu/libdw-0.163.so)
0x6dc857 libdwGetBacktrace (rts/Libdw.c:248.0)
0x6e6126 backtrace_handler (rts/posix/Signals.c:541.0)
0x7f317677017f (null) (/lib/x86_64-linux-gnu/libc-2.19.so)
0x642e1c integerzmgmp_GHCziIntegerziType_eqIntegerzh_info (libraries/integer-gmp/src/GHC/Integer/Type.hs:320.1)
0x643023 integerzmgmp_GHCziIntegerziType_eqInteger_info (libraries/integer-gmp/src/GHC/Integer/Type.hs:312.1)
0x406eca roz_info (/opt/exp/ghc/ghc-dwarf//Fib.hs:2.1)
0x6eafc0 stg_upd_frame_info (rts/Updates.cmm:31.1)
0x64ee06 integerzmgmp_GHCziIntegerziType_plusInteger_info (libraries/integer-gmp/src/GHC/Integer/Type.hs:393.1)
0x6eafc0 stg_upd_frame_info (rts/Updates.cmm:31.1)
...
0x6439d0 integerzmgmp_GHCziIntegerziType_ltIntegerzh_info (libraries/integer-gmp/src/GHC/Integer/Type.hs:343.1)
0x4efed4 base_GHCziShow_zdwintegerToString_info (libraries/base/GHC/Show.hs:442.1)
0x4f069d base_GHCziShow_zdfShowIntegerzuzdcshow_info (libraries/base/GHC/Show.hs:145.5)
0x488833 base_GHCziIOziHandleziText_zdwa8_info (libraries/base/GHC/IO/Handle/Text.hs:582.1)
0x6ebbb0 stg_catch_frame_info (rts/Exception.cmm:370.1)
0x6e7220 stg_stop_thread_info (rts/StgStartup.cmm:42.1)
9.4. Примечания для разработчиков: аннотации DWARF
Примечание
Большинству пользователей не нужно беспокоиться о подробностях, описанных в этом разделе. Он предназначен главным образом для разработчиков инструментов, которым необходимо интерпретировать специфичные для GHC аннотации DWARF в скомпилированных бинарных файлах.
При вызове с флагом -g GHC создаёт стандартную отладочную информацию DWARF v4. Этот формат используется почти всеми POSIX-совместимыми целевыми платформами и позволяет средствам отладки и анализа производительности (например, gdb, lldb и perf) исследовать структуру программ, скомпилированных GHC.
В частности, GHC создаёт следующие разделы DWARF:
-
.debug_info -
Сущности отладочной информации (DIE), описывающие все базовые блоки скомпилированной программы.
-
.debug_line -
Информация о номерах строк, необходимая для сопоставления адресов инструкций со строками исходного кода.
Обратите внимание: информация о строках в этом разделе значительно менее подробная, чем информация в
.debug_info. Если.debug_lineтребует сопоставлять каждой инструкции ровно одно место в исходном коде, то DIE в.debug_infoпозволяют определить все соответствующие места в исходном коде. -
.debug_frames -
Информация о кадрах вызовов (CFI), необходимая для раскрутки стека и построения трассировки стека вызовов.
-
.debug_arange -
Информация о диапазонах адресов, необходимая для эффективного поиска в отладочной информации.
9.4.1. Сущности отладочной информации
В разделе .debug_info GHC может создавать следующие стандартные DIE:
-
DW_TAG_compile_unit -
Представляет единицу компиляции (например, модуль Haskell).
-
DW_TAG_subprogram -
Представляет базовый блок верхнего уровня C-\-.
-
DW_TAG_lexical_block -
Представляет базовый блок C-\-. Обратите внимание: это несколько отличается от предполагаемого смысла данного типа DIE, поскольку он не обязательно отражает лексическую область видимости в исходной программе.
Поскольку результаты компиляции GHC не полностью соответствуют конструкциям DWARF, GHC использует расширяемость стандарта DWARF для предоставления дополнительной информации.
К сожалению, выразительных возможностей DWARF недостаточно, чтобы полностью описать код, создаваемый GHC. Особенно заметно это в информации о строках: GHC вынужден выбирать одно из нескольких возможных исходных мест, из которых был получен код. Это ограничивает полезность информации DWARF при использовании традиционных средств статистического профилирования. Для профилирования рекомендуется использовать расширенную отладочную информацию. См. раздел Профилирование ниже.
Помимо обычных DIE, определённых спецификацией DWARF, GHC создаёт и другие сущности, используя области пространства тегов и атрибутов, зарезервированные для расширений производителя.
9.4.1.1. DW_TAG_ghc_src_note
DIE DW_TAG_ghc_src_note (тег 0x5b01) являются дочерними элементами DIE DW_TAG_lexical_block. Они описывают исходные диапазоны, из которых был получен блок; формально эти диапазоны причинно обусловливают создаваемый код: изменения кода в указанном диапазоне могут изменить код блока, а изменения за его пределами гарантированно не повлияют на код блока.
Диапазоны описываются следующими атрибутами:
-
DW_AT_ghc_span_file (0x2b00, string) -
имя исходного файла
-
DW_AT_ghc_span_start_line (0x2b01, integer) -
номер строки, в которой начинается диапазон
-
DW_AT_ghc_span_start_col (0x2b02, integer) -
номер столбца, в котором начинается диапазон
-
DW_AT_ghc_span_end_line (0x2b03, integer) -
номер строки, в которой заканчивается диапазон
-
DW_AT_ghc_span_end_col (0x2b04, integer) -
номер столбца, в котором заканчивается диапазон
9.5. Дополнительная литература
Подробнее об отладочной информации, создаваемой GHC, см. в докторской диссертации Питера Вортмана *Profiling Optimized Haskell: Causal Analysis and Implementation*.
9.6. Прямое сопоставление
Помимо отладочной информации DWARF, которую могут использовать многие стандартные инструменты, существует специфичный для GHC способ сопоставить указатели на таблицы информации с местами в исходном коде. Эта таблица поиска создаётся с помощью флага -finfo-table-map.
-
-finfo-table-map -
- Версия:
-
9.2
Этот флаг включает создание таблицы, сопоставляющей адрес таблицы информации с приблизительной позицией в исходном коде, откуда эта таблица информации была статически создана. Если вам нужна более точная информация о таблицах информации конструкторов, используйте также флаг
-fdistinct-constructor-tables.Флаг
-finfo-table-mapможет значительно увеличить размер бинарного файла — в зависимости от размера проекта. Для проекта размером с GHC увеличение составило около 200 мегабайт.- Версия:
-
9.8
Чтобы уменьшить размер бинарных файлов, собранных с включённым
-finfo-table-map, рассмотрите возможность сборки GHC из исходного кода с передачей флага--enable-ipe-data-compressionскриптуconfigure. В этом случае GHC сожмёт связанную с-finfo-table-mapотладочную информацию в бинарных файлах с помощью библиотеки сжатия libzstd. Примечание: для этой функции требуется, чтобы на машине, собирающей GHC, была установлена libzstd. При желании библиотеку сжатияlibzstdможно статически скомпоновать с полученным компилятором (на платформах, отличных от Darwin), используя параметр конфигурации--enable-static-libzstd.В тестовой сборке самого GHC включение сжатия позволило уменьшить размер сборки с включённым
-finfo-table-mapболее чем на 20%.- Версия:
-
9.10
- Подразумевает:
- Подразумевает:
-
-fno-info-table-map-with-stack -
- Версия:
-
9.10
Таблицы информации
STACKчасто составляют большинство записей в карте таблиц информации. Однако, несмотря на их вклад в размер исполняемого файла, они редко бывают полезны, если только отладка не ведётся с помощью такого инструмента, как ghc-debug. Используйте этот флаг, чтобы исключить таблицы информацииSTACKиз карты таблиц информации и уменьшить размер исполняемых файлов с данными профилирования таблиц информации.Обратите внимание: флаг
-finfo-table-map-with-stackподразумевается флагом-finfo-table-map.
-
-finfo-table-map-with-fallback -
- Версия:
-
9.10
Включает в карту таблиц информации таблицы, для которых отсутствуют сведения о местоположении в исходном коде. Обратите внимание: этот флаг подразумевается флагом
-finfo-table-map.
-
-fno-info-table-map-with-fallback -
- Версия:
-
9.10
У некоторых таблиц информации, например таблиц для примитивных типов замыканий, нет места происхождения в исходном коде программы. При включённом
-finfo-table-mapтаким таблицам информации назначаются исходные места по умолчанию, и они включаются в карту таблиц информации. Используйте этот флаг, чтобы исключить их из карты и уменьшить размер исполняемых файлов с данными профилирования таблиц информации.
-
-fdistinct-constructor-tables -
- Версия:
-
9.2
Для каждого использования конструктора данных в исходной программе создаётся новая таблица информации. Это полезно вместе с
-finfo-table-mapи режимом профилирования-hi, поскольку каждая таблица информации будет соответствовать использованию конструктора данных, а не самому конструктору.
9.7. Запрос к карте таблиц информации
Если карта таблиц информации создана, её можно использовать двумя способами.
- Функция Haskell
whereFromпозволяет определить исходную позицию, в которой, предположительно, было создано конкретное замыкание. - Полное сопоставление также сохраняется в журнал событий.
Если вы используете gdb, то можете напрямую вызвать функцию lookupIPE (предоставляемую IPE.h и экспортируемую в публичном API), чтобы найти всю доступную информацию о таблице информации конкретного замыкания.