Skip to content

Commit 5a6a333

Browse files
committed
Export php binary debug symbols for mac, linux and windows
1 parent b05bdcd commit 5a6a333

5 files changed

Lines changed: 151 additions & 138 deletions

File tree

src/SPC/builder/linux/LinuxBuilder.php

Lines changed: 19 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace SPC\builder\linux;
66

77
use SPC\builder\unix\UnixBuilderBase;
8-
use SPC\exception\PatchException;
98
use SPC\exception\WrongUsageException;
109
use SPC\store\Config;
1110
use SPC\store\FileSystem;
@@ -194,14 +193,6 @@ protected function buildCli(): void
194193
SourcePatcher::patchFile('musl_static_readline.patch', SOURCE_PATH . '/php-src', true);
195194
}
196195

197-
if (!$this->getOption('no-strip', false)) {
198-
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-unneeded php');
199-
}
200-
if ($this->getOption('with-upx-pack')) {
201-
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')
202-
->exec(getenv('UPX_EXEC') . ' --best php');
203-
}
204-
205196
$this->deployBinary(BUILD_TARGET_CLI);
206197
}
207198

@@ -213,14 +204,6 @@ protected function buildCgi(): void
213204
->exec('sed -i "s|//lib|/lib|g" Makefile')
214205
->exec("make {$concurrency} {$vars} cgi");
215206

216-
if (!$this->getOption('no-strip', false)) {
217-
shell()->cd(SOURCE_PATH . '/php-src/sapi/cgi')->exec('strip --strip-unneeded php-cgi');
218-
}
219-
if ($this->getOption('with-upx-pack')) {
220-
shell()->cd(SOURCE_PATH . '/php-src/sapi/cgi')
221-
->exec(getenv('UPX_EXEC') . ' --best php-cgi');
222-
}
223-
224207
$this->deployBinary(BUILD_TARGET_CGI);
225208
}
226209

@@ -232,29 +215,29 @@ protected function buildMicro(): void
232215
if ($this->getPHPVersionID() < 80000) {
233216
throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
234217
}
235-
if ($this->getExt('phar')) {
236-
$this->phar_patched = true;
237-
SourcePatcher::patchMicroPhar($this->getPHPVersionID());
238-
}
239-
240-
$enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
241-
$vars = $this->getMakeExtraVars();
242-
243-
// patch fake cli for micro
244-
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
245-
$vars = SystemUtil::makeEnvVarString($vars);
246-
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
218+
try {
219+
if ($this->getExt('phar')) {
220+
$this->phar_patched = true;
221+
SourcePatcher::patchMicroPhar($this->getPHPVersionID());
222+
}
247223

248-
shell()->cd(SOURCE_PATH . '/php-src')
249-
->exec('sed -i "s|//lib|/lib|g" Makefile')
250-
->exec("make {$concurrency} {$vars} micro");
224+
$enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
225+
$vars = $this->getMakeExtraVars();
251226

252-
$this->processMicroUPX();
227+
// patch fake cli for micro
228+
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
229+
$vars = SystemUtil::makeEnvVarString($vars);
230+
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
253231

254-
$this->deployBinary(BUILD_TARGET_MICRO);
232+
shell()->cd(SOURCE_PATH . '/php-src')
233+
->exec('sed -i "s|//lib|/lib|g" Makefile')
234+
->exec("make {$concurrency} {$vars} micro");
255235

256-
if ($this->phar_patched) {
257-
SourcePatcher::unpatchMicroPhar();
236+
$this->deployBinary(BUILD_TARGET_MICRO);
237+
} finally {
238+
if ($this->phar_patched) {
239+
SourcePatcher::unpatchMicroPhar();
240+
}
258241
}
259242
}
260243

@@ -269,13 +252,6 @@ protected function buildFpm(): void
269252
->exec('sed -i "s|//lib|/lib|g" Makefile')
270253
->exec("make {$concurrency} {$vars} fpm");
271254

272-
if (!$this->getOption('no-strip', false)) {
273-
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-unneeded php-fpm');
274-
}
275-
if ($this->getOption('with-upx-pack')) {
276-
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')
277-
->exec(getenv('UPX_EXEC') . ' --best php-fpm');
278-
}
279255
$this->deployBinary(BUILD_TARGET_FPM);
280256
}
281257

