rocprofiler-sdk/cxx/codeobj/code_printing.hpp Source File

rocprofiler-sdk/cxx/codeobj/code_printing.hpp Source File#

ROCprofiler-SDK developer API: rocprofiler-sdk/cxx/codeobj/code_printing.hpp Source File
ROCprofiler-SDK developer API 1.3.5
ROCm Profiling API and tools
code_printing.hpp
1// MIT License
2//
3// Copyright (c) 2023-2025 Advanced Micro Devices, Inc. All rights reserved.
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#pragma once
24
25#include "disassembly.hpp"
26#include "funcmap.hpp"
27#include "segment.hpp"
28
29#include <dwarf.h>
30#include <elfutils/libdw.h>
31#include <hsa/amd_hsa_elf.h>
32
33#include <algorithm>
34#include <cstring>
35#include <fstream>
36#include <iostream>
37#include <limits>
38#include <map>
39#include <memory>
40#include <mutex>
41#include <optional>
42#include <string>
43#include <unordered_map>
44#include <utility>
45#include <vector>
46
47namespace rocprofiler
48{
49namespace sdk
50{
51namespace codeobj
52{
53namespace disassembly
54{
55using marker_id_t = segment::marker_id_t;
56
57struct Instruction
58{
59 Instruction() = default;
60 Instruction(std::string&& _inst, size_t _size)
61 : inst(std::move(_inst))
62 , size(_size)
63 {}
64 std::string inst{};
65 std::string comment{};
66 uint64_t faddr{0};
67 uint64_t vaddr{0};
68 size_t size{0};
69 uint64_t ld_addr{0}; // Instruction load address, if from loaded codeobj
70 marker_id_t codeobj_id{0}; // Instruction code object load id, if from loaded codeobj
71
72 static constexpr std::string_view separator = " -> ";
73};
74
75/**
76 * @brief Extracts inlined function call stack information for a given address
77 *
78 * This struct is used to recursively search through DWARF debug information to find all inlined
79 * functions that contain the specified address, building a complete call stack from the outermost
80 * function down to the innermost inlined function.
81 */
82struct DIEInfo
83{
84 struct DRange
85 {
86 Dwarf_Addr low{std::numeric_limits<Dwarf_Addr>::max()};
87 Dwarf_Addr high{0};
88
89 // Makes sure this range includes the "other" range
90 void expand(const DRange& other)
91 {
92 low = std::min(low, other.low);
93 high = std::max(high, other.high);
94 }
95
96 /**
97 * @brief Is the address inside the low/hihg range?
98 */
99 bool contains(Dwarf_Addr addr) const { return low <= addr && high > addr; }
100 };
101
102 DIEInfo(Dwarf_Die* die);
103
104 /**
105 * @brief Recursively traverses all children DIEInfos to find inlined functions at a specific
106 * address
107 *
108 * This function performs a depth-first traversal of the DWARF debug information tree,
109 * checking each DIE for inlined function information that covers the specified address.
110 * It processes both the current DIE and all its children (including siblings at each level)
111 * to ensure comprehensive coverage of all possible inlined function contexts.
112 *
113 * The traversal is necessary because inlined functions can be nested (function A inlines
114 * function B which inlines function C) and multiple inlined functions can exist at the
115 * same scope level as siblings in the DWARF tree.
116 *
117 * @param addr The address to search for inlined function information
118 * @param call_stack Reference to vector that accumulates the call stack information
119 * @return True if either this instance or one of the children added an entry to the stack
120 */
121 bool getCallStackRecursive(Dwarf_Addr addr, std::vector<std::string>& call_stack);
122
123 std::vector<DRange> all_ranges{};
124 std::vector<std::unique_ptr<DIEInfo>> children{};
125
126 // Union of ranges, or the same as dwarf_lo/hi pc
127 DRange total_range{};
128 // Union of all children's children_range + this total range
129 DRange children_range{};
130
131 std::string file_and_line{};
132
133 void addRange(const DRange& range)
134 {
135 all_ranges.push_back(range);
136 total_range.expand(range);
137 }
138};
139
140class CodeobjDecoderComponent
141{
142 struct ProtectedFd
143 {
144 ProtectedFd(std::string_view uri)
145 {
146#if defined(_GNU_SOURCE) && defined(MFD_ALLOW_SEALING) && defined(MFD_CLOEXEC)
147 m_fd = ::memfd_create(uri.data(), MFD_ALLOW_SEALING | MFD_CLOEXEC);
148#endif
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!");
151 }
152 ~ProtectedFd()
153 {
154 if(m_fd != -1) ::close(m_fd);
155 }
156 int m_fd{-1};
157 };
158
159 void loadDebugLineInfo()
160 {
161 if(!disassembly) throw std::runtime_error("No disassembly available!");
162
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!");
168
169 ::lseek(prot.m_fd, 0, SEEK_SET);
170 fsync(prot.m_fd);
171
172 m_line_number_map = {};
173
174 std::unique_ptr<Dwarf, void (*)(Dwarf*)> dbg(dwarf_begin(prot.m_fd, DWARF_C_READ),
175 [](Dwarf* _dbg) { dwarf_end(_dbg); });
176
177 if(dbg)
178 {
179 Dwarf_Off cu_offset{};
180 Dwarf_Off next_offset{};
181 size_t header_size{};
182
183 struct LineEntry
184 {
185 Dwarf_Addr end_addr;
186 std::string text;
187 };
188 std::map<Dwarf_Addr, LineEntry> line_addrs{};
189 std::unordered_map<Dwarf_Off, std::unique_ptr<DIEInfo>> diemap{};
190
191 while(
192 dwarf_nextcu(
193 dbg.get(), cu_offset, &next_offset, &header_size, nullptr, nullptr, nullptr) ==
194 0)
195 {
196 Dwarf_Die die{};
197 if(!dwarf_offdie(dbg.get(), cu_offset + header_size, &die))
198 {
199 cu_offset = next_offset;
200 continue;
201 }
202
203 Dwarf_Lines* lines;
204 size_t line_count;
205 if(dwarf_getsrclines(&die, &lines, &line_count) != 0)
206 {
207 cu_offset = next_offset;
208 continue;
209 }
210
211 // Each row in the DWARF line table covers [addr, next_row_addr).
212 // The terminator of a contiguous code sequence is a row with the
213 // end_sequence flag set -- its address is one past the last
214 // instruction in the sequence. Using the next row's address
215 // (rather than the next *kept* row's address, or codeobj_size)
216 // ensures that ranges never extend across gaps in DWARF
217 // coverage or past the end of an end_sequence boundary.
218 for(size_t i = 0; i + 1 < line_count; ++i)
219 {
220 Dwarf_Addr addr{};
221 Dwarf_Addr end_addr{};
222 int line_number{};
223 bool end_sequence = false;
224 Dwarf_Line* line = dwarf_onesrcline(lines, i);
225 Dwarf_Line* next_line = dwarf_onesrcline(lines, i + 1);
226
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;
231
232 // Skip end_sequence rows -- they only mark the end boundary
233 // of the previous row, they aren't a real source location.
234 if(dwarf_lineendsequence(line, &end_sequence) == 0 && end_sequence) continue;
235
236 // line_number == 0 is a valid DWARF row meaning "this address
237 // belongs to this source file but has no specific line"
238 // (typically compiler-synthesized code, prologue/epilogue,
239 // or optimizer-merged blocks). addr2line renders these as
240 // "<file>:?" -- keep them with the same convention so they
241 // are not silently dropped.
242 if(dwarf_lineno(line, &line_number) != 0) continue;
243
244 const char* src_cstr = dwarf_linesrc(line, nullptr, nullptr);
245 if(src_cstr == nullptr) continue;
246
247 std::string src = src_cstr;
248 auto dwarf_line = src + ':';
249 if(line_number != 0)
250 dwarf_line += std::to_string(line_number);
251 else
252 dwarf_line += '?';
253
254 std::vector<std::string> call_stack_info{};
255
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);
259
260 size_t capacity =
261 dwarf_line.size() + Instruction::separator.size() * call_stack_info.size();
262 for(const auto& call : call_stack_info)
263 capacity += call.size();
264
265 dwarf_line.reserve(capacity);
266 for(const auto& call : call_stack_info)
267 {
268 dwarf_line += Instruction::separator;
269 dwarf_line += call;
270 }
271 line_addrs[addr] = LineEntry{end_addr, std::move(dwarf_line)};
272 }
273 cu_offset = next_offset;
274 }
275
276 for(auto& [addr, entry] : line_addrs)
277 {
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));
281 }
282 }
283 }
284
285public:
286 CodeobjDecoderComponent(const char* codeobj_data, uint64_t codeobj_size)
287 {
288 // Can throw
289 disassembly = std::make_unique<DisassemblyInstance>(codeobj_data, codeobj_size);
290 try
291 {
292 m_symbol_map = disassembly->GetKernelMap(); // Can throw
293 } catch(...)
294 {}
295
296 // .sqtt_funcmap is an ASCII section emitted by the sqtt_instrumentation
297 // pass. Each newline-terminated row assigns a marker ID (the value
298 // emitted by s_ttracedata{,_imm} at runtime -- see
299 // funcmap::decode_marker_value) to a function, kernel, user scope, or
300 // point marker. See funcmap.hpp for the full grammar.
301 //
302 // .sqtt_funcmap (raw bytes -- one row per entry, '\n'-terminated):
303 // +--------------------------------------------------------------+
304 // | F:1:my_device_fn@/path/file.cpp:42 |
305 // | K:my_kernel@/path/file.cpp:8 |
306 // | U:2:my_scope_marker |
307 // | P:3:vmem_load@/path/file.cpp:71 |
308 // | W:64 |
309 // +--------------------------------------------------------------+
310 // | |
311 // | +-- optional "@source_loc" tail (file[:line])
312 // |
313 // +-- F:id:name... function -- enter/exit scope marker
314 // +-- K:name... kernel -- name -> vaddr lookup, no id
315 // +-- U:id:name user-defined scope marker (enter/exit)
316 // +-- P:id:name... point marker (barrier, mem op, user pt)
317 // +-- W:N wave size (32 or 64)
318 // |
319 // v funcmap::parse_funcmap_section
320 // m_funcmap.entries insertion-ordered list of FuncmapEntry rows
321 // m_funcmap.by_id id -> entry (K: rows have no id; not indexed)
322 // m_funcmap.wave_size value of the W: row (0 if absent)
323 //
324 // The pass below joins the freshly-parsed funcmap against
325 // m_symbol_map (which the disassembler already populated from
326 // .symtab) to back-fill `vaddr` on every F:/K: entry whose name
327 // matches a kernel symbol. This lets consumers resolve a marker to
328 // function-name to load address in one shot.
329 auto section_bytes =
330 funcmap::extract_elf_section(codeobj_data, codeobj_size, ".sqtt_funcmap");
331 if(section_bytes)
332 {
333 m_funcmap = funcmap::parse_funcmap_section(*section_bytes);
334
335 // m_symbol_map is vaddr-keyed; build the reverse index for the join.
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);
340
341 // FuncmapEntry is shared_ptr<const>, so we copy-on-write: clone,
342 // set vaddr, then swap the shared_ptr in entries[] (and by_id).
343 for(auto& entry_ptr : m_funcmap.entries)
344 {
345 if(!entry_ptr) continue;
346 if(entry_ptr->kind != funcmap::FuncmapEntryKind::Function &&
347 entry_ptr->kind != funcmap::FuncmapEntryKind::Kernel)
348 continue;
349
350 auto it = name_to_vaddr.find(entry_ptr->name);
351 if(it == name_to_vaddr.end()) continue;
352
353 auto updated = std::make_shared<funcmap::FuncmapEntry>(*entry_ptr);
354 updated->vaddr = it->second;
355 // K: rows are not present in by_id by design (they carry no
356 // marker ID -- see parse_funcmap_section). The identity guard
357 // skips slots already overwritten by a later F:/U:/P: row.
358 if(updated->kind != funcmap::FuncmapEntryKind::Kernel)
359 {
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;
363 }
364 entry_ptr = std::move(updated);
365 }
366 }
367 }
368 ~CodeobjDecoderComponent() = default;
369
370 std::optional<uint64_t> va2fo(uint64_t vaddr) const
371 {
372 if(disassembly) return disassembly->va2fo(vaddr);
373 return std::nullopt;
374 };
375
376 std::unique_ptr<Instruction> disassemble_instruction(uint64_t faddr, uint64_t vaddr)
377 {
378 if(!disassembly) throw std::exception();
379
380 auto pair = disassembly->ReadInstruction(faddr);
381 auto inst = std::make_unique<Instruction>(std::move(pair.first), pair.second);
382 inst->faddr = faddr;
383 inst->vaddr = vaddr;
384
385 std::call_once(m_debug_line_info_once, [this]() {
386 try
387 {
388 loadDebugLineInfo();
389 } catch(std::exception& e)
390 {
391 std::cerr << "rocprofiler-sdk: failed to parse DWARF line info: " << e.what()
392 << '\n';
393 } catch(...)
394 {
395 std::cerr << "rocprofiler-sdk: failed to parse DWARF line info\n";
396 }
397 });
398
399 auto it = m_line_number_map.find({vaddr, 0, 0});
400 if(it != m_line_number_map.end()) inst->comment = it->second;
401
402 return inst;
403 }
404
405 // Parsed `.sqtt_funcmap` with vaddr back-filled on F:/K: rows. Empty
406 // when the code object has no .sqtt_funcmap section.
407 const funcmap::Funcmap& getFuncmap() const { return m_funcmap; }
408
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{};
413
414 std::map<segment::address_range_t, std::string> m_line_number_map{};
415 std::once_flag m_debug_line_info_once{};
416};
417
418class LoadedCodeobjDecoder
419{
420public:
421 LoadedCodeobjDecoder(const char* filepath, uint64_t _load_addr, uint64_t _memsize)
422 : load_addr(_load_addr)
423 , load_end(_load_addr + _memsize)
424 {
425 if(!filepath) throw std::runtime_error("Empty filepath.");
426
427 std::string_view fpath(filepath);
428
429 if(fpath.rfind(".out") + 4 == fpath.size())
430 {
431 std::ifstream file(filepath, std::ios::in | std::ios::binary);
432
433 if(!file.is_open()) throw std::runtime_error("Invalid file " + std::string(filepath));
434
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());
440
441 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
442 }
443 else
444 {
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());
448 }
449 }
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)
453 {
454 decoder = std::make_unique<CodeobjDecoderComponent>(static_cast<const char*>(data), size);
455 }
456 std::unique_ptr<Instruction> get(uint64_t ld_addr)
457 {
458 if(!decoder || ld_addr < load_addr) return nullptr;
459
460 uint64_t voffset = ld_addr - load_addr;
461 auto faddr = decoder->va2fo(voffset);
462 if(!faddr) return nullptr;
463
464 auto unique = decoder->disassemble_instruction(*faddr, voffset);
465 if(unique == nullptr || unique->size == 0) return nullptr;
466 unique->ld_addr = ld_addr;
467 return unique;
468 }
469
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(); }
474
475 const char* getSymbolName(uint64_t addr) const
476 {
477 if(!decoder) return nullptr;
478
479 auto it = decoder->m_symbol_map.find(addr - load_addr);
480 if(it != decoder->m_symbol_map.end()) return it->second.name.data();
481
482 return nullptr;
483 }
484
485 std::map<uint64_t, SymbolInfo>& getSymbolMap() const
486 {
487 if(!decoder) throw std::exception();
488 return decoder->m_symbol_map;
489 }
490 const funcmap::Funcmap& getFuncmap() const;
491 bool has_decoder() const noexcept { return bool(decoder); }
492 const uint64_t load_addr;
493
494private:
495 uint64_t load_end{0};
496
497 std::unique_ptr<CodeobjDecoderComponent> decoder{nullptr};
498};
499
500/**
501 * @brief Maps ID and offsets into instructions
502 */
504{
505public:
506 CodeobjMap() = default;
507 virtual ~CodeobjMap() = default;
508
509 virtual void addDecoder(const char* filepath,
510 marker_id_t id,
511 uint64_t load_addr,
512 uint64_t memsize)
513 {
514 decoders[id] = std::make_shared<LoadedCodeobjDecoder>(filepath, load_addr, memsize);
515 }
516
517 virtual void addDecoder(const void* data,
518 size_t memory_size,
519 marker_id_t id,
520 uint64_t load_addr,
521 uint64_t memsize)
522 {
523 decoders[id] =
524 std::make_shared<LoadedCodeobjDecoder>(data, memory_size, load_addr, memsize);
525 }
526
527 virtual bool removeDecoderbyId(marker_id_t id) { return decoders.erase(id) != 0; }
528
529 std::unique_ptr<Instruction> get(marker_id_t id, uint64_t offset)
530 {
531 try
532 {
533 auto& decoder = decoders.at(id);
534 auto inst = decoder->get(decoder->begin() + offset);
535 if(inst != nullptr) inst->codeobj_id = id;
536 return inst;
537 } catch(std::out_of_range&)
538 {}
539 return nullptr;
540 }
541
542 const char* getSymbolName(marker_id_t id, uint64_t offset)
543 {
544 try
545 {
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&)
550 {}
551 return nullptr;
552 }
553
554 funcmap::Funcmap::EntryPtr getMarker(marker_id_t id, uint32_t funcmap_id) const;
555
556 // Returns nullopt if `id` has no registered decoder (or the decoder is
557 // uninitialized). Caller-side existence test replaces what used to be a
558 // try/catch on decoders.at(id). Funcmap is returned by value (copy of a
559 // vector + unordered_map of shared_ptrs) since std::optional cannot hold
560 // a reference in C++17.
561 std::optional<funcmap::Funcmap> getFuncmap(marker_id_t id) const;
562
563protected:
564 std::unordered_map<marker_id_t, std::shared_ptr<LoadedCodeobjDecoder>> decoders{};
565};
566
567/**
568 * @brief Translates virtual addresses to elf file offsets
569 */
571{
572 using Super = CodeobjMap;
573
574public:
575 CodeobjAddressTranslate() = default;
576 ~CodeobjAddressTranslate() override = default;
577
578 void addDecoder(const char* filepath,
579 marker_id_t id,
580 uint64_t load_addr,
581 uint64_t memsize) override
582 {
583 this->Super::addDecoder(filepath, id, load_addr, memsize);
584 auto ptr = decoders.at(id);
585 table.insert({ptr->begin(), ptr->size(), id});
586 }
587
588 void addDecoder(const void* data,
589 size_t memory_size,
590 marker_id_t id,
591 uint64_t load_addr,
592 uint64_t memsize) override
593 {
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});
597 }
598
599 bool removeDecoder(marker_id_t id, uint64_t load_addr)
600 {
601 return table.remove(load_addr) && this->Super::removeDecoderbyId(id);
602 }
603
604 bool removeDecoder(marker_id_t id)
605 {
606 uint64_t addr = 0;
607 if(decoders.find(id) != decoders.end()) addr = decoders.at(id)->begin();
608
609 return removeDecoder(id, addr);
610 }
611
612 std::unique_ptr<Instruction> get(uint64_t vaddr)
613 {
614 auto addr_range = table.find_codeobj_in_range(vaddr);
615 return this->Super::get(addr_range.id, vaddr - addr_range.addr);
616 }
617
618 std::unique_ptr<Instruction> get(marker_id_t id, uint64_t offset)
619 {
620 if(id == 0)
621 return get(offset);
622 else
623 return this->Super::get(id, offset);
624 }
625
626 const char* getSymbolName(uint64_t vaddr)
627 {
628 for(auto& [_, decoder] : decoders)
629 {
630 if(!decoder->inrange(vaddr)) continue;
631 return decoder->getSymbolName(vaddr);
632 }
633 return nullptr;
634 }
635
636 std::map<uint64_t, SymbolInfo> getSymbolMap() const
637 {
638 std::map<uint64_t, SymbolInfo> symbols;
639
640 for(const auto& [_, dec] : decoders)
641 {
642 auto& smap = dec->getSymbolMap();
643 for(auto& [vaddr, sym] : smap)
644 symbols[vaddr + dec->load_addr] = sym;
645 }
646
647 return symbols;
648 }
649
650 std::map<uint64_t, SymbolInfo> getSymbolMap(marker_id_t id) const
651 {
652 if(decoders.find(id) == decoders.end()) return {};
653
654 try
655 {
656 return decoders.at(id)->getSymbolMap();
657 } catch(...)
658 {
659 return {};
660 }
661 }
662
663 std::vector<std::pair<marker_id_t, funcmap::Funcmap::EntryPtr>> findMarkerAny(
664 uint32_t funcmap_id) const;
665
666 uint32_t getWaveSize() const;
667
668private:
670};
671
672inline const funcmap::Funcmap&
673LoadedCodeobjDecoder::getFuncmap() const
674{
675 if(!decoder) throw std::exception();
676 return decoder->getFuncmap();
677}
678
679inline funcmap::Funcmap::EntryPtr
680CodeobjMap::getMarker(marker_id_t id, uint32_t funcmap_id) const
681{
682 auto it = decoders.find(id);
683 if(it == decoders.end()) return nullptr;
684
685 try
686 {
687 return it->second->getFuncmap().find(funcmap_id);
688 } catch(...)
689 {
690 return nullptr;
691 }
692}
693
694inline std::optional<funcmap::Funcmap>
695CodeobjMap::getFuncmap(marker_id_t id) const
696{
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();
700}
701
702inline std::vector<std::pair<marker_id_t, funcmap::Funcmap::EntryPtr>>
703CodeobjAddressTranslate::findMarkerAny(uint32_t funcmap_id) const
704{
705 std::vector<std::pair<marker_id_t, funcmap::Funcmap::EntryPtr>> out;
706 for(const auto& [id, dec] : decoders)
707 {
708 try
709 {
710 if(auto entry = dec->getFuncmap().find(funcmap_id))
711 out.emplace_back(id, std::move(entry));
712 } catch(...)
713 {}
714 }
715 return out;
716}
717
718inline uint32_t
719CodeobjAddressTranslate::getWaveSize() const
720{
721 uint32_t agreed = 0;
722 for(const auto& [id, dec] : decoders)
723 {
724 uint32_t w = 0;
725 try
726 {
727 w = dec->getFuncmap().wave_size;
728 } catch(...)
729 {
730 continue;
731 }
732
733 if(w == 0) continue;
734 if(agreed == 0)
735 {
736 agreed = w;
737 }
738 else if(agreed != w)
739 {
740 std::cerr << "rocprofiler-sdk: .sqtt_funcmap wave size disagreement (" << agreed
741 << " vs " << w << " from codeobj id " << id << ")\n";
742 return 0;
743 }
744 }
745 return agreed;
746}
747
748inline DIEInfo::DIEInfo(Dwarf_Die* die)
749{
750 if(dwarf_tag(die) == DW_TAG_inlined_subroutine)
751 {
752 Dwarf_Addr low_pc{};
753 Dwarf_Addr high_pc{};
754
755 // Check if this inlined subroutine covers the target address
756 // First try simple contiguous range (low_pc to high_pc)
757 if(dwarf_lowpc(die, &low_pc) == 0 && dwarf_highpc(die, &high_pc) == 0)
758 {
759 addRange(DRange{low_pc, high_pc});
760 }
761 else
762 {
763 // Function may have non-contiguous ranges
764 // Check all address ranges associated with this DIE
765 Dwarf_Addr base{};
766 ptrdiff_t offset{};
767 while((offset = dwarf_ranges(die, offset, &base, &low_pc, &high_pc)) > 0)
768 addRange(DRange{low_pc, high_pc});
769 }
770
771 // Extract call site information - where this function was inlined
772 Dwarf_Attribute call_file_attr{};
773 Dwarf_Attribute call_line_attr{};
774 Dwarf_Word call_file{};
775 Dwarf_Word call_line{};
776
777 // Get the file and line number where this function was called/inlined
778 // Do not return early - children must always be traversed for nested inlining
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)
783 {
784 // Get the compilation unit to resolve file names
785 Dwarf_Die cu_die{};
786 if(dwarf_diecu(die, &cu_die, nullptr, nullptr))
787 {
788 // Get the source files table for this compilation unit
789 Dwarf_Files* files{};
790 size_t nfiles{};
791 if(dwarf_getsrcfiles(&cu_die, &files, &nfiles) == 0 && call_file < nfiles)
792 {
793 if(const char* filename = dwarf_filesrc(files, call_file, nullptr, nullptr))
794 {
795 file_and_line = std::string(filename) + ":" + std::to_string(call_line);
796 }
797 }
798 }
799 }
800
801 // Always include this node's range so parents can find it via children_range
802 children_range = total_range;
803 }
804
805 Dwarf_Die child{};
806
807 // Traverse children (recursive part)
808 if(dwarf_child(die, &child) == 0)
809 {
810 do
811 {
812 children.emplace_back(std::make_unique<DIEInfo>(&child));
813 children_range.expand(children.back()->children_range);
814
815 } while(dwarf_siblingof(&child, &child) == 0);
816 }
817}
818
819inline bool
820DIEInfo::getCallStackRecursive(Dwarf_Addr addr, std::vector<std::string>& call_stack)
821{
822 if(!children_range.contains(addr)) return false;
823
824 bool addedOne = false;
825
826 for(auto& child : children)
827 {
828 // Only add from one of the children
829 addedOne = child->getCallStackRecursive(addr, call_stack);
830 if(addedOne) break;
831 }
832
833 if(total_range.contains(addr))
834 {
835 for(auto& range : all_ranges)
836 {
837 if(!range.contains(addr)) continue;
838
839 call_stack.emplace_back(file_and_line);
840 return true;
841 }
842 }
843
844 // Check if one of the child nodes added to the stack
845 return addedOne;
846}
847
848} // namespace disassembly
849} // namespace codeobj
850} // namespace sdk
851} // namespace rocprofiler
Translates virtual addresses to elf file offsets.
Maps ID and offsets into instructions.
Finds a candidate codeobj for the given vaddr.
Definition segment.hpp:66
STL namespace.
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.