php-src/ext/opcache/jit/tls/zend_jit_tls_win.c
Arnaud Le Blanc 73b1ebfa20
Fix linker failure when building Opcache statically
We use linker relocations to fetch the TLS index and offset of _tsrm_ls_cache.
When building Opcache statically, linkers may attempt to optimize that into a
more efficient code sequence (relaxing from "General Dynamic" to "Local Exec"
model [1]). Unfortunately, linkers will fail, rather than ignore our
relocations, when they don't recognize the exact code sequence they are
expecting.

This results in errors as reported by GH-15074:

    TLS transition from R_X86_64_TLSGD to R_X86_64_GOTTPOFF against
    `_tsrm_ls_cache' at 0x12fc3 in section `.text' failed"

Here I take a different approach:

 * Emit the exact full code sequence expected by linkers
 * Extract the TLS index/offset by inspecting the linked ASM code, rather than
   executing it (execution would give us the thread-local address).
 * We detect when the code was relaxed, in which case we can extract the TCB
   offset instead.
 * This is done in a conservative way so that if the linker did something we
   didn't expect, we fallback to a safer (but slower) mechanism.

One additional benefit of that is we are now able to use the Local Exec model in
more cases, in JIT'ed code. This makes non-glibc builds faster in these cases.

Closes GH-18939.

Related RFC: https://wiki.php.net/rfc/make_opcache_required.

[1] https://www.akkadia.org/drepper/tls.pdf
2025-07-26 16:43:41 +02:00

64 lines
2.2 KiB
C

/*
* +----------------------------------------------------------------------+
* | Zend JIT |
* +----------------------------------------------------------------------+
* | Copyright (c) The PHP Group |
* +----------------------------------------------------------------------+
* | This source file is subject to version 3.01 of the PHP license, |
* | that is bundled with this package in the file LICENSE, and is |
* | available through the world-wide-web at the following url: |
* | https://www.php.net/license/3_01.txt |
* | If you did not receive a copy of the PHP license and are unable to |
* | obtain it through the world-wide-web, please send a note to |
* | license@php.net so we can mail you a copy immediately. |
* +----------------------------------------------------------------------+
* | Authors: Dmitry Stogov <dmitry@php.net> |
* +----------------------------------------------------------------------+
*/
#include "Zend/zend_portability.h"
#include "Zend/zend_types.h"
#include "TSRM/TSRM.h"
#include "zend_accelerator_debug.h"
#include <stdint.h>
#include <stddef.h>
TSRMLS_CACHE_EXTERN();
extern uint32_t _tls_index;
extern char *_tls_start;
extern char *_tls_end;
zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
size_t *module_offset
) {
/* To find offset of "_tsrm_ls_cache" in TLS segment we perform a linear scan of local TLS memory */
/* Probably, it might be better solution */
#ifdef _WIN64
void ***tls_mem = ((void****)__readgsqword(0x58))[_tls_index];
#else
void ***tls_mem = ((void****)__readfsdword(0x2c))[_tls_index];
#endif
void *val = _tsrm_ls_cache;
size_t offset = 0;
size_t size = (char*)&_tls_end - (char*)&_tls_start;
while (offset < size) {
if (*tls_mem == val) {
*module_index = _tls_index * sizeof(void*);
*module_offset = offset;
return SUCCESS;
}
tls_mem++;
offset += sizeof(void*);
}
if (offset >= size) {
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: offset >= size");
}
return FAILURE;
}