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
This commit is contained in:
Arnaud Le Blanc 2025-06-24 11:29:23 +02:00
parent 04feb452ba
commit 73b1ebfa20
No known key found for this signature in database
17 changed files with 2402 additions and 171 deletions

View file

@ -73,19 +73,23 @@ if test "$PHP_OPCACHE" != "no"; then
IR_TARGET=IR_TARGET_X64
DASM_FLAGS="-D X64APPLE=1 -D X64=1"
DASM_ARCH="x86"
TLS_TARGET="darwin"
],
[*x86_64*|amd64-*-freebsd*], [
IR_TARGET=IR_TARGET_X64
DASM_FLAGS="-D X64=1"
DASM_ARCH="x86"
TLS_TARGET="x86_64"
],
[[i[34567]86*|x86*]], [
IR_TARGET=IR_TARGET_X86
DASM_ARCH="x86"
TLS_TARGET="x86"
],
[aarch64*], [
IR_TARGET=IR_TARGET_AARCH64
DASM_ARCH="aarch64"
TLS_TARGET="aarch64"
])
AS_VAR_IF([PHP_CAPSTONE], [yes],
@ -102,6 +106,10 @@ if test "$PHP_OPCACHE" != "no"; then
JIT_CFLAGS="-I@ext_builddir@/jit/ir -D$IR_TARGET -DIR_PHP"
AS_VAR_IF([ZEND_DEBUG], [yes], [JIT_CFLAGS="$JIT_CFLAGS -DIR_DEBUG"])
AS_VAR_IF([PHP_THREAD_SAFETY], [yes], [
ZEND_JIT_SRC="$ZEND_JIT_SRC jit/tls/zend_jit_tls_$TLS_TARGET.c"
])
])
AC_CHECK_FUNCS([mprotect shm_create_largepage])

View file

