14. Отладка скомпилированных программ
С момента выпуска версии 7.10 GHC может генерировать отладочную информацию, которая поможет инструментам отладки понять код, сгенерированный GHC. Эта отладочная информация может быть использована большинством инструментов отладки UNIX.
-
-g -
-g⟨n⟩ -
- С момента
-
7.10, числовые уровни начиная с 8.0
Генерировать отладочную информацию в объектный код. В настоящее время поддерживается только отладочная информация DWARF для x86-64 и i386. В настоящее время поддерживаются уровни отладки от 0 до 3:
-
-g0: отладочная информация не генерируется -
-g1: генерируются записи разворачивания стека для функций верхнего уровня (достаточно для базовых трассировок стека) -
-g2: генерируются записи разворачивания стека для функций верхнего уровня, а также внутренних блоков (позволяя более точные трассировки стека, чем с-g1). -
-g3: то же, что и-g2.
Если ⟨n⟩ опущено, предполагается уровень 2.
14.1. Руководство
Рассмотрим простой пример:
1 2 3 4 5 6 7 8
Сначала посмотрим, как выполняется этот код. Начнём с того, что сообщим 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)
Здесь мы использовали опцию runtime системы -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 всё ещё немного ограничен по сравнению с типичными императивными отладчиками.
14.2. Запрос трассировки стека из кода Haskell
Встроенная система времени выполнения GHC поддерживает сбор информации о трассировке стека из выполняемой программы Haskell. В настоящее время это требует наличия библиотеки libdw из пакета elfutils. Конечно, трассировка стека будет мало полезна, если отладочная информация не доступна в исполняемом файле и его зависимых библиотеках.
Функциональность трассировки стека экспонирована для использования в программах Haskell в модуле GHC.ExecutionStack. Смотрите документацию Haddock в этом модуле для получения подробной информации об использовании.
14.3. Запрос трассировки стека с помощью SIGQUIT
На платформах, совместимых с POSIX, система времени выполнения GHC (при построении с поддержкой libdw ) будет генерировать трассировку стека при получении сигнала 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)
14.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 -
Информация об диапазонах адресов, необходимая для эффективного поиска в отладочной информации.
14.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 генерирует различные другие, используя области расширения для поставщиков в пространстве тегов и атрибутов.
14.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) -
номер столбца конца фрагмента
14.5. Дополнительная литература
Дополнительную информацию об отладочной информации, генерируемой GHC, можно найти в диссертации Петра Вортмана *Профилирование оптимизированного Haskell: причинный анализ и реализация*.
© 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/8.10.2/docs/html/users_guide/debug-info.html