CreateThread() не работает на 64-битной Windows, работает на 32-битной Windows. Зачем?

Операционная система: Windows XP 64 бит, SP2.

У меня необычная проблема. Я портирую некоторый код с 32 бит на 64 бит. 32-битный код работает просто отлично. Но когда я вызываю CreateThread() для 64-битной версии, вызов не выполняется. У меня есть три места, где это не удается. 2 вызвать CreateThread(). 1 вызывает метод beginthreadex(), который вызывает метод CreateThread().

Все три вызова не выполняются с кодом ошибки 0x3E6, "Недопустимый доступ к ячейке памяти".

Проблема в том, что все входные параметры верны.

HANDLE  h;
DWORD   threadID;

h = CreateThread(0,            // default security
                 0,            // default stack size
                 myThreadFunc, // valid function to call
                 myParam,      // my param
                 0,            // no flags, start thread immediately
                 &threadID);

Все три вызова CreateThread() сделаны из DLL, которую я вставил в целевую программу в начале выполнения программы (это происходит до того, как программа добралась до запуска main()/WinMain()). Если я вызываю CreateThread() из целевой программы (те же параметры), скажем, через меню, это работает. Те же параметры и т.д. Причудливый.

Если я передаю NULL вместо &threadID, он все равно не работает.

Если я передаю NULL как myParam, он все равно не работает.

Я не вызываю CreateThread изнутри DllMain(), так что это не проблема. Я запутался, и поиск в Google и т. Д. Не дал соответствующих ответов.

Если кто-то видел это раньше или есть какие-либо идеи, пожалуйста, дайте мне знать.

Спасибо за прочтение.

ОТВЕТ

Краткий ответ: стековые фреймы на x64 должны быть выровнены на 16 байт.

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

Добавление дополнительных данных в стек изменяет выравнивание кадров стека. Рано или поздно один из тестов возвращает вас к 16-байтовому выравниванию стека. На тот момент код работал. Поэтому я пересмотрел свои шаги и начал помещать в стек NULL-данные, а не то, что считал правильными (я выдвигал адреса возврата, чтобы подделать кадр вызова). Он все еще работал - поэтому данные не важны, это должны быть реальные адреса стеков.

Я быстро понял, что это было 16-байтовое выравнивание для стека. Ранее я знал только о 8-байтовом выравнивании данных. Этот документ Microsoft объясняет все требования выравнивания.

Если на x64 не выровнен кадр стека на 16 байт, компилятор может поместить большие (8 байт или более) данные на неправильные границы выравнивания, когда он помещает данные в стек.

Отсюда проблема, с которой я столкнулся - код перехвата вызывался со стеком, который не был выровнен по 16-байтовой границе.

Краткое резюме требований к выравниванию, выраженное в виде размера: выравнивание

  • 1: 1
  • 2: 2
  • 4: 4
  • 8: 8
  • 10: 16
  • 16: 16

Все, что больше 8 байтов, выровнено по следующей степени 2 границы.

Я думаю, что код ошибки Microsoft немного вводит в заблуждение. Начальный STATUS_DATATYPE_MISALIGNMENT может быть выражен как STATUS_STACK_MISALIGNMENT, что будет более полезным. Но затем превращение STATUS_DATATYPE_MISALIGNMENT в ERROR_NOACCESS - это фактически маскирует и вводит в заблуждение относительно того, в чем проблема. Очень бесполезно

Спасибо всем, кто разместил предложения. Даже если я не согласен с предложениями, они побудили меня провести тестирование в самых разных направлениях (включая те, с которыми я не согласен).

Здесь написано более подробное описание проблемы смещения типов данных: 64-битное портирование, погрешность №1! x64 Несовпадение типов данных.

3 ответа

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


Хорошо, эта идея не так. Вы уверены, что можно вызывать CreateThread перед main/WinMain? Это объяснило бы, почему это работает в меню - потому что это после main/WinMain.

