debugging-trick/

Бывает так, что ошибка в коде воспроизводится лишь при определенном стечение обстоятельств. Эти обстоятельства могут быть довольно сложными, особенно если приложение распределенное и каждый его экземпляр состоит из N процессов. Подцепиться отладчиком к правильному процессу в правильный момент времени практически невозможно. В подобных случаях я использую один незамысловатый прием, речь о котором и пойдет далее.

Идея очень простая. Она заключается в модификации кода приложения так, чтобы он сам определял возникновение ошибки, сигнализировал об этом, а потом ждал, когда программист придет с отладчиком. Рассмотрим упрощенный пример:

#include <stdio.h>
#include <unistd.h>

int main ( ) {
/* if( some condition violated ) { */
int ret ;
printf ( «Attach with the debugger, pid = %d n » , getpid ( ) ) ;
do {
ret = sleep ( 1 ) ;
} while ( ret == 0 ) ;
/* } */
printf ( «sleep() returned %d n » , ret ) ;
return 0 ;
}

В реальном коде вместо printf(...) будет какой-нибудь log(WARNING, ...) . Логи приложения можно мониторирить командой watch:

watch «grep -r ‘Attach with the debugger’ /some/patch && grep -r …»

Компилируем с отладочными символами, запускаем, гоняем тесты, на которых воспроизводится проблема, ждем. (Бывает и так, что нет нормальных шагов для воспроизведения проблемы, но подобный сценарий выходит за рамки статьи.) Когда проблема воспроизвелась, цепляемся к процессу отладчиком:

gdb -p 1234

Ставим бряк на while(ret == 0) :

(gdb) b test.c:9
Breakpoint 1 at 0x10498: file test.c, line 9.

… и возобновляем работу приложения:

(gdb) c
Continuing

Когда встали на бряке, модифицируем переменную ret:

Breakpoint 1, main () at test.c:9
9   } while(ret == 0);
(gdb) p ret=1

Поздравляю, мы оказались в правильном процессе в правильный момент времени. Проблема решена. Далее приложение можно отлаживать, как обычно . Кстати, проверок в коде может быть больше одной. Источник ошибки можно быстро локализовать по принципу бинарного поиска.

Fun fact! В MacOS, в отличие от Linux, системный вызов sleep() сразу возвращает управление при подключении к процессу с отладчиком. На этой платформе можно написать sleep(1000) и не добавлять цикл.

Уверен, что опытные разработчики не узнали для себя ничего нового. Но я подумал , что кому-то данная информация все же может пригодится. В примерах были использованы C и GDB, но прием работает с любым стеком технологий, где есть отладчик.

EnglishRussianUkrainian