Skip to content

Conversation

@henderkes
Copy link
Collaborator

@henderkes henderkes commented Aug 29, 2025

make sure the linker doesn't drop libphp.a symbols that extensions need

What does this PR do?

previously, if we built libphp.a, the linker cleans up dead symbols when creating the executable. This is undesirable, because when you try to load shared extensions with frankenphp, it will fail to load them with undefined references to zend_ini_display_cb... and other functions.

Now, instead of marking all symbols as dynamic, which massively bloats the executable size, we loop through all shared extensions, figure out what symbols they need, match that against what libphp.a provides and then mark those as export-dynamic.

Keep in mind that this does still increase the executable size a little! But it only takes effect when people build shared extensions.

…ker doesn't drop libphp.a symbols that extensions need
@henderkes henderkes force-pushed the fix/frankenphp-dynamic-exports branch from 2833314 to 460eb02 Compare August 29, 2025 02:02
@henderkes henderkes marked this pull request as ready for review August 29, 2025 03:55
@henderkes henderkes requested a review from crazywhalecc August 29, 2025 03:55
@henderkes
Copy link
Collaborator Author

I don't know how to get macos to work :(

@crazywhalecc
Copy link
Owner

Do we have any other scenario of exporting dynamic symbols?

@henderkes
Copy link
Collaborator Author

I don't think so. Only for things that use libphp.a. The php code itself (libphp.so, cli, fpm, likely micro) uses all necessary symbols so they aren't stripped.

@crazywhalecc
Copy link
Owner

I can't think of any other uses for this, but I think it might be clearer to separate it into "exporting the archive's symbol list to a file" and "getting dynamic symbol parameters from the symbol file." Also, I don't see the point of caching this arguments, as we only need to read the file at most twice.

@henderkes
Copy link
Collaborator Author

henderkes commented Aug 31, 2025

I had considered splitting it up before, but found that I can't think of a situation where it makes sense to use for anything but libphp.a. We don't want to export symbols from anything but core php code - extensions will pull in the library symbols they need themselves.

@crazywhalecc
Copy link
Owner

crazywhalecc commented Aug 31, 2025

How about:

// after building embed
SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a');

// use symbols in sanity check, frankenphp builds, etc.
$dyn_symbols = SystemUtil::getDynamicExportedSymbols(BUILD_LIB_PATH . '/libphp.a');

$exportDynamicSymbols = function (string $lib_file) {
    // check
    if (!is_file($lib_file)) {
        throw new WrongUsageException("The lib archive file {$lib_file} does not exist, please build it first.");
    }
    // shell out
    $cmd = 'nm -g --defined-only -P ' . escapeshellarg($lib_file) . ' | grep -vE ".o]?:$" | grep -vE "^@?$" | awk -F "[\t| ]" "{print $1}"';
    $result = shell()->execWithResult($cmd);
    if ($result[0] !== 0) {
        throw new ExecutionException($cmd, 'Failed to get defined symbols from ' . $lib_file);
    }
    $defined = array_unique($result[1]);
    sort($defined);
    // export
    if (SPCTarget::getTargetOS() === 'Linux') {
        file_put_contents("{$lib_file}.dynsym", "{\n" . implode(";\n", array_map(fn ($x) => "  {$x}", $defined)) . ";\n};\n");
    } else {
        file_put_contents("{$lib_file}.dynsym", implode("\n", $defined) . "\n");
    }
};

$getDynamicExportedSymbols = function (string $lib_file): ?string {
    $symbol_file = "{$lib_file}.dynsym";
    if (!is_file($symbol_file)) {
        return null;
    }
    $argument = "-Wl,--dynamic-list={$symbol_file}";
    if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
        $argument = '-Wl,--export-dynamic'; // https://github.com/ziglang/zig/issues/24662
    }
    if (SPCTarget::getTargetOS() !== 'Linux') {
        $argument = "-Wl,-exported_symbols_list,{$symbol_file}";
    }
    return $argument;
};

@henderkes
Copy link
Collaborator Author

I think the idea is good, but

    $cmd = 'nm -g --defined-only -P ' . escapeshellarg($lib_file) . ' | grep -vE ".o]?:$" | grep -vE "^@?$" | awk -F "[\t| ]" "{print $1}"';

is overkill. Keep some good old php code in there to keep it debuggable.

@henderkes henderkes merged commit 38ec03f into main Aug 31, 2025
10 checks passed
@henderkes henderkes deleted the fix/frankenphp-dynamic-exports branch August 31, 2025 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants