Using C/C++ TLS callbacks in Visual Studio with your 32 or 64bits programs

In the following article, I share with you how to use TLS callbacks in your C/C++ program compiled with Visual Studio.

Background

TLS (thread local storage) callbacks are a mechanism provided by the Windows loader to give your program a chance to do initialization/deinitialization tasks when the process starts, terminates, a thread is created or terminated.

A TLS callback has the following prototype:

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

The Reason argument can be any of the following constants:

  • DLL_PROCESS_ATTACH   = 1
  • DLL_PROCESS_DETACH  = 0
  • DLL_THREAD_ATTACH  = 2
  • DLL_THREAD_DETACH  = 3

The TLS callbacks are encoded inside the compiled program’s TLS data directory(IMAGE_DIRECTORY_ENTRY_TLS). Please refer to the PE file structure.

Visual Studio’s C runtime (CRT) provide easy support for programmers to register their callbacks. That supports comes from the file “tlssup.c” usually located in “C:\Program Files (x86)\Microsoft Visual Studio xx.0\VC\crt\src\”

For x86 targets, the tlssup.c source file, creates the TLS data directory like this:

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

…and for x64, the TLS data directory, called “_tls_used” is defined like this:

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

Now, without going more into other details and to stay within the confines of this topic, I want to explain about the “&__xl_a+1” syntax used in the snippets above.

flower separator
batchography-good-resDo you want to master Batch Files programming? Look no further, the Batchography is the best book on the topic and the most up to date!

Available in print or e-book editions from Amazon.

 

In the TLS data directory, the field “pointer to callback array” should point to an array of TLS callbacks. The address after “__xl_a” designates the start of the array. Therefore, any pointer coming afterwards is part of the array.

The CRT defines the “__xl_a” in a special PE section called “.CRT” (which later will be merged into other PE sections):

_CRTALLOC(".CRT$XLA") PIMAGE_TLS_CALLBACK __xl_a = 0;

Now notice the “$” sign after the section name. Anything after the “$” sign will be used by the linker to sort the location of the variable in that segment. For that reason, in order to have our callback sit exactly at “&_xl_a+1”, it should be allocated in the “.CRT” segment with an ordering that goes after “_xl__a”, therefore, something like the following should do the trick:

_CRTALLOC(".CRT$XLB") PIMAGE_TLS_CALLBACK my_callback;
_CRTALLOC(".CRT$XLC") PIMAGE_TLS_CALLBACK my_second_callback = 0;

//etc...

The string “$XLB” sorts after the string “$XLA”, and the string “$XLC” after “$XLB”, etc.

Example code

Here’s a full example exhibiting two TLS callbacks that set a global variable when the process starts. They execute before main():

#include "stdafx.h"

static int v1 = 0;
static int v2 = 0;

VOID WINAPI tls_callback1(
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved)
{
    if (Reason == DLL_PROCESS_ATTACH)
        v1 = 1;
}

VOID WINAPI tls_callback2(
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved)
{
    if (Reason == DLL_PROCESS_ATTACH)
        v2 = 2;
}

//-------------------------------------------------------------------------
// TLS 32/64 bits example by Elias Bachaalany <lallousz-x86@yahoo.com>
#ifdef _M_AMD64
    #pragma comment (linker, "/INCLUDE:_tls_used")
    #pragma comment (linker, "/INCLUDE:p_tls_callback1")
    #pragma const_seg(push)
    #pragma const_seg(".CRT$XLAAA")
        EXTERN_C const PIMAGE_TLS_CALLBACK p_tls_callback1 = tls_callback1;
    #pragma const_seg(".CRT$XLAAB")
        EXTERN_C const PIMAGE_TLS_CALLBACK p_tls_callback2 = tls_callback2;
    #pragma const_seg(pop)
#endif
#ifdef _M_IX86
    #pragma comment (linker, "/INCLUDE:__tls_used")
    #pragma comment (linker, "/INCLUDE:_p_tls_callback1")
    #pragma data_seg(push)
    #pragma data_seg(".CRT$XLAAA")
        EXTERN_C PIMAGE_TLS_CALLBACK p_tls_callback1 = tls_callback1;
    #pragma data_seg(".CRT$XLAAB")
        EXTERN_C PIMAGE_TLS_CALLBACK p_tls_callback2 = tls_callback2;
    #pragma data_seg(pop)
#endif


//-------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
    printf("test values from tls callbacks are: tls1 = %d, tls2 = %d\n", v1, v2);
    return 0;
}

The variables “v1” and “v2” are initialized with zero but when the program starts, if the TLS callbacks got successfully executed, then they should be initialized to 1 and 2 respectively.

The output of the program should read:

test values from tls callbacks are: tls1 = 1, tls2 = 2

There’s a subtle difference in the declarations of the TLS callbacks above. The x86 callbacks are allocated in the data segment and the x64 callbacks are allocated in a constant segment.

The last thing to note which I have not yet explained in the previous section is the usage of the following syntax:

#pragma comment (linker, "/INCLUDE:_tls_used")

If you recall, the “tls_used” was the name of the TLS data directory emitted by the CRT. The linker will not link any unused code or data and therefore, it is imperative that we hint the linker that we want to use that piece of data (the TLS data directory).

Reference

You might also like:

4 Replies to “Using C/C++ TLS callbacks in Visual Studio with your 32 or 64bits programs”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.