From aece11079cc67b6cbb72ffa2743f7feed09551e0 Mon Sep 17 00:00:00 2001 From: Giovanni Lenzi Baraldi Date: Wed, 24 Sep 2025 01:54:53 +0200 Subject: [PATCH] SWDEV-553006: Fix slow lookup of debug symbols (#821) * SWDEV-553006: Fix slow lookup of debug symbols * Refactor * Better docs * Update projects/rocprofiler-sdk/source/include/rocprofiler-sdk/cxx/codeobj/code_printing.hpp --- .../cxx/codeobj/code_printing.hpp | 360 +++++++----------- 1 file changed, 148 insertions(+), 212 deletions(-) diff --git a/projects/rocprofiler-sdk/source/include/rocprofiler-sdk/cxx/codeobj/code_printing.hpp b/projects/rocprofiler-sdk/source/include/rocprofiler-sdk/cxx/codeobj/code_printing.hpp index 515c0c1907..4b0fe13526 100644 --- a/projects/rocprofiler-sdk/source/include/rocprofiler-sdk/cxx/codeobj/code_printing.hpp +++ b/projects/rocprofiler-sdk/source/include/rocprofiler-sdk/cxx/codeobj/code_printing.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,71 @@ struct Instruction static constexpr std::string_view separator = " -> "; }; +/** + * @brief Extracts inlined function call stack information for a given address + * + * This struct is used to recursively search through DWARF debug information to find all inlined + * functions that contain the specified address, building a complete call stack from the outermost + * function down to the innermost inlined function. + */ +struct DIEInfo +{ + struct DRange + { + Dwarf_Addr low{std::numeric_limits::max()}; + Dwarf_Addr high{0}; + + // Makes sure this range includes the "other" range + void expand(const DRange& other) + { + low = std::min(low, other.low); + high = std::max(high, other.high); + } + + /** + * @brief Is the address inside the low/hihg range? + */ + bool contains(Dwarf_Addr addr) const { return low <= addr && high > addr; } + }; + + DIEInfo(Dwarf_Die* die); + + /** + * @brief Recursively traverses all children DIEInfos to find inlined functions at a specific + * address + * + * This function performs a depth-first traversal of the DWARF debug information tree, + * checking each DIE for inlined function information that covers the specified address. + * It processes both the current DIE and all its children (including siblings at each level) + * to ensure comprehensive coverage of all possible inlined function contexts. + * + * The traversal is necessary because inlined functions can be nested (function A inlines + * function B which inlines function C) and multiple inlined functions can exist at the + * same scope level as siblings in the DWARF tree. + * + * @param addr The address to search for inlined function information + * @param call_stack Reference to vector that accumulates the call stack information + * @return True if either this instance or one of the children added an entry to the stack + */ + bool getCallStackRecursive(Dwarf_Addr addr, std::vector& call_stack); + + std::vector all_ranges{}; + std::vector> children{}; + + // Union of ranges, or the same as dwarf_lo/hi pc + DRange total_range{}; + // Union of all children's children_range + this total range + DRange children_range{}; + + std::string file_and_line{}; + + void addRange(const DRange& range) + { + all_ranges.push_back(range); + total_range.expand(range); + } +}; + class CodeobjDecoderComponent { struct ProtectedFd @@ -104,17 +170,19 @@ public: if(dbg) { - Dwarf_Off cu_offset{0}, next_offset; - size_t header_size; + Dwarf_Off cu_offset{}; + Dwarf_Off next_offset{}; + size_t header_size{}; - std::map line_addrs; + std::map line_addrs{}; + std::unordered_map> diemap{}; while( dwarf_nextcu( dbg.get(), cu_offset, &next_offset, &header_size, nullptr, nullptr, nullptr) == 0) { - Dwarf_Die die; + Dwarf_Die die{}; if(!dwarf_offdie(dbg.get(), cu_offset + header_size, &die)) { cu_offset = next_offset; @@ -138,9 +206,14 @@ public: if(line && dwarf_lineaddr(line, &addr) == 0 && dwarf_lineno(line, &line_number) == 0 && line_number != 0) { - std::string src = dwarf_linesrc(line, nullptr, nullptr); - auto dwarf_line = src + ':' + std::to_string(line_number); - auto call_stack_info = extractInlinedCallStackInfo(dbg.get(), addr); + std::string src = dwarf_linesrc(line, nullptr, nullptr); + auto dwarf_line = src + ':' + std::to_string(line_number); + + std::vector call_stack_info{}; + + auto& die_ptr = diemap[dwarf_dieoffset(&die)]; + if(die_ptr == nullptr) die_ptr = std::make_unique(&die); + die_ptr->getCallStackRecursive(addr, call_stack_info); size_t capacity = dwarf_line.size() + Instruction::separator.size() * call_stack_info.size(); @@ -210,79 +283,6 @@ public: std::unique_ptr disassembly{}; std::map m_line_number_map{}; - -private: - /** - * @brief Extracts inlined function call stack information for a given address - * - * This function searches through DWARF debug information to find all inlined functions - * that contain the specified address, building a complete call stack from the outermost - * function down to the innermost inlined function. - * - * @param dbg DWARF debug information handle - * @param addr The address to analyze for inlined function information - * @return Vector of strings representing the call stack, formatted as "filename:line" - * The stack is ordered from caller to callee (outermost to innermost) - */ - static std::vector extractInlinedCallStackInfo(Dwarf* dbg, Dwarf_Addr addr); - - /** - * @brief Checks if a DWARF Debug Information Entry (DIE) contains a specific address - * - * This function recursively searches through a DIE and its children to determine - * if any of them contain the specified address within their address ranges. - * Used as an optimization to quickly determine if a compilation unit or function - * contains the target address before doing expensive traversal. - * - * @param die Pointer to the DWARF DIE to check - * @param addr The address to search for - * @return true if the DIE or any of its children contain the address, false otherwise - */ - static bool checkDIEContainsAddress(Dwarf_Die* die, Dwarf_Addr addr); - - /** - * @brief Recursively traverses all DWARF DIEs to find inlined functions at a specific address - * - * This function performs a depth-first traversal of the DWARF debug information tree, - * checking each DIE for inlined function information that covers the specified address. - * It processes both the current DIE and all its children (including siblings at each level) - * to ensure comprehensive coverage of all possible inlined function contexts. - * - * The traversal is necessary because inlined functions can be nested (function A inlines - * function B which inlines function C) and multiple inlined functions can exist at the - * same scope level as siblings in the DWARF tree. - * - * @param die Pointer to the current DWARF DIE to examine - * @param addr The address to search for inlined function information - * @param call_stack Reference to vector that accumulates the call stack information - */ - static void traverseAllDIEs(Dwarf_Die* die, - Dwarf_Addr addr, - std::vector& call_stack); - - /** - * @brief Examines a specific DWARF DIE for inlined function information at an address - * - * This function checks if a given DIE represents an inlined subroutine that contains - * the specified address. If it does, it extracts the call site information (filename - * and line number where the function was inlined) and adds it to the call stack. - * - * The function specifically looks for DW_TAG_inlined_subroutine DIEs and validates - * that the address falls within the DIE's address range (either contiguous via - * low_pc/high_pc or non-contiguous via ranges attribute). It then extracts the - * call site information using DW_AT_call_file and DW_AT_call_line attributes. - * - * @param die Pointer to the DWARF DIE to examine - * @param addr The address to check against the DIE's address ranges - * @param call_stack Reference to vector where call site info will be added - * - * @note Only processes DW_TAG_inlined_subroutine DIEs. Regular functions - * (DW_TAG_subprogram) are ignored since this function specifically - * extracts inlined function call information. - */ - static void checkDIEForInlinedFunction(Dwarf_Die* die, - Dwarf_Addr addr, - std::vector& call_stack); }; class LoadedCodeobjDecoder @@ -523,168 +523,104 @@ private: segment::CodeobjTableTranslator table{}; }; -inline std::vector -CodeobjDecoderComponent::extractInlinedCallStackInfo(Dwarf* dbg, Dwarf_Addr addr) +inline DIEInfo::DIEInfo(Dwarf_Die* die) { - std::vector call_stack{}; - - // Iterate through all compilation units to find the one containing our address - Dwarf_Off cu_offset{}; - Dwarf_Off next_offset{}; - size_t header_size{}; - - while(dwarf_nextcu(dbg, cu_offset, &next_offset, &header_size, nullptr, nullptr, nullptr) == 0) + if(dwarf_tag(die) == DW_TAG_inlined_subroutine) { - Dwarf_Die cu_die{}; - if(!dwarf_offdie(dbg, cu_offset + header_size, &cu_die)) - { - cu_offset = next_offset; - continue; - } - - bool cu_contains_addr = false; - - // Try to get low_pc and high_pc from CU - // If no simple range, check if any child DIE contains this address Dwarf_Addr low_pc{}; Dwarf_Addr high_pc{}; - if(dwarf_lowpc(&cu_die, &low_pc) == 0 && dwarf_highpc(&cu_die, &high_pc) == 0) - cu_contains_addr = (addr >= low_pc && addr < high_pc); - else - cu_contains_addr = checkDIEContainsAddress(&cu_die, addr); - if(cu_contains_addr) + // Check if this inlined subroutine covers the target address + // First try simple contiguous range (low_pc to high_pc) + if(dwarf_lowpc(die, &low_pc) == 0 && dwarf_highpc(die, &high_pc) == 0) { - traverseAllDIEs(&cu_die, addr, call_stack); - break; + addRange(DRange{low_pc, high_pc}); + } + else + { + // Function may have non-contiguous ranges + // Check all address ranges associated with this DIE + Dwarf_Addr base{}; + ptrdiff_t offset{}; + while((offset = dwarf_ranges(die, offset, &base, &low_pc, &high_pc)) > 0) + addRange(DRange{low_pc, high_pc}); } - cu_offset = next_offset; + // Extract call site information - where this function was inlined + Dwarf_Attribute call_file_attr{}; + Dwarf_Attribute call_line_attr{}; + Dwarf_Word call_file{}; + Dwarf_Word call_line{}; + + // Get the file and line number where this function was called/inlined + + if(!dwarf_attr(die, DW_AT_call_file, &call_file_attr) || + !dwarf_attr(die, DW_AT_call_line, &call_line_attr) || + dwarf_formudata(&call_file_attr, &call_file) != 0 || + dwarf_formudata(&call_line_attr, &call_line) != 0) + return; // No call site information available + + // Get the compilation unit to resolve file names + Dwarf_Die cu_die{}; + if(!dwarf_diecu(die, &cu_die, nullptr, nullptr)) return; + + // Get the source files table for this compilation unit + Dwarf_Files* files{}; + size_t nfiles{}; + if(dwarf_getsrcfiles(&cu_die, &files, &nfiles) == 0 && call_file < nfiles) + { + if(const char* filename = dwarf_filesrc(files, call_file, nullptr, nullptr)) + { + // Add "filename:line" to call stack showing where this function was inlined + file_and_line = std::string(filename) + ":" + std::to_string(call_line); + return; + } + } + + children_range = total_range; } - // Reverse the call stack to show from caller to callee - std::reverse(call_stack.begin(), call_stack.end()); - - return call_stack; -} - -inline bool -CodeobjDecoderComponent::checkDIEContainsAddress(Dwarf_Die* die, Dwarf_Addr addr) -{ - if(die == nullptr) return false; - // Check current DIE's address range - Dwarf_Addr low_pc{}; - Dwarf_Addr high_pc{}; - if(dwarf_lowpc(die, &low_pc) == 0 && dwarf_highpc(die, &high_pc) == 0) - { - if(addr >= low_pc && addr < high_pc) return true; - } - else - { - // Check ranges attribute for non-contiguous ranges - Dwarf_Addr base{}; - ptrdiff_t offset = 0; - while((offset = dwarf_ranges(die, offset, &base, &low_pc, &high_pc)) > 0) - if(addr >= low_pc && addr < high_pc) return true; - } - - // Check children recursively Dwarf_Die child{}; - if(dwarf_child(die, &child) == 0) - if(checkDIEContainsAddress(&child, addr)) return true; - return false; -} - -inline void -CodeobjDecoderComponent::traverseAllDIEs(Dwarf_Die* die, - Dwarf_Addr addr, - std::vector& call_stack) -{ - if(die == nullptr) return; - // Check current DIE for inlined function information - checkDIEForInlinedFunction(die, addr, call_stack); - - // Traverse children recursively (depth-first) - Dwarf_Die child{}; + // Traverse children (recursive part) if(dwarf_child(die, &child) == 0) { - // Check all children AND their siblings at this level - // This is crucial because inlined functions can appear as siblings - // when multiple functions are inlined at the same scope level do { - traverseAllDIEs(&child, addr, call_stack); + children.emplace_back(std::make_unique(&child)); + children_range.expand(children.back()->children_range); + } while(dwarf_siblingof(&child, &child) == 0); } } -inline void -CodeobjDecoderComponent::checkDIEForInlinedFunction(Dwarf_Die* die, - Dwarf_Addr addr, - std::vector& call_stack) +inline bool +DIEInfo::getCallStackRecursive(Dwarf_Addr addr, std::vector& call_stack) { - // Only process inlined subroutines - these are functions that were - // expanded inline at compile time and have call site information - if(die == nullptr || dwarf_tag(die) != DW_TAG_inlined_subroutine) return; + if(!children_range.contains(addr)) return false; - Dwarf_Addr low_pc{}; - Dwarf_Addr high_pc{}; - bool has_range{false}; + bool addedOne = false; - // Check if this inlined subroutine covers the target address - // First try simple contiguous range (low_pc to high_pc) - - if(dwarf_lowpc(die, &low_pc) == 0 && dwarf_highpc(die, &high_pc) == 0) + for(auto& child : children) { - // Simple contiguous range - check if address falls within - has_range = (addr >= low_pc && addr < high_pc); + // Only add from one of the children + addedOne = child->getCallStackRecursive(addr, call_stack); + if(addedOne) break; } - else - { - // Function may have non-contiguous ranges (optimized code) - // Check all address ranges associated with this DIE - Dwarf_Addr base{}; - ptrdiff_t offset{}; - while((offset = dwarf_ranges(die, offset, &base, &low_pc, &high_pc)) > 0) + if(total_range.contains(addr)) + { + for(auto& range : all_ranges) { - if(addr >= low_pc && addr < high_pc) - { - has_range = true; - break; - } + if(!range.contains(addr)) continue; + + call_stack.emplace_back(file_and_line); + return true; } } - // If address doesn't fall within this inlined function, skip it - if(!has_range) return; - - // Extract call site information - where this function was inlined - Dwarf_Attribute call_file_attr{}; - Dwarf_Attribute call_line_attr{}; - Dwarf_Word call_file{}; - Dwarf_Word call_line{}; - - // Get the file and line number where this function was called/inlined - - if(!dwarf_attr(die, DW_AT_call_file, &call_file_attr) || - !dwarf_attr(die, DW_AT_call_line, &call_line_attr) || - dwarf_formudata(&call_file_attr, &call_file) != 0 || - dwarf_formudata(&call_line_attr, &call_line) != 0) - return; // No call site information available - - // Get the compilation unit to resolve file names - Dwarf_Die cu_die{}; - if(!dwarf_diecu(die, &cu_die, nullptr, nullptr)) return; - - // Get the source files table for this compilation unit - Dwarf_Files* files{}; - size_t nfiles{}; - if(dwarf_getsrcfiles(&cu_die, &files, &nfiles) == 0 && call_file < nfiles) - if(const char* filename = dwarf_filesrc(files, call_file, nullptr, nullptr)) - // Add "filename:line" to call stack showing where this function was inlined - call_stack.push_back(std::string(filename) + ":" + std::to_string(call_line)); + // Check if one of the child nodes added to the stack + return addedOne; } } // namespace disassembly