@@ -390,31 +366,4 @@ private function getMakeExtraVars(): array
390366
'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie",
391367
]);
392368
}
393-
394-
/**
395-
* Strip micro.sfx for Linux.
396-
* The micro.sfx does not support UPX directly, but we can remove UPX-info segment to adapt.
397-
* This will also make micro.sfx with upx-packed more like a malware fore antivirus :(
398-
*/
399-
private function processMicroUPX(): void
400-
{
401-
if (version_compare($this->getMicroVersion(), '0.2.0') >= 0 && !$this->getOption('no-strip', false)) {
402-
shell()->exec('strip --strip-unneeded ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx');
403-
404-
if ($this->getOption('with-upx-pack')) {
405-
// strip first
406-
shell()->exec(getenv('UPX_EXEC') . ' --best ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx');
407-
// cut binary with readelf
408-
[$ret, $out] = shell()->execWithResult('readelf -l ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx | awk \'/LOAD|GNU_STACK/ {getline; print $1, $2, $3, $4, $6, $7}\'');
409-
$out[1] = explode(' ', $out[1]);
410-
$offset = $out[1][0];
411-
if ($ret !== 0 || !str_starts_with($offset, '0x')) {
412-
throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output');
413-
}
414-
$offset = hexdec($offset);
415-
// remove upx extra wastes
416-
file_put_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx', substr(file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'), 0, $offset));
417-
}
418-
}
419-
}
420369
}

src/SPC/builder/macos/MacOSBuilder.php

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,6 @@ protected function buildCli(): void
189189
$shell = shell()->cd(SOURCE_PATH . '/php-src');
190190
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
191191
$shell->exec("make {$concurrency} {$vars} cli");
192-
if (!$this->getOption('no-strip', false)) {
193-
$shell->exec('dsymutil -f sapi/cli/php')->exec('strip -S sapi/cli/php');
194-
}
195192
$this->deployBinary(BUILD_TARGET_CLI);
196193
}
197194

@@ -202,9 +199,6 @@ protected function buildCgi(): void
202199
$shell = shell()->cd(SOURCE_PATH . '/php-src');
203200
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
204201
$shell->exec("make {$concurrency} {$vars} cgi");
205-
if (!$this->getOption('no-strip', false)) {
206-
$shell->exec('dsymutil -f sapi/cgi/php-cgi')->exec('strip -S sapi/cgi/php-cgi');
207-
}
208202
$this->deployBinary(BUILD_TARGET_CGI);
209203
}
210204

@@ -216,31 +210,30 @@ protected function buildMicro(): void
216210
if ($this->getPHPVersionID() < 80000) {
217211
throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
218212
}
219-
if ($this->getExt('phar')) {
220-
$this->phar_patched = true;
221-
SourcePatcher::patchMicroPhar($this->getPHPVersionID());
222-
}
223213

224-
$enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
225-
$vars = $this->getMakeExtraVars();
214+
try {
215+
if ($this->getExt('phar')) {
216+
$this->phar_patched = true;
217+
SourcePatcher::patchMicroPhar($this->getPHPVersionID());
218+
}
226219

227-
// patch fake cli for micro
228-
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
229-
$vars = SystemUtil::makeEnvVarString($vars);
220+
$enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
221+
$vars = $this->getMakeExtraVars();
230222

231-
$shell = shell()->cd(SOURCE_PATH . '/php-src');
232-
// build
233-
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
234-
$shell->exec("make {$concurrency} {$vars} micro");
235-
// strip
236-
if (!$this->getOption('no-strip', false)) {
237-
$shell->exec('dsymutil -f sapi/micro/micro.sfx')->exec('strip -S sapi/micro/micro.sfx');
238-
}
223+
// patch fake cli for micro
224+
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
225+
$vars = SystemUtil::makeEnvVarString($vars);
239226

240-
$this->deployBinary(BUILD_TARGET_MICRO);
227+
$shell = shell()->cd(SOURCE_PATH . '/php-src');
228+
// build
229+
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
230+
$shell->exec("make {$concurrency} {$vars} micro");
241231

242-
if ($this->phar_patched) {
243-
SourcePatcher::unpatchMicroPhar();
232+
$this->deployBinary(BUILD_TARGET_MICRO);
233+
} finally {
234+
if ($this->phar_patched) {
235+
SourcePatcher::unpatchMicroPhar();
236+
}
244237
}
245238
}
246239

@@ -254,9 +247,6 @@ protected function buildFpm(): void
254247
$shell = shell()->cd(SOURCE_PATH . '/php-src');
255248
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
256249
$shell->exec("make {$concurrency} {$vars} fpm");
257-
if (!$this->getOption('no-strip', false)) {
258-
$shell->exec('dsymutil -f sapi/fpm/php-fpm')->exec('strip -S sapi/fpm/php-fpm');
259-
}
260250
$this->deployBinary(BUILD_TARGET_FPM);
261251
}
262252

