Passed
Pull Request — 0.9.x (#308)
by Shinji
02:20
created

CallTraceReader::getExecutorGlobals()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 3
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the reliforp/reli-prof package.
5
 *
6
 * (c) sji <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Reli\Lib\PhpProcessReader\CallTraceReader;
15
16
use Reli\Lib\PhpInternals\Opcodes\OpcodeFactory;
17
use Reli\Lib\PhpInternals\Types\C\RawDouble;
18
use Reli\Lib\PhpInternals\Types\Zend\Opline;
19
use Reli\Lib\PhpInternals\Types\Zend\ZendCastedTypeProvider;
20
use Reli\Lib\PhpInternals\Types\Zend\ZendExecuteData;
21
use Reli\Lib\PhpInternals\Types\Zend\ZendExecutorGlobals;
22
use Reli\Lib\PhpInternals\Types\Zend\ZendFunction;
23
use Reli\Lib\PhpInternals\Types\Zend\ZendOp;
24
use Reli\Lib\PhpInternals\ZendTypeReader;
25
use Reli\Lib\PhpInternals\ZendTypeReaderCreator;
26
use Reli\Lib\Process\MemoryReader\MemoryReaderInterface;
27
use Reli\Lib\Process\MemoryReader\MemoryReaderException;
28
use Reli\Lib\Process\Pointer\Dereferencer;
29
use Reli\Lib\Process\Pointer\Pointer;
30
use Reli\Lib\Process\Pointer\RemoteProcessDereferencer;
31
use Reli\Lib\Process\ProcessSpecifier;
32
33
final class CallTraceReader
34
{
35
    private ?ZendTypeReader $zend_type_reader = null;
36
37
    public function __construct(
38
        private MemoryReaderInterface $memory_reader,
39
        private ZendTypeReaderCreator $zend_type_reader_creator,
40
        private OpcodeFactory $opcode_factory,
41
    ) {
42
    }
43
44
    /**
45
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
46
     */
47
    public function getTypeReader(string $php_version): ZendTypeReader
48
    {
49
        if (is_null($this->zend_type_reader)) {
50
            $this->zend_type_reader = $this->zend_type_reader_creator->create($php_version);
51
        }
52
        return $this->zend_type_reader;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->zend_type_reader could return the type null which is incompatible with the type-hinted return Reli\Lib\PhpInternals\ZendTypeReader. Consider adding an additional type-check to rule them out.
Loading history...
53
    }
54
55
    /**
56
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
57
     */
58
    private function getDereferencer(int $pid, string $php_version): Dereferencer
59
    {
60
        return new RemoteProcessDereferencer(
61
            $this->memory_reader,
62
            new ProcessSpecifier($pid),
63
            new ZendCastedTypeProvider(
64
                $this->getTypeReader($php_version),
65
            )
66
        );
67
    }
68
69
    /**
70
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
71
     */
72
    private function getExecutorGlobals(
73
        int $eg_address,
74
        string $php_version,
75
        Dereferencer $dereferencer
76
    ): ZendExecutorGlobals {
77
        $zend_type_reader = $this->getTypeReader($php_version);
78
        $eg_pointer = new Pointer(
79
            ZendExecutorGlobals::class,
80
            $eg_address,
81
            $zend_type_reader->sizeOf('zend_executor_globals')
82
        );
83
        return $dereferencer->deref($eg_pointer);
84
    }
85
86
    /**
87
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
88
     */
89
    public function getGlobalRequestTime(
90
        int $sg_address,
91
        string $php_version,
92
        Dereferencer $dereferencer,
93
    ): float {
94
        $zend_type_reader = $this->getTypeReader($php_version);
95
        [$offset, $size] = $zend_type_reader->getOffsetAndSizeOfMember(
96
            'sapi_globals_struct',
97
            'global_request_time'
98
        );
99
100
        $pointer = new Pointer(
101
            RawDouble::class,
102
            $sg_address + $offset,
103
            $size
104
        );
105
        return $dereferencer->deref($pointer)->value;
106
    }
107
108
    /**
109
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
110
     * @throws MemoryReaderException
111
     */
112
    public function readCallTrace(
113
        int $pid,
114
        string $php_version,
115
        int $executor_globals_address,
116
        int $sapi_globals_address,
117
        int $depth,
118
        TraceCache $trace_cache,
119
    ): ?CallTrace {
120
        $dereferencer = $this->getDereferencer($pid, $php_version);
121
        $eg = $this->getExecutorGlobals($executor_globals_address, $php_version, $dereferencer);
122
        if (is_null($eg->current_execute_data)) {
123
            return null;
124
        }
125
126
        $trace_cache->updateCacheKey($this->getGlobalRequestTime($sapi_globals_address, $php_version, $dereferencer));
127
        $cached_dereferencer = $trace_cache->getDereferencer($dereferencer);
128
129
        $current_execute_data = $dereferencer->deref($eg->current_execute_data);
130
131
        $stack = [];
132
        $stack[] = $current_execute_data;
133
        for ($i = 0; $i < $depth; $i++) {
134
            if (is_null($current_execute_data->prev_execute_data)) {
135
                break;
136
            }
137
            $current_execute_data = $dereferencer->deref($current_execute_data->prev_execute_data);
138
            $stack[] = $current_execute_data;
139
        }
140
141
        $result = [];
142
        foreach ($stack as $current_execute_data) {
143
            if (is_null($current_execute_data->func)) {
144
                $result[] = new CallFrame(
145
                    '',
146
                    '<unknown>',
147
                    '<unknown>',
148
                    null
149
                );
150
                continue;
151
            }
152
            $current_function = $cached_dereferencer->deref($current_execute_data->func);
153
154
            $function_name = $current_function->getFunctionName($cached_dereferencer) ?? '<main>';
155
            $class_name = $current_function->getClassName($cached_dereferencer) ?? '';
156
            $file_name = $current_function->getFileName($cached_dereferencer) ?? '<unknown>';
157
158
            $opline = null;
159
            if (
160
                $file_name !== '<internal>'
161
                and $file_name !== '<unknown>'
162
                and !is_null($current_execute_data->opline)
163
            ) {
164
                $opline = $this->readOpline(
165
                    $php_version,
166
                    $cached_dereferencer->deref($current_execute_data->opline)
167
                );
168
            }
169
170
            $result[] = new CallFrame(
171
                $class_name,
172
                $function_name,
173
                $file_name,
174
                $opline
175
            );
176
        }
177
178
        return new CallTrace(...$result);
179
    }
180
181
    /**
182
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
183
     */
184
    private function readOpline(string $php_version, ZendOp $zend_op): Opline
185
    {
186
        return new Opline(
187
            $zend_op->op1,
188
            $zend_op->op2,
189
            $zend_op->result,
190
            $zend_op->extended_value,
191
            $zend_op->lineno,
192
            $this->opcode_factory->create($php_version, $zend_op->opcode),
193
            $zend_op->op1_type,
194
            $zend_op->op2_type,
195
            $zend_op->result_type
196
        );
197
    }
198
}
199