Skip to content
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

Improve support of non-executable ZTS binaries (support frankenphp) #426

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/Command/Inspector/MemoryCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Reli\Command\Inspector;

use Reli\Inspector\RetryingLoopProvider;
use Reli\Inspector\Settings\MemoryProfilerSettings\MemoryProfilerSettingsFromConsoleInput;
use Reli\Inspector\Settings\TargetPhpSettings\TargetPhpSettingsFromConsoleInput;
use Reli\Inspector\Settings\TargetProcessSettings\TargetProcessSettingsFromConsoleInput;
Expand Down Expand Up @@ -72,11 +73,6 @@ public function execute(InputInterface $input, OutputInterface $output): int
$target_php_settings
);

if ($memory_profiler_settings->stop_process) {
$this->process_stopper->stop($process_specifier->pid);
defer($scope_guard, fn () => $this->process_stopper->resume($process_specifier->pid));
}

$eg_address = $this->php_globals_finder->findExecutorGlobals(
$process_specifier,
$target_php_settings_version_decided
Expand All @@ -86,6 +82,11 @@ public function execute(InputInterface $input, OutputInterface $output): int
$target_php_settings_version_decided
);

if ($memory_profiler_settings->stop_process) {
$this->process_stopper->stop($process_specifier->pid);
defer($scope_guard, fn () => $this->process_stopper->resume($process_specifier->pid));
}

