Spec-Zone.ru › Haskell 8

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

Spec-Zone.ru

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