Browse Source
Fix linker failure when building Opcache statically
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
pull/19252/head
Failed to extract signature
17 changed files with 2402 additions and 171 deletions
-
8ext/opcache/config.m4
-
6ext/opcache/config.w32
-
4ext/opcache/jit/tls/testing/.gitignore
-
1030ext/opcache/jit/tls/testing/def-vars.h
-
33ext/opcache/jit/tls/testing/def.c
-
48ext/opcache/jit/tls/testing/main.c
-
232ext/opcache/jit/tls/testing/test.sh
-
28ext/opcache/jit/tls/testing/user.c
-
40ext/opcache/jit/tls/zend_jit_tls.h
-
255ext/opcache/jit/tls/zend_jit_tls_aarch64.c
-
82ext/opcache/jit/tls/zend_jit_tls_darwin.c
-
64ext/opcache/jit/tls/zend_jit_tls_win.c
-
239ext/opcache/jit/tls/zend_jit_tls_x86.c
-
222ext/opcache/jit/tls/zend_jit_tls_x86_64.c
-
200ext/opcache/jit/zend_jit_ir.c
-
1ext/opcache/tests/zzz_basic_logging.phpt
-
81run-extra-tests.php
@ -0,0 +1,4 @@ |
|||
*.so |
|||
*.o |
|||
main |
|||
main.dSYM |
|||
1030
ext/opcache/jit/tls/testing/def-vars.h
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
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(); |
|||
} |
|||
|
|||
@ -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; |
|||
} |
|||
@ -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 |
|||
@ -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; |
|||
} |
|||
@ -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 */ |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue