это тестовый код
#include "windows.h"#include "iostream"using namespace std;
__declspec(thread) int tls_int = 0;
void NTAPI tls_callback(PVOID, DWORD dwReason, PVOID)
{
tls_int = 1;
}
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;
#pragma data_seg()
int main()
{
cout<<"main thread tls value = "<<tls_int<<endl;
return 0;
}
сборка с многопоточной отладочной DLL (/ MDd)
результат выполнения: значение основного потока tls = 1
сборка с многопоточной отладкой (/ MTd)
результат выполнения: основной поток значение tls = 0
Похоже, не может захватить основной поток, созданный при использовании MTd
Зачем ?
В то время как Офек Шилон верно, что в коде отсутствует какой-либо ингредиент, его ответ содержит только часть всего ингредиента. Полное рабочее решение может быть найдено Вот который в свою очередь взят из Вот.
Для объяснения того, как это работает, вы можете обратиться к этот блог (предположим, мы работаем с компилятором VC ++).
Для удобства код размещен ниже (обратите внимание, оба x86 & Поддерживаются платформы x64):
#include <windows.h>
// Explained in p. 2 below
void NTAPI tls_callback(PVOID DllHandle, DWORD dwReason, PVOID)
{
if (dwReason == DLL_THREAD_ATTACH)
{
MessageBox(0, L"DLL_THREAD_ATTACH", L"DLL_THREAD_ATTACH", 0);
}
if (dwReason == DLL_PROCESS_ATTACH)
{
MessageBox(0, L"DLL_PROCESS_ATTACH", L"DLL_PROCESS_ATTACH", 0);
}
}
#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used") // See p. 1 below
#pragma comment (linker, "/INCLUDE:tls_callback_func") // See p. 3 below
#else
#pragma comment (linker, "/INCLUDE:__tls_used") // See p. 1 below
#pragma comment (linker, "/INCLUDE:_tls_callback_func") // See p. 3 below
#endif
// Explained in p. 3 below
#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK tls_callback_func = tls_callback;
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif //_WIN64
DWORD WINAPI ThreadProc(CONST LPVOID lpParam)
{
ExitThread(0);
}
int main(void)
{
MessageBox(0, L"hello from main", L"main", 0);
CreateThread(NULL, 0, &ThreadProc, 0, 0, NULL);
return 0;
}
РЕДАКТИРОВАТЬ:
Определенно требуются некоторые пояснения, поэтому давайте посмотрим, что происходит в коде.
Если мы хотим использовать обратные вызовы TLS, мы должны явно сообщить об этом компилятору. Это делается с включением переменной _tls_used
который имеет указатель на массив обратного вызова (завершается нулем). Для типа этой переменной вы можете проконсультироваться tlssup.c
в исходном коде CRT, поставляемом вместе с Visual Studio:
c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\
c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\
Это определяется следующим образом:
#ifdef _WIN64
_CRTALLOC(".rdata$T") const IMAGE_TLS_DIRECTORY64 _tls_used =
{
(ULONGLONG) &_tls_start, // start of tls data
(ULONGLONG) &_tls_end, // end of tls data
(ULONGLONG) &_tls_index, // address of tls_index
(ULONGLONG) (&__xl_a+1), // pointer to call back array
(ULONG) 0, // size of tls zero fill
(ULONG) 0 // characteristics
};
#else /* _WIN64 */
_CRTALLOC(".rdata$T")
const IMAGE_TLS_DIRECTORY _tls_used =
{
(ULONG)(ULONG_PTR) &_tls_start, // start of tls data
(ULONG)(ULONG_PTR) &_tls_end, // end of tls data
(ULONG)(ULONG_PTR) &_tls_index, // address of tls_index
(ULONG)(ULONG_PTR) (&__xl_a+1), // pointer to call back array
(ULONG) 0, // size of tls zero fill
(ULONG) 0 // characteristics
};
Этот код инициализирует значения для IMAGE_TLS_DIRECTORY(64)
структура, на которую указывает запись в каталоге TLS. И указатель на массив обратного вызова является одним из его полей. Этот массив пересекается загрузчиком ОС, и указанные функции вызываются до тех пор, пока не будет достигнут нулевой указатель.
Для получения информации о каталогах в PE-файле обратитесь к эта ссылка из MSDN и искать описание IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
,
примечание x86: как видите, то же имя _tls_used
встречается в tlssup.c
для обоих x86 & платформы x64, но дополнительные _
добавляется при включении этого имени для сборки x86. Это не опечатка, а функция компоновщика, так что эффективное именование __tls_used
взят.
IMAGE_TLS_DIRECTORY(64)
который можно найти в winnt.h
есть поледля х64:
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *;
и для x86:
DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *
Тип обратных вызовов определяется следующим образом (также из winnt.h
):
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason, PVOID Reserved);
Это так же, как для DllMain
и он может обрабатывать тот же набор событий: process \ thread attach \ detach.
tlssup.c
:Разделы, выделенные в нем:
_CRTALLOC(".CRT$XLA") PIMAGE_TLS_CALLBACK __xl_a = 0;
/* NULL terminator for TLS callback array. This symbol, __xl_z, is never
* actually referenced anywhere, but it must remain. The OS loader code
* walks the TLS callback array until it finds a NULL pointer, so this makes
* sure the array is properly terminated.
*/
_CRTALLOC(".CRT$XLZ") PIMAGE_TLS_CALLBACK __xl_z = 0;
Очень важно знать, что особенного в $
при названии раздела PE, поэтому цитата из статьи называется «Поддержка компилятора и компоновщика для неявного TLS»:
Данные без заголовка в изображении PE помещаются в один или несколько разделов,
которые являются областями памяти с общим набором атрибутов (таких как
защита страницы).__declspec(allocate(“section-name”))
ключевое слово
(Специфичный для CL) сообщает компилятору, что должна быть определенная переменная
помещается в определенный раздел в конечном исполняемом файле. Компилятор
дополнительно имеется поддержка объединения разделов с одинаковыми именами
в одну большую секцию. Эта поддержка активируется с помощью префикса
название раздела с$
символ сопровождается любым другим текстом.
компилятор объединяет полученный раздел с разделом
то же имя, усеченное на$
характер (включительно).Компилятор упорядочивает отдельные разделы в алфавитном порядке, когда
объединяя их (из-за использования символа $ в разделе
название). Это означает, что в памяти (в конечном исполняемом образе)
переменная в“.CRT$XLB”
раздел будет после переменной в
“.CRT$XLA”
раздел, но перед переменной в“.CRT$XLZ”
раздел. C
runtime использует эту причуду компилятора для создания массива с нулем
указатели на завершенные функции на обратные вызовы TLS (с сохраненным указателем
в“.CRT$XLZ”
раздел является нулевым терминатором). Таким образом, по порядку
чтобы убедиться, что объявленный указатель на функцию находится в пределах
ограничивается массивом обратного вызова TLS, на который ссылается_tls_used
, Это
необходимо место в разделе формы“.CRT$XLx“
,
На самом деле может быть более 2 обратных вызовов (мы будем использовать только один), и мы можем захотеть вызвать их по порядку, теперь мы знаем как. Просто поместите эти обратные вызовы в разделы, названные в алфавитном порядке.
EXTERN_C
добавлено для запрета искажения имен в стиле C ++ и использования стиля в стиле C
const
а также const_seg
используются для x64-версии кода, потому что в противном случае он не работает, я не знаю точной причины этого, можно предположить, что права доступа к разделам CRT отличаются для x86 & платформы x64.
И, наконец, мы должны включить имя функции обратного вызова, чтобы компоновщик знал, что она должна быть добавлена в массив обратного вызова TLS. Для заметки о дополнительных _
для сборки x64 см. конец п. 1 выше.
Вы также должны явно добавить символ __tls_used. С этим ваш код должен работать:
#pragma comment(linker,"/include:__tls_used")