feat(build): add full static linking support with musl libc

- Implement getFullStaticTargetTriple method to determine musl target triple based on architecture
- Add getFullStaticMuslDir method to resolve musl startup files directory with fallback candidates
- Configure linker flags with -static and -B options when fullStatic flag is enabled
- Update target platform to musl triple during static linking
- Add detailed documentation explaining musl libc integration and TLS offset issues
- Ensure consistent toolchain usage with clang for full static builds
- Provide
master
韩天峰 1 day ago
parent 1c0c02e57e
commit cec03af291
  1. 62
      src/Build/NativeBuildConfigurationTrait.php
  2. 17
      src/Build/NativeCommandOptionsTrait.php
  3. 12
      src/Translator.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 ?? '<sdk>') . "/lib/musl, or set PHPX_MUSL_LIB_DIR."
);
}
protected function getIncludePaths(): array
{
$sdkDir = $this->getFullStaticSdkDir();

@ -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());

@ -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=<arch>-unknown-linux-musl
// plus -static -B <musl dir>, 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');

Loading…
Cancel
Save