char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op, HashTable *vars) /*{{{ */ { char *decode[4] = {NULL, NULL, NULL, NULL}; switch (op->opcode) { case ZEND_JMP: case ZEND_GOTO: case ZEND_FAST_CALL: asprintf(&decode[1], "J%ld", OP_JMP_ADDR(op, op->op1) - ops->opcodes); goto format; case ZEND_JMPZNZ: decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars); asprintf(&decode[2], "J%u or J%" PRIu32, op->op2.opline_num, op->extended_value); goto result; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars); asprintf(&decode[2], "J%ld", OP_JMP_ADDR(op, op->op2) - ops->opcodes); goto result; case ZEND_RECV_INIT: goto result; default: decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars); decode[2] = phpdbg_decode_op(ops, &op->op2, op->op2_type, vars); result: decode[3] = phpdbg_decode_op(ops, &op->result, op->result_type, vars); format: asprintf(&decode[0], "%-20s %-20s %-20s", decode[1] ? decode[1] : "", decode[2] ? decode[2] : "", decode[3] ? decode[3] : ""); } if (decode[1]) free(decode[1]); if (decode[2]) free(decode[2]); if (decode[3]) free(decode[3]); return decode[0]; } /* }}} */
char *phpdbg_decode_input_op( zend_op_array *ops, const zend_op *opline, znode_op op, zend_uchar op_type, uint32_t flags) { char *result = NULL; if (op_type != IS_UNUSED) { result = phpdbg_decode_op(ops, &op, op_type); } else if (ZEND_VM_OP_JMP_ADDR == (flags & ZEND_VM_OP_MASK)) { spprintf(&result, 0, "J%td", OP_JMP_ADDR(opline, op) - ops->opcodes); } else if (ZEND_VM_OP_NUM == (flags & ZEND_VM_OP_MASK)) { spprintf(&result, 0, "%" PRIu32, op.num); } else if (ZEND_VM_OP_TRY_CATCH == (flags & ZEND_VM_OP_MASK)) { if (op.num != (uint32_t)-1) { spprintf(&result, 0, "try-catch(%" PRIu32 ")", op.num); } } else if (ZEND_VM_OP_LIVE_RANGE == (flags & ZEND_VM_OP_MASK)) { if (opline->extended_value & ZEND_FREE_ON_RETURN) { spprintf(&result, 0, "live-range(%" PRIu32 ")", op.num); } } else if (ZEND_VM_OP_THIS == (flags & ZEND_VM_OP_MASK)) { result = estrdup("THIS"); } else if (ZEND_VM_OP_NEXT == (flags & ZEND_VM_OP_MASK)) { result = estrdup("NEXT"); } else if (ZEND_VM_OP_CLASS_FETCH == (flags & ZEND_VM_OP_MASK)) { //zend_dump_class_fetch_type(op.num); } else if (ZEND_VM_OP_CONSTRUCTOR == (flags & ZEND_VM_OP_MASK)) { result = estrdup("CONSTRUCTOR"); } return result; }
char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op) /*{{{ */ { const char *opcode_name = phpdbg_decode_opcode(op->opcode); char *result, *decode[4] = {NULL, NULL, NULL, NULL}; /* EX */ switch (op->opcode) { case ZEND_FAST_CALL: if (op->extended_value == ZEND_FAST_CALL_FROM_FINALLY) { decode[0] = estrdup("FAST_CALL<FROM_FINALLY>"); } break; case ZEND_FAST_RET: if (op->extended_value != 0) { spprintf(&decode[0], 0, "FAST_RET<%s>", op->extended_value == ZEND_FAST_RET_TO_CATCH ? "TO_CATCH" : "TO_FINALLY"); } break; } /* OP1 */ switch (op->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: spprintf(&decode[1], 0, "J%td", OP_JMP_ADDR(op, op->op1) - ops->opcodes); break; case ZEND_INIT_FCALL: case ZEND_RECV: case ZEND_RECV_INIT: case ZEND_RECV_VARIADIC: spprintf(&decode[1], 0, "%" PRIu32, op->op1.num); break; default: decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type); break; } /* OP2 */ switch (op->opcode) { case ZEND_JMPZNZ: spprintf(&decode[2], 0, "J%td or J%td", OP_JMP_ADDR(op, op->op2) - ops->opcodes, ZEND_OFFSET_TO_OPLINE(op, op->extended_value) - ops->opcodes); break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_ASSERT_CHECK: spprintf(&decode[2], 0, "J%td", OP_JMP_ADDR(op, op->op2) - ops->opcodes); break; case ZEND_FAST_CALL: case ZEND_FAST_RET: if (op->extended_value != 0) { spprintf(&decode[2], 0, "J%" PRIu32, op->op2.opline_num); } break; case ZEND_SEND_VAL: case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_REF: case ZEND_SEND_VAR_EX: case ZEND_SEND_USER: spprintf(&decode[2], 0, "%" PRIu32, op->op2.num); break; default: decode[2] = phpdbg_decode_op(ops, &op->op2, op->op2_type); break; } /* RESULT */ switch (op->opcode) { case ZEND_CATCH: spprintf(&decode[3], 0, "%" PRIu32, op->result.num); break; default: decode[3] = phpdbg_decode_op(ops, &op->result, op->result_type); break; } spprintf(&result, 0, "%-23s %-20s %-20s %-20s", decode[0] ? decode[0] : opcode_name, decode[1] ? decode[1] : "", decode[2] ? decode[2] : "", decode[3] ? decode[3] : ""); if (decode[0]) efree(decode[0]); if (decode[1]) efree(decode[1]); if (decode[2]) efree(decode[2]); if (decode[3]) efree(decode[3]); return result; } /* }}} */
int uopz_mock_handler(UOPZ_OPCODE_HANDLER_ARGS) { /* {{{ */ int UOPZ_VM_ACTION = ZEND_USER_OPCODE_DISPATCH; zend_string *key; zval *mock = NULL; zend_class_entry *ce; if (EX(opline)->op1_type == IS_CONST) { ce = CACHED_PTR(Z_CACHE_SLOT_P(EX_CONSTANT(EX(opline)->op1))); if (UNEXPECTED(ce == NULL)) { key = Z_STR_P(EX_CONSTANT(EX(opline)->op1)); } else { key = ce->name; } key = zend_string_tolower(key); } else if(EX(opline)->op1_type == IS_UNUSED) { ce = zend_fetch_class(NULL, EX(opline)->op1.num); if (UNEXPECTED(ce == NULL)) { return UOPZ_VM_ACTION; } key = zend_string_tolower(ce->name); } else { key = zend_string_tolower( Z_CE_P(EX_VAR(EX(opline)->op1.var))->name); } if (UNEXPECTED((mock = zend_hash_find(&UOPZ(mocks), key)))) { switch (Z_TYPE_P(mock)) { case IS_OBJECT: ZVAL_COPY( EX_VAR(EX(opline)->result.var), mock); #if PHP_VERSION_ID < 70100 EX(opline) = OP_JMP_ADDR(EX(opline), EX(opline)->op2); #else if (EX(opline)->extended_value == 0 && (EX(opline)+1)->opcode == ZEND_DO_FCALL) { EX(opline) += 2; } #endif UOPZ_VM_ACTION = ZEND_USER_OPCODE_CONTINUE; break; case IS_STRING: ce = zend_lookup_class(Z_STR_P(mock)); if (EXPECTED(ce)) { if (EX(opline)->op1_type == IS_CONST) { CACHE_PTR(Z_CACHE_SLOT_P(EX_CONSTANT(EX(opline)->op1)), ce); } else if (EX(opline)->op1_type != IS_UNUSED) { Z_CE_P(EX_VAR(EX(opline)->op1.var)) = ce; } else { /* oh dear, can't do what is requested */ } } break; } } zend_string_release(key); if (UOPZ_VM_ACTION == ZEND_USER_OPCODE_DISPATCH) { if (uopz_new_handler) { return uopz_new_handler(UOPZ_OPCODE_HANDLER_ARGS_PASSTHRU); } } return UOPZ_VM_ACTION; } /* }}} */
int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg, uint32_t *func_flags) /* {{{ */ { uint32_t flags = 0; uint32_t i; int j; uint32_t *block_map; zend_function *fn; int blocks_count = 0; zend_basic_block *blocks; zval *zv; cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t)); if (!block_map) { return FAILURE; } /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */ BB_START(0); for (i = 0; i < op_array->last; i++) { zend_op *opline = op_array->opcodes + i; switch(opline->opcode) { case ZEND_RETURN: case ZEND_RETURN_BY_REF: case ZEND_GENERATOR_RETURN: case ZEND_EXIT: case ZEND_THROW: if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_INCLUDE_OR_EVAL: flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; case ZEND_YIELD: case ZEND_YIELD_FROM: if (build_flags & ZEND_CFG_STACKLESS) { BB_START(i + 1); } break; case ZEND_DO_FCALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: flags |= ZEND_FUNC_HAS_CALLS; if (build_flags & ZEND_CFG_STACKLESS) { BB_START(i + 1); } break; case ZEND_DO_ICALL: flags |= ZEND_FUNC_HAS_CALLS; break; case ZEND_INIT_FCALL: case ZEND_INIT_NS_FCALL_BY_NAME: zv = CRT_CONSTANT(opline->op2); if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { /* The third literal is the lowercased unqualified name */ zv += 2; } if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { if (fn->type == ZEND_INTERNAL_FUNCTION) { if (zend_string_equals_literal(Z_STR_P(zv), "extract")) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "compact")) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "parse_str") && opline->extended_value == 1) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "mb_parse_str") && opline->extended_value == 1) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "get_defined_vars")) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_num_args")) { flags |= ZEND_FUNC_VARARG; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_arg")) { flags |= ZEND_FUNC_VARARG; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_args")) { flags |= ZEND_FUNC_VARARG; } } } break; case ZEND_FAST_CALL: BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); BB_START(i + 1); break; case ZEND_FAST_RET: if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMP: BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMPZNZ: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; case ZEND_CATCH: if (!opline->result.num) { BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); } BB_START(i + 1); break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); BB_START(i + 1); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_NEW: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; case ZEND_UNSET_VAR: case ZEND_ISSET_ISEMPTY_VAR: if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) && !(opline->extended_value & ZEND_QUICK_SET)) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && !op_array->function_name) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } break; case ZEND_FETCH_R: case ZEND_FETCH_W: case ZEND_FETCH_RW: case ZEND_FETCH_FUNC_ARG: case ZEND_FETCH_IS: case ZEND_FETCH_UNSET: if ((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && !op_array->function_name) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } break; } } for (j = 0; j < op_array->last_live_range; j++) { BB_START(op_array->live_range[j].start); BB_START(op_array->live_range[j].end); } if (op_array->last_try_catch) { for (j = 0; j < op_array->last_try_catch; j++) { BB_START(op_array->try_catch_array[j].try_op); if (op_array->try_catch_array[j].catch_op) { BB_START(op_array->try_catch_array[j].catch_op); } if (op_array->try_catch_array[j].finally_op) { BB_START(op_array->try_catch_array[j].finally_op); } if (op_array->try_catch_array[j].finally_end) { BB_START(op_array->try_catch_array[j].finally_end); } } } cfg->blocks_count = blocks_count; /* Build CFG, Step 2: Build Array of Basic Blocks */ cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count); if (!blocks) { return FAILURE; } for (i = 0, blocks_count = -1; i < op_array->last; i++) { if (block_map[i]) { if (blocks_count >= 0) { blocks[blocks_count].end = i - 1; } blocks_count++; blocks[blocks_count].flags = 0; blocks[blocks_count].start = i; blocks[blocks_count].successors[0] = -1; blocks[blocks_count].successors[1] = -1; blocks[blocks_count].predecessors_count = 0; blocks[blocks_count].predecessor_offset = -1; blocks[blocks_count].idom = -1; blocks[blocks_count].loop_header = -1; blocks[blocks_count].level = -1; blocks[blocks_count].children = -1; blocks[blocks_count].next_child = -1; block_map[i] = blocks_count; } else { block_map[i] = (uint32_t)-1; } } blocks[blocks_count].end = i - 1; blocks_count++; /* Build CFG, Step 3: Calculate successors */ for (j = 0; j < blocks_count; j++) { zend_op *opline = op_array->opcodes + blocks[j].end; switch(opline->opcode) { case ZEND_FAST_RET: case ZEND_RETURN: case ZEND_RETURN_BY_REF: case ZEND_GENERATOR_RETURN: case ZEND_EXIT: case ZEND_THROW: break; case ZEND_JMP: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); break; case ZEND_JMPZNZ: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_CATCH: if (!opline->result.num) { record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); record_successor(blocks, j, 1, j + 1); } else { record_successor(blocks, j, 0, j + 1); } break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); record_successor(blocks, j, 1, j + 1); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_NEW: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_FAST_CALL: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; default: record_successor(blocks, j, 0, j + 1); break; } } /* Build CFG, Step 4, Mark Reachable Basic Blocks */ zend_mark_reachable_blocks(op_array, cfg, 0); if (func_flags) { *func_flags |= flags; } return SUCCESS; }
char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op) /*{{{ */ { char *decode[4] = {NULL, NULL, NULL, NULL}; /* OP1 */ switch (op->opcode) { case ZEND_JMP: case ZEND_FAST_CALL: asprintf(&decode[1], "J%ld", OP_JMP_ADDR(op, op->op1) - ops->opcodes); break; case ZEND_INIT_FCALL: case ZEND_RECV: case ZEND_RECV_INIT: case ZEND_RECV_VARIADIC: asprintf(&decode[1], "%" PRIu32, op->op1.num); break; default: decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type); break; } /* OP2 */ switch (op->opcode) { /* TODO: ZEND_FAST_CALL, ZEND_FAST_RET op2 */ case ZEND_JMPZNZ: asprintf(&decode[2], "J%u or J%" PRIu32, OP_JMP_ADDR(op, op->op2) - ops->opcodes, ZEND_OFFSET_TO_OPLINE(op, op->extended_value) - ops->opcodes); break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_ASSERT_CHECK: asprintf(&decode[2], "J%ld", OP_JMP_ADDR(op, op->op2) - ops->opcodes); break; case ZEND_SEND_VAL: case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_REF: case ZEND_SEND_VAR_EX: case ZEND_SEND_USER: asprintf(&decode[2], "%" PRIu32, op->op2.num); break; default: decode[2] = phpdbg_decode_op(ops, &op->op2, op->op2_type); break; } /* RESULT */ switch (op->opcode) { case ZEND_CATCH: asprintf(&decode[2], "%" PRIu32, op->result.num); break; default: decode[3] = phpdbg_decode_op(ops, &op->result, op->result_type); break; } #if 0 if (ops->T_liveliness) { uint32_t *var = ops->T_liveliness + (op - ops->opcodes); if (*var != (uint32_t)-1) { smart_str str = {0}; var = ops->T_liveliness + (*var); smart_str_appends(&str, "; [@"); smart_str_append_long(&str, EX_VAR_TO_NUM(((*var) & ~0x3)) - ops->last_var); while (*(++var) != (uint32_t)-1) { smart_str_appends(&str, ", @"); smart_str_append_long(&str, EX_VAR_TO_NUM(((*var) & ~0x3)) - ops->last_var); } smart_str_appendc(&str, ']'); smart_str_0(&str); asprintf(&decode[0], "%-20s %-20s %-20s%-20s", decode[1] ? decode[1] : "", decode[2] ? decode[2] : "", decode[3] ? decode[3] : "", ZSTR_VAL(str.s)); smart_str_free(&str); if (decode[1]) free(decode[1]); if (decode[2]) free(decode[2]); if (decode[3]) free(decode[3]); return decode[0]; } } #endif asprintf(&decode[0], "%-20s %-20s %-20s", decode[1] ? decode[1] : "", decode[2] ? decode[2] : "", decode[3] ? decode[3] : ""); if (decode[1]) free(decode[1]); if (decode[2]) free(decode[2]); if (decode[3]) free(decode[3]); return decode[0]; } /* }}} */
char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op, HashTable *vars) /*{{{ */ { char *decode[4] = {NULL, NULL, NULL, NULL}; /* OP1 */ switch (op->opcode) { case ZEND_JMP: case ZEND_GOTO: case ZEND_FAST_CALL: asprintf(&decode[1], "J%ld", OP_JMP_ADDR(op, op->op1) - ops->opcodes); break; case ZEND_INIT_FCALL: case ZEND_RECV: case ZEND_RECV_INIT: case ZEND_RECV_VARIADIC: asprintf(&decode[1], "%" PRIu32, op->op1.num); break; default: decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars); break; } /* OP2 */ switch (op->opcode) { /* TODO: ZEND_FAST_CALL, ZEND_FAST_RET op2 */ case ZEND_JMPZNZ: asprintf(&decode[2], "J%u or J%" PRIu32, op->op2.opline_num, op->extended_value); break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_ASSERT_CHECK: asprintf(&decode[2], "J%ld", OP_JMP_ADDR(op, op->op2) - ops->opcodes); break; case ZEND_SEND_VAL: case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR: case ZEND_SEND_VAR_NO_REF: case ZEND_SEND_REF: case ZEND_SEND_VAR_EX: case ZEND_SEND_USER: asprintf(&decode[2], "%" PRIu32, op->op2.num); break; default: decode[2] = phpdbg_decode_op(ops, &op->op2, op->op2_type, vars); break; } /* RESULT */ switch (op->opcode) { case ZEND_CATCH: asprintf(&decode[2], "%" PRIu32, op->result.num); break; default: decode[3] = phpdbg_decode_op(ops, &op->result, op->result_type, vars); break; } asprintf(&decode[0], "%-20s %-20s %-20s", decode[1] ? decode[1] : "", decode[2] ? decode[2] : "", decode[3] ? decode[3] : ""); if (decode[1]) free(decode[1]); if (decode[2]) free(decode[2]); if (decode[3]) free(decode[3]); return decode[0]; } /* }}} */
int zend_build_cfg(zend_arena **arena, zend_op_array *op_array, int rt_constants, int stackless, zend_cfg *cfg, uint32_t *func_flags) /* {{{ */ { uint32_t flags = 0; uint32_t i; int j; uint32_t *block_map; zend_function *fn; int blocks_count = 0; zend_basic_block *blocks; zval *zv; cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t)); if (!block_map) { return FAILURE; } /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */ BB_START(0); if ((op_array->fn_flags & ZEND_ACC_CLOSURE) && op_array->static_variables) { // FIXME: Really we should try to perform variable initialization flags |= ZEND_FUNC_TOO_DYNAMIC; } for (i = 0; i < op_array->last; i++) { zend_op *opline = op_array->opcodes + i; switch(opline->opcode) { case ZEND_RETURN: case ZEND_RETURN_BY_REF: case ZEND_GENERATOR_RETURN: case ZEND_EXIT: case ZEND_THROW: if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_INCLUDE_OR_EVAL: case ZEND_YIELD: case ZEND_YIELD_FROM: flags |= ZEND_FUNC_TOO_DYNAMIC; if (stackless) { BB_START(i + 1); } break; case ZEND_DO_FCALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: flags |= ZEND_FUNC_HAS_CALLS; if (stackless) { BB_START(i + 1); } break; case ZEND_DO_ICALL: flags |= ZEND_FUNC_HAS_CALLS; break; case ZEND_INIT_FCALL: if (rt_constants) { zv = RT_CONSTANT(op_array, opline->op2); } else { zv = CT_CONSTANT_EX(op_array, opline->op2.constant); } if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { if (fn->type == ZEND_INTERNAL_FUNCTION) { if (Z_STRLEN_P(zv) == sizeof("extract")-1 && memcmp(Z_STRVAL_P(zv), "extract", sizeof("extract")-1) == 0) { flags |= ZEND_FUNC_TOO_DYNAMIC; } else if (Z_STRLEN_P(zv) == sizeof("compact")-1 && memcmp(Z_STRVAL_P(zv), "compact", sizeof("compact")-1) == 0) { flags |= ZEND_FUNC_TOO_DYNAMIC; } else if (Z_STRLEN_P(zv) == sizeof("parse_str")-1 && memcmp(Z_STRVAL_P(zv), "parse_str", sizeof("parse_str")-1) == 0) { flags |= ZEND_FUNC_TOO_DYNAMIC; } else if (Z_STRLEN_P(zv) == sizeof("mb_parse_str")-1 && memcmp(Z_STRVAL_P(zv), "mb_parse_str", sizeof("mb_parse_str")-1) == 0) { flags |= ZEND_FUNC_TOO_DYNAMIC; } else if (Z_STRLEN_P(zv) == sizeof("get_defined_vars")-1 && memcmp(Z_STRVAL_P(zv), "get_defined_vars", sizeof("get_defined_vars")-1) == 0) { flags |= ZEND_FUNC_TOO_DYNAMIC; } else if (Z_STRLEN_P(zv) == sizeof("func_num_args")-1 && memcmp(Z_STRVAL_P(zv), "func_num_args", sizeof("func_num_args")-1) == 0) { flags |= ZEND_FUNC_VARARG; } else if (Z_STRLEN_P(zv) == sizeof("func_get_arg")-1 && memcmp(Z_STRVAL_P(zv), "func_get_arg", sizeof("func_get_arg")-1) == 0) { flags |= ZEND_FUNC_VARARG; } else if (Z_STRLEN_P(zv) == sizeof("func_get_args")-1 && memcmp(Z_STRVAL_P(zv), "func_get_args", sizeof("func_get_args")-1) == 0) { flags |= ZEND_FUNC_VARARG; } } } break; case ZEND_FAST_CALL: flags |= ZEND_FUNC_TOO_DYNAMIC; BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); BB_START(i + 1); break; case ZEND_FAST_RET: flags |= ZEND_FUNC_TOO_DYNAMIC; if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); BB_START(i + 1); break; case ZEND_JMP: BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMPZNZ: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; case ZEND_CATCH: flags |= ZEND_FUNC_TOO_DYNAMIC; if (!opline->result.num) { BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); } BB_START(i + 1); break; case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); BB_START(i + 1); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_NEW: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; case ZEND_DECLARE_LAMBDA_FUNCTION: { //??? zend_op_array *lambda_op_array; //??? //??? if (rt_constants) { //??? zv = RT_CONSTANT(op_array, opline->op1); //??? } else { //??? zv = CT_CONSTANT_EX(op_array, opline->op1.constant); //??? } //??? if (ctx->main_script && //??? (lambda_op_array = zend_hash_find_ptr(&ctx->main_script->function_table, Z_STR_P(zv))) != NULL) { //??? if (lambda_op_array->type == ZEND_USER_FUNCTION && //??? lambda_op_array->static_variables) { //??? // FIXME: Really we should try to perform alias //??? // analysis on variables used by the closure //??? info->flags |= ZEND_FUNC_TOO_DYNAMIC; //??? } //??? } else { //??? // FIXME: how to find the lambda function? flags |= ZEND_FUNC_TOO_DYNAMIC; //??? } } break; case ZEND_UNSET_VAR: if (!(opline->extended_value & ZEND_QUICK_SET)) { flags |= ZEND_FUNC_TOO_DYNAMIC; } break; case ZEND_FETCH_R: case ZEND_FETCH_W: case ZEND_FETCH_RW: case ZEND_FETCH_FUNC_ARG: case ZEND_FETCH_IS: case ZEND_FETCH_UNSET: if ((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) { flags |= ZEND_FUNC_TOO_DYNAMIC; } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && !op_array->function_name) { flags |= ZEND_FUNC_TOO_DYNAMIC; } break; } } for (j = 0; j < op_array->last_live_range; j++) { BB_START(op_array->live_range[j].start); BB_START(op_array->live_range[j].end); } if (op_array->last_try_catch) { for (j = 0; j < op_array->last_try_catch; j++) { BB_START(op_array->try_catch_array[j].try_op); if (op_array->try_catch_array[j].catch_op) { BB_START(op_array->try_catch_array[j].catch_op); } if (op_array->try_catch_array[j].finally_op) { BB_START(op_array->try_catch_array[j].finally_op); } if (op_array->try_catch_array[j].finally_end) { BB_START(op_array->try_catch_array[j].finally_end); } } } cfg->blocks_count = blocks_count; /* Build CFG, Step 2: Build Array of Basic Blocks */ cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count); if (!blocks) { return FAILURE; } for (i = 0, blocks_count = -1; i < op_array->last; i++) { if (block_map[i]) { if (blocks_count >= 0) { blocks[blocks_count].end = i - 1; } blocks_count++; blocks[blocks_count].flags = 0; blocks[blocks_count].start = i; blocks[blocks_count].successors[0] = -1; blocks[blocks_count].successors[1] = -1; blocks[blocks_count].predecessors_count = 0; blocks[blocks_count].predecessor_offset = -1; blocks[blocks_count].idom = -1; blocks[blocks_count].loop_header = -1; blocks[blocks_count].level = -1; blocks[blocks_count].children = -1; blocks[blocks_count].next_child = -1; block_map[i] = blocks_count; } else { block_map[i] = (uint32_t)-1; } } blocks[blocks_count].end = i - 1; blocks_count++; /* Build CFG, Step 3: Calculate successors */ for (j = 0; j < blocks_count; j++) { zend_op *opline = op_array->opcodes + blocks[j].end; switch(opline->opcode) { case ZEND_FAST_RET: case ZEND_RETURN: case ZEND_RETURN_BY_REF: case ZEND_GENERATOR_RETURN: case ZEND_EXIT: case ZEND_THROW: break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_JMP: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); break; case ZEND_JMPZNZ: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_CATCH: if (!opline->result.num) { record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); record_successor(blocks, j, 1, j + 1); } else { record_successor(blocks, j, 0, j + 1); } break; case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); record_successor(blocks, j, 1, j + 1); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_NEW: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_FAST_CALL: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; default: record_successor(blocks, j, 0, j + 1); break; } } /* Build CFG, Step 4, Mark Reachable Basic Blocks */ zend_mark_reachable_blocks(op_array, cfg, 0); if (func_flags) { *func_flags = flags; } return SUCCESS; }