Add option to dump the best guess at opcode being executed by the VM?
TysonAndre opened this issue · comments
zend_vm_call_opcode_handler is what implements it. This is the number of a function pointer in memory.
struct _zend_execute_data {
const zend_op *opline; /* executed opline */
It seems like there's already some code in phpspy for working with execute_data and opline.
// phpspy_trace.c
if (zfunc.type == 2) {
try(rv, copy_zstring(context, "filename", zfunc.op_array.filename, frame->loc.file, sizeof(frame->loc.file), &frame->loc.file_len));
frame->loc.lineno = zfunc.op_array.line_start;
/* TODO add comments */
if (HASH_CNT(hh, varpeek_map) > 0) {
try_copy_proc_mem("opline", execute_data.opline, &zop, sizeof(zop));
varpeek_find(context, &zop, remote_execute_data, &zfunc.op_array, frame->loc.file, frame->loc.file_len);
}
// Zend/zend_vm_execute.h, php 8.0
#if defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)
#if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)
handler = (opcode_handler_t)zend_vm_get_opcode_handler_func(zend_user_opcodes[opline->opcode], opline);
handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
if (EXPECTED(opline != &hybrid_halt_op)) {
#else
((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
if (EXPECTED(opline)) {
#endif
To start, having an option to just dump the raw opcode number might help.
Motivations:
- End-users may want to see slow operations that can be rewritten (e.g. is it comparison, function calling, manipulating arrays, etc)
- Look into what opcodes in php-src itself could have the implementation optimized
This doesn't seem as straightforward as I initially thought.
- Without patching php-src, the only oplines that will get saved are the oplines that can potentially throw/emit notices (i.e. the ones calling
SAVE_OPLINE
), which gives a very inaccurate profile of expensive opline. - There are many different types of VMs.
- Often, the current opline will be saved in a register. Overriding the USE_OPLINE macro to save the current opline to a global variable to manually instrument the code seems like the most reliable approach.
- Time will elapse between reads to the process's memory, so the opline won't correspond to the currently evaluated function. (EDIT: phpspy --pause-process exists and I didn't realize it)
In case anyone finds issue in the future wants to know how I implemented this, the patches I used for phpspy and php-src (8.0-dev) are below. This only targets one of many possible VM configurations (The hybrid VM + using a local variable for the opline)
diff --git a/event_fout.c b/event_fout.c
index 72650a4..124b3bd 100644
--- a/event_fout.c
+++ b/event_fout.c
@@ -45,13 +45,14 @@ int event_handler_fout(struct trace_context_s *context, int event_type) {
&udata->rem,
&len,
1,
- "%d %.*s%s%.*s %.*s:%d",
+ "%d %.*s%s%.*s %.*s:%d opcode=%u",
frame->depth,
(int)frame->loc.class_len, frame->loc.class,
frame->loc.class_len > 0 ? "::" : "",
(int)frame->loc.func_len, frame->loc.func,
(int)frame->loc.file_len, frame->loc.file,
- frame->loc.lineno
+ frame->loc.lineno,
+ (unsigned int)frame->loc.opcode
));
try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim));
break;
diff --git a/php_structs_70.h b/php_structs_70.h
index d6848e7..23a00ba 100644
--- a/php_structs_70.h
+++ b/php_structs_70.h
@@ -70,6 +70,7 @@ struct __attribute__((__packed__)) _zend_string_70 {
struct __attribute__((__packed__)) _zend_op_70 {
uint8_t pad0[24]; /* 0 +24 */
uint32_t lineno; /* 24 +4 */
+ uint8_t opcode; /* 28 +1 */
};
struct __attribute__((__packed__)) _sapi_request_info_70 {
diff --git a/php_structs_71.h b/php_structs_71.h
index 4cbd9ca..f22e90a 100644
--- a/php_structs_71.h
+++ b/php_structs_71.h
@@ -70,6 +70,7 @@ struct __attribute__((__packed__)) _zend_string_71 {
struct __attribute__((__packed__)) _zend_op_71 {
uint8_t pad0[24]; /* 0 +24 */
uint32_t lineno; /* 24 +4 */
+ uint8_t opcode; /* 28 +1 */
};
struct __attribute__((__packed__)) _sapi_request_info_71 {
diff --git a/php_structs_72.h b/php_structs_72.h
index cb85cf5..16fda1d 100644
--- a/php_structs_72.h
+++ b/php_structs_72.h
@@ -70,6 +70,7 @@ struct __attribute__((__packed__)) _zend_string_72 {
struct __attribute__((__packed__)) _zend_op_72 {
uint8_t pad0[24]; /* 0 +24 */
uint32_t lineno; /* 24 +4 */
+ uint8_t opcode; /* 28 +1 */
};
struct __attribute__((__packed__)) _sapi_request_info_72 {
diff --git a/php_structs_73.h b/php_structs_73.h
index a4404e0..649b289 100644
--- a/php_structs_73.h
+++ b/php_structs_73.h
@@ -70,6 +70,7 @@ struct __attribute__((__packed__)) _zend_string_73 {
struct __attribute__((__packed__)) _zend_op_73 {
uint8_t pad0[24]; /* 0 +24 */
uint32_t lineno; /* 24 +4 */
+ uint8_t opcode; /* 28 +1 */
};
struct __attribute__((__packed__)) _sapi_request_info_73 {
diff --git a/php_structs_74.h b/php_structs_74.h
index 7629834..1feee6e 100644
--- a/php_structs_74.h
+++ b/php_structs_74.h
@@ -70,6 +70,7 @@ struct __attribute__((__packed__)) _zend_string_74 {
struct __attribute__((__packed__)) _zend_op_74 {
uint8_t pad0[24]; /* 0 +24 */
uint32_t lineno; /* 24 +4 */
+ uint8_t opcode; /* 28 +1 */
};
struct __attribute__((__packed__)) _sapi_request_info_74 {
diff --git a/phpspy.c b/phpspy.c
index bcf0f22..5e95d37 100644
--- a/phpspy.c
+++ b/phpspy.c
@@ -522,6 +522,9 @@ static int find_addresses(trace_target_t *target) {
if (get_symbol_addr(&memo, target->pid, "basic_functions_module", &target->basic_functions_module_addr) != 0) {
target->basic_functions_module_addr = 0;
}
+ if (get_symbol_addr(&memo, target->pid, "instrumented_current_opcode", &target->instrumented_current_opcode_addr) != 0) {
+ target->instrumented_current_opcode_addr = 0;
+ }
/* TODO probably don't need zend_string_val_offset */
#ifdef USE_ZEND
diff --git a/phpspy.h b/phpspy.h
index 69ca13a..e000435 100644
--- a/phpspy.h
+++ b/phpspy.h
@@ -103,6 +103,7 @@ typedef struct trace_loc_s {
size_t class_len;
size_t file_len;
int lineno;
+ int opcode;
} trace_loc_t;
typedef struct trace_frame_s {
@@ -141,6 +142,7 @@ typedef struct trace_target_s {
uint64_t core_globals_addr;
uint64_t alloc_globals_addr;
uint64_t basic_functions_module_addr;
+ uint64_t instrumented_current_opcode_addr;
} trace_target_t;
typedef struct trace_context_s {
diff --git a/phpspy_trace.c b/phpspy_trace.c
index 1662a07..006faba 100644
--- a/phpspy_trace.c
+++ b/phpspy_trace.c
@@ -45,6 +45,27 @@ static int do_trace(trace_context_t *context) {
try_copy_proc_mem("execute_data", remote_execute_data, &execute_data, sizeof(execute_data));
try_copy_proc_mem("zfunc", execute_data.func, &zfunc, sizeof(zfunc));
+ /* XXX the opline contents will change because the process is actively running */
+ if (zfunc.type == 2) {
+ if (depth > 0) {
+ int result = copy_proc_mem(context->target.pid, "opline", execute_data.opline, &zop, sizeof(zop));
+ if (result == 0) {
+ frame->loc.opcode = zop.opcode;
+ } else {
+ frame->loc.opcode = 0xff;
+ }
+ } else {
+ uint8_t opcode = 0;
+ int result = copy_proc_mem(context->target.pid, "instrumented_current_opcode", (void*)target->instrumented_current_opcode_addr, &opcode, sizeof(opcode));
+ if (result == 0) {
+ frame->loc.opcode = opcode;
+ } else {
+ frame->loc.opcode = 0xff;
+ }
+ }
+ } else {
+ frame->loc.opcode = 0;
+ }
if (zfunc.common.function_name) {
try(rv, copy_zstring(context, "function_name", zfunc.common.function_name, frame->loc.func, sizeof(frame->loc.func), &frame->loc.func_len));
} else {
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 153d8e3bc4..f0cca55239 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -4506,3 +4506,5 @@ ZEND_API zval *zend_get_zval_ptr(const zend_op *opline, int op_type, const znode
}
return ret;
}
+uint8_t instrumented_current_opcode;
+
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 812a7e5bd2..1bdab61485 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -17,6 +17,7 @@
| Dmitry Stogov <dmitry@php.net> |
+----------------------------------------------------------------------+
*/
+extern uint8_t instrumented_current_opcode;
#ifdef ZEND_WIN32
# pragma warning(disable : 4101)
@@ -401,7 +402,7 @@ typedef ZEND_OPCODE_HANDLER_RET (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_H
#define DCL_OPLINE
#ifdef ZEND_VM_IP_GLOBAL_REG
# define OPLINE opline
-# define USE_OPLINE
+# define USE_OPLINE instrumented_current_opcode = OPLINE->opcode;
# define LOAD_OPLINE() opline = EX(opline)
# define LOAD_OPLINE_EX()
# define LOAD_NEXT_OPLINE() opline = EX(opline) + 1
@@ -48060,7 +48061,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_NULL_HANDLER(ZEND_OPCODE_HANDL
# define ZEND_VM_RETURN() goto HYBRID_HALT_LABEL
#endif
-
#if (ZEND_VM_KIND != ZEND_VM_KIND_CALL) && (ZEND_GCC_VERSION >= 4000) && !defined(__clang__)
# pragma GCC push_options
# pragma GCC optimize("no-gcse")
@@ -51304,6 +51304,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
int ret;
#endif
#if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)
+ instrumented_current_opcode = OPLINE->opcode;
HYBRID_SWITCH() {
#else
#if defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)
Feel free to reopen this if there are plans to do this.