Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Параллельное программирование над общей памятью. POSIX Threads. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
1 Мб
Скачать

которые ошибки могут обнаруживаться не при каждом запуске. К ин-

струментам этого типа относятся: Valgrind, Intel Thread Checker, Allinea DDT, RogueWave TotalView.

ОТЛАДКА МНОГОПОТОЧНЫХ ПРОГРАММ С ПОМОЩЬЮ GDB

GDB (GNU Debugger) – популярный отладчик в системах UNIX и Linux. Имеет текстовый интерфейс: управление отладкой осуществляется через ввод команд в командной строке отладчика.

Для использования отладчика программу нужно скомпилировать с отладочной информацией (см. подробней в разделе «Компиляция программ» лабораторного практикума). Начать отладку программы можно тремя способами:

1)запустить программу в отладчике;

2)подключить отладчик к работающей программе;

3)открыть дамп памяти программы в отладчике.

Для запуска программы в отладчике нужно выполнить команду:

$ gdb <путь к исполняемому файлу>

Команда запустит отладчик для указанной программы и переведет его в режим ожидания команд (появится приглашение «(gdb)»).

Для подключения отладчика к уже работающей программе (процессу) нужно выполнить команду:

$ gdb <путь к исполняемому файлу> <PID процесса>

Узнать идентификатор работающего процесса можно с помощью системной команды ps (см. раздел «Команды операционной системы» лабораторного практикума).

Для просмотра дампа памяти программы в отладчике выполните команду:

$ gdb <путь к исполняемому файлу> <путь к файлу дампа>

