Как отладить многопоточное приложение в C ++, которое зависает (тупик)? - PullRequest
2 голосов
/ 02 октября 2009

В java отладке зависшего приложения легко. Вы можете взять дамп памяти приложения и использовать и использовать eclipse jvm dump analyzer для просмотра состояния потоков и того, где были заблокированы все потоки?

Существует ли что-то подобное для C ++?

Ответы [ 9 ]

4 голосов
/ 02 октября 2009

Волшебный вызов в GDB:

нить все применяются BT

Запускает команду bt (backtrace) для всех потоков. Если вы полностью не удалили свою программу, вы сможете увидеть названия каждой функции.

Это работает как для оперативной, так и для посмертной (т. Е. Запуска gdb против ядра).

3 голосов
/ 02 октября 2009

Вы можете сделать то же самое с C ++; форсировать дамп ядра и заглянуть в него после.

Или, если вы используете MSVC, вы можете просто подключить отладчик к приложению во время его работы. Хит "сломай все" и ковыряйся в темах.

2 голосов
/ 06 октября 2009

В Windows родные приложения Windbg - инструмент для меня. Если возможно, я буду отлаживать в реальном времени заблокированный процесс, в случае неудачи из-за полного дампа памяти процесса.

Мой подход - нарисовать график ожидания , документирующий отношения между потоками и ресурсами. Обычно я запускаю команду ! Locks , чтобы определить, какие потоки содержат какие-либо критические разделы в заблокированном процессе.