@ -33,6 +33,7 @@ if (PHP_OPCACHE != "no") {
DEFINE("IR_TARGET", ir_target);
DEFINE("DASM_FLAGS", dasm_flags);
DEFINE("DASM_ARCH", "x86");
DEFINE("TLS_TARGET", "win");
AC_DEFINE('HAVE_JIT', 1, 'Define to 1 to enable JIT.');
@ -52,6 +53,11 @@ if (PHP_OPCACHE != "no") {
ADD_SOURCES(configure_module_dirname + "\\jit",
"zend_jit.c zend_jit_vm_helpers.c",
"opcache", "ext\\opcache\\jit");
if (PHP_ZTS == "yes") {
ADD_SOURCES(configure_module_dirname + "\\jit\\tls",
"zend_jit_tls_win.c",
"opcache", "ext\\opcache\\jit\\tls");
}
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",
"ir.c", "opcache", "ext\\opcache\\jit\\ir");
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",

View file

@ -0,0 +1,4 @@
*.so
*.o
main
main.dSYM

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
/* _tsrm_ls_cache is defined here */
#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#ifdef NO_SURPLUS
# include "def-vars.h"
DEF_VARS(def);
#endif
__thread void* _tsrm_ls_cache;
size_t tsrm_get_ls_cache_tcb_offset(void) {
return 0;
}
void zend_accel_error(int type, const char *format, ...) {
if (type < 4) {
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
}
}
int test(void);
int decl(void) {
return test();
}

View file

@ -0,0 +1,48 @@
#include <stdio.h>
#include <dlfcn.h>
#ifdef NO_SURPLUS
# include "def-vars.h"
DEF_VARS(main);
#endif
__thread int some_tls_var;
#ifndef DL_DECL
int decl(void);
#endif
int main(void) {
/* Ensure TLS vars are allocated */
some_tls_var = 1;
int (*decl_p)(void);
#ifdef DL_DECL
int flags = RTLD_LAZY | RTLD_GLOBAL;
# ifdef RTLD_DEEPBIND
flags |= RTLD_DEEPBIND;
# endif
void *handle = dlopen("./libdef.so", flags);
if (!handle) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
decl_p = (int (*)(void)) dlsym(handle, "decl");
if (!decl_p) {
fprintf(stderr, "dlsym: %s\n", dlerror());
return 1;
}
#else
decl_p = decl;
#endif
int ret = decl_p();
if (!ret) {
fprintf(stderr, "FAIL\n");
} else {
fprintf(stderr, "OK\n");
}
return !ret;
}

View file

@ -0,0 +1,232 @@
#!/bin/sh
set -e
cd "$(dirname "$0")"
print_test() {
echo "Testing: $1 (CC=$CC LD=$LD CFLAGS=$CFLAGS)"
}
exe_def_static_user() {
print_test "TLS var defined in executable, used in same object"
rm -f main
$CC $CFLAGS -ggdb3 -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -o user.o -c user.c
$CC $CFLAGS -ggdb3 -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -ggdb3 -o main main.c def.o user.o tls.o
./main
}
exe_def_shared_user() {
print_test "TLS var defined in executable, used in shared library"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS $LDFLAGS -shared -o libuser.so user.o tls.o
$CC $CFLAGS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -ggdb3 -fPIC -o main main.c def.o -Wl,-rpath,$(pwd) -L. -luser
./main
}
shared_def_static_user() {
print_test "TLS var defined in shared library, used in same object"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o user.o tls.o
$CC $CFLAGS $LDFLAGS -ggdb3 -fPIC -o main main.c -Wl,-rpath,$(pwd) -L. -ldef
./main
}
shared_def_shared_user() {
print_test "TLS var defined in shared object, used in other shared object"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS $LDFLAGS -shared -o libuser.so user.o tls.o
$CC $CFLAGS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o -Wl,-rpath,$(pwd) -L. -luser
$CC $CFLAGS $LDFLAGS -ggdb3 -fPIC -o main main.c -Wl,-rpath,$(pwd) -L. -ldef
./main
}
shared_def_static_user_no_surplus() {
print_test "TLS var defined in shared library, used in same object. Likely no static TLS surplus."
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS -DNO_SURPLUS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o tls.o user.o
$CC $CFLAGS -DNO_SURPLUS $LDFLAGS -ggdb3 -fPIC -o main main.c -Wl,-rpath,$(pwd) -L. -ldef
./main
}
shared_def_shared_user_no_surplus() {
print_test "TLS var defined in shared object, used in other shared object. Likely no static TLS surplus."
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS $LDFLAGS -shared -o libuser.so user.o tls.o
$CC $CFLAGS -DNO_SURPLUS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o -Wl,-rpath,$(pwd) -L. -luser
$CC $CFLAGS -DNO_SURPLUS $LDFLAGS -ggdb3 -fPIC -o main main.c -Wl,-rpath,$(pwd) -L. -ldef
./main
}
dl_def_static_user() {
print_test "TLS var defined in dl()'ed object, used in same object"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o user.o tls.o
$CC $CFLAGS $LDFLAGS -DDL_DECL -ggdb3 -fPIC -o main main.c
./main
}
dl_def_shared_user() {
print_test "TLS var defined in dl()'ed object, used in other shared object"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS $LDFLAGS -shared -o libuser.so user.o tls.o
$CC $CFLAGS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o -Wl,-rpath,$(pwd) -L. -luser
$CC $CFLAGS $LDFLAGS -DDL_DECL -ggdb3 -fPIC -o main main.c
./main
}
dl_def_static_user_no_surplus() {
print_test "TLS var defined in dl()'ed object, used in same object. Likely no surplus TLS"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS -DNO_SURPLUS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o user.o tls.o
$CC $CFLAGS -DNO_SURPLUS $LDFLAGS -DDL_DECL -ggdb3 -fPIC -o main main.c
./main
}
dl_def_shared_user_no_surplus() {
print_test "TLS var defined in dl()'ed object, used in other shared object. Likely no surplus TLS"
rm -f main
$CC $CFLAGS -ggdb3 -fPIC -o tls.o -c $TLSC
$CC $CFLAGS -ggdb3 -fPIC -o user.o -c user.c
$CC $CFLAGS $LDFLAGS -shared -o libuser.so user.o tls.o
$CC $CFLAGS -DNO_SURPLUS -ggdb3 -fPIC -o def.o -c def.c
$CC $CFLAGS $LDFLAGS -shared -o libdef.so def.o -Wl,-rpath,$(pwd) -L. -luser
$CC $CFLAGS -DNO_SURPLUS $LDFLAGS -DDL_DECL -ggdb3 -fPIC -o main main.c
./main
}
if [ -z "$TLSC" ]; then
echo "Variable TLSC is not set" >&2
exit 1
fi
root=$(pwd)/../../../../..
# Cheap musl detection
if test -f /etc/alpine-release; then
MUSL="$CFLAGS -D__MUSL__"
else
MUSL=
fi
if [ "${STATIC_SUPPORT:-yes}" = "yes" ]; then
STATIC=-static
fi
for CC in clang gcc; do
if [ $CC = gcc ] && [ -f /etc/freebsd-update.conf ]; then
RPATH=-Wl,-rpath,/usr/local/lib/gcc13
else
RPATH=
fi
case $CC in
gcc)
LDs=""
for l in bdf gold; do
if command -v ld.$l >/dev/null 2>&1; then
LDs="$LDs $l"
fi
done
if [ -z "$LDs" ]; then
LDs=ld
fi
;;
clang)
LDs="ld"
if command -v ld.lld >/dev/null 2>&1; then
LDs="$LDs lld"
fi
;;
esac
for LD in $LDs; do
for opt in -O0 -O3; do
CFLAGS="$MACHINE $MUSL $opt -Werror -I$root/ext/opcache -I$root/Zend -I$root"
LDFLAGS="$MACHINE -fuse-ld=$LD $RPATH"
for pic in "-fPIC" "-fno-PIC $STATIC"; do
CFLAGS="$CFLAGS $pic" exe_def_static_user
done
shared_def_static_user
shared_def_static_user_no_surplus
dl_def_static_user
dl_def_static_user_no_surplus
if [ "$EXTERN_TLS_SUPPORT" = yes ]; then
exe_def_shared_user
shared_def_shared_user
shared_def_shared_user_no_surplus
dl_def_shared_user
dl_def_shared_user_no_surplus
fi
done
done
done
echo "All OK" >&2

View file

@ -0,0 +1,28 @@
/* _tsrm_ls_cache is used / inspected here */
#include "../zend_jit_tls.h"
extern __thread void* _tsrm_ls_cache;
int test(void)
{
size_t tcb_offset = 0;
size_t module_index = -1;
size_t module_offset = -1;
/* Ensure the slot is allocated */
_tsrm_ls_cache = NULL;
zend_result result = zend_jit_resolve_tsrm_ls_cache_offsets(
&tcb_offset, &module_index, &module_offset);
printf("tcb_offset: %zd; module_index: %zd; module_offset: %zd\n",
tcb_offset, module_index, module_offset);
if (result != SUCCESS) {
return 0;
}
return zend_jit_tsrm_ls_cache_address(tcb_offset, module_index, module_offset) == &_tsrm_ls_cache;
}

View file

