Skip to content

Commit

Permalink
Merge pull request #426 from reliforp/support-franken
Browse files Browse the repository at this point in the history
Improve support of non-executable ZTS binaries (support frankenphp)
  • Loading branch information
sj-i committed Feb 14, 2024
2 parents b7151b1 + a0de675 commit f982f1a
Show file tree
Hide file tree
Showing 37 changed files with 608 additions and 111 deletions.
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

0 comments on commit f982f1a

Please sign in to comment.