Configuring floating point unit context in WIN32 vs WIN64 - winapi

I am attempting to write an unhandled exception filter ( see SetUnhandledExceptionFilter()) to use with Windows SEH to report invalid floating point operations. I'd like to trap the exception, print a stack trace, then disable floating point exceptions and resume execution with the resulting non-finite or not-a-number value.
I wrote the simple program below to demonstrate the ability to catch the exception and resume execution. Note the use of #ifdef _WIN64, as the definition of the ContextRecord changes depending on the targeted architecture (WIN32 uses "FloatSave", WIN64 uses "FltSave").
#include "stdafx.h"
#include <float.h>
#include <Windows.h>
double zero = 0.0;
LONG WINAPI myfunc(EXCEPTION_POINTERS * ExceptionInfo){
/* clear the exception */
unsigned int stat = _clear87();
/* disable fp exceptions*/
unsigned int ctrl1 = _control87(_MCW_EM, _MCW_EM);
/* Disable and clear fp exceptions in the exception context */
#if _WIN64
ExceptionInfo->ContextRecord->FltSave.ControlWord = ctrl1;
ExceptionInfo->ContextRecord->FltSave.StatusWord = 0;
#else
ExceptionInfo->ContextRecord->FloatSave.ControlWord = ctrl1;
ExceptionInfo->ContextRecord->FloatSave.StatusWord = 0;
#endif
printf("#########Caught Ya#####!\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
int _tmain(int argc, _TCHAR* argv[])
{
double a;
/* enable fp exceptions*/
_controlfp(0, _MCW_EM);
/* Setup our unhandled exception filter */
SetUnhandledExceptionFilter(myfunc);
/* do something bad */
a = 5.0 / zero;
printf("a = %f\n",a);
return 0;
}
When run as a WIN32 .exe, the above program runs as expected, with the output:
#########Caught Ya#####!
a = -1.#IND00
However, when run as a WIN64 .exe, the program enters an infinite loop, continuously re-catching the floating point exception:
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
...
This seems to indicate that I'm not successful at configuring the floating point unit, which I imagine is related to the different definition of ContextRecord between WIN32 and WIN64, but I haven't been able to find any good related documentation on exactly how to set the floating point context.
Any thoughts on how to correctly set the floating point context for WIN64?
Thanks in advance

I've got a working example now. Thanks to #IInspectable for pointing me in the right direction (SSE). The issue was that the MxCsr register(s?) need to be set in the ExecutionContext. Note that calling controlfp DID indeed set the MxCsr register correctly, however it appears that when the filter function returns, ALL registers are reset to the context given in ExceptionInfo. The trick is to overwrite MxCsr in that context, which the code below does.
I am still a bit confused as to the two locations of MxCsr in the context. controlfp() seems to set BOTH together and to the same value (as observed in debugger).
#include "stdafx.h"
#include <float.h>
#include <Windows.h>
#include <xmmintrin.h>
double zero = 0.0;
LONG WINAPI myfunc(EXCEPTION_POINTERS * ExceptionInfo){
/* clear the exception */
unsigned int stat = _clearfp();
/* disable all fp exceptions*/
unsigned int ctrlwrd;
errno_t err = _controlfp_s(&ctrlwrd, _MCW_EM, _MCW_EM);
/* Disable and clear the exceptions in the exception context */
#if _WIN64
/* Get current context to get the values of MxCsr register, which was
* set by the calls to _controlfp above, we need to copy these into
* the exception context so that exceptions really stay disabled.
* References:
* https://msdn.microsoft.com/en-us/library/yxty7t75.aspx
* https://software.intel.com/en-us/articles/x87-and-sse-floating-point-assists-in-ia-32-flush-to-zero-ftz-and-denormals-are-zero-daz
*/
CONTEXT myContext;
RtlCaptureContext(&myContext);
ExceptionInfo->ContextRecord->FltSave.ControlWord = ctrlwrd;
ExceptionInfo->ContextRecord->FltSave.StatusWord = 0;
ExceptionInfo->ContextRecord->FltSave.MxCsr = myContext.FltSave.MxCsr;
ExceptionInfo->ContextRecord->FltSave.MxCsr_Mask = myContext.FltSave.MxCsr_Mask;
ExceptionInfo->ContextRecord->MxCsr = myContext.MxCsr;
#else
ExceptionInfo->ContextRecord->FloatSave.ControlWord = ctrlwrd;
ExceptionInfo->ContextRecord->FloatSave.StatusWord = 0;
#endif
printf("#########Caught Ya#####!\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
int _tmain(int argc, _TCHAR* argv[])
{
double a;
/* Enable fp exceptions */
_controlfp_s(0, 0, _MCW_EM);
/* Setup our unhandled exception filter */
SetUnhandledExceptionFilter(myfunc);
/* do something bad */
a = 5.0 / zero;
printf("a = %f\n",a);
return 0;
}

Related

Trap memory accesses inside a standard executable built with MinGW

So my problem sounds like this.
I have some platform dependent code (embedded system) which writes to some MMIO locations that are hardcoded at specific addresses.
I compile this code with some management code inside a standard executable (mainly for testing) but also for simulation (because it takes longer to find basic bugs inside the actual HW platform).
To alleviate the hardcoded pointers, i just redefine them to some variables inside the memory pool. And this works really well.
The problem is that there is specific hardware behavior on some of the MMIO locations (w1c for example) which makes "correct" testing hard to impossible.
These are the solutions i thought of:
1 - Somehow redefine the accesses to those registers and try to insert some immediate function to simulate the dynamic behavior. This is not really usable since there are various ways to write to the MMIO locations (pointers and stuff).
2 - Somehow leave the addresses hardcoded and trap the illegal access through a seg fault, find the location that triggered, extract exactly where the access was made, handle and return. I am not really sure how this would work (and even if it's possible).
3 - Use some sort of emulation. This will surely work, but it will void the whole purpose of running fast and native on a standard computer.
4 - Virtualization ?? Probably will take a lot of time to implement. Not really sure if the gain is justifiable.
Does anyone have any idea if this can be accomplished without going too deep? Maybe is there a way to manipulate the compiler in some way to define a memory area for which every access will generate a callback. Not really an expert in x86/gcc stuff.
Edit: It seems that it's not really possible to do this in a platform independent way, and since it will be only windows, i will use the available API (which seems to work as expected). Found this Q here:
Is set single step trap available on win 7?
I will put the whole "simulated" register file inside a number of pages, guard them, and trigger a callback from which i will extract all the necessary info, do my stuff then continue execution.
Thanks all for responding.
I think #2 is the best approach. I routinely use approach #4, but I use it to test code that is running in the kernel, so I need a layer below the kernel to trap and emulate the accesses. Since you have already put your code into a user-mode application, #2 should be simpler.
The answers to this question may provide help in implementing #2. How to write a signal handler to catch SIGSEGV?
What you really want to do, though, is to emulate the memory access and then have the segv handler return to the instruction after the access. This sample code works on Linux. I'm not sure if the behavior it is taking advantage of is undefined, though.
#include <stdint.h>
#include <stdio.h>
#include <signal.h>
#define REG_ADDR ((volatile uint32_t *)0x12340000f000ULL)
static uint32_t read_reg(volatile uint32_t *reg_addr)
{
uint32_t r;
asm("mov (%1), %0" : "=a"(r) : "r"(reg_addr));
return r;
}
static void segv_handler(int, siginfo_t *, void *);
int main()
{
struct sigaction action = { 0, };
action.sa_sigaction = segv_handler;
action.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &action, NULL);
// force sigsegv
uint32_t a = read_reg(REG_ADDR);
printf("after segv, a = %d\n", a);
return 0;
}
static void segv_handler(int, siginfo_t *info, void *ucontext_arg)
{
ucontext_t *ucontext = static_cast<ucontext_t *>(ucontext_arg);
ucontext->uc_mcontext.gregs[REG_RAX] = 1234;
ucontext->uc_mcontext.gregs[REG_RIP] += 2;
}
The code to read the register is written in assembly to ensure that both the destination register and the length of the instruction are known.
This is how the Windows version of prl's answer could look like:
#include <stdint.h>
#include <stdio.h>
#include <windows.h>
#define REG_ADDR ((volatile uint32_t *)0x12340000f000ULL)
static uint32_t read_reg(volatile uint32_t *reg_addr)
{
uint32_t r;
asm("mov (%1), %0" : "=a"(r) : "r"(reg_addr));
return r;
}
static LONG WINAPI segv_handler(EXCEPTION_POINTERS *);
int main()
{
SetUnhandledExceptionFilter(segv_handler);
// force sigsegv
uint32_t a = read_reg(REG_ADDR);
printf("after segv, a = %d\n", a);
return 0;
}
static LONG WINAPI segv_handler(EXCEPTION_POINTERS *ep)
{
// only handle read access violation of REG_ADDR
if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION ||
ep->ExceptionRecord->ExceptionInformation[0] != 0 ||
ep->ExceptionRecord->ExceptionInformation[1] != (ULONG_PTR)REG_ADDR)
return EXCEPTION_CONTINUE_SEARCH;
ep->ContextRecord->Rax = 1234;
ep->ContextRecord->Rip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
So, the solution (code snippet) is as follows:
First of all, i have a variable:
__attribute__ ((aligned (4096))) int g_test;
Second, inside my main function, i do the following:
AddVectoredExceptionHandler(1, VectoredHandler);
DWORD old;
VirtualProtect(&g_test, 4096, PAGE_READWRITE | PAGE_GUARD, &old);
The handler looks like this:
LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
static DWORD last_addr;
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {
last_addr = ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
ExceptionInfo->ContextRecord->EFlags |= 0x100; /* Single step to trigger the next one */
return EXCEPTION_CONTINUE_EXECUTION;
}
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) {
DWORD old;
VirtualProtect((PVOID)(last_addr & ~PAGE_MASK), 4096, PAGE_READWRITE | PAGE_GUARD, &old);
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
This is only a basic skeleton for the functionality. Basically I guard the page on which the variable resides, i have some linked lists in which i hold pointers to the function and values for the address in question. I check that the fault generating address is inside my list then i trigger the callback.
On first guard hit, the page protection will be disabled by the system, but i can call my PRE_WRITE callback where i can save the variable state. Because a single step is issued through the EFlags, it will be followed immediately by a single step exception (which means that the variable was written), and i can trigger a WRITE callback. All the data required for the operation is contained inside the ExceptionInformation array.
When someone tries to write to that variable:
*(int *)&g_test = 1;
A PRE_WRITE followed by a WRITE will be triggered,
When i do:
int x = *(int *)&g_test;
A READ will be issued.
In this way i can manipulate the data flow in a way that does not require modifications of the original source code.
Note: This is intended to be used as part of a test framework and any penalty hit is deemed acceptable.
For example, W1C (Write 1 to clear) operation can be accomplished:
void MYREG_hook(reg_cbk_t type)
{
/** We need to save the pre-write state
* This is safe since we are assured to be called with
* both PRE_WRITE and WRITE in the correct order
*/
static int pre;
switch (type) {
case REG_READ: /* Called pre-read */
break;
case REG_PRE_WRITE: /* Called pre-write */
pre = g_test;
break;
case REG_WRITE: /* Called after write */
g_test = pre & ~g_test; /* W1C */
break;
default:
break;
}
}
This was possible also with seg-faults on illegal addresses, but i had to issue one for each R/W, and keep track of a "virtual register file" so a bigger penalty hit. In this way i can only guard specific areas of memory or none, depending on the registered monitors.

Need help understanding stack frame layout

While implementing a stack walker for a debugger I am working on I reached the point to extract the arguments to a function call and display them. To make it simple I started with the cdecl convention in pure 32-bit (both debugger and debuggee), and a function that takes 3 parameters. However, I cannot understand why the arguments in the stack trace are out of order compared to what cdecl defines (right-to-left, nothing in registers), despite trying to figure it out for a few days now.
Here is a representation of the function call I am trying to stack trace:
void Function(unsigned long long a, const void * b, unsigned int c) {
printf("a=0x%llX, b=%p, c=0x%X\n", a, b, c);
_asm { int 3 }; /* Because I don't have stepping or dynamic breakpoints implemented yet */
}
int main(int argc, char* argv[]) {
Function(2, (void*)0x7A3FE8, 0x2004);
return 0;
}
This is what the function (unsurprisingly) printed to the console:
a=0x2, c=0x7a3fe8, c=0x2004
This is the stack trace generated at the breakpoint (the debugger catches the breakpoint and there I try to walk the stack):
0x3EF5E0: 0x10004286 /* previous pc */
0x3EF5DC: 0x3EF60C /* previous fp */
0x3EF5D8: 0x7A3FE8 /* arg b --> Wait... why is b _above_ c here? */
0x3EF5D4: 0x2004 /* arg c */
0x3EF5D0: 0x0 /* arg a, upper 32 bit */
0x3EF5CC: 0x2 /* arg a, lower 32 bit */
The code that's responsible for dumping the stack frames (implemented using the DIA SDK, though, I don't think that is relevant to my problem) looks like this:
ULONGLONG stackframe_top = 0;
m_frame->get_base(&stackframe_top); /* IDiaStackFrame */
/* dump 30 * 4 bytes */
for (DWORD i = 0; i < 30; i++)
{
ULONGLONG address = stackframe_top - (i * 4);
DWORD value;
SIZE_T read_bytes;
if (ReadProcessMemory(m_process, reinterpret_cast<LPVOID>(address), &value, sizeof(value), &read_bytes) == TRUE)
{
debugprintf(L"0x%llX: 0x%X\n", address, value); /* wrapper around OutputDebugString */
}
}
I am compiling the test program without any optimization in vs2015 update 3.
I have validated that I am indeed compiling it as cdecl by looking in the pdb with the dia2dump sample application.
I do not understand what is causing the stack to look like this, it doesn't match anything I learned, nor does it match the documentation provided by Microsoft.
I also checked google a whole lot (including osdev wiki pages, msdn blog posts, and so on), and checked my (by now probably outdated) books on 32-bit x86 assembly programming (that were released before 64-bit CPUs existed).
Thank you very much in advance for any explanations or links!
I had somehow misunderstood where the arguments to a function call end up in memory compared to the base of the stack frame, as pointed out by Raymond. This is the fixed code snippet:
ULONGLONG stackframe_top = 0;
m_frame->get_base(&stackframe_top); /* IDiaStackFrame */
/* dump 30 * 4 bytes */
for (DWORD i = 0; i < 30; i++)
{
ULONGLONG address = stackframe_top + (i * 4); /* <-- Read before the stack frame */
DWORD value;
SIZE_T read_bytes;
if (ReadProcessMemory(m_process, reinterpret_cast<LPVOID>(address), &value, sizeof(value), &read_bytes) == TRUE)
{
debugprintf(L"0x%llX: 0x%X\n", address, value); /* wrapper around OutputDebugString */
}
}

Dereferencing void* warnings on Xcode

I'm aware of this SO question and this SO question. The element
of novelty in this one is in its focus on Xcode, and in its use of
square brackets to dereference a pointer to void.
The following program compiles with no warning in Xcode 4.5.2, compiles
with a warning on GCC 4.2 and, even though I don't have Visual Studio
right now, I remember that it would consider this a compiler
error, and MSDN and Internet agree.
#include <stdio.h>
int main(int argc, const char * argv[])
{
int x = 24;
void *xPtr = &x;
int *xPtr2 = (int *)&xPtr[1];
printf("%p %p\n", xPtr, xPtr2);
}
If I change the third line of the body of main to:
int *xPtr2 = (int *)(xPtr + 1);
It compiles with no warnings on both GCC and Xcode.
I would like to know how can I turn this silence into warnings or errors, on
GDB and especially Xcode/LLVM, including the fact that function main is int but
does not explicitly return any value (By the way I think -Wall does
the trick on GDB).
that isnt wrong at all...
the compiler doesnt know how big the pointer is ... a void[] ~~ void*
thats why char* used as strings need to be \0-terminated
you cannot turn on a warning for that as it isnt possible to determine a 'size of memory pointer to by a pointer' at compile time
void *v = nil;
*v[1] = 0 //invalid
void *v = malloc(sizeof(int)*2);
*v[1] = 0 //valid
*note typed inline on SO -- sorry for any non-working code

Why could i get an Unhandled exception Access violation writing in c++/CLI?

I have been struggeling writing a solution excisting out of an c++ win32console and a c++ dll. i finally managed to get them talking without linker errors (so i am assuming both are fully managed c++/CLI projects) but when i run the console i get the following error.
Unhandled exception at 0x03f71849 in
Company.Pins.Bank.Win32Console.exe:
0xC0000005: Access violation writing
location 0x00000001.
The console also shows the following
Unhandled Exception:
System.NullReferenceException: Object
reference not set to an instance of an
object. at wmain in c:...\win32console.cpp:line
20 at _wmainCRTStartup()
but i am assuming this is because of the unhandled exception.
tracking down this error as well as i can the error occurs when doing the return in the below code block. (the method linked by the return seems to step through fine, just when returning it seems to go bad.) Just in case you hadn't noticed, i did not write the below code myself, it was generated by visual studio.
#ifdef WPRFLAG
int wmainCRTStartup(
#else /* WPRFLAG */
int mainCRTStartup(
#endif /* WPRFLAG */
#endif /* _WINMAIN_ */
void
)
{
/*
* The /GS security cookie must be initialized before any exception
* handling targetting the current image is registered. No function
* using exception handling can be called in the current image until
* after __security_init_cookie has been called.
*/
__security_init_cookie();
return __tmainCRTStartup();
}
#include "stdafx.h"
#include "UInstruction.h"
#define DllExport __declspec(dllexport)
#define DllImport __declspec(dllimport)
using namespace System;
edit: and the win32console.cpp code is below.
//int main(array<System::String ^> ^args)
int _tmain(int argc, _TCHAR* argv[])
{
auto P2 = (TCHAR *)"3 Barrowstead";
TCHAR* P3 = (TCHAR *)"3 Barrowstead";
double* P1;
P1[0] = 13;
UserInstruction(P1, P2, P3);
}
You declare a pointer and do not initialize it so it doesn't point at an object (it contains some garbage address):
double* P1;
Then you try to write to wherever this uninitialized pointer points:
P1[0] = 13;
You cannot use an uninitialized variable. You need to initialize P1 to point at some object before you dereference it.
double* P1;
is uninitialized. You then attempt to set its first entry to 13. Boom, access violation, or worse.
Any of these snippets should work:
double P1;
P1 = 13;
UserInstruction(&P1, P2, P3);
or
double P1[1];
P1[0] = 13;
UserInstruction(P1, P2, P3);
or
double *P1 = new double[1];
P1[0] = 13;
UserInstruction(P1, P2, P3);
delete[] P1;
The following statements are also wrong when using UNICODE build:
auto P2 = (TCHAR *)"3 Barrowstead";
TCHAR* P3 = (TCHAR *)"3 Barrowstead";
because you're casting a normal (char) array to a wchar_t pointer.
if you build with UNICODE then you should change these in:
LPCTSTR P2 = _T("3 Barrowstead");
LPCTSTR P3 = _T("Barrowstead");
It is undefined behaviour to convert a string literal to a TCHAR*, as if UNICODE is defined then TCHAR* will become wchar_t*, and a string literal is not a wchar_t* and this pointer conversion is undefined.
I managed to find error the following way:
It was not in the line that the error poped-up.
It was actually in the last location I have PIN_PTR the memory.
I have used the following to copy a vector:
memcpy(&pined_ptr[0],&unmanagedvector[0],sizeofunmanagedvector);
The problem was PINED_PRT SIZE < unmanagedVectorSize ! Stupid error.
This messed up all managed memory, an expoded couple of lines & functions later.
How you can find in your code: Go disableling ranges of the code, until your code does not crash.

OpenSSL and multi-threads

I've been reading about the requirement that if OpenSSL is used in a multi-threaded application, you have to register a thread identification function (and also a mutex creation function) with OpenSSL.
On Linux, according to the example provided by OpenSSL, a thread is normally identified by registering a function like this:
static unsigned long id_function(void){
return (unsigned long)pthread_self();
}
pthread_self() returns a pthread_t, and this works on Linux since pthread_t is just a typedef of unsigned long.
On Windows pthreads, FreeBSD, and other operating systems, pthread_t is a struct, with the following structure:
struct {
void * p; /* Pointer to actual object */
unsigned int x; /* Extra information - reuse count etc */
}
This can't be simply cast to an unsigned long, and when I try to do so, it throws a compile error. I tried taking the void *p and casting that to an unsigned long, on the theory that the memory pointer should be consistent and unique across threads, but this just causes my program to crash a lot.
What can I register with OpenSSL as the thread identification function when using Windows pthreads or FreeBSD or any of the other operating systems like this?
Also, as an additional question:
Does anyone know if this also needs to be done if OpenSSL is compiled into and used with QT, and if so how to register QThreads with OpenSSL? Surprisingly, I can't seem to find the answer in QT's documentation.
I will just put this code here. It is not panacea, as it doesn't deal with FreeBSD, but it is helpful in most cases when all you need is to support Windows and and say Debian. Of course, the clean solution assumes usage of CRYPTO_THREADID_* family introduced recently. (to give an idea, it has a CRYPTO_THREADID_cmp callback, which can be mapped to pthread_equal)
#include <pthread.h>
#include <openssl/err.h>
#if defined(WIN32)
#define MUTEX_TYPE HANDLE
#define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)
#define MUTEX_CLEANUP(x) CloseHandle(x)
#define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)
#define MUTEX_UNLOCK(x) ReleaseMutex(x)
#define THREAD_ID GetCurrentThreadId()
#else
#define MUTEX_TYPE pthread_mutex_t
#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
#define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
#define THREAD_ID pthread_self()
#endif
/* This array will store all of the mutexes available to OpenSSL. */
static MUTEX_TYPE *mutex_buf=NULL;
static void locking_function(int mode, int n, const char * file, int line)
{
if (mode & CRYPTO_LOCK)
MUTEX_LOCK(mutex_buf[n]);
else
MUTEX_UNLOCK(mutex_buf[n]);
}
static unsigned long id_function(void)
{
return ((unsigned long)THREAD_ID);
}
int thread_setup(void)
{
int i;
mutex_buf = malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
if (!mutex_buf)
return 0;
for (i = 0; i < CRYPTO_num_locks( ); i++)
MUTEX_SETUP(mutex_buf[i]);
CRYPTO_set_id_callback(id_function);
CRYPTO_set_locking_callback(locking_function);
return 1;
}
int thread_cleanup(void)
{
int i;
if (!mutex_buf)
return 0;
CRYPTO_set_id_callback(NULL);
CRYPTO_set_locking_callback(NULL);
for (i = 0; i < CRYPTO_num_locks( ); i++)
MUTEX_CLEANUP(mutex_buf[i]);
free(mutex_buf);
mutex_buf = NULL;
return 1;
}
I only can answer the Qt part. Use QThread::currentThreadId(), or even QThread::currentThread() as the pointer value should be unique.
From the OpenSSL doc you linked:
threadid_func(CRYPTO_THREADID *id) is needed to record the currently-executing thread's identifier into id. The implementation of this callback should not fill in id directly, but should use CRYPTO_THREADID_set_numeric() if thread IDs are numeric, or CRYPTO_THREADID_set_pointer() if they are pointer-based. If the application does not register such a callback using CRYPTO_THREADID_set_callback(), then a default implementation is used - on Windows and BeOS this uses the system's default thread identifying APIs, and on all other platforms it uses the address of errno. The latter is satisfactory for thread-safety if and only if the platform has a thread-local error number facility.
As shown providing your own ID is really only useful if you can provide a better ID than OpenSSL's default implementation.
The only fail-safe way to provide IDs, when you don't know whether pthread_t is a pointer or an integer, is to maintain your own per-thread IDs stored as a thread-local value.

Resources