Sergi Álvarez & Roi Martín - Radare2 Preview [RootedCON 2010]
Php opcodes sep2008
1.
2.
3. Execution path for a script php_execute_script() zend_execute_scripts() zend_execute() user call (function/method ) include/require zend_compile_file()
16. State of play after compile complete opcodes zend_op_array active_oparray symbol_table active_symbol_table function_table class_table executor_globals symbol_table function_table <?php function add5($a) { return $a + 5; } function sub5($a) { return $a - 5; } $a = add5(10); $b = sub5(15); ?> zend_op_array zend_op_array zend_internal_fucntion op_array for global scope zend_internal_fucntion GLOBALS _ENV HTTP_ENV_VARS …… . <internal func> add5 sub5 …… . <internal func>
17.
18.
19. State of play after compile complete : opcodes zend_op_array active_oparray symbol_table active_smbol_table function_table class_table executor_globals symbol_table class_table <?php class Dog { function bark() { print "Woof!"; } function sit() { print “Sit!!”; } } $pooch = new Dog; $pooch ->bark(); $pooch ->sit(); ?> zend_op_array function_table function_table zend_op_array function_table zend_internal_fucntion zend_internal_fucntion GLOBALS _ENV HTTP_ENV_VARS …… . <internal class> Dog …… . bark sit …… . <internal func> <internal func> …… . <internal method> <internal method> …… .
20.
21.
22.
23.
24.
25. VLD output line # op fetch ext operands -------------------------------------------------------------------------- 2 0 ECHO '%0A' 4 1 ASSIGN !0, 5 5 2 ASSIGN !1, 10 6 3 ADD ~2, !0, !1 4 ADD ~3, ~2, 99 5 ASSIGN !2, ~3 8 6 INIT_STRING ~5 7 ADD_STRING ~5, ~5, 'c' 8 ADD_STRING ~5, ~5, '%3D+' 9 ADD_VAR ~5, ~5, !2 10 ADD_STRING ~5, ~5, '+' 11 ADD_CHAR ~5, ~5, 10 12 ECHO ~5 11 13 RETURN 1 14 ZEND_HANDLE_EXCEPTION c= 114 <?php $a = 5; $b = 10; $c = $a + $b + 99; echo "c= $c "; ?> php -f test.php -dvld.active=1 KEY ! == compiler variable $ == variable ~ == temporary There are TMP’s defied for results here but they are not used and VLD does not list them
26. Why all these “+” in VLD output for CONST’s ? <?php echo "Hello World"; echo "Hello World"; echo "Hello + World"; ?> line # op fetch ext operands ------------------------------------------------------------------------------- 2 0 ECHO '%0D%0A+' 4 1 ECHO 'Hello+World' 5 2 ECHO 'Hello++++++++++++++++++++++++++++++++World' 6 3 ECHO 'Hello+%2B+World' 9 4 RETURN 1 5 ZEND_HANDLE_EXCEPTION Answer: VLD calls php_url_encode() on the CONST to format it before output which amongst other things converts all spaces to “+”. Internally white space is stored as 0x20 as you would expect.
33. Execution path for a script php_execute_script() zend_execute_scripts() zend_execute() user call (function/method ) include/require zend_compile_file()
37. zend_vm_defs.h ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV) { zend_op *opline = EX(opline); zend_free_op free_op1, free_op2; add_function(&EX_T(opline->result.u.var).tmp_var, GET_OP1_ZVAL_PTR(BP_VAR_R), GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC); FREE_OP1(); FREE_OP2(); ZEND_VM_NEXT_OPCODE(); } opcode opcode name types accepted for op1 types accepted for op2 .. although this is just a macro! helper function triggers to php code to replace text
51. VAR variables FETCH_W $0, 'a' /* Retrieve the $a variable for writing */ ASSIGN $0, 123 /* Assign the numeric value 123 to retrieved variable 0 */ FETCH_W $2, 'b' /* Retrieve the $b variable for writing */ ASSIGN $2, 456 /* Assign the numeric value 456 to retrieved variable 2 */ FETCH_R $5, 'a' /* Retrieve the $a variable for reading */ FETCH_R $6, 'b' /* Retrieve the $b variable for reading */ ADD ~7, $5, $6 /* Add the retrieved variables (5 & 6) together and store the result in 7 */ FETCH_W $4, 'c' /* Retrieve the $c variable for writing */ ASSIGN $4, ~7 /* Assign the value in temporary variable 7 into retrieved variable 4 */ FETCH_R $9, 'c' /* Retrieve the $c variable for reading */ ECHO $9 /* Echo the retrieved variable 9 */ <?php $a = 123; $b = 456; $c = $a + $b; echo $c; ?> Note: Each time $a is accessed we look it up in symbol table and store result in a different VAR
52. VAR variables typedef union _temp_variable { zval tmp_var; struct { zval **ptr_ptr; zval *ptr; zend_bool fcall_returned_reference; } var; … etc } temp_variable; ZVAL typedef union _temp_variable { zval tmp_var; struct { zval **ptr_ptr; zval *ptr; zend_bool fcall_returned_reference; } var; … etc } temp_variable; ZVAL After FETCH_R After FETCH_RW or FETCH_W pDataPtr symbol_table pDataPtr symbol_table
53.
54.
55.
56. Compiled variables: with regular variables <?php $a = 123; $b = 456; $c = $a + $b; echo $c; ?> FETCH_W $0, 'a' /* Retrieve the $a variable for writing */ ASSIGN $0, 123 /* Assign the numeric value 123 to retrieved variable 0 */ FETCH_W $2, 'b' /* Retrieve the $b variable for writing */ ASSIGN $2, 456 /* Assign the numeric value 456 to retrieved variable 2 */ FETCH_R $5, 'a' /* Retrieve the $a variable for reading */ FETCH_R $6, 'b' /* Retrieve the $b variable for reading */ ADD ~7, $5, $6 /* Add the retrieved variables (5 & 6) together and store the result in 7 */ FETCH_W $4, 'c' /* Retrieve the $c variable for writing */ ASSIGN $4, ~7 /* Assign the value in temporary variable 7 into retrieved variable 4 */ FETCH_R $9, 'c' /* Retrieve the $c variable for reading */ ECHO $9 /* Echo the retrieved variable 9 */ ASSIGN !0, 123 /* Assign the numeric value 123 to compiled variable 0 */ ASSIGN !1, 456 /* Assign the numeric value 456 to compiled variable 1 */ ADD ~2, !0, !1 /* Add compiled variable 0 to compiled variable 1 */ ASSIGN !2, ~2 /* Assign the value of temporary variable 2 to compiled variable 2 */ ECHO !2 /* Echo the value of compiled variable 2 */ Without CV With CV
57. Compiled variables : with Object variables <?php $f->a = 123; $f->b = 456; $f->c = $f->a + $F->b; echo $f->c; ?> ASSIGN_OBJ !0, 'a' /* Assign the numeric value 123 to property 'a' of compiled variable 0 object */ OP_DATA 123 /* Additional data for ASSIGN_OBJ opcode */ ASSIGN_OBJ !0, 'b' /* Assign the numeric value 456 to property 'b' of compiled variable 0 object */ OP_DATA 456 /* Additional data for ASSIGN_OBJ opcode */ FETCH_OBJ_R $3, !0, 'a‘ /* Retrieve property 'a' from compiled variable 0 object */ FETCH_OBJ_R $4, !0, 'b‘ /* Retrieve property 'b' from compiled variable 0 object */ ADD ~5, $3, $4 /* Add those values and store the result in temp var 5 */ ASSIGN_OBJ !0, 'c' /* Assign the ADD result to property 'c' of compiled variable 0 object */ OP_DATA ~5 /* Additional data for ASSIGN_OBJ opcode */ FETCH_OBJ_R $6, !0, 'c‘ /* Retrieve property 'c' from compiled variable 0 object */ ECHO $6 /* Echo the value */ With CV Note: Properties are re-fetched every time a read or write is performed on them which cannot be avoided due to the magic methods _get(), _set() e.t.c. which can return a different variable on different fetches
63. Function calling <?php $a= 10; $b= 5; foo($a, $b); function foo(&$x, &$y) { echo "foo called with: $x $y"; } foo($a, $b); ?> line # op fetch ext operands ---------------------------------------------------- ------------------- 2 0 ECHO '%0D%0A+' 4 1 ASSIGN !0, 10 5 2 ASSIGN !1, 5 6 3 INIT_FCALL_BY_NAME 'foo' 4 SEND_VAR !0 5 SEND_VAR !1 6 DO_FCALL_BY_NAME 2 0 8 7 NOP 14 8 SEND_REF !0 9 SEND_REF !1 10 DO_FCALL 2 'foo', 0 17 11 RETURN 1 12 ZEND_HANDLE_EXCEPTION SEND_VAR opcode’s extended_value set when FCALL_BY_NAME to force SEND_VAR handler to check expected args at RUNTIME and if call by REF expected it re-dispatches SEND_REF handler Uses EX(fbc) set by INIT_FCALL_BY_NAME to access required arg info. extended_value on FCALL opcode is number of arguments passed opcodes for global scope
64.
65. Example 1: Passing arguments by value without splitting line # op ext operands -------------------------------------------------- 3 0 NOP 8 1 ASSIGN !0, 10 9 2 ASSIGN !1, 5 10 3 ASSIGN !2, !0 12 4 SEND_VAR !0 5 SEND_VAR !1 6 DO_FCALL 2 'foo', 0 15 7 RETURN 1 8 ZEND_HANDLE_EXCEPTION value=10 refcount= 3 is_ref= 0 argument_stack <?php function foo($x, $y) { echo "foo called with: $x $y"; } $a= 10; $b= 5; $c= $a foo($a, $b); ?> value=5 refcount=2 is_ref= 0 a b c symbol_table After opcode #5: number of args opcodes for global scope no need to split zval for $a as its part of a “copy on write” set NULL NULL arg1 arg2 2
66. Example 2: Passing arguments by value when splitting required line # op ext operands ------------------------------------------------- 3 0 NOP 8 1 ASSIGN !0, 10 9 2 ASSIGN !1, 5 10 3 ASSIGN !2, !0 12 4 SEND_VAR !0 5 SEND_VAR !1 6 DO_FCALL 2 'foo', 0 15 7 RETURN 1 8 ZEND_HANDLE_EXCEPTION <?php function foo($x, $y) { echo "foo called with: $x $y"; } $a= 10; $b= 5; $c= &$a foo($a, $b); ?> After opcode #5: we have to split zval for $a as its part of a “change on write” set opcodes for global scope NULL NULL arg1 arg2 2 value=10 refcount= 2 is_ref= 1 argument_stack value=5 refcount=2 is_ref= 0 a b c symbol_table value=10 refcount=1 is_ref= 0
67. Example 3: Passing arguments by reference when splitting required value=10 refcount= 1 is_ref= 0 argument_stack <?php function foo($x, $y) { echo "foo called with: $x $y"; } $a= 10; $b= 5; $c = $a foo(&$a, &$b); ?> value=10 refcount=2 is_ref= 1 a b c symbol_table CV is actually in op1 not result operand line # op ext operands ------------------------------------------------ 3 0 NOP 8 1 ASSIGN !0, 10 9 2 ASSIGN !1, 5 10 3 ASSIGN !2, !0 11 4 SEND_REF !0 5 SEND_REF !1 6 DO_FCALL 2 'foo', 0 14 7 RETURN 1 8 ZEND_HANDLE_EXCEPTION value=10 refcount= 2 is_ref= 1 After opcode #5: here we have to split zval for $a as its part of a “copy on write” set NULL NULL arg1 arg2 2
68. Example 4: Passing arguments by reference (compile time) value=10 refcount= 1 is_ref= 0 argument_stack <?php $a= 10; $b= 5; $c= $a; foo($a, $b); function foo(&$x, &$y) { echo "foo called with: $x $y"; } ?> value=10 refcount=2 is_ref= 1 a b c symbol_table line # op ext operands ---------------------------------------------------- 5 0 ASSIGN !0, 10 6 1 ASSIGN !1, 5 7 2 ASSIGN !2, !0 8 3 INIT_FCALL_BY_NAME 'foo' 4 SEND_VAR !0 5 SEND_VAR !1 6 DO_FCALL_BY_NAME 2 0 10 7 NOP 16 8 RETURN 1 9 ZEND_HANDLE_EXCEPTION value=10 refcount= 2 is_ref= 1 After opcode #5: As its FCALL_BY_NAME extra checks in SEND_VAR kick in to check arg info for compile time call by ref. If so redispatch SEND_REF to do right thing ! Not shown but op2 znode is used to save argument number which is used to index into arg info structure NULL NULL arg1 arg2 2
69. Receiving arguments passed by value value=10 refcount= 4 is_ref= 0 argument_stack <?php function foo($x, $y) { echo "foo called with: $x $y"; } $a= 10; $b= 5; $c = $a foo($a, $b); ?> value=10 refcount=3 is_ref= 0 line # op ext operands -------------------------------------------------- 3 0 RECV 1 1 RECV 2 4 2 INIT_STRING ~0 3 ADD_STRING ~0, ~0, 'foo' 4 ADD_STRING ~0, ~0, '+' 5 ADD_STRING ~0, ~0, 'called‘ e.t.c After opcode #1: this is argument number Result op is CV for arg NULL NULL arg1 arg2 2 a b c callers symbol_table x y callee symbol_table
70. Receiving arguments passed by reference value=10 refcount= 1 is_ref= 0 argument_stack <?php function foo($x, $y) { echo "foo called with: $x $y"; } $a= 10; $b= 5; $c = $a foo(&$a, &$b); ?> value=10 refcount=3 is_ref= 1 line # op ext operands -------------------------------------------------- 3 0 RECV 1 1 RECV 2 4 2 INIT_STRING ~0 3 ADD_STRING ~0, ~0, 'foo' 4 ADD_STRING ~0, ~0, '+' 5 ADD_STRING ~0, ~0, 'called‘ e.t.c value=10 refcount= 3 is_ref= 1 After opcode #1: NULL NULL arg1 arg2 2 a b c symbol_table x y symbol_table
71. Function binding <?php function a() { echo "called function a"; } function b() { echo "called fucntio b"; } a(); b(); ?> line # op fetch ext operands ------------------------------------------------------------------------------- 3 0 NOP 6 1 NOP 10 2 DO_FCALL 0 'a', 0 11 3 DO_FCALL 0 'b', 0 14 4 RETURN 1 5 ZEND_HANDLE_EXCEPTION What are these NOP’s ?