Up: Opcode Handlers [Contents]
First, we define a general-purpose handler function template in C++. The
handler
argument contains the implementation of the opcode handler.
It accepts three zval
pointers (i.e. two operands and result), and
returns a bool
indicating whether the instruction is executed within
this handler.
template <typename F> int op_handler( zend_execute_data *execute_data, F handler ) { // ... initialization here if (!handler(op1, op2, result)) { return ZEND_USER_OPCODE_DISPATCH; } // ... clean up here return ZEND_USER_OPCODE_CONTINUE; }
Then, we initialize the handler function. Fetch the pointer to the current
line of opcode from execute_data
, and pointers to each operand
zval
from opline
.
const zend_op *opline = EX(opline); zend_free_op free_op1, free_op2; zval *op1 = zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, execute_data, &free_op1, 0); zval *op2 = zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, execute_data, &free_op2, 0); zval *result = opline->result_type ? EX_VAR(opline->result.var) : nullptr;
A operand may be a reference to another zval
. We would want to first
dereference it before use.
if (EXPECTED(op1)) { ZVAL_DEREF(op1); } if (op2) { ZVAL_DEREF(op2); }
Now handler
can be invoked. Before continuing to the next line of
opcode, don’t forget to free the operands (if necessary) and increment
EX(opline)
.
if (free_op2) { zval_ptr_dtor_nogc(free_op2); } if (free_op1) { zval_ptr_dtor_nogc(free_op1); } // No need to free `result` here. EX(opline) = opline + 1;
Finally, register the handler functions.
int add_handler( zend_execute_data *execute_data ) { return op_handler(execute_data, [] (auto zv1, auto zv2, auto rv) { if (/* Whether we should overload "+" operator */) { // ... do something return true; } return false; }); } // Opcode handlers are usually registered on module init. PHP_MINIT_FUNCTION(my_extension) { zend_set_user_opcode_handler(ZEND_ADD, add_handler); }