Является ли первый поток, который запускается внутри процесса Win32, основным потоком? Необходимо понимать семантику

Я создаю процесс, используя CreateProcess() с CREATE_SUSPENDED, а затем создаю небольшой фрагмент кода внутри удаленного процесса для загрузки DLL и вызова функции (экспортируемой этой DLL), используя VirtualAllocEx()..., MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE), WriteProcessMemory(), затем вызываю FlushInstructionCache() на том участке памяти с кодом.

После этого я вызываю CreateRemoteThread(), чтобы вызвать этот код, создавая мне hRemoteThread. Я убедился, что удаленный код работает должным образом. Примечание. этот код просто возвращает, он не вызывает никаких API, кроме LoadLibrary() и GetProcAddress(), с последующим вызовом экспортируемой функции-заглушки, которая в настоящее время просто возвращает значение, которое затем будет передано как статус выхода для нить.

Теперь следует любопытное наблюдение: помните, что PROCESS_INFORMATION::hThread все еще приостановлен. Когда я просто игнорирую код выхода hRemoteThread и не жду его выхода, все идет "нормально". Подпрограмма, которая вызывает CreateRemoteThread(), возвращается, и PROCESS_INFORMATION::hThread возобновляется, и (удаленная) программа фактически запускается.

Однако, если я позвоню WaitForSingleObject(hRemoteThread, INFINITE) или сделаю следующее (что даст тот же эффект):

DWORD exitCode = STILL_ACTIVE;
while(STILL_ACTIVE == exitCode)
{
    Sleep(500);
    if(!GetExitCodeThread(hRemoteThread, &exitCode))
        break;
}

за которым следует CloseHandle(), это приводит к завершению hRemoteThread до возобновления PROCESS_INFORMATION::hThread, и процесс просто «исчезает». Достаточно позволить hRemoteThread каким-то образом завершить работу без PROCESS_INFORMATION::hThread, чтобы процесс умер.

Это подозрительно похоже на состояние гонки, поскольку при определенных обстоятельствах hRemoteThread может быть быстрее, и процесс, скорее всего, все равно «исчезнет», даже если я оставлю код как есть.

Означает ли это, что первый поток, который запускается внутри процесса, автоматически становится основным потоком и что для этого основного потока существуют особые правила?

У меня всегда было впечатление, что процесс завершается, когда умирает его последний поток, а не когда умирает конкретный поток.

Также обратите внимание: здесь никоим образом не задействован вызов ExitProcess(), потому что hRemoteThread просто возвращается, а PROCESS_INFORMATION::hThread все еще приостанавливается, когда я жду, пока hRemoteThread вернется.

Это происходит в 32-битной Windows XP SP3.

Изменить: Я только что попробовал Sysinternals Process Monitor, чтобы увидеть, что происходит, и смог проверить свои предыдущие наблюдения. Введенный код не падает или что-то в этом роде, вместо этого я вижу, что если я не дождусь потока, он не завершится, прежде чем я закрою программу, в которую был введен код. Думаю, нужно ли отложить звонок на CloseHandle(hRemoteThread) или что-то в этом роде ...

Изменить + 1: это не CloseHandle(). Если я оставлю это только для теста, поведение не изменится при ожидании завершения потока.


person 0xC0000022L    schedule 14.03.2012    source источник


Ответы (2)


Первый запускаемый поток не особенный.

Например, создайте консольное приложение, которое создает приостановленный поток и завершает исходный поток (путем вызова ExitThread). Этот процесс никогда не завершается (по крайней мере, в Windows 7).

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

Я не знаю, что происходит с вашим примером. Самый простой способ избежать гонки - заставить новый поток возобновить исходный поток.

Размышляя сейчас, я действительно задаюсь вопросом, вряд ли то, что вы делаете, в любом случае вызовет проблемы. Например, что происходит со всеми DllMain вызовами неявно загруженных библиотек DLL? Они неожиданно происходят в неправильном потоке, пропускаются или откладываются до тех пор, пока ваш код не запустится и не запустится основной поток?

person arx    schedule 14.03.2012
comment
пожалуйста, объясните последний пункт. Я знаю, что это вызовет проблемы, если предположить, что DLL загружается по тому же базовому адресу. в моем процессе, как и в удаленном процессе, поэтому явный старый добрый LoadLibrary, переданный в CreateRemoteThread, не сработает - вот почему так. Кроме того, когда основной поток приостанавливается после создания процесса, библиотеки DLL уже загружены, хотя я не уверен, были ли к тому времени вызваны их DllMain. Но на самом деле это не имеет значения, если я не вызываю свой LoadLibrary после запуска основного потока и могу находиться внутри самого DllMain. Спасибо - person 0xC0000022L; 14.03.2012
comment
Windows обычно никогда не видит вызова LoadLibrary до инициализации статических библиотек DLL. Вероятно, это могло вызвать проблемы. В качестве альтернативы, если статические DllMains вызываются в вашем потоке, это может запутать библиотеки DLL, которые предполагают, что поток инициализации будет длиться в течение всего времени жизни процесса, что обычно верно. Как я уже сказал, я размышляю, но, похоже, существует большая вероятность поломки. - person arx; 14.03.2012
comment
Хм, хорошо. Как я могу проверить, были ли вызваны DllMain других DLL? Из прошлого опыта я знаю, что я не смогу даже вызвать CreateRemoteProcess успешно, если kernel32.dll не был инициализирован и не было установлено соединение с Win32. Однако вызов CreateRemoteProcess здесь успешен. Возможно, вы правы, но сейчас я так не думаю. - person 0xC0000022L; 14.03.2012
comment
Вы можете проверить это, создав простой целевой процесс, который статически ссылается на простую DLL. Простая DLL может выводить идентификатор потока, для которого вызывается DllMain (например, с помощью OutputDebugString). Я почти хочу попробовать это сам, но мне нужно лечь спать. - person arx; 14.03.2012
comment
Я поиграл немного дальше. Кажется, вы правы в том, что фаза инициализации, которая так или иначе необходима для поддержания процесса в рабочем состоянии, к тому времени не закончилась. Что я сделал для тестирования, так это создал процесс без приостановки, спящий на 500 мс, затем приостановил его и затем внедрил свой код. Тогда это становится действительно нестабильным, потому что иногда процесс полностью блокируется, а иногда он запускается, как ожидалось, что доказывает подозрение о состоянии гонки. Хотя об этом предстоит еще много узнать, я приму ваш ответ. - person 0xC0000022L; 14.03.2012
comment
В зависимости от времени, в которое вам нужно запустить внедренный код, возможно, вы могли бы использовать WaitForInputIdle() вместо Sleep(500). - person Remy Lebeau; 14.03.2012
comment
@Remy: только что видел твой комментарий. Я попробую, спасибо. - person 0xC0000022L; 19.03.2012

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

Я не знаю, есть ли хороший способ заставить основной поток ждать завершения вашего ...

person Mike Caron    schedule 14.03.2012
comment
Как? Как поток, содержащий main(), сможет вызывать ExitProcess(), если ti не запускается? Я написал в своем вопросе, что основной поток не запускается перед введенным кодом, когда я сталкиваюсь с проблемой. Когда они работают параллельно, проблемы не. - person 0xC0000022L; 14.03.2012
comment
О, мои извинения, я неправильно понял ваш вопрос. Это загадка. Ваш поток завершается чисто и не выходит из процесса, но процесс все равно умирает? Странный. - person Mike Caron; 14.03.2012