diff --git a/src/Command/Inspector/GetTraceCommand.php b/src/Command/Inspector/GetTraceCommand.php index 3aea6350..4e75a8d5 100644 --- a/src/Command/Inspector/GetTraceCommand.php +++ b/src/Command/Inspector/GetTraceCommand.php @@ -27,6 +27,7 @@ use PhpProfiler\Lib\Elf\Process\ProcessSymbolReaderException; use PhpProfiler\Lib\Elf\Tls\TlsFinderException; use PhpProfiler\Lib\PhpProcessReader\PhpGlobalsFinder; +use PhpProfiler\Lib\PhpProcessReader\PhpVersionDetector; use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException; use PhpProfiler\Lib\PhpProcessReader\PhpMemoryReader\ExecutorGlobalsReader; use PhpProfiler\Lib\Process\ProcessStopper\ProcessStopper; @@ -51,6 +52,7 @@ public function __construct( private ProcessStopper $process_stopper, private TargetProcessResolver $target_process_resolver, private RetryingLoopProvider $retrying_loop_provider, + private PhpVersionDetector $php_version_detector, ) { parent::__construct(); } @@ -85,6 +87,14 @@ public function execute(InputInterface $input, OutputInterface $output): int $process_specifier = $this->target_process_resolver->resolve($target_process_settings); + $version = $this->php_version_detector->tryDetection( + $process_specifier, + $target_php_settings + ); + if (!is_null($version)) { + $target_php_settings = $target_php_settings->alterPhpVersion($version); + } + // On targeting ZTS, it's possible that libpthread.so of the target process isn't yet loaded // at this point. In that case the TLS block can't be located, then the address of EG can't // be found also. So simply retrying the whole process of finding EG here. diff --git a/src/Inspector/Daemon/Reader/Worker/PhpReaderTraceLoop.php b/src/Inspector/Daemon/Reader/Worker/PhpReaderTraceLoop.php index dadf8516..1a14a735 100644 --- a/src/Inspector/Daemon/Reader/Worker/PhpReaderTraceLoop.php +++ b/src/Inspector/Daemon/Reader/Worker/PhpReaderTraceLoop.php @@ -20,6 +20,7 @@ use PhpProfiler\Inspector\Settings\TraceLoopSettings\TraceLoopSettings; use PhpProfiler\Lib\PhpProcessReader\PhpGlobalsFinder; use PhpProfiler\Lib\PhpProcessReader\PhpMemoryReader\ExecutorGlobalsReader; +use PhpProfiler\Lib\PhpProcessReader\PhpVersionDetector; use PhpProfiler\Lib\Process\ProcessSpecifier; use PhpProfiler\Lib\Process\ProcessStopper\ProcessStopper; @@ -33,6 +34,7 @@ public function __construct( private ExecutorGlobalsReader $executor_globals_reader, private ReaderLoopProvider $reader_loop_provider, private ProcessStopper $process_stopper, + private PhpVersionDetector $php_version_detector, ) { } @@ -49,6 +51,13 @@ public function run( TargetPhpSettings $target_php_settings, GetTraceSettings $get_trace_settings ): Generator { + $version = $this->php_version_detector->tryDetection( + $process_specifier, + $target_php_settings + ); + if (!is_null($version)) { + $target_php_settings = $target_php_settings->alterPhpVersion($version); + } $eg_address = $this->php_globals_finder->findExecutorGlobals($process_specifier, $target_php_settings); $loop = $this->reader_loop_provider->getMainLoop( diff --git a/src/Lib/PhpProcessReader/PhpVersionDetector.php b/src/Lib/PhpProcessReader/PhpVersionDetector.php new file mode 100644 index 00000000..b58fd4c6 --- /dev/null +++ b/src/Lib/PhpProcessReader/PhpVersionDetector.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\PhpProcessReader; + +use FFI\CPointer; +use PhpProfiler\Inspector\Settings\TargetPhpSettings\TargetPhpSettings; +use PhpProfiler\Lib\PhpInternals\ZendTypeCData; +use PhpProfiler\Lib\PhpInternals\ZendTypeReader; +use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderInterface; +use PhpProfiler\Lib\Process\ProcessSpecifier; + +final class PhpVersionDetector +{ + private const VERSION_STRING_CONVERTOR = [ + '7.0' => ZendTypeReader::V70, + '7.1' => ZendTypeReader::V71, + '7.2' => ZendTypeReader::V72, + '7.3' => ZendTypeReader::V73, + '7.4' => ZendTypeReader::V74, + '8.0' => ZendTypeReader::V80, + '8.1' => ZendTypeReader::V81, + ]; + + + public function __construct( + private PhpSymbolReaderCreator $php_symbol_reader_creator, + private ZendTypeReader $zend_type_reader, + private MemoryReaderInterface $memory_reader, + ) { + } + + /** @return null|value-of */ + public function tryDetection( + ProcessSpecifier $process_specifier, + TargetPhpSettings $target_php_settings, + ): ?string { + try { + $php_symbol_reader = $this->php_symbol_reader_creator->create( + $process_specifier->pid, + $target_php_settings->getDelimitedPhpRegex(), + $target_php_settings->getDelimitedLibPthreadRegex(), + $target_php_settings->php_path, + $target_php_settings->libpthread_path, + ); + $basic_functions_module = $php_symbol_reader->read('basic_functions_module') + ?? throw new \Exception(); + /** @var ZendTypeCData<\FFI\PhpInternals\zend_module_entry> $module_entry */ + $module_entry = $this->zend_type_reader->readAs('zend_module_entry', $basic_functions_module); + /** @var CPointer $version_string_pointer */ + $version_string_pointer = \FFI::cast('long', $module_entry->typed->version) + ?? throw new \Exception(); + $version_string_cdata = $this->memory_reader->read( + $process_specifier->pid, + $version_string_pointer->cdata, + 3 + ); + $php_version = \FFI::string($version_string_cdata, 3); + } catch (\Throwable) { + return null; + } + return self::VERSION_STRING_CONVERTOR[$php_version] ?? null; + } +} diff --git a/tools/stubs/ffi/php.php b/tools/stubs/ffi/php.php index 05233d98..5c0c22da 100644 --- a/tools/stubs/ffi/php.php +++ b/tools/stubs/ffi/php.php @@ -78,3 +78,8 @@ class zend_class_entry extends CData { public zend_string $name; } + +class zend_module_entry extends CData +{ + public int $version; // pointer +}