GDB не прерывает программу немедленно

При отладке большого приложения на C я вижу странное поведение gdb: я всегда могу прервать программу, нажав Ctrl+C:

^C
Program received signal SIGINT, Interrupt.
0x76f58964 in select () at ../sysdeps/unix/syscall-template.S:81
81      in ../sysdeps/unix/syscall-template.S
(gdb)

Однако после достаточного времени работы программы (например,> 1 дня) я больше не могу легко прерывать программу. При попытке прервать программу с помощью Ctrl+C просто показывает gdb

^C
Program received signal SIGINT, Interrupt.

и висит там от нескольких минут до часов. Если это занимает больше нескольких минут, я обычно открываю другой терминал и вручную убиваю gdb, чтобы иметь возможность продолжить.

Вопрос. Это ожидаемое поведение от gdb? Могу ли я установить параметр, чтобы избежать этого?

Более подробная информация:

  • Приложение FTL (https://github.com/pi-hole/FTL)
  • Это многопоточность с использованием pthreads
  • В течение времени ожидания после нажатия Ctrl+C gdb загружает процессор на 100%.

Изменить: дополнительные сведения

Я запустил perf record -p $(pidof gdb) примерно на 10 секунд, пока gdb был заморожен. perf report возвращает:

90,82%  gdb      gdb                [.] find_thread_ptid                                                                                                   
 9,13%  gdb      gdb                [.] ptid_equal                                                                                                         
 0,02%  gdb      gdb                [.] iterate_over_threads                                                                                               
 0,01%  gdb      [kernel.kallsyms]  [k] run_timer_softirq                                                                                                  
 0,01%  gdb      gdb                [.] 0x0016a9a4                                                                                                         
 0,00%  gdb      gdb                [.] 0x0015a480                                                                                                         
 0,00%  gdb      gdb                [.] 0x0016a998                                                                                                         
 0,00%  gdb      gdb                [.] is_exited

Через несколько минут gdb завершился, и я запустил info threads, который по-прежнему показывал только три потока (как и раньше):

(gdb) info threads
  Id   Target Id         Frame 
  3    Thread 0x764b8460 (LWP 10114) "socket listener" 0x76f60260 in accept () at ../sysdeps/unix/syscall-template.S:81
  2    Thread 0x76cb8460 (LWP 10113) "loganalyzer" 0x76f58964 in select () at ../sysdeps/unix/syscall-template.S:81
* 1    Thread 0x76e65000 (LWP 10098) "pihole-FTL" 0x76f58964 in select () at ../sysdeps/unix/syscall-template.S:81

person MrD    schedule 30.09.2017    source источник
comment
файл signal.c, кажется, имеет некоторое несоответствие тому, как он обрабатывает сигнал SIGINT. Это может быть связано с наблюдаемой проблемой   -  person user3629249    schedule 30.09.2017


Ответы (1)


gdb просто показывает... и висит там от нескольких минут до часов.

Предположение: ваша программа создает, но неправильно объединяет и завершает потоки.

Вы можете подтвердить или опровергнуть это, запустив программу на несколько часов, прервав ее с помощью Control-C и введя команду info threads.

В Linux потоки — это просто процессы, которые совместно используют виртуальную память и файловые дескрипторы (и управляют терминалом). Когда вы нажмете Control-C, только одна из ваших тем получит SIGINT.

В режиме all-stop по умолчанию GDB получает уведомление (от ядра), что один поток имеет ожидающий SIGINT. Затем GDB необходимо остановить все другие потоки вашего процесса, и это может занять нетривиальное количество времени.

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

person Employed Russian    schedule 30.09.2017
comment
Существует по крайней мере один поток, который существует на протяжении всего жизненного цикла процесса (который прослушивает входящие соединения на сокете). Предполагается, что этот поток никогда не завершается, пока процесс выполняется. Я бы ожидал, что gdb просто остановит этот поток (где бы он ни был в данный момент). Интересно, что это прекрасно работает для коротких прогонов. Согласно info threads, даже при очень длительном времени работы количество потоков не превышается. - person MrD; 30.09.2017
comment
Если в info threads нет дополнительных потоков, то моя догадка неверна и я понятия не имею, что делает GDB. Вы можете запустить perf record -p $pid-of-gdb (пока GDB занят на 100%), прервать perf через 10 секунд, затем запустить perf report и обновить свой вопрос выводом. Этот вывод должен позволить лучше угадать. - person Employed Russian; 30.09.2017
comment
@MrD Ваш вывод perf соответствует моему ответу: GDB перебирает потоки. Их должно быть много сотен или тысяч. Что выводит из ls /proc/$(pid-of-target)/task | wc -l? (Обратите внимание, здесь нам нужен pid подчиненного (отлаживаемого) процесса, а не GDB.) - person Employed Russian; 01.10.2017
comment
Результат ls /proc/$(pidof pihole-FTL)/task | wc -l равен 3 - person MrD; 01.10.2017
comment
@MrD Интересно. Получаете ли вы какие-либо предупреждения от GDB о недоступности libthread_db? - person Employed Russian; 01.10.2017
comment
Предупреждений gdb вообще нет (ни при запуске, ни во время выполнения). - person MrD; 01.10.2017
comment
@MrD Я исчерпал свои предположения о том, что может происходить. Попробуйте придумать stackoverflow.com/help/mcve, или дождитесь кого-то еще, или отладьте GDB самостоятельно, чтобы понять, почему он перебирает потоки, которых нет. - person Employed Russian; 01.10.2017