$collected_memories = $this->memory_locations_collector->collectAll(
$process_specifier,
$target_php_settings_version_decided,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ final class TargetPhpSettings
{
public const PHP_REGEX_DEFAULT = '.*/((php|php-fpm)(7\.?[01234]|8\.?[0123])?|libphp[78]?.*\.so)$';
public const LIBPTHREAD_REGEX_DEFAULT = '.*/libpthread.*\.so';
public const ZTS_GLOBALS_REGEX_DEFAULT = self::PHP_REGEX_DEFAULT;
public const TARGET_PHP_VERSION_DEFAULT = 'auto';

/** @param TVersion $php_version */
public function __construct(
public string $php_regex = self::PHP_REGEX_DEFAULT,
public string $libpthread_regex = self::LIBPTHREAD_REGEX_DEFAULT,
public string $zts_globals_regex = self::ZTS_GLOBALS_REGEX_DEFAULT,
public string $php_version = self::TARGET_PHP_VERSION_DEFAULT,
public ?string $php_path = null,
public ?string $libpthread_path = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ final class TargetPhpSettingsException extends InspectorSettingsException
{
public const PHP_REGEX_IS_NOT_STRING = 3;
public const LIBPTHREAD_REGEX_IS_NOT_STRING = 4;
public const PHP_PATH_IS_NOT_STRING = 5;
public const LIBPTHREAD_PATH_IS_NOT_STRING = 6;
public const TARGET_PHP_VERSION_INVALID = 7;
public const ZTS_GLOBALS_REGEX_IS_NOT_STRING = 5;
public const PHP_PATH_IS_NOT_STRING = 6;
public const LIBPTHREAD_PATH_IS_NOT_STRING = 7;
public const TARGET_PHP_VERSION_INVALID = 8;

protected const ERRORS = [
self::PHP_REGEX_IS_NOT_STRING => 'php-regex must be a string',
self::LIBPTHREAD_REGEX_IS_NOT_STRING => 'libpthread-regex must be a string',
self::ZTS_GLOBALS_REGEX_IS_NOT_STRING => 'zts-globals-regex must be a string',
self::PHP_PATH_IS_NOT_STRING => 'php-path must be a string',
self::LIBPTHREAD_PATH_IS_NOT_STRING => 'libpthread-path must be a string',
self::TARGET_PHP_VERSION_INVALID => 'php-version must be valid version string (eg: v80)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public function setOptions(Command $command): void
InputOption::VALUE_OPTIONAL,
'regex to find the libpthread.so loaded in the target process'
)
->addOption(
'zts-globals-regex',
null,
InputOption::VALUE_OPTIONAL,
'regex to find the binary containing globals symbols for ZTS loaded in the target process'
)
->addOption(
'php-version',
null,
Expand Down Expand Up @@ -77,6 +83,13 @@ public function createSettings(InputInterface $input): TargetPhpSettings
);
}

$zts_globals_regex = $input->getOption('zts-globals-regex') ?? $php_regex;
if (!is_string($zts_globals_regex)) {
throw TargetPhpSettingsException::create(
TargetPhpSettingsException::ZTS_GLOBALS_REGEX_IS_NOT_STRING
);
}

$php_version = $input->getOption('php-version') ?? TargetPhpSettings::TARGET_PHP_VERSION_DEFAULT;
if ($php_version !== 'auto' and !in_array($php_version, ZendTypeReader::ALL_SUPPORTED_VERSIONS, true)) {
throw TargetPhpSettingsException::create(
Expand All @@ -98,6 +111,13 @@ public function createSettings(InputInterface $input): TargetPhpSettings
);
}

return new TargetPhpSettings($php_regex, $libpthread_regex, $php_version, $php_path, $libpthread_path);
return new TargetPhpSettings(
$php_regex,
$libpthread_regex,
$zts_globals_regex,
$php_version,
$php_path,
$libpthread_path
);
}
}
19 changes: 13 additions & 6 deletions src/Lib/Elf/Parser/Elf64Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Reli\Lib\Elf\Structure\Elf64\Elf64StringTable;
use Reli\Lib\Elf\Structure\Elf64\Elf64SymbolTable;
use Reli\Lib\Elf\Structure\Elf64\Elf64SymbolTableEntry;
use Reli\Lib\Integer\UInt64;

final class Elf64Parser
{
Expand Down Expand Up @@ -111,23 +112,27 @@ public function parseProgramHeader(ByteReaderInterface $data, Elf64Header $elf_h

public function parseDynamicStructureArray(
ByteReaderInterface $data,
Elf64ProgramHeaderEntry $pt_dynamic
UInt64 $dynamic_offset,
UInt64 $dynamic_v_addr,
): Elf64DynamicStructureArray {
$dynamic_array = [];
$offset = $pt_dynamic->p_offset->lo;
$offset = $dynamic_offset->toInt();
$v_addr = $dynamic_v_addr->toInt();
do {
$d_tag = $this->integer_reader->read64($data, $offset);
$d_un = $this->integer_reader->read64($data, $offset + 8);
$dynamic_structure = new Elf64DynamicStructure($d_tag, $d_un);
$dynamic_structure = new Elf64DynamicStructure($offset, $v_addr, $d_tag, $d_un);
$dynamic_array[] = $dynamic_structure;
$offset += 16;
$v_addr += 16;
} while (!$dynamic_structure->isEnd());

return new Elf64DynamicStructureArray(...$dynamic_array);
}

public function parseStringTable(
ByteReaderInterface $data,
UInt64 $base_address,
Elf64DynamicStructureArray $dynamic_structure_array
): Elf64StringTable {
/**
Expand All @@ -138,7 +143,7 @@ public function parseStringTable(
Elf64DynamicStructure::DT_STRTAB => $dt_strtab,
Elf64DynamicStructure::DT_STRSZ => $dt_strsz
] = $dynamic_structure_array->findStringTableEntries();
$offset = $dt_strtab->d_un->toInt();
$offset = $dt_strtab->d_un->toInt() - $base_address->toInt();
$size = $dt_strsz->d_un->toInt();
$string_table_region = $data->createSliceAsString($offset, $size);

Expand All @@ -159,6 +164,7 @@ public function parseStringTableFromSectionHeader(

public function parseSymbolTableFromDynamic(
ByteReaderInterface $data,
UInt64 $base_address,
Elf64DynamicStructureArray $dynamic_structure_array,
int $number_of_symbols
): Elf64SymbolTable {
Expand All @@ -171,7 +177,7 @@ public function parseSymbolTableFromDynamic(
Elf64DynamicStructure::DT_SYMENT => $dt_syment
] = $dynamic_structure_array->findSymbolTablEntries();

$start_offset = $dt_symtab->d_un->toInt();
$start_offset = $dt_symtab->d_un->toInt() - $base_address->toInt();
$entry_size = $dt_syment->d_un->toInt();

return $this->parseSymbolTable($data, $start_offset, $number_of_symbols, $entry_size);
Expand Down Expand Up @@ -223,13 +229,14 @@ public function parseSymbolTable(
*/
public function parseGnuHashTable(
ByteReaderInterface $data,
Uint64 $base_address,
Elf64DynamicStructureArray $dynamic_structure_array
): ?Elf64GnuHashTable {
$dt_gnu_hash = $dynamic_structure_array->findGnuHashTableEntry();
if (is_null($dt_gnu_hash)) {
return null;
}
$offset = $dt_gnu_hash->d_un->toInt();
$offset = $dt_gnu_hash->d_un->toInt() - $base_address->toInt();
$nbuckets = $this->integer_reader->read32($data, $offset);
$symoffset = $this->integer_reader->read32($data, $offset + 4);
$bloom_size = $this->integer_reader->read32($data, $offset + 8);
Expand Down
17 changes: 17 additions & 0 deletions src/Lib/Elf/Process/Elf64LazyParseSymbolResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Reli\Lib\Elf\Structure\Elf64\Elf64SymbolTableEntry;
use Reli\Lib\Elf\SymbolResolver\Elf64SymbolResolver;
use Reli\Lib\Elf\SymbolResolver\SymbolResolverCreatorInterface;
use Reli\Lib\Integer\UInt64;
use Reli\Lib\Process\MemoryMap\ProcessModuleMemoryMap;
use Reli\Lib\Process\MemoryReader\MemoryReaderInterface;

Expand Down Expand Up @@ -57,4 +58,20 @@ public function resolve(string $symbol_name): Elf64SymbolTableEntry
}
return $this->resolver_cache->resolve($symbol_name);
}

public function getDtDebugAddress(): ?int
{
if (!isset($this->resolver_cache)) {
$this->resolver_cache = $this->loadResolver();
}
return $this->resolver_cache->getDtDebugAddress();
}

public function getBaseAddress(): UInt64
{
if (!isset($this->resolver_cache)) {
$this->resolver_cache = $this->loadResolver();
}
return $this->resolver_cache->getBaseAddress();
}
}
27 changes: 27 additions & 0 deletions src/Lib/Elf/Process/LinkMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/**
* This file is part of the reliforp/reli-prof package.
*
* (c) sji <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Reli\Lib\Elf\Process;

final class LinkMap
{
public function __construct(
public int $this_address,
public int $l_addr,
public string $l_name,
public int $l_ld,
public int $l_next_address,
public int $l_prev_address,
) {
}
}
86 changes: 86 additions & 0 deletions src/Lib/Elf/Process/LinkMapLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

/**
* This file is part of the reliforp/reli-prof package.
*
* (c) sji <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Reli\Lib\Elf\Process;

use Reli\Lib\ByteStream\CDataByteReader;
use Reli\Lib\ByteStream\IntegerByteSequence\IntegerByteSequenceReader;
use Reli\Lib\Process\MemoryReader\MemoryReaderInterface;

class LinkMapLoader
{
public function __construct(
private MemoryReaderInterface $memory_reader,
private IntegerByteSequenceReader $integer_reader,
) {
}

public function loadFromAddress(int $pid, int $address): LinkMap
{
$bytes = new CDataByteReader($this->memory_reader->read($pid, $address, $this->getSize()));
$l_addr = $this->integer_reader->read64($bytes, 0);
$l_name_address = $this->integer_reader->read64($bytes, 8);
$l_ld = $this->integer_reader->read64($bytes, 16);
$l_next_address = $this->integer_reader->read64($bytes, 24);
$l_prev_address = $this->integer_reader->read64($bytes, 32);

return new LinkMap(
$address,
$l_addr->toInt(),
$this->readCString($pid, $l_name_address->toInt()),
$l_ld->toInt(),
$l_next_address->toInt(),
$l_prev_address->toInt()
);
}

public function searchByName(string $name, int $pid, int $root_address): ?LinkMap
{
$address = $root_address;
do {
$link_map = $this->loadFromAddress($pid, $address);
if ($link_map->l_name === $name) {
return $link_map;
}
$address = $link_map->l_next_address;
} while ($address !== 0);
return null;
}

private function getSize(): int
{
return 8 // l_addr
+ 8 // l_name
+ 8 // l_ld
+ 8 // l_next
+ 8 // l_prev
;
}

private function readCString(int $pid, int $address): string
{
$bytes = $this->memory_reader->read($pid, $address, 1);
$str = '';
while (true) {
/** @var int $c */
$c = $bytes[0];
if ($c === 0) {
break;
}
$str .= chr($c);
$address += 1;
$bytes = $this->memory_reader->read($pid, $address, 1);
}
return $str;
}
}
40 changes: 39 additions & 1 deletion src/Lib/Elf/Process/ProcessModuleSymbolReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace Reli\Lib\Elf\Process;

