diff --git a/dyninstAPI/src/Relocation/DynCFGMaker.C b/dyninstAPI/src/Relocation/DynCFGMaker.C index 3a67c9f037..c2ec5c74ad 100644 --- a/dyninstAPI/src/Relocation/DynCFGMaker.C +++ b/dyninstAPI/src/Relocation/DynCFGMaker.C @@ -47,6 +47,10 @@ PatchFunction* DynCFGMaker::makeFunction(Function* f, PatchObject* obj) { Address code_base = obj->codeBase(); mapped_object* mo = SCAST_MO(obj); + + // func_instance being constructed is unusable without blocks + if (!f->entry()) mo->analyzeIfDeferred(); + parse_func* img_func = SCAST_PF(f); if (!img_func) return NULL; assert(img_func->getSymtabFunction()); diff --git a/dyninstAPI/src/dynProcess.C b/dyninstAPI/src/dynProcess.C index 96fc823f20..2f6a174f50 100644 --- a/dyninstAPI/src/dynProcess.C +++ b/dyninstAPI/src/dynProcess.C @@ -1721,6 +1721,30 @@ void PCProcess::installInstrRequests(const std::vector &requests) FILE__, __LINE__, matchingFuncs.size(), req->func.c_str(), req->lib.c_str()); } + // Matches in libraries that import the symbol are PLT stubs that + // jump to the definition, so instrumenting the definition covers them. + // Dropping them also avoids relocate()'s overlap check, whose + // Block::getFuncs() forces a full CFG parse of the stub's library. + // If nothing is left, every match was a stub and we keep them all, + // since then only the stub can carry the probe. Static binaries have + // no stubs and are unaffected. + if (!matchingFuncs.empty()) { + std::vector definitions; + for (func_instance *func : matchingFuncs) { + if (func && func->ifunc() && !func->ifunc()->isPLTFunction()) + definitions.push_back(func); + } + + if (!definitions.empty() && definitions.size() < matchingFuncs.size()) { + inst_printf("%s[%d]: %lu of %lu matches for %s are PLT stubs, " + "instrumenting the definition(s) only\n", + FILE__, __LINE__, + matchingFuncs.size() - definitions.size(), + matchingFuncs.size(), req->func.c_str()); + matchingFuncs.swap(definitions); + } + } + for (unsigned funcIter = 0; funcIter < matchingFuncs.size(); funcIter++) { func_instance *func = matchingFuncs[funcIter]; if (!func) { diff --git a/dyninstAPI/src/image.C b/dyninstAPI/src/image.C index aeed7c37b4..7a123503c8 100644 --- a/dyninstAPI/src/image.C +++ b/dyninstAPI/src/image.C @@ -1071,7 +1071,6 @@ image::getAllFunctions() const std::vector &image::getAllVariables() { - analyzeIfNeeded(); return everyUniqueVariable; } @@ -1079,7 +1078,6 @@ const std::vector &image::getExportedVariables() const { return const std::vector &image::getCreatedVariables() { - analyzeIfNeeded(); return createdVariables; } @@ -1152,7 +1150,8 @@ unsigned int int_addrHash(const Address& addr) { image *image::parseImage(fileDescriptor &desc, BPatch_hybridMode mode, - bool parseGaps) + bool parseGaps, + bool delayedParse) { /* * Check to see if we have parsed this image before. We will @@ -1186,7 +1185,7 @@ image *image::parseImage(fileDescriptor &desc, #endif startup_printf("%s[%d]: about to create image\n", FILE__, __LINE__); - image *ret = new image(desc, err, mode, parseGaps); + image *ret = new image(desc, err, mode, parseGaps, delayedParse); startup_printf("%s[%d]: created image\n", FILE__, __LINE__); if (ret->isSharedObject()) @@ -1290,6 +1289,11 @@ void image::analyzeIfNeeded() { } } +void image::analyzeIfDeferred() { + if (deferredParse_) + analyzeIfNeeded(); +} + static bool CheckForPowerPreamble(parse_block* entryBlock, Address &tocBase) { ParseAPI::Block::Insns insns; entryBlock->getInsns(insns); @@ -1390,7 +1394,8 @@ void image::analyzeImage() { image::image(fileDescriptor &desc, bool &err, BPatch_hybridMode mode, - bool parseGaps) : + bool parseGaps, + bool delayedParse) : desc_(desc), imageOffset_(0), imageLen_(0), @@ -1413,9 +1418,11 @@ image::image(fileDescriptor &desc, trackNewBlocks_(false), refCount(1), parseState_(unparsed), + deferredParse_(false), parseGaps_(parseGaps), mode_(mode), - arch(Dyninst::Arch_none) + arch(Dyninst::Arch_none), + pltStubAddrsInitialized_(false) { #if defined(os_linux) || defined(os_freebsd) string file = desc_.file().c_str(); @@ -1523,7 +1530,13 @@ image::image(fileDescriptor &desc, // fprintf(stderr, "#### create CodeObject for %s\n", desc.file().c_str()); img_fact_ = new DynCFGFactory(this); parse_cb_ = new DynParseCallback(this); - obj_ = new CodeObject(cs_,img_fact_,parse_cb_,BPatch_defensiveMode == mode); + + // Only normal mode defers CFG when delayedParsing is enabled. + // ppc64 is excluded because the code below walks functions and their entry + // blocks, which requires a parsed CFG. + const bool is_ppc64 = (cs_->getArch() == Arch_ppc64); + deferredParse_ = delayedParse && (mode == BPatch_normalMode) && !is_ppc64; + obj_ = new CodeObject(cs_,img_fact_,parse_cb_,BPatch_defensiveMode == mode, deferredParse_); if (obj_->cs()->getArch() == Arch_ppc64) { // The PowerPC new ABI typically generate two entries per function. @@ -1878,6 +1891,28 @@ const std::vector *image::findFuncVectorByPretty(const std::string } } +// A PLT stub has no symbol of its own, so only parsing creates its parse_func +bool image::parsePltStubs(const std::string &name) +{ + // linkage() is address -> name; we need name -> addresses. Build it once + if (!pltStubAddrsInitialized_) { + pltStubAddrsInitialized_ = true; + for (auto const &entry : cs_->linkage()) { + pltStubAddrs_[entry.second].push_back(entry.first); + } + } + + // The parser names stubs from this same table, so absent means none exists + auto iter = pltStubAddrs_.find(name); + if (iter == pltStubAddrs_.end()) + return false; + + for (Address stub : iter->second) { + codeObject()->parse(stub, false); + } + return true; +} + // Return the vector of functions associated with a mangled name // Very well might be more than one! -- multiple static functions in different .o files @@ -1900,6 +1935,15 @@ const std::vector *image::findFuncVectorByMangled(const std::stri if (res->empty()) { // Lookup PLT stubs auto it = plt_parse_funcs.find(name); + + // A miss here can be a false negative: only parsing fills + // plt_parse_funcs, and the image may not have been parsed yet. + // Parse just the stubs rather than the whole image. + if (it == plt_parse_funcs.end() && deferredParse_ && !isParsed()) { + if (parsePltStubs(name)) + it = plt_parse_funcs.find(name); + } + if (it != plt_parse_funcs.end()) { res->push_back(it->second); } diff --git a/dyninstAPI/src/image.h b/dyninstAPI/src/image.h index fb235f0f9f..10deeb6fce 100644 --- a/dyninstAPI/src/image.h +++ b/dyninstAPI/src/image.h @@ -253,7 +253,8 @@ class image : public codeRange { public: static image *parseImage(fileDescriptor &desc, BPatch_hybridMode mode, - bool parseGaps); + bool parseGaps, + bool delayedParse); // And to get rid of them if we need to re-parse static void removeImage(image *img); @@ -266,9 +267,11 @@ class image : public codeRange { image(fileDescriptor &desc, bool &err, BPatch_hybridMode mode, - bool parseGaps); + bool parseGaps, + bool delayedParse); void analyzeIfNeeded(); + void analyzeIfDeferred(); bool isParsed() { return parseState_ == analyzed; } parse_func* addFunction(Address functionEntryAddr, const char *name=NULL); @@ -422,6 +425,10 @@ class image : public codeRange { bool determineImageType(); bool addSymtabVariables(); + // Parse the PLT stubs for an external name, located via the linkage table so + // no CFG is needed. False when the name is not linked through a stub. + bool parsePltStubs(const std::string &name); + void setModuleLanguages(std::unordered_map *mod_langs); // We have a _lot_ of lookup types; this handles proper entry @@ -504,11 +511,14 @@ class image : public codeRange { int refCount; imageParseState_t parseState_; + bool deferredParse_; bool parseGaps_; BPatch_hybridMode mode_; Dyninst::Architecture arch; dyn_hash_map plt_parse_funcs; + std::map > pltStubAddrs_; + bool pltStubAddrsInitialized_; }; diff --git a/dyninstAPI/src/mapped_object.C b/dyninstAPI/src/mapped_object.C index 18f2cc7dbc..bfa5119833 100644 --- a/dyninstAPI/src/mapped_object.C +++ b/dyninstAPI/src/mapped_object.C @@ -47,6 +47,7 @@ #include "Parsing.h" #include "instPoint.h" #include +#include "BPatch.h" #include "BPatch_image.h" #include "PatchCFG.h" #include "PCProcess.h" @@ -151,7 +152,9 @@ mapped_object *mapped_object::createMappedObject(fileDescriptor &desc, startup_printf("%s[%d]: about to parseImage\n", FILE__, __LINE__); startup_printf("%s[%d]: name %s, codeBase 0x%lx, dataBase 0x%lx\n", FILE__, __LINE__, desc.file().c_str(), desc.code(), desc.data()); - image *img = image::parseImage( desc, analysisMode, parseGaps); + const bool delayedParse = + BPatch::bpatch != NULL && BPatch::bpatch->delayedParsingOn(); + image *img = image::parseImage( desc, analysisMode, parseGaps, delayedParse); if (!img) { startup_printf("%s[%d]: failed to parseImage\n", FILE__, __LINE__); return NULL; @@ -648,6 +651,9 @@ func_instance *mapped_object::findFuncByEntry(const Address addr) { return NULL; } +void mapped_object::analyzeIfDeferred() { parse_img()->analyzeIfDeferred(); } + +void mapped_object::ensureParsed() { analyzeIfDeferred(); } const std::vector &mapped_object::getModules() { // everyModule may be out of date... diff --git a/dyninstAPI/src/mapped_object.h b/dyninstAPI/src/mapped_object.h index f0c695324d..5b87fb0b11 100644 --- a/dyninstAPI/src/mapped_object.h +++ b/dyninstAPI/src/mapped_object.h @@ -308,6 +308,9 @@ class mapped_object : public codeRange, public Dyninst::PatchAPI::DynObject { func_instance *findFunction(ParseAPI::Function *img_func); + // Builds the CFG of an image whose parse was deferred. No-op otherwise. + void analyzeIfDeferred(); + int_variable *findVariable(image_variable *img_var); block_instance *findBlock(ParseAPI::Block *); @@ -331,6 +334,10 @@ class mapped_object : public codeRange, public Dyninst::PatchAPI::DynObject { void destroy(PatchAPI::PatchBlock *b); // void destroy(PatchAPI::PatchEdge *e); // don't need to destroy anything + protected: + // Builds the CFG of a deferred image before PatchObject walks its functions. + void ensureParsed() override; + private: // // PRIVATE DATA MEMBERS diff --git a/parseAPI/src/Parser.C b/parseAPI/src/Parser.C index 4a83b8dfb4..4be3637a4b 100644 --- a/parseAPI/src/Parser.C +++ b/parseAPI/src/Parser.C @@ -214,6 +214,31 @@ Parser::parse_at( return; } + // Nothing has consumed hint_funcs yet, so the clear below would strand every + // unparsed function and the COMPLETE downgrade would lock parse() out for good. + // Park the seeds and put them back once this targeted parse has finalized its + // own batch; re-queuing them here instead would double-finalize them. + struct parked_seeds { + Parser &parser; + const bool active; + dyn_c_vector hints; + dyn_c_vector discovered; + + parked_seeds(Parser &p, bool a) : parser(p), active(a) { + if (active) { + hints.swap(parser.hint_funcs); + discovered.swap(parser.discover_funcs); + } + } + ~parked_seeds() { + if (active) { + parser.hint_funcs.swap(hints); + parser.discover_funcs.swap(discovered); + parser._parse_state = UNPARSED; + } + } + } seeds(*this, _parse_state == UNPARSED); + // Reset parser status _parse_state = PARTIAL; hint_funcs.clear(); @@ -245,7 +270,7 @@ Parser::parse_at( finalize(); // downgrade state if necessary - if(_parse_state > COMPLETE) + if(!seeds.active && _parse_state > COMPLETE) _parse_state = COMPLETE; } diff --git a/patchAPI/h/PatchObject.h b/patchAPI/h/PatchObject.h index c9d37be567..6d4b8779c9 100644 --- a/patchAPI/h/PatchObject.h +++ b/patchAPI/h/PatchObject.h @@ -121,6 +121,10 @@ class PATCHAPI_EXPORT PatchObject { void copyCFG(PatchObject* par_obj); bool splitBlock(PatchBlock *first, ParseAPI::Block *second); + // Allows a subclass to build its CFG before createFuncs() runs. + // createFuncs() walks co()->funcs(), requiring a full parse + virtual void ensureParsed() {} + void createFuncs(); void createBlocks(); void createEdges(); diff --git a/patchAPI/src/PatchObject.C b/patchAPI/src/PatchObject.C index 1faffa68e9..c089b3d600 100644 --- a/patchAPI/src/PatchObject.C +++ b/patchAPI/src/PatchObject.C @@ -135,6 +135,7 @@ Address PatchObject::codeOffsetToAddr(Address offset) const { } void PatchObject::createFuncs() { + ensureParsed(); for (auto iter = co()->funcs().begin(); iter != co()->funcs().end(); ++iter) { getFunc(*iter, true); }