mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
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:
parent
04feb452ba
commit
73b1ebfa20
17 changed files with 2402 additions and 171 deletions
|
@ -73,19 +73,23 @@ if test "$PHP_OPCACHE" != "no"; then
|
||||||
IR_TARGET=IR_TARGET_X64
|
IR_TARGET=IR_TARGET_X64
|
||||||
DASM_FLAGS="-D X64APPLE=1 -D X64=1"
|
DASM_FLAGS="-D X64APPLE=1 -D X64=1"
|
||||||
DASM_ARCH="x86"
|
DASM_ARCH="x86"
|
||||||
|
TLS_TARGET="darwin"
|
||||||
],
|
],
|
||||||
[*x86_64*|amd64-*-freebsd*], [
|
[*x86_64*|amd64-*-freebsd*], [
|
||||||
IR_TARGET=IR_TARGET_X64
|
IR_TARGET=IR_TARGET_X64
|
||||||
DASM_FLAGS="-D X64=1"
|
DASM_FLAGS="-D X64=1"
|
||||||
DASM_ARCH="x86"
|
DASM_ARCH="x86"
|
||||||
|
TLS_TARGET="x86_64"
|
||||||
],
|
],
|
||||||
[[i[34567]86*|x86*]], [
|
[[i[34567]86*|x86*]], [
|
||||||
IR_TARGET=IR_TARGET_X86
|
IR_TARGET=IR_TARGET_X86
|
||||||
DASM_ARCH="x86"
|
DASM_ARCH="x86"
|
||||||
|
TLS_TARGET="x86"
|
||||||
],
|
],
|
||||||
[aarch64*], [
|
[aarch64*], [
|
||||||
IR_TARGET=IR_TARGET_AARCH64
|
IR_TARGET=IR_TARGET_AARCH64
|
||||||
DASM_ARCH="aarch64"
|
DASM_ARCH="aarch64"
|
||||||
|
TLS_TARGET="aarch64"
|
||||||
])
|
])
|
||||||
|
|
||||||
AS_VAR_IF([PHP_CAPSTONE], [yes],
|
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"
|
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([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])
|
AC_CHECK_FUNCS([mprotect shm_create_largepage])
|
||||||
|
|
|
@ -33,6 +33,7 @@ if (PHP_OPCACHE != "no") {
|
||||||
DEFINE("IR_TARGET", ir_target);
|
DEFINE("IR_TARGET", ir_target);
|
||||||
DEFINE("DASM_FLAGS", dasm_flags);
|
DEFINE("DASM_FLAGS", dasm_flags);
|
||||||
DEFINE("DASM_ARCH", "x86");
|
DEFINE("DASM_ARCH", "x86");
|
||||||
|
DEFINE("TLS_TARGET", "win");
|
||||||
|
|
||||||
AC_DEFINE('HAVE_JIT', 1, 'Define to 1 to enable JIT.');
|
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",
|
ADD_SOURCES(configure_module_dirname + "\\jit",
|
||||||
"zend_jit.c zend_jit_vm_helpers.c",
|
"zend_jit.c zend_jit_vm_helpers.c",
|
||||||
"opcache", "ext\\opcache\\jit");
|
"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",
|
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",
|
||||||
"ir.c", "opcache", "ext\\opcache\\jit\\ir");
|
"ir.c", "opcache", "ext\\opcache\\jit\\ir");
|
||||||
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",
|
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",
|
||||||
|
|
4
ext/opcache/jit/tls/testing/.gitignore
vendored
Normal file
4
ext/opcache/jit/tls/testing/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.so
|
||||||
|
*.o
|
||||||
|
main
|
||||||
|
main.dSYM
|
1030
ext/opcache/jit/tls/testing/def-vars.h
Normal file
1030
ext/opcache/jit/tls/testing/def-vars.h
Normal file
File diff suppressed because it is too large
Load diff
33
ext/opcache/jit/tls/testing/def.c
Normal file
33
ext/opcache/jit/tls/testing/def.c
Normal 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();
|
||||||
|
}
|
||||||
|
|
48
ext/opcache/jit/tls/testing/main.c
Normal file
48
ext/opcache/jit/tls/testing/main.c
Normal 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;
|
||||||
|
}
|
232
ext/opcache/jit/tls/testing/test.sh
Executable file
232
ext/opcache/jit/tls/testing/test.sh
Executable 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
|
28
ext/opcache/jit/tls/testing/user.c
Normal file
28
ext/opcache/jit/tls/testing/user.c
Normal 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;
|
||||||
|
}
|
40
ext/opcache/jit/tls/zend_jit_tls.h
Normal file
40
ext/opcache/jit/tls/zend_jit_tls.h
Normal 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 */
|
255
ext/opcache/jit/tls/zend_jit_tls_aarch64.c
Normal file
255
ext/opcache/jit/tls/zend_jit_tls_aarch64.c
Normal 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;
|
||||||
|
}
|
82
ext/opcache/jit/tls/zend_jit_tls_darwin.c
Normal file
82
ext/opcache/jit/tls/zend_jit_tls_darwin.c
Normal 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;
|
||||||
|
}
|
64
ext/opcache/jit/tls/zend_jit_tls_win.c
Normal file
64
ext/opcache/jit/tls/zend_jit_tls_win.c
Normal 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;
|
||||||
|
}
|
239
ext/opcache/jit/tls/zend_jit_tls_x86.c
Normal file
239
ext/opcache/jit/tls/zend_jit_tls_x86.c
Normal 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;
|
||||||
|
}
|
222
ext/opcache/jit/tls/zend_jit_tls_x86_64.c
Normal file
222
ext/opcache/jit/tls/zend_jit_tls_x86_64.c
Normal 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;
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
#include "jit/ir/ir.h"
|
#include "jit/ir/ir.h"
|
||||||
#include "jit/ir/ir_builder.h"
|
#include "jit/ir/ir_builder.h"
|
||||||
|
#include "jit/tls/zend_jit_tls.h"
|
||||||
|
|
||||||
#if defined(IR_TARGET_X86)
|
#if defined(IR_TARGET_X86)
|
||||||
# define IR_REG_SP 4 /* IR_REG_RSP */
|
# 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) ???
|
static bool delayed_call_chain = 0; // TODO: remove this var (use jit->delayed_call_level) ???
|
||||||
|
|
||||||
#ifdef ZTS
|
#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_ls_cache_tcb_offset = 0;
|
||||||
static size_t tsrm_tls_index = 0;
|
static size_t tsrm_tls_index = -1;
|
||||||
static size_t tsrm_tls_offset = 0;
|
static size_t tsrm_tls_offset = -1;
|
||||||
|
|
||||||
# define EG_TLS_OFFSET(field) \
|
# define EG_TLS_OFFSET(field) \
|
||||||
(executor_globals_offset + offsetof(zend_executor_globals, 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,
|
zend_jit_addr ref_addr,
|
||||||
bool check_exception);
|
bool check_exception);
|
||||||
|
|
||||||
|
static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags);
|
||||||
|
|
||||||
typedef struct _zend_jit_stub {
|
typedef struct _zend_jit_stub {
|
||||||
const char *name;
|
const char *name;
|
||||||
int (*stub)(zend_jit_ctx *jit);
|
int (*stub)(zend_jit_ctx *jit);
|
||||||
|
@ -463,6 +460,11 @@ static const char* zend_reg_name(int8_t reg)
|
||||||
/* IR helpers */
|
/* IR helpers */
|
||||||
|
|
||||||
#ifdef ZTS
|
#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)
|
static ir_ref jit_TLS(zend_jit_ctx *jit)
|
||||||
{
|
{
|
||||||
ZEND_ASSERT(jit->ctx.control);
|
ZEND_ASSERT(jit->ctx.control);
|
||||||
|
@ -482,9 +484,15 @@ static ir_ref jit_TLS(zend_jit_ctx *jit)
|
||||||
ref = insn->op1;
|
ref = insn->op1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
jit->tls = ir_TLS(
|
||||||
tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index,
|
tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index,
|
||||||
tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset);
|
tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset);
|
||||||
|
}
|
||||||
|
|
||||||
return jit->tls;
|
return jit->tls;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -3125,6 +3133,8 @@ static void zend_jit_setup_disasm(void)
|
||||||
REGISTER_DATA(EG(symbol_table));
|
REGISTER_DATA(EG(symbol_table));
|
||||||
|
|
||||||
REGISTER_DATA(CG(map_ptr_base));
|
REGISTER_DATA(CG(map_ptr_base));
|
||||||
|
#else /* ZTS */
|
||||||
|
REGISTER_HELPER(zend_jit_get_tsrm_ls_cache);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -3291,7 +3301,6 @@ static void zend_jit_setup_unwinder(void)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static void zend_jit_setup(bool reattached)
|
static void zend_jit_setup(bool reattached)
|
||||||
{
|
{
|
||||||
#if defined(IR_TARGET_X86)
|
#if defined(IR_TARGET_X86)
|
||||||
|
@ -3310,166 +3319,17 @@ static void zend_jit_setup(bool reattached)
|
||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ZTS
|
#ifdef ZTS
|
||||||
#if defined(IR_TARGET_AARCH64)
|
zend_result result = zend_jit_resolve_tsrm_ls_cache_offsets(
|
||||||
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
|
&tsrm_ls_cache_tcb_offset,
|
||||||
|
&tsrm_tls_index,
|
||||||
# ifdef __FreeBSD__
|
&tsrm_tls_offset
|
||||||
if (tsrm_ls_cache_tcb_offset == 0) {
|
);
|
||||||
TLSDescriptor **where;
|
if (result == FAILURE) {
|
||||||
|
zend_accel_error(ACCEL_LOG_INFO,
|
||||||
__asm__(
|
"Could not get _tsrm_ls_cache offsets, will fallback to runtime resolution");
|
||||||
"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;
|
|
||||||
}
|
}
|
||||||
# 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
|
#endif
|
||||||
|
|
||||||
#if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
|
#if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
|
||||||
|
|
|
@ -19,6 +19,7 @@ opcache
|
||||||
<?php
|
<?php
|
||||||
// Prints "Debug Restarting!" message on next request.
|
// Prints "Debug Restarting!" message on next request.
|
||||||
if (getenv('SKIP_REPEAT')) die("skip Not repeatable");
|
if (getenv('SKIP_REPEAT')) die("skip Not repeatable");
|
||||||
|
if (PHP_ZTS) die("skip ZTS prints extra messages");
|
||||||
?>
|
?>
|
||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
|
@ -62,11 +62,90 @@ function main(int $argc, array $argv): void
|
||||||
echo "DEBUG: " . ($environment->debug ? 'Yes' : 'No') . "\n";
|
echo "DEBUG: " . ($environment->debug ? 'Yes' : 'No') . "\n";
|
||||||
echo "=====================================================================\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";
|
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
|
function output_group_start(Environment $environment, string $name): void
|
||||||
{
|
{
|
||||||
if ($environment->githubAction) {
|
if ($environment->githubAction) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue