diff --git a/src/Build/NativeBuildConfigurationTrait.php b/src/Build/NativeBuildConfigurationTrait.php index aa3d1ae0..76808b01 100644 --- a/src/Build/NativeBuildConfigurationTrait.php +++ b/src/Build/NativeBuildConfigurationTrait.php @@ -32,6 +32,68 @@ trait NativeBuildConfigurationTrait return $sdkDir; } + /** + * The target triple used for fully-static links. + * + * libphp.a embeds musl libc, so the executable must be linked as a musl + * target; the triple also drives the linker's search for musl startup files. + */ + protected function getFullStaticTargetTriple(): string + { + $arch = match (php_uname('m')) { + 'aarch64', 'arm64' => 'aarch64', + 'riscv64' => 'riscv64', + default => 'x86_64', + }; + return $arch . '-unknown-linux-musl'; + } + + /** + * Resolve the directory holding the musl startup files (crt1.o/crti.o/crtn.o). + * + * A fully-static link must not pull crt1.o from glibc: glibc's _start lets + * musl's __libc_start_main (bundled in libphp.a) install a thread pointer + * whose layout does not match the TLS offsets the linker computed, so every + * thread-local read (ZTS globals, and PHP's ZEND_TLS data) lands on the + * wrong address and the process crashes during php_module_startup. + * + * Resolution order: PHPX_MUSL_LIB_DIR, the bundled SDK, then the usual + * system locations. + */ + protected function getFullStaticMuslDir(): string + { + $sdkDir = $this->getFullStaticSdkDir(); + $arch = explode('-', $this->getFullStaticTargetTriple())[0]; + + $candidates = []; + $env = getenv('PHPX_MUSL_LIB_DIR'); + if (is_string($env) && $env !== '') { + $candidates[] = $env; + } + if ($sdkDir !== null) { + $candidates[] = $sdkDir . '/lib/musl'; + } + // Alpine and other musl-native systems keep the startup files in /usr/lib + $candidates[] = '/usr/lib/' . $arch . '-linux-musl'; + $candidates[] = '/usr/lib/' . $arch . '-alpine-linux-musl'; + $candidates[] = '/usr/lib/musl'; + $candidates[] = '/usr/local/musl/lib'; + $candidates[] = '/usr/lib'; + + foreach ($candidates as $dir) { + if (is_file($dir . '/crt1.o')) { + return $dir; + } + } + + $this->error( + "--full-static requires musl startup files (crt1.o).\n" + . " Looked in: " . implode(', ', $candidates) . "\n" + . " Install musl (e.g. 'apt install musl-dev'), or copy crt1.o/crti.o/crtn.o from the\n" + . ' swoole-cli build container into ' . ($sdkDir ?? '') . "/lib/musl, or set PHPX_MUSL_LIB_DIR." + ); + } + protected function getIncludePaths(): array { $sdkDir = $this->getFullStaticSdkDir(); diff --git a/src/Build/NativeCommandOptionsTrait.php b/src/Build/NativeCommandOptionsTrait.php index 18f73071..39499129 100644 --- a/src/Build/NativeCommandOptionsTrait.php +++ b/src/Build/NativeCommandOptionsTrait.php @@ -127,16 +127,29 @@ trait NativeCommandOptionsTrait } $libraries = array_merge($libraries, $this->linkLibs); + $ldflags = $this->ldflags; + $targetPlatform = $this->targetPlatform; + if ($this->fullStatic) { + // The bundled libphp.a carries musl libc, so the link must use musl's + // C runtime instead of glibc's. -B points the driver at the musl + // startup files; -static keeps the result free of NEEDED entries. + // Only the link step switches target: the translation units are still + // compiled against the host libstdc++ headers, which is fine because + // TLS relocations are resolved here rather than at compile time. + $targetPlatform = $this->getFullStaticTargetTriple(); + $ldflags = trim('-static -B ' . escapeshellarg($this->getFullStaticMuslDir()) . ' ' . $ldflags); + } + $options = [ 'library_paths' => $libraryPaths, 'libraries' => $libraries, - 'ldflags' => $this->ldflags, + 'ldflags' => $ldflags, 'debug' => $this->debug, 'no_console' => $this->noConsole, 'build_mode' => $this->buildMode, 'sanitize' => $this->sanitize, 'lto' => $this->enableLto, - 'target_platform' => $this->targetPlatform, + 'target_platform' => $targetPlatform, ]; $rpaths = $this->getPlatform()->getDefaultRpaths($this->getPhpxDir(), $this->getPhpDir()); diff --git a/src/Translator.php b/src/Translator.php index fc9a05ea..e4b0f774 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -218,11 +218,13 @@ class Translator extends Preprocessor ? $this->platform->getDefaultCompiler() : CompilerFactory::detectCompilerName($this->platform); - // --full-static: the bundled libphp.a/libphpx.a are built by clang. - // gcc and clang compute TLS offsets differently (gcc's layout differs - // by 64 bytes here), which corrupts thread-local variables at runtime, - // so fully-static builds must use a real Linux clang (PHPX_CC/CXX take - // precedence; otherwise fall back to clang) to keep the toolchain consistent. + // --full-static: libphp.a embeds musl libc, so the link must produce a + // musl binary. That is driven by clang's --target=-unknown-linux-musl + // plus -static -B , which gcc does not accept; a glibc link + // would let musl's __libc_start_main install a thread pointer whose + // layout does not match the linker's TLS offsets, corrupting every + // thread-local read in PHP's ZTS globals. PHPX_CC/CXX take precedence, + // otherwise fall back to clang. if ($this->climate->arguments->defined('full-static')) { $this->fullStatic = true; $this->cppCompiler = getenv('PHPX_CC') ?: (getenv('CXX') ?: 'clang');