Skip to content

Fix linker failure when building opcache statically #18939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Next Next commit
Fix linker failure when building opcache statically
This fixes the following linker error:

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

The error arises from how we obtain information about the _tsrm_ls_cache TLS
variable for use in JIT'ed code:

Normally, TLS variables are resolved via linker relocations [1], which of course
can not be used in JIT'ed code. Therefore we emit the relocation in AOT code and
use the result in JIT.

Specifically we use a fragment of the "General Dynamic" code sequence described
in [1]. Using the full code sequence would give us the address of the variable
in the current thread. Therefore we only use a fragment that gives us the
variable's TLS index and offset.

When Opcache is statically linked into the binary, linkers attempt to relax
(rewrite) this code sequence into a more efficient one. However, this fails
because they will not recognize the code sequence.

We now take a different approach:

 * Emit the exact full code sequence expected by linkers
 * Extract the TLS index/offset or TCB offset by inspecting the ASM code, rather
   than executing it (execution would give us the thread-local address).
 * 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.

[1] https://www.akkadia.org/drepper/tls.pdf
  • Loading branch information
arnaud-lb committed Jun 30, 2025
commit 1cd193886545ce363e45357acea2cdc024ba88dd
6 changes: 5 additions & 1 deletion .github/actions/freebsd/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ runs:
webp \
libavif \
`#sqlite3` \
curl
curl \
gcc

./buildconf -f
./configure \
Expand Down Expand Up @@ -108,3 +109,6 @@ runs:
--show-slow 1000 \
--set-timeout 120 \
-d zend_extension=opcache.so

export TLSC=$(pwd)/ext/opcache/jit/tls/zend_jit_tls_x86_64.c
./ext/opcache/jit/tls/testing/test.sh
10 changes: 10 additions & 0 deletions .github/actions/test-tls-alpine/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Test TLS
runs:
using: composite
steps:
- shell: bash
run: |
set -x
apk add clang gcc binutils-gold lld
export TLSC=$(pwd)/ext/opcache/jit/tls/zend_jit_tls_x86_64.c
./ext/opcache/jit/tls/testing/test.sh
9 changes: 9 additions & 0 deletions .github/actions/test-tls-macos/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Test TLS
runs:
using: composite
steps:
- shell: bash
run: |
set -x
export TLSC=$(pwd)/ext/opcache/jit/tls/zend_jit_tls_darwin.c
./ext/opcache/jit/tls/testing/test.sh
12 changes: 12 additions & 0 deletions .github/actions/test-tls-x32/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Test TLS
runs:
using: composite
steps:
- shell: bash
run: |
set -x
export DEBIAN_FRONTEND=noninteractive
apt-get install -y gcc clang lld
export TLSC=$(pwd)/ext/opcache/jit/tls/zend_jit_tls_x86.c
export MACHINE=-m32
./ext/opcache/jit/tls/testing/test.sh
11 changes: 11 additions & 0 deletions .github/actions/test-tls-x64/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Test TLS
runs:
using: composite
steps:
- shell: bash
run: |
set -x
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y gcc clang lld
export TLSC=$(pwd)/ext/opcache/jit/tls/zend_jit_tls_x86_64.c
./ext/opcache/jit/tls/testing/test.sh
12 changes: 12 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ jobs:
--asan -x
-d zend_extension=opcache.so
-d opcache.enable_cli=1
- name: Test TLS resolution
uses: ./.github/actions/test-tls-alpine
- name: Notify Slack
if: failure()
uses: ./.github/actions/notify-slack
Expand Down Expand Up @@ -263,6 +265,9 @@ jobs:
${{ matrix.run_tests_parameters }}
-d zend_extension=opcache.so
-d opcache.enable_cli=1
- name: Test TLS resolution
if: matrix.debug && matrix.zts && !matrix.asan
uses: ./.github/actions/test-tls-x64
- name: Verify generated files are up to date
uses: ./.github/actions/verify-generated-files
- name: Notify Slack
Expand Down Expand Up @@ -352,6 +357,9 @@ jobs:
${{ matrix.run_tests_parameters }}
-d zend_extension=opcache.so
-d opcache.enable_cli=1
- name: Test TLS resolution
if: matrix.debug && matrix.zts
uses: ./.github/actions/test-tls-x32
- name: Notify Slack
if: failure()
uses: ./.github/actions/notify-slack
Expand Down Expand Up @@ -411,6 +419,10 @@ jobs:
runTestsParameters: >-
-d zend_extension=opcache.so
-d opcache.enable_cli=1
- name: Test TLS resolution
# JIT+ZTS not supported yet on ARM64
if: matrix.os != '14' && matrix.debug && matrix.zts
uses: ./.github/actions/test-tls-macos
- name: Verify generated files are up to date
uses: ./.github/actions/verify-generated-files
- name: Notify Slack
Expand Down
8 changes: 8 additions & 0 deletions ext/opcache/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,23 @@ if test "$PHP_OPCACHE" != "no"; then
IR_TARGET=IR_TARGET_X64
DASM_FLAGS="-D X64APPLE=1 -D X64=1"
DASM_ARCH="x86"
TLS_TARGET="darwin"
],
[*x86_64*|amd64-*-freebsd*], [
IR_TARGET=IR_TARGET_X64
DASM_FLAGS="-D X64=1"
DASM_ARCH="x86"
TLS_TARGET="x86_64"
],
[[i[34567]86*|x86*]], [
IR_TARGET=IR_TARGET_X86
DASM_ARCH="x86"
TLS_TARGET="x86"
],
[aarch64*], [
IR_TARGET=IR_TARGET_AARCH64
DASM_ARCH="aarch64"
TLS_TARGET="aarch64"
])

AS_VAR_IF([PHP_CAPSTONE], [yes],
Expand All @@ -102,6 +106,10 @@ if test "$PHP_OPCACHE" != "no"; then

JIT_CFLAGS="-I@ext_builddir@/jit/ir -D$IR_TARGET -DIR_PHP"
AS_VAR_IF([ZEND_DEBUG], [yes], [JIT_CFLAGS="$JIT_CFLAGS -DIR_DEBUG"])

AS_VAR_IF([PHP_THREAD_SAFETY], [yes], [
ZEND_JIT_SRC="$ZEND_JIT_SRC jit/tls/zend_jit_tls_$TLS_TARGET.c"
])
])

AC_CHECK_FUNCS([mprotect shm_create_largepage])
Expand Down
6 changes: 6 additions & 0 deletions ext/opcache/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ if (PHP_OPCACHE != "no") {
DEFINE("IR_TARGET", ir_target);
DEFINE("DASM_FLAGS", dasm_flags);
DEFINE("DASM_ARCH", "x86");
DEFINE("TLS_TARGET", "win");

AC_DEFINE('HAVE_JIT', 1, 'Define to 1 to enable JIT.');

Expand All @@ -52,6 +53,11 @@ if (PHP_OPCACHE != "no") {
ADD_SOURCES(configure_module_dirname + "\\jit",
"zend_jit.c zend_jit_vm_helpers.c",
"opcache", "ext\\opcache\\jit");
if (PHP_ZTS == "yes") {
ADD_SOURCES(configure_module_dirname + "\\jit\\tls",
"zend_jit_tls_win.c",
"opcache", "ext\\opcache\\jit\\tls");
}
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",
"ir.c", "opcache", "ext\\opcache\\jit\\ir");
ADD_SOURCES(configure_module_dirname + "\\jit\\ir",
Expand Down
4 changes: 4 additions & 0 deletions ext/opcache/jit/tls/testing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.so
*.o
main
main.dSYM
Loading