From 4aa3971208cbd4752637c0547c7f1c14902e5f61 Mon Sep 17 00:00:00 2001 From: sji Date: Thu, 3 Nov 2022 18:22:06 +0900 Subject: [PATCH] Cache symbols per PHP binary --- src/Lib/Elf/Process/BinaryFingerprint.php | 44 ++++++++++++++ .../Process/Elf64LazyParseSymbolResolver.php | 60 +++++++++++++++++++ .../Process/PerBinarySymbolCacheRetriever.php | 30 ++++++++++ .../ProcessModuleSymbolReaderCreator.php | 56 +++++++---------- .../Elf64CachedSymbolResolver.php | 36 +++++++++++ .../Elf/SymbolResolver/Elf64SymbolCache.php | 37 ++++++++++++ .../Process/MemoryMap/ProcessMemoryArea.php | 4 +- .../MemoryMap/ProcessMemoryMapParser.php | 24 ++++---- .../MemoryMap/ProcessModuleMemoryMap.php | 15 +++++ .../ProcessModuleMemoryMapInterface.php | 6 ++ .../ProcessModuleSymbolReaderCreatorTest.php | 13 +++- .../Process/ProcessModuleSymbolReaderTest.php | 10 ++++ .../PhpProcessReader/PhpGlobalsFinderTest.php | 4 +- .../PhpMemoryReader/CallTraceReaderTest.php | 2 + .../PhpVersionDetectorTest.php | 2 + .../MemoryMap/ProcessMemoryAreaTest.php | 2 + .../MemoryMap/ProcessMemoryMapTest.php | 4 ++ .../MemoryMap/ProcessModuleMemoryMapTest.php | 2 + 18 files changed, 302 insertions(+), 49 deletions(-) create mode 100644 src/Lib/Elf/Process/BinaryFingerprint.php create mode 100644 src/Lib/Elf/Process/Elf64LazyParseSymbolResolver.php create mode 100644 src/Lib/Elf/Process/PerBinarySymbolCacheRetriever.php create mode 100644 src/Lib/Elf/SymbolResolver/Elf64CachedSymbolResolver.php create mode 100644 src/Lib/Elf/SymbolResolver/Elf64SymbolCache.php diff --git a/src/Lib/Elf/Process/BinaryFingerprint.php b/src/Lib/Elf/Process/BinaryFingerprint.php new file mode 100644 index 00000000..ba17bd33 --- /dev/null +++ b/src/Lib/Elf/Process/BinaryFingerprint.php @@ -0,0 +1,44 @@ + + * + * 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\Process\MemoryMap\ProcessModuleMemoryMap; + +final class BinaryFingerprint +{ + public function __construct( + private string $fingerprint + ) { + } + + public static function fromProcessModuleMemoryMap( + ProcessModuleMemoryMap $process_module_memory_map + ): self { + return new self( + join( + '_', + [ + $process_module_memory_map->getDeviceId(), + $process_module_memory_map->getInodeNumber(), + $process_module_memory_map->getModuleName(), + ] + ) + ); + } + + public function __toString(): string + { + return $this->fingerprint; + } +} diff --git a/src/Lib/Elf/Process/Elf64LazyParseSymbolResolver.php b/src/Lib/Elf/Process/Elf64LazyParseSymbolResolver.php new file mode 100644 index 00000000..34a62e4a --- /dev/null +++ b/src/Lib/Elf/Process/Elf64LazyParseSymbolResolver.php @@ -0,0 +1,60 @@ + + * + * 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\Elf\Parser\ElfParserException; +use Reli\Lib\Elf\Structure\Elf64\Elf64SymbolTableEntry; +use Reli\Lib\Elf\SymbolResolver\Elf64SymbolResolver; +use Reli\Lib\Elf\SymbolResolver\SymbolResolverCreatorInterface; +use Reli\Lib\Process\MemoryMap\ProcessModuleMemoryMap; +use Reli\Lib\Process\MemoryReader\MemoryReaderInterface; + +final class Elf64LazyParseSymbolResolver implements Elf64SymbolResolver +{ + private ?Elf64SymbolResolver $resolver_cache = null; + + public function __construct( + private string $path, + private MemoryReaderInterface $memory_reader, + private int $pid, + private ProcessModuleMemoryMap $module_memory_map, + private SymbolResolverCreatorInterface $symbol_resolver_creator, + ) { + } + + private function loadResolver(): Elf64SymbolResolver + { + try { + return $this->symbol_resolver_creator->createLinearScanResolverFromPath($this->path); + } catch (ElfParserException $e) { + try { + return $this->symbol_resolver_creator->createDynamicResolverFromPath($this->path); + } catch (ElfParserException $e) { + return $this->symbol_resolver_creator->createDynamicResolverFromProcessMemory( + $this->memory_reader, + $this->pid, + $this->module_memory_map + ); + } + } + } + + public function resolve(string $symbol_name): Elf64SymbolTableEntry + { + if (!isset($this->resolver_cache)) { + $this->resolver_cache = $this->loadResolver(); + } + return $this->resolver_cache->resolve($symbol_name); + } +} diff --git a/src/Lib/Elf/Process/PerBinarySymbolCacheRetriever.php b/src/Lib/Elf/Process/PerBinarySymbolCacheRetriever.php new file mode 100644 index 00000000..5519dd5f --- /dev/null +++ b/src/Lib/Elf/Process/PerBinarySymbolCacheRetriever.php @@ -0,0 +1,30 @@ + + * + * 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\Elf\SymbolResolver\Elf64SymbolCache; + +final class PerBinarySymbolCacheRetriever +{ + /** @var array */ + private array $cache = []; + + public function get(BinaryFingerprint $binary_fingerprint): Elf64SymbolCache + { + if (!isset($this->cache[(string)$binary_fingerprint])) { + $this->cache[(string)$binary_fingerprint] = new Elf64SymbolCache(); + } + return $this->cache[(string)$binary_fingerprint]; + } +} diff --git a/src/Lib/Elf/Process/ProcessModuleSymbolReaderCreator.php b/src/Lib/Elf/Process/ProcessModuleSymbolReaderCreator.php index 3f4da49b..0b335904 100644 --- a/src/Lib/Elf/Process/ProcessModuleSymbolReaderCreator.php +++ b/src/Lib/Elf/Process/ProcessModuleSymbolReaderCreator.php @@ -14,6 +14,7 @@ namespace Reli\Lib\Elf\Process; use Reli\Lib\Elf\Parser\ElfParserException; +use Reli\Lib\Elf\SymbolResolver\Elf64CachedSymbolResolver; use Reli\Lib\Elf\SymbolResolver\SymbolResolverCreatorInterface; use Reli\Lib\Process\MemoryMap\ProcessMemoryMap; use Reli\Lib\Process\MemoryMap\ProcessModuleMemoryMap; @@ -23,7 +24,8 @@ final class ProcessModuleSymbolReaderCreator { public function __construct( private SymbolResolverCreatorInterface $symbol_resolver_creator, - private MemoryReaderInterface $memory_reader + private MemoryReaderInterface $memory_reader, + private PerBinarySymbolCacheRetriever $per_binary_symbol_cache_retriever, ) { } @@ -40,42 +42,28 @@ public function createModuleReaderByNameRegex( } $module_memory_map = new ProcessModuleMemoryMap($memory_areas); - $module_name = current($memory_areas)->name; + $module_name = $module_memory_map->getModuleName(); $path = $binary_path ?? $this->createContainerAwarePath($pid, $module_name); - try { - $symbol_resolver = $this->symbol_resolver_creator->createLinearScanResolverFromPath($path); - return new ProcessModuleSymbolReader( + + $symbol_resolver = new Elf64CachedSymbolResolver( + new Elf64LazyParseSymbolResolver( + $path, + $this->memory_reader, $pid, - $symbol_resolver, $module_memory_map, - $this->memory_reader, - $tls_block_address - ); - } catch (ElfParserException $e) { - try { - $symbol_resolver = $this->symbol_resolver_creator->createDynamicResolverFromPath($path); - return new ProcessModuleSymbolReader( - $pid, - $symbol_resolver, - $module_memory_map, - $this->memory_reader, - $tls_block_address - ); - } catch (ElfParserException $e) { - $symbol_resolver = $this->symbol_resolver_creator->createDynamicResolverFromProcessMemory( - $this->memory_reader, - $pid, - $module_memory_map - ); - return new ProcessModuleSymbolReader( - $pid, - $symbol_resolver, - $module_memory_map, - $this->memory_reader, - $tls_block_address - ); - } - } + $this->symbol_resolver_creator, + ), + $this->per_binary_symbol_cache_retriever->get( + BinaryFingerprint::fromProcessModuleMemoryMap($module_memory_map) + ), + ); + return new ProcessModuleSymbolReader( + $pid, + $symbol_resolver, + $module_memory_map, + $this->memory_reader, + $tls_block_address + ); } private function createContainerAwarePath(int $pid, string $path): string diff --git a/src/Lib/Elf/SymbolResolver/Elf64CachedSymbolResolver.php b/src/Lib/Elf/SymbolResolver/Elf64CachedSymbolResolver.php new file mode 100644 index 00000000..37bfa7d0 --- /dev/null +++ b/src/Lib/Elf/SymbolResolver/Elf64CachedSymbolResolver.php @@ -0,0 +1,36 @@ + + * + * 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\SymbolResolver; + +use Reli\Lib\Elf\Structure\Elf64\Elf64SymbolTableEntry; + +final class Elf64CachedSymbolResolver implements Elf64SymbolResolver +{ + public function __construct( + private Elf64SymbolResolver $resolver, + private Elf64SymbolCache $symbol_cache, + ) { + } + + public function resolve(string $symbol_name): Elf64SymbolTableEntry + { + if (!$this->symbol_cache->has($symbol_name)) { + $this->symbol_cache->set( + $symbol_name, + $this->resolver->resolve($symbol_name), + ); + } + return $this->symbol_cache->get($symbol_name); + } +} diff --git a/src/Lib/Elf/SymbolResolver/Elf64SymbolCache.php b/src/Lib/Elf/SymbolResolver/Elf64SymbolCache.php new file mode 100644 index 00000000..ee6c0363 --- /dev/null +++ b/src/Lib/Elf/SymbolResolver/Elf64SymbolCache.php @@ -0,0 +1,37 @@ + + * + * 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\SymbolResolver; + +use Reli\Lib\Elf\Structure\Elf64\Elf64SymbolTableEntry; + +final class Elf64SymbolCache +{ + /** @var array */ + private array $cache = []; + + public function has(string $symbol_name): bool + { + return isset($this->cache[$symbol_name]); + } + + public function set(string $symbol_name, Elf64SymbolTableEntry $entry): void + { + $this->cache[$symbol_name] = $entry; + } + + public function get(string $symbol_name): Elf64SymbolTableEntry + { + return $this->cache[$symbol_name]; + } +} diff --git a/src/Lib/Process/MemoryMap/ProcessMemoryArea.php b/src/Lib/Process/MemoryMap/ProcessMemoryArea.php index ac0e2b6a..fcdea16c 100644 --- a/src/Lib/Process/MemoryMap/ProcessMemoryArea.php +++ b/src/Lib/Process/MemoryMap/ProcessMemoryArea.php @@ -20,7 +20,9 @@ public function __construct( public string $end, public string $file_offset, public ProcessMemoryAttribute $attribute, - public string $name + public string $device_id, + public int $inode_num, + public string $name, ) { } diff --git a/src/Lib/Process/MemoryMap/ProcessMemoryMapParser.php b/src/Lib/Process/MemoryMap/ProcessMemoryMapParser.php index 0dbe9deb..09b73854 100644 --- a/src/Lib/Process/MemoryMap/ProcessMemoryMapParser.php +++ b/src/Lib/Process/MemoryMap/ProcessMemoryMapParser.php @@ -13,6 +13,7 @@ namespace Reli\Lib\Process\MemoryMap; +use PhpCast\Cast; use Reli\Lib\String\LineFetcher; final class ProcessMemoryMapParser @@ -46,17 +47,20 @@ private function parseLine(string $line): ?ProcessMemoryArea if ($matches === []) { return null; } - $begin = $matches[1]; - $end = $matches[2]; $attribute_string = $matches[3]; - $attribute = new ProcessMemoryAttribute( - $attribute_string[0] === 'r', - $attribute_string[1] === 'w', - $attribute_string[2] === 'x', - $attribute_string[3] === 'p', + return new ProcessMemoryArea( + begin: $matches[1], + end: $matches[2], + file_offset: $matches[4], + attribute: new ProcessMemoryAttribute( + read: $attribute_string[0] === 'r', + write: $attribute_string[1] === 'w', + execute: $attribute_string[2] === 'x', + protected: $attribute_string[3] === 'p', + ), + device_id: $matches[5], + inode_num: Cast::toInt($matches[6]), + name: $matches[7], ); - $file_offset = $matches[4]; - $name = $matches[7]; - return new ProcessMemoryArea($begin, $end, $file_offset, $attribute, $name); } } diff --git a/src/Lib/Process/MemoryMap/ProcessModuleMemoryMap.php b/src/Lib/Process/MemoryMap/ProcessModuleMemoryMap.php index 5c2322cb..355260ed 100644 --- a/src/Lib/Process/MemoryMap/ProcessModuleMemoryMap.php +++ b/src/Lib/Process/MemoryMap/ProcessModuleMemoryMap.php @@ -72,4 +72,19 @@ private function getSortedOffsetToMemoryAreaMap(): array } return $this->sorted_offset_to_memory_map; } + + public function getDeviceId(): string + { + return $this->memory_areas[0]->device_id; + } + + public function getInodeNumber(): int + { + return $this->memory_areas[0]->inode_num; + } + + public function getModuleName(): string + { + return $this->memory_areas[0]->name; + } } diff --git a/src/Lib/Process/MemoryMap/ProcessModuleMemoryMapInterface.php b/src/Lib/Process/MemoryMap/ProcessModuleMemoryMapInterface.php index 8912f918..7b101fb8 100644 --- a/src/Lib/Process/MemoryMap/ProcessModuleMemoryMapInterface.php +++ b/src/Lib/Process/MemoryMap/ProcessModuleMemoryMapInterface.php @@ -20,4 +20,10 @@ public function getBaseAddress(): int; public function getMemoryAddressFromOffset(int $offset): int; public function isInRange(int $address): bool; + + public function getDeviceId(): string; + + public function getInodeNumber(): int; + + public function getModuleName(): string; } diff --git a/tests/Lib/Elf/Process/ProcessModuleSymbolReaderCreatorTest.php b/tests/Lib/Elf/Process/ProcessModuleSymbolReaderCreatorTest.php index 102943c4..91df234a 100644 --- a/tests/Lib/Elf/Process/ProcessModuleSymbolReaderCreatorTest.php +++ b/tests/Lib/Elf/Process/ProcessModuleSymbolReaderCreatorTest.php @@ -38,7 +38,8 @@ public function testCreateModuleReaderByNameRegexFallbackToDynamic() $memory_reader = Mockery::mock(MemoryReaderInterface::class); $symbol_reader_creator = new ProcessModuleSymbolReaderCreator( $symbol_resolver_creator, - $memory_reader + $memory_reader, + new PerBinarySymbolCacheRetriever(), ); $process_memory_map = new ProcessMemoryMap([ new ProcessMemoryArea( @@ -51,6 +52,8 @@ public function testCreateModuleReaderByNameRegexFallbackToDynamic() true, false ), + '00:01', + 1, '/test_module' ), ]); @@ -93,7 +96,8 @@ function ($actual) { $symbol_reader_creator = new ProcessModuleSymbolReaderCreator( $symbol_resolver_creator, - $memory_reader + $memory_reader, + new PerBinarySymbolCacheRetriever(), ); $process_memory_map = new ProcessMemoryMap([ new ProcessMemoryArea( @@ -106,6 +110,8 @@ function ($actual) { true, false ), + '00:01', + 1, '/test_module' ), ]); @@ -127,7 +133,8 @@ public function testCreateModuleReaderByNameRegexModuleNotFound() $memory_reader = Mockery::mock(MemoryReaderInterface::class); $symbol_reader_creator = new ProcessModuleSymbolReaderCreator( $symbol_resolver_creator, - $memory_reader + $memory_reader, + new PerBinarySymbolCacheRetriever(), ); $process_memory_map = new ProcessMemoryMap([]); diff --git a/tests/Lib/Elf/Process/ProcessModuleSymbolReaderTest.php b/tests/Lib/Elf/Process/ProcessModuleSymbolReaderTest.php index 90a8a3ba..d297f22d 100644 --- a/tests/Lib/Elf/Process/ProcessModuleSymbolReaderTest.php +++ b/tests/Lib/Elf/Process/ProcessModuleSymbolReaderTest.php @@ -39,6 +39,8 @@ public function testRead() true, true ), + '00:01', + 1, 'test_area' ) ]; @@ -86,6 +88,8 @@ public function testReturnNullOnTryingToReadUndefinedSymbol() true, true ), + '00:01', + 1, 'test_area' ) ]; @@ -135,6 +139,8 @@ public function testResolveAddress() true, true ), + '00:01', + 1, 'test_area' ) ]; @@ -178,6 +184,8 @@ public function testReadTlsSymbol() true, true ), + '00:01', + 1, 'test_area' ) ]; @@ -223,6 +231,8 @@ public function testReadTlsSymbolOnTlsBlockNotSpecified() true, true ), + '00:01', + 1, 'test_area' ) ]; diff --git a/tests/Lib/PhpProcessReader/PhpGlobalsFinderTest.php b/tests/Lib/PhpProcessReader/PhpGlobalsFinderTest.php index 13492b50..d5293d4f 100644 --- a/tests/Lib/PhpProcessReader/PhpGlobalsFinderTest.php +++ b/tests/Lib/PhpProcessReader/PhpGlobalsFinderTest.php @@ -16,6 +16,7 @@ use Reli\Inspector\Settings\TargetPhpSettings\TargetPhpSettings; use Reli\Lib\ByteStream\IntegerByteSequence\LittleEndianReader; use Reli\Lib\Elf\Parser\Elf64Parser; +use Reli\Lib\Elf\Process\PerBinarySymbolCacheRetriever; use Reli\Lib\Elf\Process\ProcessModuleSymbolReaderCreator; use Reli\Lib\Elf\SymbolResolver\Elf64SymbolResolverCreator; use Reli\Lib\File\CatFileReader; @@ -52,9 +53,10 @@ public function testFindModuleRegistry() new CatFileReader(), new Elf64Parser( new LittleEndianReader() - ) + ), ), $memory_reader, + new PerBinarySymbolCacheRetriever(), ), ProcessMemoryMapCreator::create(), new LittleEndianReader() diff --git a/tests/Lib/PhpProcessReader/PhpMemoryReader/CallTraceReaderTest.php b/tests/Lib/PhpProcessReader/PhpMemoryReader/CallTraceReaderTest.php index 4134a9b8..c2ff22b9 100644 --- a/tests/Lib/PhpProcessReader/PhpMemoryReader/CallTraceReaderTest.php +++ b/tests/Lib/PhpProcessReader/PhpMemoryReader/CallTraceReaderTest.php @@ -17,6 +17,7 @@ use Reli\Inspector\Settings\TargetProcessSettings\TargetProcessSettings; use Reli\Lib\ByteStream\IntegerByteSequence\LittleEndianReader; use Reli\Lib\Elf\Parser\Elf64Parser; +use Reli\Lib\Elf\Process\PerBinarySymbolCacheRetriever; use Reli\Lib\Elf\Process\ProcessModuleSymbolReaderCreator; use Reli\Lib\Elf\SymbolResolver\Elf64SymbolResolverCreator; use Reli\Lib\File\CatFileReader; @@ -98,6 +99,7 @@ public function wait() { ) ), $memory_reader, + new PerBinarySymbolCacheRetriever(), ), ProcessMemoryMapCreator::create(), new LittleEndianReader() diff --git a/tests/Lib/PhpProcessReader/PhpVersionDetectorTest.php b/tests/Lib/PhpProcessReader/PhpVersionDetectorTest.php index f271e56d..b78d534f 100644 --- a/tests/Lib/PhpProcessReader/PhpVersionDetectorTest.php +++ b/tests/Lib/PhpProcessReader/PhpVersionDetectorTest.php @@ -16,6 +16,7 @@ use Reli\Inspector\Settings\TargetPhpSettings\TargetPhpSettings; use Reli\Lib\ByteStream\IntegerByteSequence\LittleEndianReader; use Reli\Lib\Elf\Parser\Elf64Parser; +use Reli\Lib\Elf\Process\PerBinarySymbolCacheRetriever; use Reli\Lib\Elf\Process\ProcessModuleSymbolReaderCreator; use Reli\Lib\Elf\SymbolResolver\Elf64SymbolResolverCreator; use Reli\Lib\File\CatFileReader; @@ -56,6 +57,7 @@ public function testDetectVersion() ) ), $memory_reader, + new PerBinarySymbolCacheRetriever(), ), ProcessMemoryMapCreator::create(), new LittleEndianReader() diff --git a/tests/Lib/Process/MemoryMap/ProcessMemoryAreaTest.php b/tests/Lib/Process/MemoryMap/ProcessMemoryAreaTest.php index cedf4064..2eca4041 100644 --- a/tests/Lib/Process/MemoryMap/ProcessMemoryAreaTest.php +++ b/tests/Lib/Process/MemoryMap/ProcessMemoryAreaTest.php @@ -29,6 +29,8 @@ public function testIsInRange() true, false ), + '00:01', + 1, 'test' ); $this->assertFalse($process_memory_area->isInRange(0x00000000)); diff --git a/tests/Lib/Process/MemoryMap/ProcessMemoryMapTest.php b/tests/Lib/Process/MemoryMap/ProcessMemoryMapTest.php index 18c6c25f..46dbe3e2 100644 --- a/tests/Lib/Process/MemoryMap/ProcessMemoryMapTest.php +++ b/tests/Lib/Process/MemoryMap/ProcessMemoryMapTest.php @@ -30,6 +30,8 @@ public function testFindByNameRegex() true, false ), + '00:01', + 1, 'test_area_1' ), new ProcessMemoryArea( @@ -42,6 +44,8 @@ public function testFindByNameRegex() true, false ), + '00:02', + 2, 'test_area_2' ), ]); diff --git a/tests/Lib/Process/MemoryMap/ProcessModuleMemoryMapTest.php b/tests/Lib/Process/MemoryMap/ProcessModuleMemoryMapTest.php index 6609c4ee..8dfd2f43 100644 --- a/tests/Lib/Process/MemoryMap/ProcessModuleMemoryMapTest.php +++ b/tests/Lib/Process/MemoryMap/ProcessModuleMemoryMapTest.php @@ -72,6 +72,8 @@ private function createProcessMemoryArea(string $begin, string $end, string $fil false, true ), + '00:01', + 1, 'test' ); }