Поиск по сайту:

Отладка с помощью 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.

Наслаждайтесь отладкой!