Дамп памяти создается при завершении процесса из-за критической ошибки (например, ошибки сегментации (англ. Segmentation fault). Открытие дампа в отладчике позволяет узнать место ошибки и провести частичную отладку, например, просмотреть стек вызовов функций и значения переменных.

51

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

$ ulimit -c unlimited

Рассмотрим основные группы команд GDB. Почти каждая команда имеет короткий псевдоним, состоящий из первой или двух первых букв команды (псевдоним показан в квадратных скобках после имени команды).

Запуск программы. Для запуска программы в отладчике используется команда run [r]. Дополнительно можно указать аргументы командной строки для вызываемой программы. Синтаксис команды:

(gdb) run <аргумент 1> <аргумент 2> … <аргумент n>

После запуска выполнение программы продолжается до ее успешного завершения или до возникновения ошибки/исключения, или до достижения точки останова (во всех случаях выводится соответствующее сообщение), после чего управление возвращается в командную строку для продолжения отладки.

Подключение к процессам. Для подключения к работающему процессу используется команда attach, для отключения от процесса – команда detach. При подключении к процессу GDB приостанавливает его для возможности дальнейшей отладки. При отключении от процесса его исполнение возобновляется.

Например, команда

(gdb) attach <PID процесса>

позволяет подключиться к процессу с номером <PID>, а команда

(gdb) detach

позволяет отключиться от текущего процесса.

Работа с точками останова. Точка останова (англ. breakpoint) поз-

воляет приостановить выполнение программы в определенном месте программного кода для анализа состояния программы в этот момент. Для установки точки останова используется команда break [b]:

(gdb) break [<имя файла>:]<номер строки>

или

(gdb) break [<имя файла>:]<имя функции>

52

Команда позволяет создать точку останова или на заданной строке файла (выполнение программы будет приостановлено перед началом выполнения инструкций в этой строке), или на входе в указанную функцию (выполнение будет приостановлено перед началом выполнения тела функции). Точки останова (минимум одну) нужно устанавливать перед запуском программы командой run.

Для вывода информации об установленных точках останова используется команда info [i]:

(gdb) info breakpoints

Для удаления точек останова предназначены команды clear

(gdb) clear [<имя файла>:]<номер строки>

(gdb) clear [<имя файла>:]<имя функции>

и delete

delete [<номер точки>]

Команда clear позволяет удалить точку останова на заданной строке или функции, команда delete удаляет точку по ее номеру (номер можно узнать, вызвав команду info breakpoints). Вызов delete без параметров удаляет все точки останова.

Точки останова могут быть не удалены, а временно отключены, для чего служит команда

disable <номер точки>

При отключении точки останова отладчик перестает на нее реагировать. Для повторного включения точки останова используется команда

enable <номер точки>

Работа с точками наблюдения. В отличие от точек останова точка наблюдения (англ. watchpoint) позволяет отслеживать изменение значения переменной или целого выражения. Для установки точки наблюдения предназначена команда watch:

watch [-l | -location] <выражение>

53

Исполнение программы будет прервано, если изменится значение указанного выражения. Выражение может быть именем переменной, выражением с участием имен переменных и арифметических и логических операций или адресом, приведенным к нужному типу (например, *(int*)0x12345678). Опциональный аргумент -location позволяет наблюдать за изменением значения не самого выражения, а места в памяти, на которое выражение ссылается. Так как переменные принадлежат определенной области видимости в программе, перед установкой точки наблюдения рекомендуется установить точку останова в нужном месте в программе.

Для отслеживания только чтения или чтения / записи переменных применяются команды rwatch и awatch соответственно:

rwatch [-l | -location] <выражение>

awatch [-l | -location] <выражение>

Просмотр и удаление точек наблюдения выполняется теми же командами, что и для точек останова.

Просмотр исходного кода. Для вывода на экран части исходного кода (например, для получения имен функций и номеров строк) ис-

пользуется команда list [l]. Вызов команды list без аргументов

(gdb) list

выводит на экран 10 (по умолчанию) строк текущего исходного файла. Указание номера строки или имени функции

(gdb) list [<имя файла>:]<номер строки>

(gdb) list [<имя файла>:]<имя функции>

позволяет вывести строки вокруг указанной строки или заголовка функции.

При выводе запоминается текущая позиция в файле, поэтому последовательные вызовы команды list позволяют выводить большие

участки файлов. Для возврата к предыдущему блоку строк нужно передать дефис в качестве аргумента:

(gdb) list -

54

Число строк для вывода может быть изменено с помощью команд:

(gdb) set listsize <число>

(gdb) set listsize unlimited

Работа с данными программы. При достижении точки останова или места ошибки полезной является возможность просмотра значений интересующих нас данных и переменных программы. Для вывода значений переменных и выражений используется команда print [p]:

(gdb) print <имя переменной>

(gdb) print <выражение>

В качестве аргумента команда принимает имя одной переменной или выражение, содержащее имена разных переменных, арифметические и логические операции, а также вызовы функций. Имя переменной может быть задано как локальное (по отношению к текущему месту в программе), так и в расширенной форме в виде <имя фай-

ла>::<имя переменной> или <имя функции>::<имя перемен-

ной>.

GDB позволяет изменять значения переменных с помощью коман-

ды set:

(gdb) set var <имя переменной>=<выражение>

Можно также создавать вспомогательные переменные для сеанса отладки:

(gdb) set $<имя вспомогательной переменной>=<выражение>

При обращении к вспомогательной переменной перед ее именем также ставится знак $.

Пошаговое исполнение. После достижении точки останова и выполнения нужных действий (анализ и изменение значения переменных и т. д.) исполнение программы можно продолжить как в обычном режиме, так и в пошаговом. Команда continue [c] продолжает обычное исполнение программы до ее завершения или возникновения следующего события останова (достижение следующей точки останова, возникновение исключения).

(gbd) continue

55

Команда step [s] продолжает выполнение программы до достижения следующей строки кода, после чего исполнение снова приостанавливается. При встрече вызова функции происходит заход в эту функцию и исполнение приостанавливается перед первой строкой тела функции. Опциональный числовой аргумент позволяет повторить команду заданное число раз.

(gdb) step [<число>]

Команда next [n], как и команда step, продолжает выполнение программы до достижения следующей строки кода, но заход в вызываемые функции не производится. Опциональный числовой аргумент позволяет повторить команду заданное число раз.

(gdb) next [<число>]

Работа со стеком и фреймами. Для каждого вызова функции GDB

хранит информацию о вызове: место вызова функции, значения ее аргументов и локальных переменных. Блок данных с такой информацией для каждого вызова называется фреймом (кадром) стека, сокращенно – просто фрейм. Фреймы формируют стек вызовов, по одному фрейму на элемент стека.

Для просмотра стека вызовов используется команда backtrace [bt]. Для каждого фрейма выводится его номер, место вызова функции и значения ее фактических аргументов. Фрейму с номером 0 соответствует самый «вложенный» уровень (место останова), фрейм с наибольшим номером обычно соответствует вызову функции

main.

(gdb) backtrace

Команда frame [f] позволяет перейти на фрейм с указанным номером. Команды up и down сдвинутся на указанное число фреймов вверх (по направлению к внешним фреймам) или вниз (по направлению к вложенным фреймам) по стеку соответственно.

(gdb) frame <номер фрейма>

(gdb) up <число>

(gdb) down <число>

56

Для вывода информации о фрейме используется команда frame без аргументов

(gdb) frame

или команда info с аргументами: frame – для вывода подробного описания фрейма;

(gdb) info frame

args – для вывода значений аргументов фрейма;

(gdb) info args

locals – для вывода значений локальных переменных.

(gdb) info locals

Отладка многопоточных программ. Для многопоточных про-

грамм GDB хранит информацию о созданных потоках. Каждому потоку присваивается уникальный номер. Для просмотра информации о потоках используется команда

(gdb) info threads

Команда thread позволяет переключиться на поток с указанным номером:

(gdb) thread <номер потока>

Чтобы установить точку останова для определенного потока, команде break передается дополнительный аргумент:

(gdb) break <место точки останова> thread <номер потока>

Имеется возможность исполнить некоторую команду для заданного списка потоков либо для всех потоков сразу:

(gbd) thread apply [<список номеров потоков> | all] <команда>

Список потоков задается в виде перечисления их номеров через пробел, например, 1 2 4. Можно также указывать диапазоны, напри-

мер, 1 3-5.

57

Существует два режима работы GDB при отладке многопоточных программ: all-stop и non-stop. В режиме all-stop (используется по умолчанию) при остановке исполнения на точке останова все потоки программы приостанавливают свое исполнение. Команды продолжения исполнения (continue, step, next) также запускают все потоки. В режиме non-stop при остановке исполнения приостанавливается только текущий поток (в котором была точка останова), в то время как остальные потоки продолжают исполнение. Команды продолжения исполнения действуют только на текущий поток.

Для включения режима non-stop в GDB перед запуском программы нужно использовать следующие команды:

(gdb) set pagination off

(gdb) set non-stop on

Для выключения режима используется следующая команда:

(gdb) set non-stop off

Режим non-stop может не поддерживаться на некоторых архитектурах. В этом случае будет использоваться режим all-stop.

Завершение работы. Для завершения сеанса отладки используется команда quit [q].

(gdb) quit

ПРИМЕРЫ СЕАНСОВ РАБОТЫ С GDB

Рассмотрим программу из примера 8, в которую специально внесена ошибка, приводящая к зависанию программы.

Пример 8. Программа с ошибкой

#include <pthread.h> #include <stdio.h>

int a = 0; pthread_mutex_t m;

void* thread_func(void*) { pthread_mutex_lock(&m); a++;

58

//

return 0;

}

int main() { pthread_t t[2];

//

pthread_mutex_init(&m, NULL); pthread_create(&t[0], NULL, thread_func, NULL); pthread_create(&t[1], NULL, thread_func, NULL);

pthread_join(t[0], NULL); pthread_join(t[1], NULL);

pthread_mutex_destroy(&m);

//

return 0;

}

Пусть эта программа находится в файле error.cpp. Скомпилируем и запустим ее на исполнение:

$ g++ -pthread -g -O0 error.cpp $ ./a.out

В результате программа зависнет. Используем отладчик GDB, чтобы обнаружить ошибку.

Способ 1. Анализ стека вызовов

Переключимся на другой терминал (например, нажатием комбинации клавиш Alt+F2) и выполним команду ps, чтобы узнать номер работающего (зависшего) процесса:

$ ps ax | grep

a.out

 

7621

pts/19

Sl+

0:00 ./a.out

7634

pts/5

S+

0:00 grep --color=auto a.out

Нужный нам номер – 7621. Подключимся к процессу из отладчика:

$ sudo gdb ./a.out 7621

Attaching to program: /home/gdb/a.out, process 7621

59

[New LWP 7623]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/x86_64-linux- gnu/libthread_db.so.1".

0x00007fca656bf98d in pthread_join (threadid=140507249293056, thread_return=0x0) at pthread_join.c:92

92 lll_wait_tid (pd->tid);

Главный поток сейчас находится в функции pthread_join. Узнаем информацию обо всех потоках:

(gdb) info threads

 

Id

Target

Id

Frame

* 1

Thread

0x7fca65acd700 (LWP 7621) "a.out" 0x00007fca656bf98d

in pthread_join (threadid=140507249293056, thread_return=0x0) at pthread_join.c:92

2 Thread 0x7fca64aeb700 (LWP 7623) "a.out" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

Второй поток находится в низкоуровневой функции ожидания. Переключимся на него:

(gdb) thread 2

[Switching to thread 2 (Thread 0x7fca64aeb700 (LWP 7623))]

#0

__lll_lock_wait ()

 

 

at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135

2:

movl

%edx, %eax

Посмотрим стек вызовов:

(gdb) bt

#0 __lll_lock_wait ()

at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007fca656c0dbd in __GI___pthread_mutex_lock (mutex=0x6010a0 <m>) at ../nptl/pthread_mutex_lock.c:80

#2 0x00000000004007cc in thread_func () at error.cpp:8

#3 0x00007fca656be6ba in start_thread (arg=0x7fca64aeb700) at pthread_create.c:333

#4 0x00007fca653f43dd in clone ()

at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

60

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]