У меня есть программа C ++ для Windows, которая не может установить код выхода. Программа очень сложная, и в настоящее время я не могу воспроизвести ее с помощью простого контрольного примера. Я знаю, что программа вызывает exit(1)
потому что у меня есть точка останова на этой линии. Сразу после того, как я перешагнул через него, отладчик (VS2010) печатает The program program.exe has exited with code 0 (0x0).
Когда я запускаю его из оболочки, %ERRORLEVEL%
также установлен на 0.
я использую subsystem:console
и старый добрый main
(нет WinMain).
Это происходит только на Windows Server 2008 R2, а не на моем ноутбуке с Windows 8.1. Я запускаю один и тот же исполняемый файл на обоих.
Я пытался использовать exit
, _exit
, ExitProcess
, а также return
(оскорбительный звонок находится в main
), но ни один из них, кажется, не имеет никакого эффекта. Я также пытался вернуть другие коды, также безрезультатно.
Есть аналогичный вопрос но я не могу воспроизвести результаты, описанные в нем. Моя программа использует потоки.
Как я могу подойти к отладке этой проблемы? Я довольно сбит с толку.
Я пытался использовать exit, _exit, ExitProcess и return
Вы устранили все разумные объяснения, особенно с ExitProcess (). Осталась только одна возможность, вам нужно попробовать TerminateProcess (). Если это еще не устанавливает код выхода, тогда вам нужно вытолкнуть эту машину из окна 4-го этажа.
Но с ожиданием, что это сейчас работает. Разница между ExitProcess () и TerminateProcess () заключается в том, что первый гарантирует, что все DLL-файлы будут уведомлены об окончании. Их функция DllMain () вызывается с помощью fdwReason = DLL_PROCESS_DETACH. Что дает DLL возможность сделать что-то непристойное, например, вызвать сам Exit / TerminateProcess (), что испортит код выхода.
Найти такую DLL может быть сложно, если у вас нет всего исходного кода. Может быть и инъекционным, их слишком много в наши дни. Лучше всего установить точку останова на системном вызове, чтобы вы могли перехватить ее в действии, и вы, вероятно, захотите сделать это независимо.
Как только вы войдете в main (), используйте «Отладка»> «Новая точка останова»> «Разрыв в функции» и введите {,,ntdll.dll}_NtTerminateProcess@8
, Нажмите F5, и теперь отладчик останавливается непосредственно перед завершением программы. Посмотрите на стек вызовов, чтобы найти злодея.
Странные симптомы, включающие exit (), _exit (), ExitProcess () и другие в многопоточной программе, особенно если симптомы различаются между хостами, имеют запах переменной, изменяемой или доступной различными потоками без синхронизации.
Глядя на другой поток, с которым вы связались, кажется, что вы используете переменную переменную для связи между потоками, но не используете какую-либо форму синхронизации (например, код, который получает доступ к значению этой переменной, и код, который изменяет это значение, необходимо взаимодействовать). с помощью критической секции, мьютекса или аналогичной конструкции).
Это небольшое косвенное свидетельство делает запах еще сильнее.
Основная проблема, которую я подозреваю, состоит в том, что объявление переменной как volatile не является ни необходимым, ни достаточным для обеспечения того, чтобы переменная всегда имела значения, которые будут иметь смысл для вашей программы. В частности, недостаточно предотвратить прерывание потока, модифицирующего переменную, когда модификация завершена только частично, и чтобы другой поток попытался получить доступ или изменить уязвимую переменную.
Если вы посмотрите некоторые статьи Херба Саттера (особенно те, которые касаются синхронизации потоков в его серии «Гуру недели»), вы найдете подробные объяснения, почему это так. Другие авторы также описывают такие вещи, но статьи Саттера я вспоминаю не случайно.
Решение состоит в том, чтобы ввести некоторые средства синхронизации, и КАЖДЫЙ поток в вашей программе может использовать его религиозным образом перед доступом или изменением переменных, которыми они обмениваются. Это позволяет избежать различных проблем (состояния гонки, операции, выполняемые на полпути), которые могут вызвать симптомы, подобные описанным вами.
Такие проблемы редко выявляются при помощи отладчика. Причина этого заключается в том, что симптомы являются неотъемлемым свойством. Несколько маловероятных и часто независимых вхождений в разных потоках исполнения должны происходить вместе. Отладчики обычно меняют время событий в программах, и время появления симптомов является критическим фактором.
Опции включают в себя превращение ключевых переменных в атомарные (таким образом, определенные операции не могут быть прерваны), критические секции (где потоки явно взаимодействуют внутри программы) или мьютексы (которые, в зависимости от определения, позволяют потокам в разных программах явно взаимодействовать перед доступом к общей памяти) ,
Да, это создает узкое место в вашей программе — момент, когда каждый поток должен встретиться и потенциально ожидать друг друга. Это может повлиять на пропускную способность вашей программы. Некоторые люди выступают за использование изменчивых переменных, чтобы избежать таких опасений. Чаще всего это приводит к перемежающимся симптомам в долго работающих программах, которые вы описали в этом вопросе, и к «похожему вопросу», с которым вы связались.
Не имеет значения, используете ли вы стандартные средства синхронизации (например, введенные в C ++ 11) или средства, специфичные для Windows (функции WIN API). Важно то, что вы используете преднамеренный метод синхронизации, а не просто переменные переменные. Различные варианты синхронизации имеют разные компромиссы, поэтому вам нужно будет принять решение, соответствующее потребностям вашей программы.
Другое соображение состоит в том, чтобы сигнализировать всем потокам о том, что они полностью закрываются, ждать, пока они все не будут закрыты, захватывать их коды выхода и затем выходить из программы. Это часто менее подвержено ошибкам в потоке, выполняющем main (), что в конечном итоге запускает процесс, поэтому, скорее всего, будет иметь доступ к информации, необходимой для правильной очистки. Если другой поток решит, что программа должна выйти, то лучше, если он сообщит, что для этого нужно вернуться к main ().