Отладка с помощью GDB: начало работы
Этот сбой приложения не должен быть концом пути! Изучите основы использования GDB, мощного отладчика GNU, и узнайте, как отлаживать дампы ядра в Linux. Идеально подходит как для конечных пользователей, так и для новичков в отладке.
Что такое ГБД?
Инструмент GDB — старая, очень уважаемая утилита отладки в Linux GNU Toolset. Он предоставляет собственную командную строку, широкий набор команд и функций, а также пошаговое выполнение программы (компьютерного кода) и даже функциональность модификации.
Разработка GDB началась где-то в 1986-1988 годах, а в 1988 году инструмент стал частью Free Software Foundation. Он абсолютно бесплатный и может быть легко установлен во всех основных дистрибутивах Linux.
Если вы работаете в Windows, вам может быть интересно прочитать Дампы памяти Windows: для чего они нужны? вместо!
Пользователям Linux важно понимать, какое место GDB занимает в потоке процесса при рассмотрении компьютерных багов и ошибок. Возможны три сценария. Во-первых, может быть конечный пользователь, столкнувшийся со сбоем приложения, который хотел бы узнать немного больше о том, что произошло, выяснить, известна ли уже сообществу эта ошибка и т. д.
Это обычная ситуация, и большинство продвинутых пользователей в тот или иной момент обнаруживают, что отлаживают сбой приложения. Знание GDB чрезвычайно помогает в решении этой задачи. Подробнее об этом ниже.
Второй сценарий — это профессионал (например, ИТ-консультант или инженер-испытатель), который сталкивается с аварией приложения, которое они также поддерживают или обслуживают. В таких случаях инженер, вероятно, захочет отладить замеченный сбой, особенно если он инженер-испытатель, например, чтобы получить обратную трассировку (обзор) того, какие функции работали во время сбоя и т. д. Это может помочь лучше определить ошибку и может помочь сузить тестовый пример.
Третий сценарий — это сценарий разработчика, который захочет использовать GDB на более профессиональном уровне, например, для установки точек останова, отладки слежения за переменными, анализа дампа памяти и т. д. Несмотря на то, что объем этой статьи немного мал для таких профессионалов, позже будет более подробная статья GDB. И, если вы разработчик и никогда не работали с GDB, продолжайте читать.
Что такое дамп ядра?
Если вы когда-нибудь смотрели «Звездный путь» и слышали, как капитан Пикард (или Джейнвей!) давала указание «сбросить варп-ядро», у вас будет довольно хорошее представление о том, как может выглядеть дамп ядра. По сути, это был основной компонент (варп-ядро), выброшенный в результате какого-то предполагаемого сбоя или проблемы. Скорее всего, это был каламбур на тему сброса ядра Linux.
Помимо всего прочего, дамп ядра — это просто файл, сгенерированный с (полным или частичным) состоянием (и памятью) приложения на момент его сбоя.
Дамп ядра — это двоичный файл, который может быть прочитан только отладчиком. GDB является таким отладчиком, и одним из лучших. Дамп ядра может быть записан самим приложением, вызвавшим сбой (обычно не используется, хотя это возможно, и некоторые более крупные программы с несколькими масштабами могут использовать это), но чаще записывается самой операционной системой.
В некоторых операционных системах дамп ядра отключен по умолчанию, или они минимизируют дампы ядра до мини-дампа, что может помочь при отладке приложения, но, вероятно, будет гораздо более ограниченным, чем полный дамп ядра. Опять же, полный дамп ядра может быть проблематичным; например, если у вас есть система с 128 ГБ памяти, и ваше приложение использует большую часть этой памяти, дамп ядра (файл на диске) может быть примерно такого же размера.
Настройка дампов ядра в вашей конкретной ОС выходит за рамки этой статьи, но эту информацию относительно легко найти в Интернете. Просто введите в своей любимой поисковой системе фразу вроде «Настроить дампы ядра в Linux Mint», заменив «Mint» названием вашей операционной системы Linux.
В зависимости от вашей операционной системы и ее текущей настройки может потребоваться небольшая настройка и редактирование файлов конфигурации, несколько перезагрузок и иногда некоторое легкое решение проблем, но после установки вы сможете использовать GDB против дампов ядра. пишется всякий раз, когда приложение аварийно завершает работу.
Обратите внимание, что включение записи дампов ядра в вашей операционной системе всегда будет ограничено изменением параметров конфигурации в существующих (или новых) файлах конфигурации. Никаких других приложений не нужно устанавливать, чтобы дампы ядра можно было включить и записать при сбое приложения. Инструмент GDB, однако, должен быть установлен, но по умолчанию он будет доступен в репозитории приложений вашего основного дистрибутива Linux.
Установка ГБД
Чтобы установить GDB в свой дистрибутив Linux на основе Debian/Apt (например, Ubuntu и Mint), выполните в терминале следующую команду:
sudo apt установить gdb
Чтобы установить GDB в ваш дистрибутив Linux на основе RedHat/Yum (например, RHEL, Centos и Fedora), выполните в терминале следующую команду:
sudo yum установить gdb
Расположение основных дампов и воспроизводимость проблем
После того, как вы включили дампы ядра и установили GDB, пришло время найти и прочитать ваш дамп ядра (файл, созданный операционной системой при сбое вашего приложения) с помощью GDB.
Если вы настроили свою систему для создания дампов ядра после сбоя приложения, вполне вероятно, что для предыдущего сбоя дамп ядра недоступен. Попробуйте выполнить те же действия в своем приложении, чтобы воспроизвести сбой/проблемы.
Как только это произошло, проверьте расположение по умолчанию для дампов ядра в вашем дистрибутиве Linux (например, /var/crash
или /var/lib/systemd/coredump/
в Ubuntu/Mint и Centos или /var/spool/abrt
в RedHat). Регулярно, а иногда и в зависимости от сделанных настроек, дамп ядра может также записываться в каталог, где находится двоичный файл (приложение) (вероятнее всего), или в его основной рабочий каталог (что несколько менее вероятно).
Из-за двусмысленности формулировки у вас может сложиться впечатление, что погоня за дампом ядра может быть чем-то неуловимым. Это будет точная оценка. В то время как сам инструмент GDB очень стабилен, отказоустойчив и зрел, написание дампов ядра является гораздо более случайным занятием. Для этого есть несколько причин, главная из которых заключается в том, что большинство основных дистрибутивов Linux имеют разные реализации поведения дампа памяти и множество соответствующих параметров конфигурации.
Написание дампов ядра также довольно существенно затрагивает вопросы безопасности; после того, как вся основная память компьютера, полностью или частично, записывается в дамп, что позволяет пользователям GDB считывать потенциально конфиденциальную информацию. Кроме того, как объяснялось ранее, иногда запись дампа ядра может быть ограничена системными ресурсами.
Наконец, мы имеем дело с вылетающим приложением в неизвестном состоянии, и не всегда есть возможность записать такое состояние на диск. Справедливо будет сказать, что можно ожидать, что потребуется некоторое время, чтобы заставить дамп ядра работать надежно и постоянно в данной системе. Это подводит нас к теме воспроизводимости проблем.
Если у вас есть проблема, которая постоянно воспроизводится, например, ваш компьютер всегда дает сбой при воспроизведении музыкального файла, то настройка дампов ядра и отладка с помощью GDB имеет большой смысл. Однажды я обнаружил ошибку в аудиодрайвере таким образом. Если вы инженер-испытатель и постоянно тестируете данную программу, то имеет смысл настраивать дампы ядра и тем более использовать GDB.
Однако, если у вас есть один случай сбоя приложения, и проблема не может быть легко воспроизведена, у вас есть выбор. Если вы хотите быть готовым к следующему сбою приложения и/или если приложение очень важно для вас (например, для обеспечения непрерывности бизнеса), вам нужно будет, по крайней мере, настроить дампы ядра, чтобы в следующий раз при сбое приложения создается дамп ядра. GDB можно установить даже после создания дампа ядра.
Чтение дампа ядра с помощью GDB
Теперь, когда у вас есть дамп ядра для данного приложения, вызвавшего сбой, и вы установили GDB, вы можете легко вызвать GDB:
gdb ./myapp ./core
Здесь у нас есть приложение с именем myapp
, которое вылетает сразу после запуска. В этой системе Ubuntu дампы ядра можно было включить, просто установив ulimit -c
на большее число от имени пользователя root, а затем запустив приложение. Результатом является дамп ядра, сгенерированный как ./core
.
Мы вызываем gdb с двумя опциями. Первый вариант — это приложение/двоичный/исполняемый файл, вызвавший сбой. Второй — это дамп ядра, созданный операционной системой в результате сбоя приложения.
Первоначальный анализ дампа ядра
Когда GDB запускается, как видно из выходных данных GDB на изображении выше, он предоставляет множество информации. Тщательный анализ может предоставить нам много информации о проблеме, с которой столкнулась ваша система, что привело к сбою приложения.
Например, мы сразу отмечаем, что программа завершилась из-за ошибки SIGFPE
, арифметического исключения. Мы также подтверждаем, что GDB правильно идентифицировал исполняемый файл по строке Core was generate by \\./myapp
.
Мы также видим интересную строку/понятие В ./myapp символы отладки не найдены
, указывающие на то, что символы отладки не могут быть прочитаны из двоичного файла приложения. Символы отладки — это то, где все может быстро стать неясным. В большинстве двоичных файлов оптимизированного/выпускного уровня (которые представляют собой большинство приложений, которые вы запускаете изо дня в день) информация об символах отладки будет удалена из результирующего двоичного файла для экономии места и увеличения времени выполнения приложений/эффективности работы.
В зависимости от того, насколько многое было удалено из полученного оптимизированного
бинарного файла, даже простые имена функций могут быть недоступны. В нашем случае имена функций по-прежнему видны, как видно из ссылки на имя функции do_the_maths()
. Однако никакие переменные не видны, и это то, на что ссылался GDB с примечанием Нет символов отладки, найденных в ./myapp
. Если имена функций недоступны, ссылки на имена функций будут отображаться как ??
вместо имени функции.
Однако мы можем видеть, каково имя сбойного фрейма/функции: #0 do_the_maths()
. Вам может быть интересно, что такое frame. Лучший способ описать фрейм и подумать о нем — подумать о функциях, например do_the_maths()
, в компьютерной программе. Один кадр — это одна функция.
Таким образом, если программа проходит через различные функции, например, функция main()
в программе C или C++, которая, в свою очередь, может вызвать функцию с именем math_function()
, которая, наконец, вызовы do_the_maths()
приведут к трем кадрам, при этом кадр #0
(последний результирующий и аварийный кадр) будет функцией do_the_maths()
, кадр #1
является функцией math_function()
, а кадр #2 (первый вызванный кадр с наибольшим номером) является функцией main()
.
Нередко в некоторых компьютерных программах можно увидеть стек
из 10-20 кадров, а случайный стек
из 40 или 50 кадров вполне возможен, например, в программном обеспечении баз данных. Обратите внимание, что порядок кадров обратный; сначала сбой кадра с номером кадра #0
, а затем переход оттуда обратно к первому кадру. Это имеет смысл, если вы думаете с точки зрения отладчика/дампа ядра; он начался с того места, где произошел сбой, а затем вернулся к кадрам и полностью к функции main()
.
Термин стек кадров теперь должен быть более понятным; стек фреймов, от наиболее конкретных (например, do_the_maths
) до наименее конкретных (например, main()
), которые помогут нам в оценке того, что произошло. Итак, можем ли мы увидеть этот стек/стек кадров в GDB для нашей текущей задачи? Мы точно можем.
Время возврата!
Как только мы получили приглашение gdb, мы можем выполнить команду bt
обратной трассировки. Эта команда будет — только для текущего потока (который чаще всего является потоком сбоя; GDB автоматически обнаруживает потоки сбоя и автоматически помещает нас в этот поток, хотя он не всегда делает это правильно) — сбрасывает трассировку кадра. стек, о котором мы говорили выше. Другими словами, мы сможем увидеть прохождение через функции, через которые прошла программа до момента ее сбоя.
Здесь мы выполнили команду bt
, которая дает нам хороший стек вызовов, или стек кадров, или стек, или обратную трассировку — какое бы слово вы ни предпочли, все они означают одно и то же. Мы видим, что main()
вызывает math_function
, которая, в свою очередь, вызывает функцию do_the_maths()
.
Как мы видим, имена переменных не отображаются в этом конкретном выводе, и GDB уведомил нас о том, что символы отладки недоступны. Хотя полученный двоичный файл по-прежнему имел некоторый уровень доступной отладочной информации, так как все имена фреймов/функций отображались правильно, и ни один фрейм не отображался как ??
.
Таким образом, я перекомпилировал приложение из исходного кода с помощью параметра -ggdb
в gcc
(как в gcc -ggdb ...
) и заметил, что доступно гораздо больше информации:
На этот раз мы ясно видим, что GDB читает символы (на что указывает Чтение символов из ./myapp...
), и что мы можем видеть имена переменных, такие как x
и г
. При условии, что исходный код доступен, мы даже можем увидеть точную строку исходного кода, в которой возникла проблема (как указано 3 o=x/y;
). Мы также можем видеть строки исходного кода для всех кадров.
Немного изучив вывод, мы сразу понимаем, что не так. Переменная o
была установлена на переменную x
, разделенную на y
. Однако, присмотревшись к вводу функции (показанному как (x=1, y=0)
), мы увидим, что приложение пыталось разделить число на ноль, что математически невозможно, что приводит к сигналу SIGFPE
(арифметическое исключение).
Таким образом, наша проблема ничем не отличается от набора 1
, деленного на 0
на вашем калькуляторе; он тоже будет жаловаться, хоть и не крашится ;)
В таком случае можно придумать обходной путь, предоставив приложению другой ввод (когда вы пытаетесь обойти определенный сбой и не имеете доступного исходного кода) — например, вы можете попробовать ввести 0,000000001 вместо 0 и принять небольшую корректировку округления, или — при условии, что исходный код доступен и вы улучшаете исходный код как разработчик — вы можете добавить в свою программу некоторый дополнительный код для обработки ошибочных входных данных в переменную y
.
Как видите, GDB — очень универсальный инструмент, в котором можно оказаться в самых разных ролях и ситуациях. Также точно в зависимости от ситуации и окружающей информации (доступен ли дамп ядра, есть ли у нас символы отладки и т. д.), насколько далеко можно зайти в пути GDB в каждом экземпляре. Но ясно одно; гораздо больше шансов более полно отладить данную ситуацию, если, когда и как используются дампы ядра и GDB.
Подведение итогов
В этой статье мы представили GDB, отладчик GNU, который можно легко установить и использовать в любом основном дистрибутиве Linux. Мы обсудили необходимость сначала настроить дампы ядра в целевой системе и их тонкости. Мы увидели, как установить GDB, что позволяет нам читать и обрабатывать сгенерированные дампы ядра.
Затем мы рассмотрели базовое взаимодействие между дампом ядра и пользователем или разработчиком и предоставили практический пример фактического анализа, взглянув на команду обратной трассировки bt
. Мы также обсудили оптимизированные и отладочные [symbol] инструментальные сборки приложений и то, как это влияет на уровень видимости информации внутри GDB.
В следующей статье мы углубимся в GDB и рассмотрим более продвинутое использование GDB.
Наслаждайтесь отладкой!