OffsetSet instrSuccOffsets(PC opc, const Unit* unit) { OffsetSet succBcOffs; auto const bcStart = unit->entry(); auto const op = peek_op(opc); if (!instrIsControlFlow(op)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); return succBcOffs; } if (instrAllowsFallThru(op)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); } if (isSwitch(op)) { foreachSwitchTarget(opc, [&](Offset offset) { succBcOffs.insert(offset + opc - bcStart); }); } else { Offset target = instrJumpTarget(bcStart, opc - bcStart); if (target != InvalidAbsoluteOffset) { succBcOffs.insert(target); } } return succBcOffs; }
OffsetSet instrSuccOffsets(Op* opc, const Unit* unit) { OffsetSet succBcOffs; Op* bcStart = (Op*)(unit->entry()); if (!instrIsControlFlow(*opc)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); return succBcOffs; } if (instrAllowsFallThru(*opc)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); } if (isSwitch(*opc)) { foreachSwitchTarget(opc, [&](Offset& offset) { succBcOffs.insert(offset + opc - bcStart); }); } else { Offset target = instrJumpTarget(bcStart, opc - bcStart); if (target != InvalidAbsoluteOffset) { succBcOffs.insert(target); } } return succBcOffs; }
/** * Create blocks for each entry point as well as ordinary control * flow boundaries. Calls are not treated as basic-block ends. */ void GraphBuilder::createBlocks() { PC bc = m_unit->entry(); m_graph->param_count = m_func->params().size(); m_graph->first_linear = createBlock(m_func->base()); // DV entry points m_graph->entries = new (m_arena) Block*[m_graph->param_count + 1]; int dv_index = 0; for (Range<Func::ParamInfoVec> p(m_func->params()); !p.empty(); ) { const Func::ParamInfo& param = p.popFront(); m_graph->entries[dv_index++] = !param.hasDefaultValue() ? 0 : createBlock(param.funcletOff()); } // main entry point assert(dv_index == m_graph->param_count); m_graph->entries[dv_index] = createBlock(m_func->base()); // ordinary basic block boundaries for (InstrRange i = funcInstrs(m_func); !i.empty(); ) { PC pc = i.popFront(); if (isCF(pc) && !i.empty()) createBlock(i.front()); if (isSwitch(*reinterpret_cast<const Op*>(pc))) { foreachSwitchTarget(reinterpret_cast<const Op*>(pc), [&](Offset& o) { createBlock(pc + o); }); } else { Offset target = instrJumpTarget((Op*)bc, pc - bc); if (target != InvalidAbsoluteOffset) createBlock(target); } } }
/** * Create blocks for each entry point as well as ordinary control * flow boundaries. Calls are not treated as basic-block ends. */ void GraphBuilder::createBlocks() { PC bc = m_unit->entry(); m_graph->param_count = m_func->params().size(); m_graph->first_linear = createBlock(m_func->base()); // DV entry points m_graph->entries = new (m_arena) Block*[m_graph->param_count + 1]; int dv_index = 0; for (auto& param : m_func->params()) { m_graph->entries[dv_index++] = !param.hasDefaultValue() ? 0 : createBlock(param.funcletOff); } // main entry point assert(dv_index == m_graph->param_count); m_graph->entries[dv_index] = createBlock(m_func->base()); // ordinary basic block boundaries for (InstrRange i = funcInstrs(m_func); !i.empty(); ) { PC pc = i.popFront(); if ((isCF(pc) || isTF(pc)) && !i.empty()) createBlock(i.front()); if (isSwitch(peek_op(pc))) { foreachSwitchTarget(pc, [&](Offset o) { createBlock(pc + o); }); } else { Offset target = instrJumpTarget(bc, pc - bc); if (target != InvalidAbsoluteOffset) createBlock(target); } } }
// Place internal breakpoints to get out of the current function. This may place // multiple internal breakpoints, and it may place them more than one frame up. // Some instructions can cause PHP to be invoked without an explicit call. A set // which causes a destructor to run, a iteration init which causes an object's // next() method to run, a RetC which causes destructors to run, etc. This // recgonizes such cases and ensures we have internal breakpoints to cover the // destination(s) of such instructions. void CmdFlowControl::setupStepOuts() { // Existing step outs should be cleaned up before making new ones. assert(!hasStepOuts()); auto fp = g_context->getFP(); if (!fp) return; // No place to step out to! Offset returnOffset; bool fromVMEntry; while (!hasStepOuts()) { fp = g_context->getPrevVMState(fp, &returnOffset, nullptr, &fromVMEntry); // If we've run off the top of the stack, just return having setup no // step outs. This will cause cmds like Next and Out to just let the program // run, which is appropriate. if (!fp) break; Unit* returnUnit = fp->m_func->unit(); PC returnPC = returnUnit->at(returnOffset); TRACE(2, "CmdFlowControl::setupStepOuts: at '%s' offset %d opcode %s\n", fp->m_func->fullName()->data(), returnOffset, opcodeToName(*reinterpret_cast<const Op*>(returnPC))); // Don't step out to generated functions, keep looking. if (fp->m_func->line1() == 0) continue; if (fromVMEntry) { TRACE(2, "CmdFlowControl::setupStepOuts: VM entry\n"); // We only execute this for opcodes which invoke more PHP, and that does // not include switches. Thus, we'll have at most two destinations. assert(!isSwitch(*reinterpret_cast<const Op*>(returnPC)) && (numSuccs(reinterpret_cast<const Op*>(returnPC)) <= 2)); // Set an internal breakpoint after the instruction if it can fall thru. if (instrAllowsFallThru(*reinterpret_cast<const Op*>(returnPC))) { Offset nextOffset = returnOffset + instrLen((Op*)returnPC); TRACE(2, "CmdFlowControl: step out to '%s' offset %d (fall-thru)\n", fp->m_func->fullName()->data(), nextOffset); m_stepOut1 = StepDestination(returnUnit, nextOffset); } // Set an internal breakpoint at the target of a control flow instruction. // A good example of a control flow op that invokes PHP is IterNext. if (instrIsControlFlow(*reinterpret_cast<const Op*>(returnPC))) { Offset target = instrJumpTarget(reinterpret_cast<const Op*>(returnPC), 0); if (target != InvalidAbsoluteOffset) { Offset targetOffset = returnOffset + target; TRACE(2, "CmdFlowControl: step out to '%s' offset %d (jump target)\n", fp->m_func->fullName()->data(), targetOffset); m_stepOut2 = StepDestination(returnUnit, targetOffset); } } // If we have no place to step out to, then unwind another frame and try // again. The most common case that leads here is Ret*, which does not // fall-thru and has no encoded target. } else { TRACE(2, "CmdFlowControl: step out to '%s' offset %d\n", fp->m_func->fullName()->data(), returnOffset); m_stepOut1 = StepDestination(returnUnit, returnOffset); } } }
/** * Link ordinary blocks with ordinary edges and set their last instruction * and end offsets */ void GraphBuilder::linkBlocks() { PC bc = m_unit->entry(); Block* block = m_graph->first_linear; block->id = m_graph->block_count++; for (InstrRange i = funcInstrs(m_func); !i.empty(); ) { PC pc = i.popFront(); block->last = pc; if (isCF(pc)) { if (isSwitch(*reinterpret_cast<const Op*>(pc))) { int i = 0; foreachSwitchTarget((Op*)pc, [&](Offset& o) { succs(block)[i++] = at(pc + o); }); } else { Offset target = instrJumpTarget((Op*)bc, pc - bc); if (target != InvalidAbsoluteOffset) { assert(numSuccBlocks(block) > 0); succs(block)[numSuccBlocks(block) - 1] = at(target); } } } PC next_pc = !i.empty() ? i.front() : m_unit->at(m_func->past()); Block* next = at(next_pc); if (next) { block->next_linear = next; block->end = next_pc; if (!isTF(pc)) { assert(numSuccBlocks(block) > 0); succs(block)[0] = next; } block = next; block->id = m_graph->block_count++; } } block->end = m_unit->at(m_func->past()); }
FuncInfo find_func_info(const Func* func) { auto finfo = FuncInfo(func->unit(), func); auto label_num = uint32_t{0}; auto gen_label = [&] (const char* kind) { return folly::format("{}{}", kind, label_num++).str(); }; auto add_target = [&] (const char* kind, Offset off) -> std::string { auto it = finfo.labels.find(off); if (it != end(finfo.labels)) return it->second; auto const label = gen_label(kind); finfo.labels[off] = label; return label; }; auto find_jump_targets = [&] { auto it = func->unit()->at(func->base()); auto const stop = func->unit()->at(func->past()); auto const bcBase = reinterpret_cast<const Op*>(func->unit()->at(0)); for (; it != stop; it += instrLen(reinterpret_cast<const Op*>(it))) { auto const pop = reinterpret_cast<const Op*>(it); auto const off = func->unit()->offsetOf(pop); if (isSwitch(*pop)) { foreachSwitchTarget(pop, [&] (Offset off) { add_target("L", pop - bcBase + off); }); continue; } auto const target = instrJumpTarget(bcBase, off); if (target != InvalidAbsoluteOffset) { add_target("L", target); continue; } } }; auto find_eh_entries = [&] { for (auto& eh : func->ehtab()) { finfo.ehInfo[&eh] = [&]() -> EHInfo { switch (eh.m_type) { case EHEnt::Type::Catch: { auto catches = EHCatch {}; for (auto& kv : eh.m_catches) { auto const clsName = func->unit()->lookupLitstrId(kv.first); catches.blocks[clsName->data()] = add_target("C", kv.second); } return catches; } case EHEnt::Type::Fault: return EHFault { add_target("F", eh.m_fault) }; } not_reached(); }(); finfo.ehStarts.emplace_back(eh.m_base, &eh); } }; auto find_dv_entries = [&] { for (auto i = uint32_t{0}; i < func->numParams(); ++i) { auto& param = func->params()[i]; if (param.hasDefaultValue()) { add_target("DV", func->params()[i].funcletOff()); } } }; find_jump_targets(); find_eh_entries(); find_dv_entries(); return finfo; }