@ -0,0 +1,40 @@
/*
* +----------------------------------------------------------------------+
* | 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: Arnaud Le Blanc <arnaud.lb@gmail.com> |
* +----------------------------------------------------------------------+
*/
#ifndef ZEND_JIT_TLS_H
#define ZEND_JIT_TLS_H
#include "Zend/zend_types.h"
#include <stdint.h>
#include <stddef.h>
zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
size_t *module_offset
);
/* Used for testing */
void *zend_jit_tsrm_ls_cache_address(
size_t tcb_offset,
size_t module_index,
size_t module_offset
);
#endif /* ZEND_JIT_TLS_H */

View file

@ -0,0 +1,255 @@
/*
* +----------------------------------------------------------------------+
* | 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: Arnaud Le Blanc <arnaud.lb@gmail.com> |
* +----------------------------------------------------------------------+
*/
#include "Zend/zend_portability.h"
#include "Zend/zend_types.h"
#include "TSRM/TSRM.h"
#include "zend_accelerator_debug.h"
#include <stdint.h>
#include <unistd.h>
TSRMLS_CACHE_EXTERN();
/* https://developer.arm.com/documentation/ddi0602/2025-03/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page- */
#define AARCH64_ADRP_IMM_MASK 0x60ffffe0 /* bits 30-29, 23-5 */
#define AARCH64_ADRP_IMMHI_MASK 0x00ffffe0 /* bits 23-5 */
#define AARCH64_ADRP_IMMLO_MASK 0x60000000 /* bits 30-29 */
#define AARCH64_ADRP_IMMHI_START 5
#define AARCH64_ADRP_IMMLO_START 29
#define AARCH64_ADRP_IMMLO_WIDTH 2
#define AARCH64_LDR_UNSIGNED_IMM_MASK 0x003ffc00 /* bits 21-10 */
#define AARCH64_ADD_IMM_MASK 0x003ffc00 /* bits 21-10 */
#define AARCH64_MOVZ_IMM_MASK 0x001fffe0 /* bits 20-5 */
#define AARCH64_MOVZ_HW_MASK 0x00600000 /* bits 22-21 */
#define AARCH64_MOVK_IMM_MASK 0x001fffe0 /* bits 20-5 */
#define AARCH64_MOVK_HW_MASK 0x00600000 /* bits 22-21 */
#define AARCH64_NOP 0xd503201f
#undef USE_FALLBACK
#ifdef __MUSL__
# define DTV_OFFSET -8
# define DTV_INDEX_GAP 0
typedef struct _dtv_pointer_t {
uintptr_t val;
} dtv_pointer_t;
typedef struct _tls_descriptor {
size_t index;
size_t offset;
} tls_descriptor;
#elif defined(__FreeBSD__)
# define DTV_OFFSET 0
/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */
# define DTV_INDEX_GAP 1
typedef struct _dtv_pointer_t {
uintptr_t val;
} dtv_pointer_t;
/* https://github.com/freebsd/freebsd-src/blob/c52ca7dd09066648b1cc40f758289404d68ab886/libexec/rtld-elf/aarch64/reloc.c#L180-L184 */
typedef struct _tls_descriptor {
void* thunk;
int index;
size_t offset;
} tls_descriptor;
#elif defined(__GLIBC__)
# define DTV_OFFSET 0
# define DTV_INDEX_GAP 0
typedef struct _dtv_pointer_t {
uintptr_t val;
uintptr_t _;
} dtv_pointer_t;
typedef struct _tls_descriptor {
size_t index;
size_t offset;
} tls_descriptor;
#else
# define USE_FALLBACK 1
#endif
zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
size_t *module_offset
) {
#ifdef USE_FALLBACK
return FAILURE;
#else
*tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (*tcb_offset != 0) {
return SUCCESS;
}
void *addr;
uint32_t *insn;
void *thread_pointer;
__asm__ __volatile__(
/* Load thread pointer address */
"mrs %0, tpidr_el0\n"
/* Load next instruction address */
"adr %1, .+4\n\t"
/* General Dynamic code sequence as expected by linkers */
"adrp x0, :tlsdesc:_tsrm_ls_cache\n"
"ldr x1, [x0, #:tlsdesc_lo12:_tsrm_ls_cache]\n"
"add x0, x0, :tlsdesc_lo12:_tsrm_ls_cache\n"
".tlsdesccall _tsrm_ls_cache\n"
"blr x1\n"
"mrs x8, tpidr_el0\n"
"add %2, x8, x0\n"
: "=r" (thread_pointer), "=r" (insn), "=r" (addr)
:
: "x0", "x1", "x8");
ZEND_ASSERT(addr == &_tsrm_ls_cache);
/* Check if the general dynamic code was relaxed by the linker */
// adrp x0, #any
if ((insn[0] & ~AARCH64_ADRP_IMM_MASK) != 0x90000000) {
zend_accel_error(ACCEL_LOG_DEBUG, "adrp insn does not match: 0x%08" PRIx32 "\n", insn[0]);
goto code_changed;
}
// ldr x1, [x0, #any]
if ((insn[1] & ~AARCH64_LDR_UNSIGNED_IMM_MASK) != 0xf9400001) {
zend_accel_error(ACCEL_LOG_DEBUG, "ldr insn does not match: 0x%08" PRIx32 "\n", insn[1]);
goto code_changed;
}
// add x0, x0, any
if ((insn[2] & ~AARCH64_ADD_IMM_MASK) != 0x91000000) {
zend_accel_error(ACCEL_LOG_DEBUG, "add insn does not match: 0x%08" PRIx32 "x\n", insn[2]);
goto code_changed;
}
/* Code is intact, we can extract immediate values */
uint64_t adrp_immhi = (uint64_t)((insn[0] & AARCH64_ADRP_IMMHI_MASK) >> AARCH64_ADRP_IMMHI_START);
uint64_t adrp_immlo = (uint64_t)((insn[0] & AARCH64_ADRP_IMMLO_MASK) >> AARCH64_ADRP_IMMLO_START);
uint64_t adrp_imm = ((adrp_immhi << AARCH64_ADRP_IMMLO_WIDTH) | adrp_immlo) << 12;
uint64_t add_imm = (uint64_t)(insn[2] & AARCH64_ADD_IMM_MASK) >> 10;
uint64_t pc = (uint64_t)insn;
uintptr_t **where = (uintptr_t**)((pc & ~(4096-1)) + adrp_imm + add_imm);
/* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst
* section "Relocations for thread-local storage".
* The first entry holds a pointer to the variable's TLS descriptor resolver
* function and the second entry holds a platform-specific offset or
* pointer. */
tls_descriptor *tlsdesc = (tls_descriptor*)(where[1]);
if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == (uintptr_t)tlsdesc) {
zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %p from thread pointer (inferred from tlsdesc)\n", tlsdesc);
*tcb_offset = (uintptr_t)tlsdesc;
return SUCCESS;
}
*module_index = (tlsdesc->index + DTV_INDEX_GAP) * sizeof(dtv_pointer_t);
*module_offset = tlsdesc->offset;
# if ZEND_DEBUG
/* We've got the TLS descriptor. Double check: */
dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET);
addr = (void*)(((dtv_pointer_t*)((char*)dtv + *module_index))->val + *module_offset);
ZEND_ASSERT(addr == &_tsrm_ls_cache);
# endif
zend_accel_error(ACCEL_LOG_DEBUG, "dynamic tls module idx %zu offset %zu (inferred from code)\n", (size_t)tlsdesc->index, tlsdesc->offset);
return SUCCESS;
code_changed:
/* Code was changed by the linker. Check if we recognize the updated code */
// movz x0, #0, lsl #16
if ((insn[0] & ~AARCH64_MOVZ_IMM_MASK) != 0xd2a00000) {
zend_accel_error(ACCEL_LOG_DEBUG, "movz insn does not match: 0x%08" PRIx32 "\n", insn[0]);
return FAILURE;
}
// movk x0, #0x10
if ((insn[1] & ~AARCH64_MOVK_IMM_MASK) != 0xf2800000) {
zend_accel_error(ACCEL_LOG_DEBUG, "movk insn does not match: 0x%08" PRIx32 "\n", insn[1]);
return FAILURE;
}
// nop
for (int i = 0; i < 2; i++) {
if (insn[2+i] != AARCH64_NOP) {
zend_accel_error(ACCEL_LOG_DEBUG, "nop(%d) insn does not match: 0x%08" PRIx32 "\n", i, insn[2+i]);
return FAILURE;
}
}
/* Extract immediate values */
uint64_t movz_imm = (insn[0] & AARCH64_MOVZ_IMM_MASK) >> 5;
uint64_t movz_shift = (((insn[0] & AARCH64_MOVZ_HW_MASK) >> 21) << 4);
uint64_t movk_imm = (insn[1] & AARCH64_MOVK_IMM_MASK) >> 5;
uint64_t offset = (movz_imm << movz_shift) | movk_imm;
if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == offset) {
zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %" PRIxPTR " from thread pointer (inferred from code)\n", offset);
*tcb_offset = offset;
return SUCCESS;
}
zend_accel_error(ACCEL_LOG_DEBUG, "static tls offset does not match: %" PRIxPTR " (expected %" PRIxPTR ")\n",
offset, ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer));
return FAILURE;
#endif
}
/* Used for testing */
void *zend_jit_tsrm_ls_cache_address(
size_t tcb_offset,
size_t module_index,
size_t module_offset
) {
char *thread_pointer;
__asm__ __volatile__(
"mrs %0, tpidr_el0\n"
: "=r" (thread_pointer)
);
if (tcb_offset) {
return thread_pointer + tcb_offset;
}
if (module_index != (size_t)-1 && module_offset != (size_t)-1) {
dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET);
return (void*)(((dtv_pointer_t*)((char*)dtv + module_index))->val + module_offset);
}
return NULL;
}

