Spec-Zone.ru › Haskell 9

9. Отладка скомпилированных программ

Начиная с версии 7.10, GHC может генерировать отладочную информацию, которая помогает отладочным инструментам понимать код, сгенерированный GHC. Эта отладочная информация используется большинством утилит отладки UNIX.

-g
-g⟨n⟩
Since:

7.10, числовые уровни с 8.0

Implies:

-fexpose-internal-symbols, когда ⟨n⟩ >= 2.

Генерирует отладочную информацию в объектный код. В настоящее время поддерживается только отладочная информация DWARF для архитектур x86-64 и i386. В настоящее время принимаются уровни отладки от 0 до 3:

  • -g0: отладочная информация не генерируется
  • -g1: генерируются записи для разворачивания стека для функций верхнего уровня (достаточно для основных трассировок стека)
  • -g2: генерируются записи для разворачивания стека для функций верхнего уровня и внутренних блоков (позволяя более точную трассировку стека, чем с -g1).
  • -g3: генерирует специфичную для GHC информацию DWARF для использования более сложными отладочными инструментами, знакомыми с Haskell (см. Сущности отладочной информации для получения подробной информации)

Если ⟨n⟩ опущено, предполагается уровень 2.

Обратите внимание, что для надежного разворачивания стека все библиотеки, включая внешние библиотеки и библиотеки, поставляемые с GHC, такие как base, должны быть скомпилированы с информацией о разворачивании стека. Бинарные дистрибутивы GHC, настроенные таким образом, предоставляются для определенного числа платформ; другим платформам рекомендуется использовать трансформер +debug_info от Hadrian. Также обратите внимание, что встроенная поддержка разворачивания стека, предоставляемая модулем 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) будет генерировать трассировку стека при получении сигнала stderr сигнала SIGQUIT (на многих системах этот сигнал можно отправить, используя 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. Сущности информации отладки

GHC может генерировать следующие стандартные DIE в секции .debug_info,

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

DW_TAG_ghc_src_note DIE (тег 0x5b01) находятся как дочерние элементы DW_TAG_lexical_block DIE. Они описывают области исходного кода, которые привели к блоку; формально эти области являются причиной сгенерированного кода: изменения в коде в заданной области могут изменить код внутри блока; наоборот, изменения за пределами области гарантированно не повлияют на код в блоке.

Области описываются с помощью следующих атрибутов:

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, см. в диссертации Петра Вортмана, *Профилирование оптимизированного Haskell: Причинный анализ и реализация*.

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

Подразумевает:

-finfo-table-map-with-stack

Подразумевает:

-finfo-table-map-with-fallback

-finfo-table-map-with-stack
С:

9.10

Включить таблицы информации для STACK замыканий в таблице поиска по информации. Обратите внимание, что этот флаг подразумевается флагом -finfo-table-map.

-fno-info-table-map-with-stack
С:

9.10

Таблицы информации STACK зачастую составляют большую часть записей в таблице поиска информации. Однако, несмотря на их вклад в размер исполняемого файла, они редко бывают полезными, за исключением отладки с помощью инструмента, такого как ghc-debug. Используйте этот флаг, чтобы исключить STACK таблицы информации из таблицы поиска информации и уменьшить размер исполняемых файлов с информацией о профилировании таблиц информации.

-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. Запрос карты таблицы информации

Если она сгенерирована, то карта таблицы информации может быть использована двумя способами.

  1. Функция whereFrom Haskell может быть использована для определения исходного положения, в котором, по нашему мнению, был создан определённый замыкатель.
  2. Полное отображение также выводится в журнал событий.

Если вы используете gdb, то можете использовать функцию lookupIPE (предоставленную IPE.h и экспортированную в общедоступном API) напрямую, чтобы найти любую информацию, известную о таблице информации для конкретного замыкателя.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/debug-info.html

Spec-Zone.ru

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