25#include "disassembly.hpp"
30#include <elfutils/libdw.h>
31#include <hsa/amd_hsa_elf.h>
43#include <unordered_map>
55using marker_id_t = segment::marker_id_t;
59 Instruction() =
default;
60 Instruction(std::string&& _inst,
size_t _size)
61 : inst(
std::move(_inst))
65 std::string comment{};
70 marker_id_t codeobj_id{0};
72 static constexpr std::string_view separator =
" -> ";
86 Dwarf_Addr low{std::numeric_limits<Dwarf_Addr>::max()};
90 void expand(
const DRange& other)
92 low = std::min(low, other.low);
93 high = std::max(high, other.high);
99 bool contains(Dwarf_Addr addr)
const {
return low <= addr && high > addr; }
123 std::vector<DRange> all_ranges{};
124 std::vector<std::unique_ptr<DIEInfo>> children{};
127 DRange total_range{};
129 DRange children_range{};
131 std::string file_and_line{};
133 void addRange(
const DRange& range)
135 all_ranges.push_back(range);
136 total_range.expand(range);
140class CodeobjDecoderComponent
144 ProtectedFd(std::string_view uri)
146#if defined(_GNU_SOURCE) && defined(MFD_ALLOW_SEALING) && defined(MFD_CLOEXEC)
147 m_fd = ::memfd_create(uri.data(), MFD_ALLOW_SEALING | MFD_CLOEXEC);
149 if(m_fd == -1) m_fd = ::open(
"/tmp", O_TMPFILE | O_RDWR, 0666);
150 if(m_fd == -1)
throw std::runtime_error(
"Could not create a file for codeobj!");
154 if(m_fd != -1) ::close(m_fd);
159 void loadDebugLineInfo()
161 if(!disassembly)
throw std::runtime_error(
"No disassembly available!");
163 ProtectedFd prot(
"");
164 auto& codeobj_data = disassembly->buffer;
165 if(::write(prot.m_fd, codeobj_data.data(), codeobj_data.size()) !=
166 static_cast<int64_t
>(codeobj_data.size()))
167 throw std::runtime_error(
"Could not write to temporary file!");
169 ::lseek(prot.m_fd, 0, SEEK_SET);
172 m_line_number_map = {};
174 std::unique_ptr<Dwarf, void (*)(Dwarf*)> dbg(dwarf_begin(prot.m_fd, DWARF_C_READ),
175 [](Dwarf* _dbg) { dwarf_end(_dbg); });
179 Dwarf_Off cu_offset{};
180 Dwarf_Off next_offset{};
181 size_t header_size{};
188 std::map<Dwarf_Addr, LineEntry> line_addrs{};
189 std::unordered_map<Dwarf_Off, std::unique_ptr<DIEInfo>> diemap{};
193 dbg.get(), cu_offset, &next_offset, &header_size,
nullptr,
nullptr,
nullptr) ==
197 if(!dwarf_offdie(dbg.get(), cu_offset + header_size, &die))
199 cu_offset = next_offset;
205 if(dwarf_getsrclines(&die, &lines, &line_count) != 0)
207 cu_offset = next_offset;
218 for(
size_t i = 0; i + 1 < line_count; ++i)
221 Dwarf_Addr end_addr{};
223 bool end_sequence =
false;
224 Dwarf_Line* line = dwarf_onesrcline(lines, i);
225 Dwarf_Line* next_line = dwarf_onesrcline(lines, i + 1);
227 if(line ==
nullptr || next_line ==
nullptr)
continue;
228 if(dwarf_lineaddr(line, &addr) != 0)
continue;
229 if(dwarf_lineaddr(next_line, &end_addr) != 0)
continue;
230 if(end_addr <= addr)
continue;
234 if(dwarf_lineendsequence(line, &end_sequence) == 0 && end_sequence)
continue;
242 if(dwarf_lineno(line, &line_number) != 0)
continue;
244 const char* src_cstr = dwarf_linesrc(line,
nullptr,
nullptr);
245 if(src_cstr ==
nullptr)
continue;
247 std::string src = src_cstr;
248 auto dwarf_line = src +
':';
250 dwarf_line += std::to_string(line_number);
254 std::vector<std::string> call_stack_info{};
256 auto& die_ptr = diemap[dwarf_dieoffset(&die)];
257 if(die_ptr ==
nullptr) die_ptr = std::make_unique<DIEInfo>(&die);
258 die_ptr->getCallStackRecursive(addr, call_stack_info);
261 dwarf_line.size() + Instruction::separator.size() * call_stack_info.size();
262 for(
const auto& call : call_stack_info)
263 capacity += call.size();
265 dwarf_line.reserve(capacity);
266 for(
const auto& call : call_stack_info)
268 dwarf_line += Instruction::separator;
271 line_addrs[addr] = LineEntry{end_addr, std::move(dwarf_line)};
273 cu_offset = next_offset;
276 for(
auto& [addr, entry] : line_addrs)
278 if(entry.end_addr <= addr)
continue;
279 auto segment = segment::address_range_t{addr, entry.end_addr - addr, 0};
280 m_line_number_map.emplace(segment, std::move(entry.text));
286 CodeobjDecoderComponent(
const char* codeobj_data, uint64_t codeobj_size)
289 disassembly = std::make_unique<DisassemblyInstance>(codeobj_data, codeobj_size);
292 m_symbol_map = disassembly->GetKernelMap();
330 funcmap::extract_elf_section(codeobj_data, codeobj_size,
".sqtt_funcmap");
333 m_funcmap = funcmap::parse_funcmap_section(*section_bytes);
336 std::unordered_map<std::string, uint64_t> name_to_vaddr;
337 name_to_vaddr.reserve(m_symbol_map.size());
338 for(
const auto& [vaddr, sym] : m_symbol_map)
339 name_to_vaddr.emplace(sym.name, vaddr);
343 for(
auto& entry_ptr : m_funcmap.entries)
345 if(!entry_ptr)
continue;
346 if(entry_ptr->kind != funcmap::FuncmapEntryKind::Function &&
347 entry_ptr->kind != funcmap::FuncmapEntryKind::Kernel)
350 auto it = name_to_vaddr.find(entry_ptr->name);
351 if(it == name_to_vaddr.end())
continue;
353 auto updated = std::make_shared<funcmap::FuncmapEntry>(*entry_ptr);
354 updated->vaddr = it->second;
358 if(updated->kind != funcmap::FuncmapEntryKind::Kernel)
360 auto bid = m_funcmap.by_id.find(updated->id);
361 if(bid != m_funcmap.by_id.end() && bid->second == entry_ptr)
362 bid->second = updated;
364 entry_ptr = std::move(updated);
368 ~CodeobjDecoderComponent() =
default;
370 std::optional<uint64_t> va2fo(uint64_t vaddr)
const
372 if(disassembly)
return disassembly->va2fo(vaddr);
376 std::unique_ptr<Instruction> disassemble_instruction(uint64_t faddr, uint64_t vaddr)
378 if(!disassembly)
throw std::exception();
380 auto pair = disassembly->ReadInstruction(faddr);
381 auto inst = std::make_unique<Instruction>(std::move(pair.first), pair.second);
385 std::call_once(m_debug_line_info_once, [
this]() {
389 }
catch(std::exception& e)
391 std::cerr <<
"rocprofiler-sdk: failed to parse DWARF line info: " << e.what()
395 std::cerr <<
"rocprofiler-sdk: failed to parse DWARF line info\n";
399 auto it = m_line_number_map.find({vaddr, 0, 0});
400 if(it != m_line_number_map.end()) inst->comment = it->second;
407 const funcmap::Funcmap& getFuncmap()
const {
return m_funcmap; }
409 std::map<uint64_t, SymbolInfo> m_symbol_map{};
410 funcmap::Funcmap m_funcmap{};
411 std::vector<std::shared_ptr<Instruction>> instructions{};
412 std::unique_ptr<DisassemblyInstance> disassembly{};
414 std::map<segment::address_range_t, std::string> m_line_number_map{};
415 std::once_flag m_debug_line_info_once{};
418class LoadedCodeobjDecoder
421 LoadedCodeobjDecoder(
const char* filepath, uint64_t _load_addr, uint64_t _memsize)
422 : load_addr(_load_addr)
423 , load_end(_load_addr + _memsize)
425 if(!filepath)
throw std::runtime_error(
"Empty filepath.");
427 std::string_view fpath(filepath);
429 if(fpath.rfind(
".out") + 4 == fpath.size())
431 std::ifstream file(filepath, std::ios::in | std::ios::binary);
433 if(!file.is_open())
throw std::runtime_error(
"Invalid file " + std::string(filepath));
435 std::vector<char> buffer;
436 file.seekg(0, file.end);
437 buffer.resize(file.tellg());
438 file.seekg(0, file.beg);
439 file.read(buffer.data(), buffer.size());
441 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
445 std::unique_ptr<CodeObjectBinary> binary = std::make_unique<CodeObjectBinary>(filepath);
446 auto& buffer = binary->buffer;
447 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
450 LoadedCodeobjDecoder(
const void* data, uint64_t size, uint64_t _load_addr,
size_t _memsize)
451 : load_addr(_load_addr)
452 , load_end(load_addr + _memsize)
454 decoder = std::make_unique<CodeobjDecoderComponent>(
static_cast<const char*
>(data), size);
456 std::unique_ptr<Instruction> get(uint64_t ld_addr)
458 if(!decoder || ld_addr < load_addr)
return nullptr;
460 uint64_t voffset = ld_addr - load_addr;
461 auto faddr = decoder->va2fo(voffset);
462 if(!faddr)
return nullptr;
464 auto unique = decoder->disassemble_instruction(*faddr, voffset);
465 if(unique ==
nullptr || unique->size == 0)
return nullptr;
466 unique->ld_addr = ld_addr;
470 uint64_t begin()
const {
return load_addr; };
471 uint64_t end()
const {
return load_end; }
472 uint64_t size()
const {
return load_end - load_addr; }
473 bool inrange(uint64_t addr)
const {
return addr >= begin() && addr < end(); }
475 const char* getSymbolName(uint64_t addr)
const
477 if(!decoder)
return nullptr;
479 auto it = decoder->m_symbol_map.find(addr - load_addr);
480 if(it != decoder->m_symbol_map.end())
return it->second.name.data();
485 std::map<uint64_t, SymbolInfo>& getSymbolMap()
const
487 if(!decoder)
throw std::exception();
488 return decoder->m_symbol_map;
490 const funcmap::Funcmap& getFuncmap()
const;
491 bool has_decoder() const noexcept {
return bool(decoder); }
492 const uint64_t load_addr;
495 uint64_t load_end{0};
497 std::unique_ptr<CodeobjDecoderComponent> decoder{
nullptr};
509 virtual void addDecoder(
const char* filepath,
514 decoders[id] = std::make_shared<LoadedCodeobjDecoder>(filepath, load_addr, memsize);
517 virtual void addDecoder(
const void* data,
524 std::make_shared<LoadedCodeobjDecoder>(data, memory_size, load_addr, memsize);
527 virtual bool removeDecoderbyId(marker_id_t
id) {
return decoders.erase(
id) != 0; }
529 std::unique_ptr<Instruction> get(marker_id_t
id, uint64_t offset)
533 auto& decoder = decoders.at(
id);
534 auto inst = decoder->get(decoder->begin() + offset);
535 if(inst !=
nullptr) inst->codeobj_id = id;
537 }
catch(std::out_of_range&)
542 const char* getSymbolName(marker_id_t
id, uint64_t offset)
546 auto& decoder = decoders.at(
id);
547 uint64_t vaddr = decoder->begin() + offset;
548 if(decoder->inrange(vaddr))
return decoder->getSymbolName(vaddr);
549 }
catch(std::out_of_range&)
554 funcmap::Funcmap::EntryPtr getMarker(marker_id_t
id, uint32_t funcmap_id)
const;
561 std::optional<funcmap::Funcmap> getFuncmap(marker_id_t
id)
const;
564 std::unordered_map<marker_id_t, std::shared_ptr<LoadedCodeobjDecoder>> decoders{};
578 void addDecoder(
const char* filepath,
581 uint64_t memsize)
override
583 this->Super::addDecoder(filepath,
id, load_addr, memsize);
584 auto ptr = decoders.at(
id);
585 table.insert({ptr->begin(), ptr->size(),
id});
588 void addDecoder(
const void* data,
592 uint64_t memsize)
override
594 this->Super::addDecoder(data, memory_size,
id, load_addr, memsize);
595 auto ptr = decoders.at(
id);
596 table.insert({ptr->begin(), ptr->size(),
id});
599 bool removeDecoder(marker_id_t
id, uint64_t load_addr)
601 return table.remove(load_addr) && this->Super::removeDecoderbyId(
id);
604 bool removeDecoder(marker_id_t
id)
607 if(decoders.find(
id) != decoders.end()) addr = decoders.at(
id)->begin();
609 return removeDecoder(
id, addr);
612 std::unique_ptr<Instruction> get(uint64_t vaddr)
614 auto addr_range = table.find_codeobj_in_range(vaddr);
615 return this->Super::get(addr_range.id, vaddr - addr_range.addr);
618 std::unique_ptr<Instruction> get(marker_id_t
id, uint64_t offset)
623 return this->Super::get(
id, offset);
626 const char* getSymbolName(uint64_t vaddr)
628 for(
auto& [_, decoder] : decoders)
630 if(!decoder->inrange(vaddr))
continue;
631 return decoder->getSymbolName(vaddr);
636 std::map<uint64_t, SymbolInfo> getSymbolMap()
const
638 std::map<uint64_t, SymbolInfo> symbols;
640 for(
const auto& [_, dec] : decoders)
642 auto& smap = dec->getSymbolMap();
643 for(
auto& [vaddr, sym] : smap)
644 symbols[vaddr + dec->load_addr] = sym;
650 std::map<uint64_t, SymbolInfo> getSymbolMap(marker_id_t
id)
const
652 if(decoders.find(
id) == decoders.end())
return {};
656 return decoders.at(
id)->getSymbolMap();
663 std::vector<std::pair<marker_id_t, funcmap::Funcmap::EntryPtr>> findMarkerAny(
664 uint32_t funcmap_id)
const;
666 uint32_t getWaveSize()
const;
672inline const funcmap::Funcmap&
673LoadedCodeobjDecoder::getFuncmap()
const
675 if(!decoder)
throw std::exception();
676 return decoder->getFuncmap();
679inline funcmap::Funcmap::EntryPtr
680CodeobjMap::getMarker(marker_id_t
id, uint32_t funcmap_id)
const
682 auto it = decoders.find(
id);
683 if(it == decoders.end())
return nullptr;
687 return it->second->getFuncmap().find(funcmap_id);
694inline std::optional<funcmap::Funcmap>
695CodeobjMap::getFuncmap(marker_id_t
id)
const
697 auto it = decoders.find(
id);
698 if(it == decoders.end() || !it->second || !it->second->has_decoder())
return std::nullopt;
699 return it->second->getFuncmap();
702inline std::vector<std::pair<marker_id_t, funcmap::Funcmap::EntryPtr>>
703CodeobjAddressTranslate::findMarkerAny(uint32_t funcmap_id)
const
705 std::vector<std::pair<marker_id_t, funcmap::Funcmap::EntryPtr>> out;
706 for(
const auto& [
id, dec] : decoders)
710 if(
auto entry = dec->getFuncmap().find(funcmap_id))
711 out.emplace_back(
id, std::move(entry));
719CodeobjAddressTranslate::getWaveSize()
const
722 for(
const auto& [
id, dec] : decoders)
727 w = dec->getFuncmap().wave_size;
740 std::cerr <<
"rocprofiler-sdk: .sqtt_funcmap wave size disagreement (" << agreed
741 <<
" vs " << w <<
" from codeobj id " <<
id <<
")\n";
748inline DIEInfo::DIEInfo(Dwarf_Die* die)
750 if(dwarf_tag(die) == DW_TAG_inlined_subroutine)
753 Dwarf_Addr high_pc{};
757 if(dwarf_lowpc(die, &low_pc) == 0 && dwarf_highpc(die, &high_pc) == 0)
759 addRange(DRange{low_pc, high_pc});
767 while((offset = dwarf_ranges(die, offset, &base, &low_pc, &high_pc)) > 0)
768 addRange(DRange{low_pc, high_pc});
772 Dwarf_Attribute call_file_attr{};
773 Dwarf_Attribute call_line_attr{};
774 Dwarf_Word call_file{};
775 Dwarf_Word call_line{};
779 if(dwarf_attr(die, DW_AT_call_file, &call_file_attr) &&
780 dwarf_attr(die, DW_AT_call_line, &call_line_attr) &&
781 dwarf_formudata(&call_file_attr, &call_file) == 0 &&
782 dwarf_formudata(&call_line_attr, &call_line) == 0)
786 if(dwarf_diecu(die, &cu_die,
nullptr,
nullptr))
789 Dwarf_Files* files{};
791 if(dwarf_getsrcfiles(&cu_die, &files, &nfiles) == 0 && call_file < nfiles)
793 if(
const char* filename = dwarf_filesrc(files, call_file,
nullptr,
nullptr))
795 file_and_line = std::string(filename) +
":" + std::to_string(call_line);
802 children_range = total_range;
808 if(dwarf_child(die, &child) == 0)
812 children.emplace_back(std::make_unique<DIEInfo>(&child));
813 children_range.expand(children.back()->children_range);
815 }
while(dwarf_siblingof(&child, &child) == 0);
822 if(!children_range.contains(addr))
return false;
824 bool addedOne =
false;
826 for(
auto& child : children)
829 addedOne = child->getCallStackRecursive(addr, call_stack);
833 if(total_range.contains(addr))
835 for(
auto& range : all_ranges)
837 if(!range.contains(addr))
continue;
839 call_stack.emplace_back(file_and_line);
Translates virtual addresses to elf file offsets.
Maps ID and offsets into instructions.
Finds a candidate codeobj for the given vaddr.
Extracts inlined function call stack information for a given address.
bool getCallStackRecursive(Dwarf_Addr addr, std::vector< std::string > &call_stack)
Recursively traverses all children DIEInfos to find inlined functions at a specific address.