Затем я начинаю рисовать график ожидания, выбирая критическую секцию с наибольшим числом конфликтов (если есть тупик, на графике будет цикл, поэтому не имеет значения, с чего начать). Найдите поток-владелец и выберите его в отладчике (команда ~ позволяет связать идентификаторы потоков с номерами потоков, используемыми отладчиком, используйте ~ *** номер потока *** s для выбора потока и kbn для отображения его стека. Если процесс заблокирован, скорее всего, он будет выполнять какую-либо операцию блокировки, например, искать вызовы RtlEnterCriticalSection или WaitForSingleObject и др. В ситуации тупика эти вызовы обычно позволяют вам определить другой ресурс, которого ожидают. Добавьте эту информацию в график ожидания и продолжайте, пока вы не вернетесь к тому, с чего начали.

Если ваш график ожидания пересекает границы процесса, вы можете обнаружить, что вам нужно найти, кто владеет объектом ядра в другом процессе (вот почему я отлаживаю вживую, если могу). Для этой цели полезен инструмент sysinternals Process Explorer.

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

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

Конечно, есть нечто большее, чем это, но я надеюсь, что это может направить вас в правильном направлении.

1 голос
/ 04 апреля 2019

Мы можем использовать команды gdb ниже для отладки тупика

  • Присоединить к запущенному процессу, который находится в состоянии зависания / взаимоблокировки, используя следующую команду

    gdb -p <PID>

  • Как только вы подключитесь к этому процессу, вы сможете увидеть все LWP, используя следующую команду

(gdb) info threads

Id   Target Id         Frame 

16   Thread 0xfff06111f0 (LWP 2791) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

15   Thread 0xffefdf01f0 (LWP 2792) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

14   Thread 0xffef5bb1f0 (LWP 2793) "abc.d" 0x000000fff26feb4c in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0

13   Thread 0xffeed351f0 (LWP 2794) "abc.d" 0x000000fff2703924 in nanosleep () from /lib64/libpthread.so.0

12   Thread 0xffee5351f0 (LWP 2795) "abc.d" 0x000000fff26fe76c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0

11   Thread 0xffec8a71f0 (LWP 2796) "abc.d" 0x000000fff26fe76c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0

10   Thread 0xffd7cd11f0 (LWP 2797) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

9    Thread 0xffd74d11f0 (LWP 2798) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

8    Thread 0xffd6cd11f0 (LWP 2801) "abc.d" 0x000000fff27022f4 in __lll_lock_wait () from /lib64/libpthread.so.0

7    Thread 0xffd64d11f0 (LWP 2802) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

6    Thread 0xffd5cd11f0 (LWP 2803) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

5    Thread 0xffd54d11f0 (LWP 2804) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

4    Thread 0xffd4cd11f0 (LWP 2805) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

3    Thread 0xffc7fff1f0 (LWP 2928) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

2    Thread 0xffc77ff1f0 (LWP 2929) "abc.d" 0x000000fff0f0104c in select () from /lib64/libc.so.6

1    Thread 0xfff0a62000 (LWP 2744 for) "abc.d" 0x000000fff0f19b9c in __lll_lock_wait_private () from /lib64/libc.so.6
  • Мы видим, что поток 1 и поток 8 находятся в состоянии ожидания, мы можем перейти к каждому потоку, как показано ниже

    (gdb) thread 1

    (gdb) bt

Вывод вышеуказанной команды будет таким, как показано ниже:

(gdb) thread 1 

[Switching to thread 1 (Thread 0xfff0a62000 (LWP 2744))]

0  0x000000fff0f19b9c in __lll_lock_wait_private () from /lib64/libc.so.6

(gdb) bt 

0  0x000000fff0f19b9c in __lll_lock_wait_private () from
/lib64/libc.so.6

1  0x000000fff0ea3238 in malloc () from /lib64/libc.so.6

2  0x000000fff115df0c in operator new(unsigned long) ()    from
/lib64/libstdc++.so.6

3  0x000000fff11ceddc in std::string::_Rep::_S_create(unsigned long,
unsigned long, std::allocator<char> const&) () from
/lib64/libstdc++.so.6

4  0x000000fff11d165c in char* std::string::_S_construct<char
const*>(char const*, char const*, std::allocator<char> const&,
std::forward_iterator_tag) ()    from /lib64/libstdc++.so.6

5  0x000000fff11d1760 in std::basic_string<char,
std::char_traits<char>, std::allocator<char> >::basic_string(char
const*, std::allocator<char> const&) ()    from /lib64/libstdc++.so.6

6  0x000000fff1eeac1c in getTime() ()    from
/usr/sbin/dir/sharedobj/liblibLite.so

7  0x000000fff1eeb18c in Logging::logBegin() ()    from
/usr/sbin/dir/sharedobj/liblibLite.so

8  0x000000fff1f324f8 in sigsegv_handler(int, siginfo_t*, void*) ()   
from /usr/sbin/dir/sharedobj/liblibLite.so

9  signal handler called

10 0x000000fff0e9f530 in malloc_consolidate () from /lib64/libc.so.6

11 0x000000fff0ea0160 in _int_free () from /lib64/libc.so.6

12 0x000000fff115b184 in operator delete(void*) () from
 /lib64/libstdc++.so.6

13 0x000000fff115b1f4 in operator delete[](void*) ()    from
 /lib64/libstdc++.so.6

14 0x000000fff20cfd60 in pstream::~pstream() ()    from
 /usr/sbin/dir/sharedobj/libconnV2.so

15 0x000000fff208ffd8 in ifaceSocket::dispatchMsg(pstream&) ()    from
 /usr/sbin/dir/sharedobj/libsockIf.so

16 0x000000fff207d5a4 in
 socketInterface::socket_callback(ConnectionEvent, char*, int) () from
 /usr/sbin/dir/sharedobj/libsockIf.so

17 0x000000fff208f43c in ifaceSocket::Callback(ConnectionEvent, char*,
 int)
 () from /usr/sbin/dir/sharedobj/libsockIf.so

18 0x000000fff20c4674 in ConnectionOS::ProcessReadEvent() ()    from
 /usr/sbin/dir/sharedobj/libconnV2.so

19 0x000000fff20cc808 in ConnectionOSManager::ProcessConns(fd_set*,
 fd_set*)
 () from /usr/sbin/dir/sharedobj/libconnV2.so

20 0x000000fff20cf3bc in SocketsManager::ProcessFds(bool) ()    from 
/usr/sbin/dir/sharedobj/libconnV2.so

21 0x000000fff1e54aa8 in EventReactorBase::IO() ()    from 
 /usr/sbin/dir/sharedobj/libthreadlib.so

22 0x000000fff1e5406c in EventReactorBase::React() ()    from 
/usr/sbin/dir/sharedobj/libthreadlib.so

23 0x000000fff1e50508 in Task::Run() ()    from 
/usr/sbin/dir/sharedobj/libthreadlib.so

24 0x000000fff1e50584 in startTask(void*) ()    from 
/usr/sbin/dir/sharedobj/libthreadlib.so

25 0x00000000104a421c in TaskMgr::Start() ()

26 0x00000000100ddddc in main ()
  • Мы можем проверить структуру pthread_mutex_t и получить сведения о владельце, которого ожидает этот поток ..
 (gdb) info reg
 From r8 field get the very first address    

 (gdb) print *((int*)(0x0000000019ff3d30))
 $1 = 2 // Locks    
 (gdb) print *((int*)(0x0000000019ff3d30)+1)
 $2 = 0 // Count        
 (gdb) print *((int*)(0x0000000019ff3d30)+2)
 $3 = 2744 // Owner PID
1 голос
/ 02 октября 2009

Некоторые платформы поддерживают pstack .

0 голосов
/ 02 октября 2009

Вы можете использовать gdb в системах Linux для просмотра статуса потоков

0 голосов
/ 02 октября 2009
  1. Определите критические разделы, принадлежащие застрявшему потоку # 1
  2. Определите критические разделы, принадлежащие застрявшему потоку # 2
  3. Определите правильный порядок получения критической секции
0 голосов
/ 02 октября 2009

Конечно, стратегически расположенные операторы cout (или другие альтернативы вывода) всегда являются вариантом, но часто далеки от идеальных.

Если компилируется с g ++, скомпилируйте с -g и используйте gdb. Вы можете присоединиться к запущенному процессу (и исходному коду) или просто запустить программу в отладчике для начала. Тогда посмотрите на стопку.

В Windows просто приостановите выполнение вашей программы и посмотрите на стек.

0 голосов
/ 02 октября 2009

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

Вы можете попробовать отладить это ядро ​​с помощью самого gdb и посмотреть, какие потоки заблокированы где?

Вышесказанное возможно на платформах Linux. Не уверен, что Cygwin на Windows может быть использован для той же цели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...