Кроме того, я бы трижды проверил срок службы myParam. CreateThread возвращает (это я знаю из опыта) задолго до вызова функции, которую вы передаете.


Разместите код подпрограммы потока (или просто несколько строк).


У меня неожиданно возникает мысль: вы уверены, что вводите свой 64-битный код в 64-битный процесс? Потому что, если у вас есть 64-битный вызов CreateThread и вы пытаетесь внедрить его в 32-битный процесс, работающий под WOW64, могут произойти плохие вещи.


Начинаю серьезно исчерпывать идеи. Компилятор сообщает о каких-либо предупреждениях?


Может ли ошибка быть связана с ошибкой в ​​основной программе, а не в DLL? Есть некоторый другой код, такой как загрузка DLL, если вы использовали __declspec(импорт / экспорт), который происходит до main/WinMain. Если в этом DLLMain, например, была ошибка.

Я столкнулся с этой проблемой сегодня. И я проверил каждый аргумент в_beginthread/CreateThread/NtCreateThreadчерез Rohitab's Windows API Monitor v2. Каждый аргумент выровнен правильно (AFAIK).


Итак, где же STATUS_DATATYPE_MISALIGNMENT родом из?

Первые несколько строк NtCreateThread проверить параметры, переданные из пользовательского режима.

ProbeForReadSmallStructure (ThreadContext, sizeof (CONTEXT), CONTEXT_ALIGN);

для i386

#define CONTEXT_ALIGN   (sizeof(ULONG))

для amd64

#define STACK_ALIGN (16UI64)
...
#define CONTEXT_ALIGN STACK_ALIGN

На amd64, если ThreadContext указатель не выровнен по 16 байтам, NtCreateThread вернется STATUS_DATATYPE_MISALIGNMENT.

CreateThread (фактически CreateRemoteThread) выделено ThreadContextиз стека и не сделал ничего особенного, чтобы гарантировать выполнение требований выравнивания. Все будет работать гладко, если каждый фрагмент вашего кода будет следовать соглашению о вызовах Microsoft x64, что, к сожалению, для меня не так.

PS: тот же код может работать в более новых версиях Windows (например, Vista и новее). Но я не проверял. Я столкнулся с этой проблемой в Windows Server 2003 R2 x64.

Я в бизнесе использования параллельных потоков под окнами для расчетов. Никаких забавных дел, никаких dll-звонков и, конечно, никаких обратных звонков. Я установил стек для своих вычислений, в пределах области, зарезервированной для моей программы. Все соответствующие данные об областях и начальных адресах содержатся в структуре данных, которая передается в CreateThread в качестве параметра 3. Вызываемый адрес содержит небольшую подпрограмму ассемблера, которая использует эту структуру данных. Действительно, эта подпрограмма находит адрес для возврата в стеке, а затем адрес структуры данных. Нет причин углубляться в это. Это просто работает и вычисляет число простых чисел ниже 2000 000 000 просто отлично, в одном потоке, в двух потоках или в 20 потоках.

Теперь CreateThread в 64 битах не выдвигает адрес структуры данных. Это кажется неправдоподобным, поэтому я покажу вам дымящийся пистолет, дамп сеанса отладки. введите описание изображения здесь

Прямо под вами виден стек, и есть просто обратный адрес среди моря нулей. Механизм, который я использую для заполнения параметров, переносим между 32 и 64 битами. Ни один другой вызов не демонстрирует разницу между размерами слов. Кроме того, почему адрес кода работает, а не адрес данных?

Итог: можно ожидать, что CreateThread передает параметр данных в стек, а затем выполняет вызов подпрограммы. На уровне ассемблера это не работает таким образом. Если есть какие-то скрытые требования, например, к RSP, которые автоматически заполняются в C++, это было бы очень неприятно.

PS Нет проблем с 16-байтовым выравниванием. Это лежит позади меня.

Попробуйте вместо этого использовать _beginthread() или _beginthreadex(), вам не следует использовать CreateThread напрямую.

Смотрите этот предыдущий вопрос.

Другие вопросы по тегам