File indexing completed on 2024-05-05 05:44:15

0001 /*
0002     dwarfdiecache.cpp
0003 
0004     SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
0005     SPDX-FileContributor: Milian Wolff <milian.wolff@kdab.com>
0006 
0007    SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "dwarfdiecache.h"
0011 
0012 #include <dwarf.h>
0013 
0014 #include <cxxabi.h>
0015 
0016 #include <cstring>
0017 
0018 namespace {
0019 enum class WalkResult
0020 {
0021     Recurse,
0022     Skip,
0023     Return
0024 };
0025 template <typename Callback>
0026 WalkResult walkDieTree(const Callback& callback, Dwarf_Die* die)
0027 {
0028     auto result = callback(die);
0029     if (result != WalkResult::Recurse)
0030         return result;
0031 
0032     Dwarf_Die childDie;
0033     if (dwarf_child(die, &childDie) == 0) {
0034         result = walkDieTree(callback, &childDie);
0035         if (result == WalkResult::Return)
0036             return result;
0037 
0038         Dwarf_Die siblingDie;
0039         while (dwarf_siblingof(&childDie, &siblingDie) == 0) {
0040             result = walkDieTree(callback, &siblingDie);
0041             if (result == WalkResult::Return)
0042                 return result;
0043             childDie = siblingDie;
0044         }
0045     }
0046     return WalkResult::Skip;
0047 }
0048 
0049 template <typename Callback>
0050 void walkRanges(const Callback& callback, Dwarf_Die* die)
0051 {
0052     Dwarf_Addr low = 0;
0053     Dwarf_Addr high = 0;
0054     Dwarf_Addr base = 0;
0055     ptrdiff_t rangeOffset = 0;
0056     while ((rangeOffset = dwarf_ranges(die, rangeOffset, &base, &low, &high)) > 0) {
0057         if (!callback(DwarfRange {low, high}))
0058             return;
0059     }
0060 }
0061 
0062 // see libdw_visit_scopes.c in elfutils
0063 bool mayHaveScopes(Dwarf_Die* die)
0064 {
0065     switch (dwarf_tag(die)) {
0066     /* DIEs with addresses we can try to match.  */
0067     case DW_TAG_compile_unit:
0068     case DW_TAG_module:
0069     case DW_TAG_lexical_block:
0070     case DW_TAG_with_stmt:
0071     case DW_TAG_catch_block:
0072     case DW_TAG_try_block:
0073     case DW_TAG_entry_point:
0074     case DW_TAG_inlined_subroutine:
0075     case DW_TAG_subprogram:
0076         return true;
0077 
0078     /* DIEs without addresses that can own DIEs with addresses.  */
0079     case DW_TAG_namespace:
0080     case DW_TAG_class_type:
0081     case DW_TAG_structure_type:
0082         return true;
0083 
0084     /* Other DIEs we have no reason to descend.  */
0085     default:
0086         break;
0087     }
0088     return false;
0089 }
0090 
0091 bool dieContainsAddress(Dwarf_Die* die, Dwarf_Addr address)
0092 {
0093     bool contained = false;
0094     walkRanges(
0095         [&contained, address](DwarfRange range) {
0096             if (range.contains(address)) {
0097                 contained = true;
0098                 return false;
0099             }
0100             return true;
0101         },
0102         die);
0103     return contained;
0104 }
0105 }
0106 
0107 const char* linkageName(Dwarf_Die* die)
0108 {
0109     Dwarf_Attribute attr;
0110     Dwarf_Attribute* result = dwarf_attr_integrate(die, DW_AT_MIPS_linkage_name, &attr);
0111     if (!result)
0112         result = dwarf_attr_integrate(die, DW_AT_linkage_name, &attr);
0113 
0114     return result ? dwarf_formstring(result) : nullptr;
0115 }
0116 
0117 Dwarf_Die* specificationDie(Dwarf_Die* die, Dwarf_Die* dieMem)
0118 {
0119     Dwarf_Attribute attr;
0120     if (dwarf_attr_integrate(die, DW_AT_specification, &attr))
0121         return dwarf_formref_die(&attr, dieMem);
0122     return nullptr;
0123 }
0124 
0125 /// prepend the names of all scopes that reference the @p die to @p name
0126 void prependScopeNames(std::string& name, Dwarf_Die* die, tsl::robin_map<Dwarf_Off, std::string>& cache)
0127 {
0128     Dwarf_Die dieMem;
0129     Dwarf_Die* scopes = nullptr;
0130     auto nscopes = dwarf_getscopes_die(die, &scopes);
0131 
0132     struct ScopesToCache
0133     {
0134         Dwarf_Off offset;
0135         std::size_t trailing;
0136     };
0137     std::vector<ScopesToCache> cacheOps;
0138 
0139     // skip scope for the die itself at the start and the compile unit DIE at end
0140     for (int i = 1; i < nscopes - 1; ++i) {
0141         auto scope = scopes + i;
0142 
0143         const auto scopeOffset = dwarf_dieoffset(scope);
0144 
0145         auto it = cache.find(scopeOffset);
0146         if (it != cache.end()) {
0147             name.insert(0, "::");
0148             name.insert(0, it->second);
0149             // we can stop, cached names are always fully qualified
0150             break;
0151         }
0152 
0153         if (auto scopeLinkageName = linkageName(scope)) {
0154             // prepend the fully qualified linkage name
0155             name.insert(0, "::");
0156             cacheOps.push_back({scopeOffset, name.size()});
0157             // we have to demangle the scope linkage name, otherwise we get a
0158             // mish-mash of mangled and non-mangled names
0159             name.insert(0, demangle(scopeLinkageName));
0160             // we can stop now, the scope is fully qualified
0161             break;
0162         }
0163 
0164         if (auto scopeName = dwarf_diename(scope)) {
0165             // prepend this scope's name, e.g. the class or namespace name
0166             name.insert(0, "::");
0167             cacheOps.push_back({scopeOffset, name.size()});
0168             name.insert(0, scopeName);
0169         }
0170 
0171         if (auto specification = specificationDie(scope, &dieMem)) {
0172             free(scopes);
0173             scopes = nullptr;
0174             cacheOps.push_back({scopeOffset, name.size()});
0175             cacheOps.push_back({dwarf_dieoffset(specification), name.size()});
0176             // follow the scope's specification DIE instead
0177             prependScopeNames(name, specification, cache);
0178             break;
0179         }
0180     }
0181 
0182     for (const auto& cacheOp : cacheOps)
0183         cache[cacheOp.offset] = name.substr(0, name.size() - cacheOp.trailing);
0184 
0185     free(scopes);
0186 }
0187 
0188 bool operator==(const Dwarf_Die& lhs, const Dwarf_Die& rhs)
0189 {
0190     return lhs.addr == rhs.addr && lhs.cu == rhs.cu && lhs.abbrev == rhs.abbrev;
0191 }
0192 
0193 std::string qualifiedDieName(Dwarf_Die* die, tsl::robin_map<Dwarf_Off, std::string>& cache)
0194 {
0195     // linkage names are fully qualified, meaning we can stop early then
0196     if (auto name = linkageName(die))
0197         return name;
0198 
0199     // otherwise do a more complex lookup that includes namespaces and other context information
0200     // this is important for inlined subroutines such as lambdas or std:: algorithms
0201     std::string name;
0202     if (auto dieName = dwarf_diename(die))
0203         name = dieName;
0204 
0205     // use the specification DIE which is within the DW_TAG_namespace
0206     Dwarf_Die dieMem;
0207     if (auto specification = specificationDie(die, &dieMem))
0208         die = specification;
0209 
0210     prependScopeNames(name, die, cache);
0211 
0212     return name;
0213 }
0214 
0215 std::string demangle(const std::string& mangledName)
0216 {
0217     if (mangledName.length() < 3) {
0218         return mangledName;
0219     } else {
0220         static size_t demangleBufferLength = 1024;
0221         static char* demangleBuffer = reinterpret_cast<char*>(malloc(demangleBufferLength));
0222 
0223         // Require GNU v3 ABI by the "_Z" prefix.
0224         if (mangledName[0] == '_' && mangledName[1] == 'Z') {
0225             int status = -1;
0226             char* dsymname = abi::__cxa_demangle(mangledName.data(), demangleBuffer, &demangleBufferLength, &status);
0227             if (status == 0 && dsymname)
0228                 return demangleBuffer = dsymname;
0229         }
0230     }
0231     return mangledName;
0232 }
0233 
0234 std::string absoluteSourcePath(const char* path, Dwarf_Die* cuDie)
0235 {
0236     if (!path || !cuDie || path[0] == '/')
0237         return path;
0238 
0239     Dwarf_Attribute attr;
0240     auto compDir = dwarf_formstring(dwarf_attr(cuDie, DW_AT_comp_dir, &attr));
0241     if (!compDir)
0242         return path;
0243 
0244     std::string ret;
0245     ret.reserve(static_cast<int>(strlen(compDir) + strlen(path) + 1));
0246     ret.append(compDir);
0247     ret.append("/");
0248     ret.append(path);
0249     return ret;
0250 }
0251 
0252 SourceLocation callSourceLocation(Dwarf_Die* die, Dwarf_Files* files, Dwarf_Die* cuDie)
0253 {
0254     SourceLocation ret;
0255 
0256     Dwarf_Attribute attr;
0257     Dwarf_Word val = 0;
0258 
0259     const auto hasCallFile = dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr), &val) == 0;
0260     if (hasCallFile) {
0261         ret.file = absoluteSourcePath(dwarf_filesrc(files, val, nullptr, nullptr), cuDie);
0262     }
0263 
0264     const auto hasCallLine = dwarf_formudata(dwarf_attr(die, DW_AT_call_line, &attr), &val) == 0;
0265     if (hasCallLine) {
0266         ret.line = static_cast<int>(val);
0267     }
0268 
0269     return ret;
0270 }
0271 
0272 std::vector<Dwarf_Die> findInlineScopes(Dwarf_Die* subprogram, Dwarf_Addr offset)
0273 {
0274     std::vector<Dwarf_Die> scopes;
0275     walkDieTree(
0276         [offset, &scopes](Dwarf_Die* die) {
0277             if (dwarf_tag(die) != DW_TAG_inlined_subroutine)
0278                 return WalkResult::Recurse;
0279             if (dieContainsAddress(die, offset)) {
0280                 scopes.push_back(*die);
0281                 return WalkResult::Recurse;
0282             }
0283             return WalkResult::Skip;
0284         },
0285         subprogram);
0286     return scopes;
0287 }
0288 
0289 SubProgramDie::SubProgramDie(Dwarf_Die die)
0290     : m_ranges {die, {}}
0291 {
0292     walkRanges(
0293         [this](DwarfRange range) {
0294             m_ranges.ranges.push_back(range);
0295             return true;
0296         },
0297         &die);
0298 }
0299 
0300 CuDieRangeMapping::CuDieRangeMapping(Dwarf_Die cudie, Dwarf_Addr bias)
0301     : m_bias {bias}
0302     , m_cuDieRanges {cudie, {}}
0303 {
0304     walkRanges(
0305         [this, bias](DwarfRange range) {
0306             m_cuDieRanges.ranges.push_back({range.low + bias, range.high + bias});
0307             return true;
0308         },
0309         &cudie);
0310 }
0311 
0312 SubProgramDie* CuDieRangeMapping::findSubprogramDie(Dwarf_Addr offset)
0313 {
0314     if (m_subPrograms.empty())
0315         addSubprograms();
0316 
0317     auto it = std::find_if(m_subPrograms.begin(), m_subPrograms.end(),
0318                            [offset](const SubProgramDie& program) { return program.contains(offset); });
0319     if (it == m_subPrograms.end())
0320         return nullptr;
0321 
0322     return &(*it);
0323 }
0324 
0325 void CuDieRangeMapping::addSubprograms()
0326 {
0327     walkDieTree(
0328         [this](Dwarf_Die* die) {
0329             if (!mayHaveScopes(die))
0330                 return WalkResult::Skip;
0331 
0332             if (dwarf_tag(die) == DW_TAG_subprogram) {
0333                 SubProgramDie program(*die);
0334                 if (!program.isEmpty())
0335                     m_subPrograms.push_back(program);
0336 
0337                 return WalkResult::Skip;
0338             }
0339             return WalkResult::Recurse;
0340         },
0341         cudie());
0342 }
0343 
0344 const std::string& CuDieRangeMapping::dieName(Dwarf_Die* die)
0345 {
0346     const auto offset = dwarf_dieoffset(die);
0347     auto it = m_dieNameCache.find(offset);
0348     if (it == m_dieNameCache.end())
0349         it = m_dieNameCache.insert({offset, demangle(qualifiedDieName(die, m_dieNameCache))}).first;
0350 
0351     return it->second;
0352 }
0353 
0354 DwarfDieCache::DwarfDieCache(Dwfl_Module* mod)
0355 {
0356     if (!mod)
0357         return;
0358 
0359     Dwarf_Die* die = nullptr;
0360     Dwarf_Addr bias = 0;
0361     while ((die = dwfl_module_nextcu(mod, die, &bias))) {
0362         CuDieRangeMapping cuDieMapping(*die, bias);
0363         if (!cuDieMapping.isEmpty())
0364             m_cuDieRanges.push_back(cuDieMapping);
0365     }
0366 }
0367 
0368 CuDieRangeMapping* DwarfDieCache::findCuDie(Dwarf_Addr addr)
0369 {
0370     auto it = std::find_if(m_cuDieRanges.begin(), m_cuDieRanges.end(),
0371                            [addr](const CuDieRangeMapping& cuDieMapping) { return cuDieMapping.contains(addr); });
0372     if (it == m_cuDieRanges.end())
0373         return nullptr;
0374 
0375     return &(*it);
0376 }