View file

@ -0,0 +1,82 @@
/*
* +----------------------------------------------------------------------+
* | 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 <unistd.h>
TSRMLS_CACHE_EXTERN();
zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
size_t *module_offset
) {
*tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (*tcb_offset != 0) {
return SUCCESS;
}
#if defined(__x86_64__)
size_t *ti;
__asm__ __volatile__(
"leaq __tsrm_ls_cache(%%rip),%0"
: "=r" (ti));
*module_offset = ti[2];
*module_index = ti[1] * 8;
return SUCCESS;
#endif
return FAILURE;
}
/* Used for testing */
void *zend_jit_tsrm_ls_cache_address(
size_t tcb_offset,
size_t module_index,
size_t module_offset
) {
#if defined(__x86_64__)
if (tcb_offset) {
char *addr;
__asm__ __volatile__(
"movq %%gs:(%1), %0\n"
: "=r" (addr)
: "r" (tcb_offset)
);
return addr;
}
if (module_index != (size_t)-1 && module_offset != (size_t)-1) {
char *base;
__asm__ __volatile__(
"movq %%gs:(%1), %0\n"
: "=r" (base)
: "r" (module_index)
);
return base + module_offset;
}
#endif
return NULL;
}

View file

@ -0,0 +1,64 @@
/*
* +----------------------------------------------------------------------+
* | 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;
}

View file

@ -0,0 +1,239 @@
/*
* +----------------------------------------------------------------------+
* | 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: Arnaud Le Blanc <arnaud.lb@gmail.com> |
* +----------------------------------------------------------------------+
*/
#include "zend_portability.h"
#include "zend_types.h"
#include "TSRM/TSRM.h"
#include "zend_accelerator_debug.h"
#include "zend_jit_tls.h"
#include <stdint.h>
#include <unistd.h>
TSRMLS_CACHE_EXTERN();
#undef USE_FALLBACK
#ifdef __MUSL__
# define DTV_OFFSET 4
# define DTV_INDEX_GAP 0
typedef struct _dtv_pointer_t {
uintptr_t val;
} dtv_pointer_t;
typedef struct _tls_descriptor {
size_t index;
size_t offset;
} tls_descriptor;
#elif defined(__FreeBSD__)
# define DTV_OFFSET 4
# define DTV_INDEX_GAP 1
typedef struct _dtv_pointer_t {
uintptr_t val;
} dtv_pointer_t;
/* https://github.com/freebsd/freebsd-src/blob/6b94546a7ea2dc593f5765bd5465a8b7bb80c325/libexec/rtld-elf/i386/rtld_machdep.h#L65 */
typedef struct _tls_descriptor {
unsigned long index;
unsigned long offset;
} tls_descriptor;
#elif defined(__GLIBC__)
# define DTV_OFFSET 4
# define DTV_INDEX_GAP 0
typedef struct _dtv_pointer_t {
uintptr_t val;
uintptr_t _;
} dtv_pointer_t;
typedef struct _tls_descriptor {
size_t index;
size_t offset;
} tls_descriptor;
#else
# define USE_FALLBACK 1
#endif
zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
size_t *module_offset
) {
#ifdef USE_FALLBACK
return FAILURE;
#else
*tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (*tcb_offset != 0) {
return SUCCESS;
}
void *t_addr;
unsigned char *code;
void *thread_pointer;
__asm__ __volatile__(
/* Load next instruction address */
"call 1f\n"
".subsection 1\n"
"1:\n"
"movl (%%esp), %%ebx\n"
"movl %%ebx, %%esi\n"
"ret\n"
".previous\n"
/* General Dynamic code sequence as expected by linkers */
"addl $_GLOBAL_OFFSET_TABLE_, %%ebx\n"
"leal _tsrm_ls_cache@TLSGD(,%%ebx,1), %%eax\n"
"call ___tls_get_addr@PLT\n"
/* Load thread pointer address */
"movl %%gs:0, %%ebx\n"
: "=a" (t_addr), "=S" (code), "=b" (thread_pointer)
);
ZEND_ASSERT(t_addr == &_tsrm_ls_cache);
/* Check if the general dynamic code was relaxed by the linker */
// addl any,%ebx
if (memcmp(&code[0], "\x81\xc3", 2) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[0], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "addl insn does not match: 0x%16" PRIx64 "\n", bytes);
goto code_changed;
}
// leal any(,%ebx,1),%eax
if (memcmp(&code[6], "\x8d\x04\x1d", 3) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[6], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "leal insn does not match: 0x%16" PRIx64 "\n", bytes);
goto code_changed;
}
// call any
if (memcmp(&code[13], "\xe8", 1) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[13], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "call insn does not match: 0x%16" PRIx64 "\n", bytes);
goto code_changed;
}
/* Code is intact, we can extract immediate values */
uint32_t addl_imm = ((uint32_t)code[5] << 24)
| ((uint32_t)code[4] << 16)
| ((uint32_t)code[3] << 8)
| ((uint32_t)code[2]);
uint32_t leal_imm = ((uint32_t)code[12] << 24)
| ((uint32_t)code[11] << 16)
| ((uint32_t)code[10] << 8)
| ((uint32_t)code[9]);
tls_descriptor *tlsdesc = (tls_descriptor*)(leal_imm + addl_imm + (uintptr_t)code);
*module_index = (tlsdesc->index + DTV_INDEX_GAP) * sizeof(dtv_pointer_t);
*module_offset = tlsdesc->offset;
# if ZEND_DEBUG
/* We've got the TLS descriptor. Double check: */
dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET);
void *addr = (void*)(((dtv_pointer_t*)((char*)dtv + *module_index))->val + *module_offset);
ZEND_ASSERT(addr == &_tsrm_ls_cache);
# endif
zend_accel_error(ACCEL_LOG_DEBUG, "dynamic tls module idx %zu offset %zu (inferred from code)\n",
(size_t)tlsdesc->index, (size_t)tlsdesc->offset);
return SUCCESS;
code_changed:
/* Code was changed by the linker. Check if we recognize the updated code */
/*
* 81 c3 98 2d 00 00 addl $0x2d98,%ebx
* 65 a1 00 00 00 00 movl %gs:0x0,%eax
* 81 e8 04 00 00 00 subl $0x4,%eax
*/
// movl %gs:0x0,%eax
if (memcmp(&code[6], "\x65\xa1\x00\x00\x00\x00", 6) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[6], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "movl insn does not match: 0x%16" PRIx64 "\n", bytes);
return FAILURE;
}
// subl $any,%eax
if (memcmp(&code[12], "\x81\xe8", 2) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[6], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "subl insn does not match: 0x%16" PRIx64 "\n", bytes);
return FAILURE;
}
/* Extract immediate values */
uint32_t offset = -(((uint32_t)code[17] << 24)
| ((uint32_t)code[16] << 16)
| ((uint32_t)code[15] << 8)
| ((uint32_t)code[14]));
if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == offset) {
zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %" PRIx32 " from thread pointer (inferred from code)\n", offset);
*tcb_offset = offset;
return SUCCESS;
}
zend_accel_error(ACCEL_LOG_DEBUG, "static tls offset does not match: 0x%" PRIx32 " (expected 0x%" PRIx32 ")\n",
offset, (uint32_t)((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer));
return FAILURE;
#endif
}
/* Used for testing */
void *zend_jit_tsrm_ls_cache_address(
size_t tcb_offset,
size_t module_index,
size_t module_offset
) {
char *thread_pointer;
__asm__ __volatile__(
"movl %%gs:0, %0\n"
: "=r" (thread_pointer)
);
if (tcb_offset) {
return thread_pointer + tcb_offset;
}
if (module_index != (size_t)-1 && module_offset != (size_t)-1) {
dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET);
return (void*)(((dtv_pointer_t*)((char*)dtv + module_index))->val + module_offset);
}
return NULL;
}

