about TLS Callback in windows

chiyer picture chiyer · Jan 26, 2013 · Viewed 12k times · Source

this is the test code

#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;
}

build with Multi-threaded Debug DLL (/MDd) run result : main thread tls value = 1

build with Multi-threaded Debug (/MTd) run result : main thread tls value = 0

Looks like can not capture the main thread created when use the MTd

why ?

Answer

greenpiece picture greenpiece · Apr 27, 2016

While Ofek Shilon is right that the code is missing an ingredient, his answer contains only part of the whole ingredient. Full working solution can be found here which is in turn taken from here.

For explanation on how it works you may refer to this blog (assume we are working with VC++ compiler).

For convenience the code is posted below (note both x86 & x64 platforms are supported):

#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;
}

EDIT:

Definitely some explanations are required so let us take a look on what's going on in the code.

  1. If we want to use TLS callbacks then we are to tell the compiler about it explicitly. It is done with the including of the variable _tls_used which has a pointer to the callback array (null-terminated). For the type of this variable you may consult tlssup.c in CRT source code coming along with Visual Studio:

    • For VS 12.0 by default it lies here: c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\
    • For VS 14.0 by default it lies here: c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\

It is defined in the following way:

#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
};

This code initializes values for IMAGE_TLS_DIRECTORY(64) structure which is pointed to by the TLS directory entry. And pointer to call back array is one of its fields. This array is traversed by the OS loader and pointed functions are being called until null pointer is met.

For info about directories in PE file consult this link from MSDN and search for a description of IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES].

x86 note: as you can see, the same name _tls_used is met in tlssup.c for both x86 & x64 platforms, but additional _ is added when including this name for x86 build. It's not a typo but linker feature, so effectively naming __tls_used is taken.

  1. Now we are at the point of creating our callback. Its type can be obtained from the definition of IMAGE_TLS_DIRECTORY(64) which can be found in winnt.h, there is a field

for x64:

ULONGLONG AddressOfCallBacks;  // PIMAGE_TLS_CALLBACK *;

and for x86:

DWORD   AddressOfCallBacks;  // PIMAGE_TLS_CALLBACK *

The type of callbacks is defined as follows (also from winnt.h):

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason, PVOID Reserved);

It is the same as for DllMain and it can handle the same set of events: process\thread attach\detach.

  1. It is time to register callback. First of all take a look at the code from tlssup.c:

Sections allocated in it:

_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;

It is very important to know what is special in $ when naming PE section, so the quote from the article named "Compiler and linker support for implicit TLS":

Non-header data in a PE image is placed into one or more sections, which are regions of memory with a common set of attributes (such as page protection). The __declspec(allocate(“section-name”)) keyword (CL-specific) tells the compiler that a particular variable is to be placed in a specific section in the final executable. The compiler additionally has support for concatenating similarly-named sections into one larger section. This support is activated by prefixing a section name with a $ character followed by any other text. The compiler concatenates the resulting section with the section of the same name, truncated at the $ character (inclusive).

The compiler alphabetically orders individual sections when concatenating them (due to the usage of the $ character in the section name). This means that in-memory (in the final executable image), a variable in the “.CRT$XLB” section will be after a variable in the “.CRT$XLA” section but before a variable in “.CRT$XLZ” section. The C runtime uses this quirk of the compiler to create an array of null terminated function pointers to TLS callbacks (with the pointer stored in the “.CRT$XLZ” section being the null terminator). Thus, in order to ensure that the declared function pointer resides within the confines of the TLS callback array being referenced by _tls_used, it is necessary place in a section of the form “.CRT$XLx“.

There may be actually 2+ callbacks (we will actually use only one) and we may want to call them in order, now we know how. Just place these callbacks in sections named in alphabetical order.

EXTERN_C is added to forbid C++-style name mangling and use a C-style one.

const and const_seg are used for x64 version of the code because otherwise it fails to work, I don't know exact reason for this, the guess may be that CRT sections' access rights are different for x86 & x64 platforms.

And finally we are to include name of the callback function for linker to know it is to be added to the TLS call back array. For note about additional _ for x64 build see the end of p.1 above.