use FFI\CData;
use Reli\Lib\ByteStream\CDataByteReader;
use Reli\Lib\ByteStream\IntegerByteSequence\IntegerByteSequenceReader;
use Reli\Lib\Elf\SymbolResolver\Elf64AllSymbolResolver;
use Reli\Lib\Elf\SymbolResolver\Elf64SymbolResolver;
use Reli\Lib\Process\MemoryMap\ProcessModuleMemoryMap;
Expand All @@ -31,9 +33,10 @@ public function __construct(
private Elf64SymbolResolver $symbol_resolver,
ProcessModuleMemoryMap $module_memory_map,
private MemoryReaderInterface $memory_reader,
private IntegerByteSequenceReader $integer_reader,
private ?int $tls_block_address
) {
$this->base_address = $module_memory_map->getBaseAddress();
$this->base_address = $module_memory_map->getBaseAddress() - $this->symbol_resolver->getBaseAddress()->toInt();
}

/**
Expand Down Expand Up @@ -85,9 +88,44 @@ private function resolveAddressAndSize(string $symbol_name): ?array
}
$base_address = $this->tls_block_address;
}

return [$base_address + $symbol->st_value->toInt(), $symbol->st_size->toInt()];
}

public function getLinkMapAddress(): ?int
{
$dt_debug_address = $this->symbol_resolver->getDtDebugAddress();
if (is_null($dt_debug_address)) {
return null;
}
$dt_debug_un_pointer = $this->base_address + $dt_debug_address + 8;
$r_debug_pointer = $this->integer_reader->read64(
new CDataByteReader(
$this->memory_reader->read(
$this->pid,
$dt_debug_un_pointer,
8
),
),
0,
)->toInt();
$root_link_map_address_pointer = $r_debug_pointer + 8;
$root_link_map_address = $this->integer_reader->read64(
new CDataByteReader(
$this->memory_reader->read(
$this->pid,
$root_link_map_address_pointer,
8
),
),
0,
)->toInt();
if ($root_link_map_address === 0) {
return null;
}
return $root_link_map_address;
}

public function isAllSymbolResolvable(): bool
{
return $this->symbol_resolver instanceof Elf64AllSymbolResolver;
Expand Down
Loading
Loading