View file

@ -0,0 +1,222 @@
/*
* +----------------------------------------------------------------------+
* | 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: Arnaud Le Blanc <arnaud.lb@gmail.com> |
* +----------------------------------------------------------------------+
*/
#include "zend_portability.h"
#include "zend_types.h"
#include "TSRM/TSRM.h"
#include "zend_accelerator_debug.h"
#include "zend_jit_tls.h"
#include <stdint.h>
#include <unistd.h>
TSRMLS_CACHE_EXTERN();
#undef USE_FALLBACK
#ifdef __MUSL__
# define DTV_OFFSET 8
# define DTV_INDEX_GAP 0
typedef struct _dtv_pointer_t {
uintptr_t val;
} dtv_pointer_t;
typedef struct _tls_descriptor {
size_t index;
size_t offset;
} tls_descriptor;
#elif defined(__FreeBSD__)
# define DTV_OFFSET 8
# define DTV_INDEX_GAP 1
typedef struct _dtv_pointer_t {
uintptr_t val;
} dtv_pointer_t;
/* https://github.com/freebsd/freebsd-src/blob/6b94546a7ea2dc593f5765bd5465a8b7bb80c325/libexec/rtld-elf/amd64/rtld_machdep.h#L65 */
typedef struct _tls_descriptor {
unsigned long index;
unsigned long offset;
} tls_descriptor;
#elif defined(__GLIBC__)
# define DTV_OFFSET 8
# define DTV_INDEX_GAP 0
typedef struct _dtv_pointer_t {
uintptr_t val;
uintptr_t _;
} dtv_pointer_t;
typedef struct _tls_descriptor {
size_t index;
size_t offset;
} tls_descriptor;
#else
# define USE_FALLBACK 1
#endif
zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
size_t *module_offset
) {
#ifdef USE_FALLBACK
return FAILURE;
#else
*tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (*tcb_offset != 0) {
return SUCCESS;
}
void *addr;
unsigned char *code;
void *thread_pointer;
__asm__ __volatile__(
/* Load next instruction address */
"leaq (%%rip), %%rbx\n"
/* General Dynamic code sequence as expected by linkers */
".byte 0x66\n"
"leaq _tsrm_ls_cache@tlsgd(%%rip), %%rdi\n"
".word 0x6666\n"
"rex64\n"
"call __tls_get_addr\n"
/* Load thread pointer address */
"movq %%fs:0, %%rsi\n"
: "=a" (addr), "=b" (code), "=S" (thread_pointer)
);
ZEND_ASSERT(addr == &_tsrm_ls_cache);
/* Check if the general dynamic code was relaxed by the linker */
// data16 leaq any(%rip),%rdi
if (memcmp(&code[0], "\x66\x48\x8d\x3d", 4) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[0], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "leaq insn does not match: 0x%016" PRIx64 "\n", bytes);
goto code_changed;
}
// data16 data16 rex.W call any
if (memcmp(&code[8], "\x66\x66\x48\xe8", 4) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[8], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "call insn does not match: 0x%016" PRIx64 "\n", bytes);
goto code_changed;
}
/* Code is intact, we can extract immediate values */
uintptr_t leaq_imm = (uintptr_t)(int32_t)((uint32_t)code[7] << 24)
| ((uint32_t)code[6] << 16)
| ((uint32_t)code[5] << 8)
| ((uint32_t)code[4]);
tls_descriptor *tlsdesc = (tls_descriptor*)(leaq_imm + (uintptr_t)code + 8 /* leaq */);
*module_index = ((size_t)tlsdesc->index + DTV_INDEX_GAP) * sizeof(dtv_pointer_t);
*module_offset = (size_t)tlsdesc->offset;
# if ZEND_DEBUG
/* We've got the TLS descriptor. Double check: */
dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET);
addr = (void*)(((dtv_pointer_t*)((char*)dtv + *module_index))->val + *module_offset);
ZEND_ASSERT(addr == &_tsrm_ls_cache);
# endif
zend_accel_error(ACCEL_LOG_DEBUG, "dynamic tls module idx %zu offset %zu (inferred from code)\n",
(size_t)tlsdesc->index, (size_t)tlsdesc->offset);
return SUCCESS;
code_changed:
/* Code was changed by the linker. Check if we recognize the updated code */
/*
* 64 48 8b 04 25 00 00 00 00 movq %fs:0x0,%rax
* 48 8d 80 f8 ff ff ff leaq -0x8(%rax),%rax
*/
// movq %fs:0x0,%rax
if (memcmp(&code[0], "\x64\x48\x8b\x04\x25\x00\x00\x00\x00", 9) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[0], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "movq insn does not match: 0x%016" PRIx64 "\n", bytes);
return FAILURE;
}
// leaq any(%rax),$rax
if (memcmp(&code[9], "\x48\x8d\x80", 3) != 0) {
uint64_t bytes;
memcpy(&bytes, &code[10], 8);
zend_accel_error(ACCEL_LOG_DEBUG, "leaq insn does not match: 0x%016" PRIx64 "\n", bytes);
return FAILURE;
}
/* Extract immediate values */
uintptr_t offset = (uintptr_t)(int32_t)(((uint32_t)code[15] << 24)
| ((uint32_t)code[14] << 16)
| ((uint32_t)code[13] << 8)
| ((uint32_t)code[12]));
if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == offset) {
*tcb_offset = offset;
zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %" PRIxPTR " from thread pointer (inferred from code)\n", offset);
return SUCCESS;
}
zend_accel_error(ACCEL_LOG_DEBUG, "static tls offset does not match: %" PRIxPTR " (expected %" PRIxPTR ")\n",
offset, ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer));
return FAILURE;
#endif
}
/* Used for testing */
void *zend_jit_tsrm_ls_cache_address(
size_t tcb_offset,
size_t module_index,
size_t module_offset
) {
char *thread_pointer;
__asm__ __volatile__(
"movq %%fs:0, %0\n"
: "=r" (thread_pointer)
);
if (tcb_offset) {
return thread_pointer + tcb_offset;
}
if (module_index != (size_t)-1 && module_offset != (size_t)-1) {
dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET);
return (void*)(((dtv_pointer_t*)((char*)dtv + module_index))->val + module_offset);
}
return NULL;
}

View file

@ -18,6 +18,7 @@
#include "jit/ir/ir.h"
#include "jit/ir/ir_builder.h"
#include "jit/tls/zend_jit_tls.h"
#if defined(IR_TARGET_X86)
# define IR_REG_SP 4 /* IR_REG_RSP */
@ -171,15 +172,9 @@ static uint32_t default_mflags = 0;
static bool delayed_call_chain = 0; // TODO: remove this var (use jit->delayed_call_level) ???
#ifdef ZTS
# ifdef _WIN32
extern uint32_t _tls_index;
extern char *_tls_start;
extern char *_tls_end;
# endif
static size_t tsrm_ls_cache_tcb_offset = 0;
static size_t tsrm_tls_index = 0;
static size_t tsrm_tls_offset = 0;
static size_t tsrm_tls_index = -1;
static size_t tsrm_tls_offset = -1;
# define EG_TLS_OFFSET(field) \
(executor_globals_offset + offsetof(zend_executor_globals, field))
@ -334,6 +329,8 @@ static int zend_jit_assign_to_variable(zend_jit_ctx *jit,
zend_jit_addr ref_addr,
bool check_exception);
static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags);
typedef struct _zend_jit_stub {
const char *name;
int (*stub)(zend_jit_ctx *jit);
@ -463,6 +460,11 @@ static const char* zend_reg_name(int8_t reg)
/* IR helpers */
#ifdef ZTS
static void * ZEND_FASTCALL zend_jit_get_tsrm_ls_cache(void)
{
return _tsrm_ls_cache;
}
static ir_ref jit_TLS(zend_jit_ctx *jit)
{
ZEND_ASSERT(jit->ctx.control);
@ -482,9 +484,15 @@ static ir_ref jit_TLS(zend_jit_ctx *jit)
ref = insn->op1;
}
}
jit->tls = ir_TLS(
tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index,
tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset);
if (tsrm_ls_cache_tcb_offset == 0 && tsrm_tls_index == -1) {
jit->tls = ir_CALL(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_get_tsrm_ls_cache));
} else {
jit->tls = ir_TLS(
tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index,
tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset);
}
return jit->tls;
}
#endif
@ -3125,6 +3133,8 @@ static void zend_jit_setup_disasm(void)
REGISTER_DATA(EG(symbol_table));
REGISTER_DATA(CG(map_ptr_base));
#else /* ZTS */
REGISTER_HELPER(zend_jit_get_tsrm_ls_cache);
#endif
#endif
}
@ -3291,7 +3301,6 @@ static void zend_jit_setup_unwinder(void)
}
#endif
static void zend_jit_setup(bool reattached)
{
#if defined(IR_TARGET_X86)
@ -3310,166 +3319,17 @@ static void zend_jit_setup(bool reattached)
}
# endif
#endif
#ifdef ZTS
#if defined(IR_TARGET_AARCH64)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
# ifdef __FreeBSD__
if (tsrm_ls_cache_tcb_offset == 0) {
TLSDescriptor **where;
__asm__(
"adrp %0, :tlsdesc:_tsrm_ls_cache\n"
"add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n"
: "=r" (where));
/* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst
* section "Relocations for thread-local storage".
* The first entry holds a pointer to the variable's TLS descriptor resolver function and the second entry holds
* a platform-specific offset or pointer. */
TLSDescriptor *tlsdesc = where[1];
tsrm_tls_offset = tlsdesc->offset;
/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */
tsrm_tls_index = (tlsdesc->index + 1) * 8;
zend_result result = zend_jit_resolve_tsrm_ls_cache_offsets(
&tsrm_ls_cache_tcb_offset,
&tsrm_tls_index,
&tsrm_tls_offset
);
if (result == FAILURE) {
zend_accel_error(ACCEL_LOG_INFO,
"Could not get _tsrm_ls_cache offsets, will fallback to runtime resolution");
}
# elif defined(__MUSL__)
if (tsrm_ls_cache_tcb_offset == 0) {
size_t **where;
__asm__(
"adrp %0, :tlsdesc:_tsrm_ls_cache\n"
"add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n"
: "=r" (where));
/* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst */
size_t *tlsdesc = where[1];
tsrm_tls_offset = tlsdesc[1];
tsrm_tls_index = tlsdesc[0] * 8;
}
# else
ZEND_ASSERT(tsrm_ls_cache_tcb_offset != 0);
# endif
# elif defined(_WIN64)
tsrm_tls_index = _tls_index * sizeof(void*);
/* 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 */
do {
void ***tls_mem = ((void****)__readgsqword(0x58))[_tls_index];
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) {
tsrm_tls_offset = offset;
break;
}
tls_mem++;
offset += sizeof(void*);
}
if (offset >= size) {
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: offset >= size");
}
} while(0);
# elif defined(ZEND_WIN32)
tsrm_tls_index = _tls_index * sizeof(void*);
/* 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 */
do {
void ***tls_mem = ((void****)__readfsdword(0x2c))[_tls_index];
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) {
tsrm_tls_offset = offset;
break;
}
tls_mem++;
offset += sizeof(void*);
}
if (offset >= size) {
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: offset >= size");
}
} while(0);
# elif defined(__APPLE__) && defined(__x86_64__)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (tsrm_ls_cache_tcb_offset == 0) {
size_t *ti;
__asm__(
"leaq __tsrm_ls_cache(%%rip),%0"
: "=r" (ti));
tsrm_tls_offset = ti[2];
tsrm_tls_index = ti[1] * 8;
}
# elif defined(__GNUC__) && defined(__x86_64__)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (tsrm_ls_cache_tcb_offset == 0) {
#if defined(__has_attribute) && __has_attribute(tls_model) && !defined(__FreeBSD__) && \
!defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__)
size_t ret;
asm ("movq _tsrm_ls_cache@gottpoff(%%rip),%0"
: "=r" (ret));
tsrm_ls_cache_tcb_offset = ret;
#elif defined(__MUSL__)
size_t *ti;
__asm__(
"leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n"
: "=D" (ti));
tsrm_tls_offset = ti[1];
tsrm_tls_index = ti[0] * 8;
#elif defined(__FreeBSD__)
size_t *ti;
__asm__(
"leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n"
: "=D" (ti));
tsrm_tls_offset = ti[1];
/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/bf56e8b9c8639ac4447d223b83cdc128107cc3cd/libexec/rtld-elf/rtld.c#L5260) */
tsrm_tls_index = (ti[0] + 1) * 8;
#else
size_t *ti;
__asm__(
"leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n"
: "=D" (ti));
tsrm_tls_offset = ti[1];
tsrm_tls_index = ti[0] * 16;
#endif
}
# elif defined(__GNUC__) && defined(__i386__)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (tsrm_ls_cache_tcb_offset == 0) {
#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__)
size_t ret;
asm ("leal _tsrm_ls_cache@ntpoff,%0\n"
: "=a" (ret));
tsrm_ls_cache_tcb_offset = ret;
#else
size_t *ti, _ebx, _ecx, _edx;
__asm__(
"call 1f\n"
".subsection 1\n"
"1:\tmovl (%%esp), %%ebx\n\t"
"ret\n"
".previous\n\t"
"addl $_GLOBAL_OFFSET_TABLE_, %%ebx\n\t"
"leal _tsrm_ls_cache@tlsldm(%%ebx), %0\n\t"
"call ___tls_get_addr@plt\n\t"
"leal _tsrm_ls_cache@tlsldm(%%ebx), %0\n"
: "=a" (ti), "=&b" (_ebx), "=&c" (_ecx), "=&d" (_edx));
tsrm_tls_offset = ti[1];
tsrm_tls_index = ti[0] * 8;
#endif
}
# endif
#endif
#if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)