src/SPC/builder/unix/UnixBuilderBase.php

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use SPC\builder\BuilderBase;
88
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
9+
use SPC\exception\PatchException;
910
use SPC\exception\SPCException;
1011
use SPC\exception\SPCInternalException;
1112
use SPC\exception\ValidationException;
@@ -210,21 +211,71 @@ protected function sanityCheck(int $build_target): void
210211
/**
211212
* Deploy the binary file to the build bin path.
212213
*
213-
* @param int $type Type integer, one of BUILD_TARGET_CLI, BUILD_TARGET_MICRO, BUILD_TARGET_FPM
214+
* @param int $type Type integer, one of BUILD_TARGET_CLI, BUILD_TARGET_MICRO, BUILD_TARGET_FPM, BUILD_TARGET_CGI, BUILD_TARGET_FRANKENPHP
214215
*/
215-
protected function deployBinary(int $type): bool
216+
protected function deployBinary(int $type): void
216217
{
218+
FileSystem::createDir(BUILD_BIN_PATH);
219+
$copy_files = [];
217220
$src = match ($type) {
218221
BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php',
219222
BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx',
220223
BUILD_TARGET_FPM => SOURCE_PATH . '/php-src/sapi/fpm/php-fpm',
221224
BUILD_TARGET_CGI => SOURCE_PATH . '/php-src/sapi/cgi/php-cgi',
225+
BUILD_TARGET_FRANKENPHP => BUILD_BIN_PATH . '/frankenphp',
222226
default => throw new SPCInternalException("Deployment does not accept type {$type}"),
223227
};
224-
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
225-
FileSystem::createDir(BUILD_BIN_PATH);
226-
shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_BIN_PATH));
227-
return true;
228+
$no_strip_option = (bool) $this->getOption('no-strip', false);
229+
$upx_option = (bool) $this->getOption('with-upx-pack', false);
230+
231+
// Generate debug symbols if needed
232+
$copy_files[] = $src;
233+
if (!$no_strip_option && PHP_OS_FAMILY === 'Darwin') {
234+
shell()
235+
->exec("dsymutil -f {$src}") // generate .dwarf file
236+
->exec("strip -S {$src}"); // strip unneeded symbols
237+
$copy_files[] = "{$src}.dwarf";
238+
} elseif (!$no_strip_option && PHP_OS_FAMILY === 'Linux') {
239+
shell()
240+
->exec("objcopy --only-keep-debug {$src} {$src}.debug") // extract debug symbols
241+
->exec("objcopy --strip-debug --add-gnu-debuglink={$src}.debug {$src}") // link debug symbols
242+
->exec("strip --strip-unneeded {$src}"); // strip unneeded symbols
243+
$copy_files[] = "{$src}.debug";
244+
}
245+
246+
// Compress binary with UPX if needed (only for Linux)
247+
if ($upx_option && PHP_OS_FAMILY === 'Linux') {
248+
if ($no_strip_option) {
249+
logger()->warning('UPX compression is not recommended when --no-strip is enabled.');
250+
}
251+
logger()->info("Compressing {$src} with UPX");
252+
shell()->exec(getenv('UPX_EXEC') . " --best {$src}");
253+
254+
// micro needs special section handling in LinuxBuilder.
255+
// The micro.sfx does not support UPX directly, but we can remove UPX-info segment to adapt.
256+
// This will also make micro.sfx with upx-packed more like a malware fore antivirus :(
257+
if ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0) {
258+
// strip first
259+
// cut binary with readelf
260+
[$ret, $out] = shell()->execWithResult("readelf -l {$src} | awk '/LOAD|GNU_STACK/ {getline; print \$1, \$2, \$3, \$4, \$6, \$7}'");
261+
$out[1] = explode(' ', $out[1]);
262+
$offset = $out[1][0];
263+
if ($ret !== 0 || !str_starts_with($offset, '0x')) {
264+
throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output');
265+
}
266+
$offset = hexdec($offset);
267+
// remove upx extra wastes
268+
file_put_contents($src, substr(file_get_contents($src), 0, $offset));
269+
}
270+
}
271+
272+
// Copy files
273+
foreach ($copy_files as $file) {
274+
if (!file_exists($file)) {
275+
throw new SPCInternalException("Deploy failed. Cannot find file: {$file}");
276+
}
277+
FileSystem::copy($file, BUILD_BIN_PATH . '/' . basename($file));
278+
}
228279
}
229280

230281
/**
@@ -325,13 +376,7 @@ protected function buildFrankenphp(): void
325376
->setEnv($env)
326377
->exec("xcaddy build --output frankenphp {$xcaddyModules}");
327378

328-
if (!$this->getOption('no-strip', false) && file_exists(BUILD_BIN_PATH . '/frankenphp')) {
329-
if (PHP_OS_FAMILY === 'Linux') {
330-
shell()->cd(BUILD_BIN_PATH)->exec('strip --strip-unneeded frankenphp');
331-
} else { // macOS doesn't understand strip-unneeded
332-
shell()->cd(BUILD_BIN_PATH)->exec('strip -S frankenphp');
333-
}
334-
}
379+
$this->deployBinary(BUILD_TARGET_FRANKENPHP);
335380
}
336381

337382
/**

0 commit comments

Comments
 (0)