о TLS Callback в Windows

это тестовый код

#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

Зачем ?

6

Решение

В то время как Офек Шилон верно, что в коде отсутствует какой-либо ингредиент, его ответ содержит только часть всего ингредиента. Полное рабочее решение может быть найдено Вот который в свою очередь взят из Вот.

Для объяснения того, как это работает, вы можете обратиться к этот блог (предположим, мы работаем с компилятором 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;
}

РЕДАКТИРОВАТЬ:

Определенно требуются некоторые пояснения, поэтому давайте посмотрим, что происходит в коде.

  1. Если мы хотим использовать обратные вызовы TLS, мы должны явно сообщить об этом компилятору. Это делается с включением переменной _tls_used который имеет указатель на массив обратного вызова (завершается нулем). Для типа этой переменной вы можете проконсультироваться tlssup.c в исходном коде CRT, поставляемом вместе с Visual Studio:

    • Для VS 12.0 по умолчанию он лежит здесь: c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\
    • Для VS 14.0 по умолчанию он лежит здесь: 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 взят.

  1. Теперь мы находимся в стадии создания нашего обратного вызова. Его тип может быть получен из определения 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.

  1. Настало время зарегистрировать обратный звонок.
    Прежде всего взгляните на код из 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 выше.

6

Другие решения

Вы также должны явно добавить символ __tls_used. С этим ваш код должен работать:

#pragma comment(linker,"/include:__tls_used")
0

По вопросам рекламы [email protected]