View file

@ -19,6 +19,7 @@ opcache
<?php
// Prints "Debug Restarting!" message on next request.
if (getenv('SKIP_REPEAT')) die("skip Not repeatable");
if (PHP_ZTS) die("skip ZTS prints extra messages");
?>
--FILE--
<?php

View file

@ -62,11 +62,90 @@ function main(int $argc, array $argv): void
echo "DEBUG: " . ($environment->debug ? 'Yes' : 'No') . "\n";
echo "=====================================================================\n";
echo "No tests in this branch yet.\n";
try {
output_group_start($environment, 'Running Opcache TLS tests');
if (!run_opcache_tls_tests($environment)) {
echo "Failed\n";
exit(1);
}
} finally {
output_group_end($environment);
}
echo "All OK\n";
}
function run_opcache_tls_tests(Environment $environment): bool
{
if (!$environment->zts) {
echo "Skipping: NTS\n";
return true;
}
if (!$environment->debug) {
echo "Skipping: NDEBUG\n";
return true;
}
$tlsc = '';
$machine = '';
$static_support = 'yes';
switch ($environment->cpuArch) {
case 'x86':
$machine = '-m32';
break;
case 'x86_64':
case 'aarch64':
break;
default:
echo "Skipping: {$environment->cpuArch}\n";
return true;
}
switch ($environment->os) {
case 'Linux':
case 'FreeBSD':
$tlsc = __DIR__ . "/ext/opcache/jit/tls/zend_jit_tls_{$environment->cpuArch}.c";
break;
case 'Darwin':
if ($environment->cpuArch === 'aarch64') {
echo "Skipping: JIT+TLS not supported on MacOS Apple Silicon\n";
return true;
}
$tlsc = __DIR__ . "/ext/opcache/jit/tls/zend_jit_tls_darwin.c";
$static_support = 'no';
break;
default:
echo "Skipping: {$environment->os}\n";
return true;
}
echo "TLSC=$tlsc MACHINE=$machine STATIC_SUPPORT=$static_support ext/opcache/jit/tls/testing/test.sh\n";
$proc = proc_open(
__DIR__ . '/ext/opcache/jit/tls/testing/test.sh',
[
0 => ['pipe', 'r'],
],
$pipes,
env_vars: array_merge(getenv(), [
'TLSC' => $tlsc,
'MACHINE' => $machine,
'STATIC_SUPPORT' => $static_support,
]),
);
if (!$proc) {
echo "proc_open() failed\n";
return false;
}
fclose($pipes[0]);
return proc_close($proc) === 0;
}
function output_group_start(Environment $environment, string $name): void
{
if ($environment->githubAction) {