Skip to content

Commit 019b0b8

Browse files
committed
Make sure we can run with no locally available objects.
Add an env var that lets us tell pstack to never open libraries locally. this makes it easier to test debuginfod, and check for correctness for some upstream apps.
1 parent c982065 commit 019b0b8

File tree

5 files changed

+119
-40
lines changed

5 files changed

+119
-40
lines changed

context.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ Context::flush(std::shared_ptr<Elf::Object> o)
7777

7878
std::shared_ptr<Elf::Object>
7979
Context::getImageForName(const std::string &name, bool isDebug) {
80+
if (options.noLocalFiles)
81+
return nullptr;
8082
auto res = getImageIfLoaded(name);
8183
if (res != nullptr)
8284
return res;

libpstack/context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct PstackOptions {
2727
bool nodienames = false; // don't use names from DWARF dies in backtraces.
2828
bool noExtDebug = false; // don't look for exernal ELF info, i.e., using debuglink, or buildid.
2929
bool noDebuginfod = false; // don't use debuginfod client library.
30+
bool noLocalFiles = false;
3031
int maxdepth = std::numeric_limits<int>::max();
3132
int maxframes = 30;
3233
};

libpstack/proc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ struct MappedObject {
241241
class Process : public ps_prochandle {
242242
Elf::Addr entry;
243243
Elf::Addr interpBase;
244+
Elf::Addr execBase{};
244245
void loadSharedObjects(Elf::Addr);
246+
Elf::Off dt_debug{};
245247
public:
246248
std::map<Elf::Addr, MappedObject> objects;
247249
Elf::Addr vdsoBase;

process.cc

Lines changed: 111 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,16 @@ Process::load()
170170
if (auxv)
171171
processAUXV(*auxv);
172172

173-
if (!execImage)
174-
throw (Exception() << "no executable image located for process");
175-
176173
try {
177174
Elf::Addr r_debug_addr = findRDebugAddr();
178175
bool isStatic = r_debug_addr == 0 || r_debug_addr == Elf::Addr(-1);
179176

180-
if (isStatic)
181-
addElfObject("", execImage, 0);
182-
else
177+
if (isStatic) {
178+
if (execImage)
179+
addElfObject("", execImage, 0);
180+
} else {
183181
loadSharedObjects(r_debug_addr);
182+
}
184183
}
185184
catch (const Exception &) {
186185
// We were unable to read the link map.
@@ -218,14 +217,35 @@ auxtype2str(int auxtype) {
218217
#undef AUX_TYPE
219218
}
220219

220+
namespace {
221+
Elf::Addr
222+
extractDtDebugFromDynamicSegment(Process &proc, const Elf::Phdr &phdr, Elf::Addr loadAddr, const char *loc) {
223+
auto dynReader = proc.io->view("PT_DYNAMIC segment", phdr.p_vaddr + loadAddr, phdr.p_filesz);
224+
ReaderArray<Elf::Dyn> dynamic(*dynReader);
225+
for (auto &dyn : dynamic) {
226+
if (dyn.d_tag == DT_DEBUG && dyn.d_un.d_ptr != 0) {
227+
if (proc.context.verbose)
228+
*proc.context.debug << "found rdebugaddr via DT_DEBUG at "
229+
<< std::hex << dyn.d_un.d_ptr << std::dec << " in " << loc << "\n";
230+
return dyn.d_un.d_ptr;
231+
}
232+
}
233+
return 0;
234+
}
235+
}
236+
237+
221238
void
222239
Process::processAUXV(const Reader &auxio)
223240
{
241+
Elf::Addr phOff = 0;
242+
size_t phNum = 0;
243+
224244
for (auto &aux : ReaderArray<Elf::auxv_t>(auxio)) {
225245
Elf::Addr hdr = aux.a_un.a_val;
246+
if (aux.a_type == AT_NULL)
247+
break;
226248
switch (aux.a_type) {
227-
case AT_NULL: // Indicates end of the AUXV vector.
228-
return;
229249
case AT_ENTRY: {
230250
if (context.verbose > 2)
231251
*context.debug << "auxv: AT_ENTRY=" << hdr << std::endl;
@@ -283,12 +303,48 @@ Process::processAUXV(const Reader &auxio)
283303

284304
break;
285305
}
306+
286307
#endif
308+
case AT_PHDR:
309+
phOff = hdr;
310+
break;
311+
case AT_PHNUM:
312+
phNum = hdr;
313+
break;
287314
default:
288315
if (context.verbose > 2)
289316
*context.debug << "auxv: " << auxtype2str( aux.a_type) << ": " << hdr << std::endl;
290317
}
291318
}
319+
if (phOff != 0 && phNum != 0) {
320+
auto view = io->view("phdrs", phOff, sizeof (Elf::Phdr) * phNum);
321+
ReaderArray<Elf::Phdr> headers { *view };
322+
std::optional<Elf::Phdr> ptDynamic;
323+
324+
std::vector<Elf::Addr> notes;
325+
try {
326+
for ( auto phdr : headers ) {
327+
switch (phdr.p_type) {
328+
case PT_PHDR:
329+
// that's the diff between the va's in the process vs the image.
330+
execBase = phOff - phdr.p_vaddr;
331+
break;
332+
case PT_NOTE:
333+
notes.push_back(phdr.p_vaddr);
334+
break;
335+
case PT_DYNAMIC:
336+
ptDynamic = phdr;
337+
break;
338+
}
339+
}
340+
}
341+
catch (const Exception &ex) {
342+
// We may not have a full image of the phdrs.
343+
}
344+
345+
if (ptDynamic && dt_debug == 0)
346+
dt_debug = extractDtDebugFromDynamicSegment(*this, *ptDynamic, execBase, "aux vector");
347+
}
292348
}
293349

294350
static bool
@@ -729,13 +785,17 @@ Process::loadSharedObjects(Elf::Addr rdebugAddr)
729785
// If we see the executable, just add it in and avoid going through the path
730786
// replacement work
731787
if (mapAddr == Elf::Addr(rDebug.r_map)) {
732-
auto loadAddr = entry - execImage->getHeader().e_entry;
733-
if (loadAddr != map.l_addr) {
734-
*context.debug << "calculated load address for executable from process entrypoint ("
735-
<< std::hex << loadAddr << ") does not match link map (" << map.l_addr
736-
<< "). Trusting link-map\n" << std::dec;
788+
if (execImage) {
789+
if (execBase == 0) {
790+
execBase = entry - execImage->getHeader().e_entry;
791+
}
792+
if (execBase != map.l_addr) {
793+
*context.debug << "calculated load address for executable from process entrypoint ("
794+
<< std::hex << execBase << ") does not match link map (" << map.l_addr
795+
<< "). Trusting link-map\n" << std::dec;
796+
}
797+
addElfObject("(exe)", execImage, map.l_addr);
737798
}
738-
addElfObject("(exe)", execImage, map.l_addr);
739799
continue;
740800
}
741801
// If we've loaded the VDSO, and we see it in the link map, just skip it.
@@ -769,34 +829,43 @@ Process::findRDebugAddr()
769829
* supplied by the kernel, and also the executable's desired entrypoint -
770830
* the difference is the load address.
771831
*/
772-
Elf::Off loadAddr = entry - execImage->getHeader().e_entry;
773-
774-
// Find DT_DEBUG in the process's dynamic section.
775-
for (auto &segment : execImage->getSegments(PT_DYNAMIC)) {
776-
// Read from the process, not the executable - the linker will have updated the content.
777-
auto dynReader = io->view("PT_DYNAMIC segment", segment.p_vaddr + loadAddr, segment.p_filesz);
778-
ReaderArray<Elf::Dyn> dynamic(*dynReader);
779-
for (auto &dyn : dynamic)
780-
if (dyn.d_tag == DT_DEBUG && dyn.d_un.d_ptr != 0)
781-
return dyn.d_un.d_ptr;
782-
}
783-
/*
784-
* If there's no DT_DEBUG, we've probably got someone executing a shared
785-
* library, which doesn't have an _r_debug symbol. Use the address of
786-
* _r_debug in the interpreter
787-
*/
788-
if (interpBase && execImage->getInterpreter() != "") {
789-
try {
790-
addElfObject(execImage->getInterpreter(), nullptr, interpBase);
791-
return resolveSymbol("_r_debug", false,
792-
[this](const std::string_view name) {
793-
return execImage->getInterpreter() == name;
794-
});
832+
if (dt_debug == 0 && execImage) {
833+
Elf::Off loadAddr = entry - execImage->getHeader().e_entry;
834+
835+
// Find DT_DEBUG in the process's dynamic section.
836+
for (auto &segment : execImage->getSegments(PT_DYNAMIC)) {
837+
// Read from the process, not the executable - the linker will have updated the content.
838+
auto dynReader = io->view("PT_DYNAMIC segment", segment.p_vaddr + loadAddr, segment.p_filesz);
839+
ReaderArray<Elf::Dyn> dynamic(*dynReader);
840+
for (auto &dyn : dynamic) {
841+
if (dyn.d_tag == DT_DEBUG && dyn.d_un.d_ptr != 0) {
842+
dt_debug = dyn.d_un.d_ptr;
843+
break;
844+
}
845+
}
846+
if (dt_debug)
847+
break;
795848
}
796-
catch (...) {
849+
}
850+
if (dt_debug == 0 && interpBase && execImage) {
851+
/*
852+
* If there's no DT_DEBUG, we've probably got someone executing a shared
853+
* library, which doesn't have an _r_debug symbol. Use the address of
854+
* _r_debug in the interpreter
855+
*/
856+
if (interpBase && execImage->getInterpreter() != "") {
857+
try {
858+
addElfObject(execImage->getInterpreter(), nullptr, interpBase);
859+
return resolveSymbol("_r_debug", false,
860+
[this](const std::string_view name) {
861+
return execImage->getInterpreter() == name;
862+
});
863+
}
864+
catch (...) {
865+
}
797866
}
798867
}
799-
return 0;
868+
return dt_debug;
800869
}
801870

802871
std::tuple<Elf::Addr, Elf::Object::sptr, const Elf::Phdr *>
@@ -806,7 +875,7 @@ Process::findSegment(Elf::Addr addr)
806875
if (it != objects.begin()) {
807876
--it;
808877
auto obj = it->second.object(context);
809-
if (it->first + obj->endVA() >= addr) {
878+
if (obj && it->first + obj->endVA() >= addr) {
810879
auto segment = obj->getSegmentForAddress(addr - it->first);
811880
if (segment)
812881
return std::make_tuple(it->first, obj, segment);
@@ -823,6 +892,8 @@ Process::resolveSymbolDetail(const char *name, bool includeDebug,
823892
if (!match(loaded.second.name()))
824893
continue;
825894
auto obj = loaded.second.object(context);
895+
if (!obj)
896+
continue;
826897
auto [sym,idx] = obj->findDynamicSymbol(name);
827898
if (sym.st_shndx != SHN_UNDEF)
828899
return std::make_tuple(obj, loaded.first, sym);

pstack.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@ emain(int argc, char **argv, Context &context)
365365
.add("no-debuginfod", Flags::LONGONLY,
366366
"disable debuginfod client", Flags::setf( context.options.noDebuginfod ) )
367367
#endif
368+
.add("no-local-files", Flags::LONGONLY,
369+
"don't assume local files match the process's view, and don't open them",
370+
Flags::setf( context.options.noLocalFiles ) )
368371

369372
.parse(argc, argv);
370373

0 commit comments

Comments
 (0)