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 0.5.0
ROCm Profiling API and tools
code_printing.hpp
Go to the documentation of this file.
1// MIT License
2//
3// Copyright (c) 2023 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 <elfutils/libdw.h>
26#include <hsa/amd_hsa_elf.h>
27
28#include <algorithm>
29#include <cstring>
30#include <iostream>
31#include <map>
32#include <memory>
33#include <optional>
34#include <string>
35#include <unordered_map>
36#include <vector>
37
38#include "disassembly.hpp"
39#include "segment.hpp"
40
41namespace rocprofiler
42{
43namespace sdk
44{
45namespace codeobj
46{
47namespace disassembly
48{
50
52{
53 Instruction() = default;
54 Instruction(std::string&& _inst, size_t _size)
55 : inst(std::move(_inst))
56 , size(_size)
57 {}
58 std::string inst{};
59 std::string comment{};
60 uint64_t faddr{0};
61 uint64_t vaddr{0};
62 size_t size{0};
63 uint64_t ld_addr{0}; // Instruction load address, if from loaded codeobj
64 marker_id_t codeobj_id{0}; // Instruction code object load id, if from loaded codeobj
65};
66
68{
69 struct ProtectedFd
70 {
71 ProtectedFd(std::string_view uri)
72 {
73#if defined(_GNU_SOURCE) && defined(MFD_ALLOW_SEALING) && defined(MFD_CLOEXEC)
74 m_fd = ::memfd_create(uri.data(), MFD_ALLOW_SEALING | MFD_CLOEXEC);
75#endif
76 if(m_fd == -1) m_fd = ::open("/tmp", O_TMPFILE | O_RDWR, 0666);
77 if(m_fd == -1) throw std::runtime_error("Could not create a file for codeobj!");
78 }
79 ~ProtectedFd()
80 {
81 if(m_fd != -1) ::close(m_fd);
82 }
83 int m_fd{-1};
84 };
85
86public:
87 CodeobjDecoderComponent(const char* codeobj_data, uint64_t codeobj_size)
88 {
89 ProtectedFd prot("");
90 if(::write(prot.m_fd, codeobj_data, codeobj_size) != static_cast<int64_t>(codeobj_size))
91 throw std::runtime_error("Could not write to temporary file!");
92
93 ::lseek(prot.m_fd, 0, SEEK_SET);
94 fsync(prot.m_fd);
95
97
98 std::unique_ptr<Dwarf, void (*)(Dwarf*)> dbg(dwarf_begin(prot.m_fd, DWARF_C_READ),
99 [](Dwarf* _dbg) { dwarf_end(_dbg); });
100
101 if(dbg)
102 {
103 Dwarf_Off cu_offset{0}, next_offset;
104 size_t header_size;
105
106 std::map<uint64_t, std::string> line_addrs;
107
108 while(!dwarf_nextcu(
109 dbg.get(), cu_offset, &next_offset, &header_size, nullptr, nullptr, nullptr))
110 {
111 Dwarf_Die die;
112 if(!dwarf_offdie(dbg.get(), cu_offset + header_size, &die)) continue;
113
114 Dwarf_Lines* lines;
115 size_t line_count;
116 if(dwarf_getsrclines(&die, &lines, &line_count)) continue;
117
118 for(size_t i = 0; i < line_count; ++i)
119 {
120 Dwarf_Addr addr;
121 int line_number;
122 Dwarf_Line* line = dwarf_onesrcline(lines, i);
123
124 if(line && !dwarf_lineaddr(line, &addr) && !dwarf_lineno(line, &line_number) &&
125 line_number)
126 {
127 std::string src = dwarf_linesrc(line, nullptr, nullptr);
128 auto dwarf_line = src + ':' + std::to_string(line_number);
129
130 if(line_addrs.find(addr) != line_addrs.end())
131 {
132 line_addrs.at(addr) += ' ' + dwarf_line;
133 continue;
134 }
135
136 line_addrs.emplace(addr, std::move(dwarf_line));
137 }
138 }
139 cu_offset = next_offset;
140 }
141
142 auto it = line_addrs.begin();
143 if(it != line_addrs.end())
144 {
145 while(std::next(it) != line_addrs.end())
146 {
147 uint64_t delta = std::next(it)->first - it->first;
148 auto segment = segment::address_range_t{it->first, delta, 0};
149 m_line_number_map.emplace(segment, std::move(it->second));
150 it++;
151 }
152 auto segment = segment::address_range_t{it->first, codeobj_size - it->first, 0};
153 m_line_number_map.emplace(segment, std::move(it->second));
154 }
155 }
156
157 // Can throw
158 disassembly = std::make_unique<DisassemblyInstance>(codeobj_data, codeobj_size);
159 try
160 {
161 m_symbol_map = disassembly->GetKernelMap(); // Can throw
162 } catch(...)
163 {}
164 }
166
167 std::optional<uint64_t> va2fo(uint64_t vaddr)
168 {
169 if(disassembly) return disassembly->va2fo(vaddr);
170 return {};
171 };
172
173 std::unique_ptr<Instruction> disassemble_instruction(uint64_t faddr, uint64_t vaddr)
174 {
175 if(!disassembly) throw std::exception();
176
177 auto pair = disassembly->ReadInstruction(faddr);
178 auto inst = std::make_unique<Instruction>(std::move(pair.first), pair.second);
179 inst->faddr = faddr;
180 inst->vaddr = vaddr;
181
182 auto it = m_line_number_map.find({vaddr, 0, 0});
183 if(it != m_line_number_map.end()) inst->comment = it->second;
184
185 return inst;
186 }
187
188 std::map<uint64_t, SymbolInfo> m_symbol_map{};
189 std::vector<std::shared_ptr<Instruction>> instructions{};
190 std::unique_ptr<DisassemblyInstance> disassembly{};
191
192 std::map<segment::address_range_t, std::string> m_line_number_map{};
193};
194
196{
197public:
198 LoadedCodeobjDecoder(const char* filepath, uint64_t _load_addr, uint64_t _memsize)
199 : load_addr(_load_addr)
200 , load_end(_load_addr + _memsize)
201 {
202 if(!filepath) throw std::runtime_error("Empty filepath.");
203
204 std::string_view fpath(filepath);
205
206 if(fpath.rfind(".out") + 4 == fpath.size())
207 {
208 std::ifstream file(filepath, std::ios::in | std::ios::binary);
209
210 if(!file.is_open()) throw std::runtime_error("Invalid file " + std::string(filepath));
211
212 std::vector<char> buffer;
213 file.seekg(0, file.end);
214 buffer.resize(file.tellg());
215 file.seekg(0, file.beg);
216 file.read(buffer.data(), buffer.size());
217
218 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
219 }
220 else
221 {
222 std::unique_ptr<CodeObjectBinary> binary = std::make_unique<CodeObjectBinary>(filepath);
223 auto& buffer = binary->buffer;
224 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
225 }
226 }
227 LoadedCodeobjDecoder(const void* data, uint64_t size, uint64_t _load_addr, size_t _memsize)
228 : load_addr(_load_addr)
229 , load_end(load_addr + _memsize)
230 {
231 decoder =
232 std::make_unique<CodeobjDecoderComponent>(reinterpret_cast<const char*>(data), size);
233 }
234 std::unique_ptr<Instruction> get(uint64_t ld_addr)
235 {
236 if(!decoder || ld_addr < load_addr) return nullptr;
237
238 uint64_t voffset = ld_addr - load_addr;
239 auto faddr = decoder->va2fo(voffset);
240 if(!faddr) return nullptr;
241
242 auto unique = decoder->disassemble_instruction(*faddr, voffset);
243 if(unique == nullptr || unique->size == 0) return nullptr;
244 unique->ld_addr = ld_addr;
245 return unique;
246 }
247
248 uint64_t begin() const { return load_addr; };
249 uint64_t end() const { return load_end; }
250 uint64_t size() const { return load_end - load_addr; }
251 bool inrange(uint64_t addr) const { return addr >= begin() && addr < end(); }
252
253 const char* getSymbolName(uint64_t addr) const
254 {
255 if(!decoder) return nullptr;
256
257 auto it = decoder->m_symbol_map.find(addr - load_addr);
258 if(it != decoder->m_symbol_map.end()) return it->second.name.data();
259
260 return nullptr;
261 }
262
263 std::map<uint64_t, SymbolInfo>& getSymbolMap() const
264 {
265 if(!decoder) throw std::exception();
266 return decoder->m_symbol_map;
267 }
268 const uint64_t load_addr;
269
270private:
271 uint64_t load_end{0};
272
273 std::unique_ptr<CodeobjDecoderComponent> decoder{nullptr};
274};
275
276/**
277 * @brief Maps ID and offsets into instructions
278 */
280{
281public:
282 CodeobjMap() = default;
283 virtual ~CodeobjMap() = default;
284
285 virtual void addDecoder(const char* filepath,
286 marker_id_t id,
287 uint64_t load_addr,
288 uint64_t memsize)
289 {
290 decoders[id] = std::make_shared<LoadedCodeobjDecoder>(filepath, load_addr, memsize);
291 }
292
293 virtual void addDecoder(const void* data,
294 size_t memory_size,
295 marker_id_t id,
296 uint64_t load_addr,
297 uint64_t memsize)
298 {
299 decoders[id] =
300 std::make_shared<LoadedCodeobjDecoder>(data, memory_size, load_addr, memsize);
301 }
302
303 virtual bool removeDecoderbyId(marker_id_t id) { return decoders.erase(id) != 0; }
304
305 std::unique_ptr<Instruction> get(marker_id_t id, uint64_t offset)
306 {
307 try
308 {
309 auto& decoder = decoders.at(id);
310 auto inst = decoder->get(decoder->begin() + offset);
311 if(inst != nullptr) inst->codeobj_id = id;
312 return inst;
313 } catch(std::out_of_range&)
314 {}
315 return nullptr;
316 }
317
318 const char* getSymbolName(marker_id_t id, uint64_t offset)
319 {
320 try
321 {
322 auto& decoder = decoders.at(id);
323 uint64_t vaddr = decoder->begin() + offset;
324 if(decoder->inrange(vaddr)) return decoder->getSymbolName(vaddr);
325 } catch(std::out_of_range&)
326 {}
327 return nullptr;
328 }
329
330protected:
331 std::unordered_map<marker_id_t, std::shared_ptr<LoadedCodeobjDecoder>> decoders{};
332};
333
334/**
335 * @brief Translates virtual addresses to elf file offsets
336 */
338{
339 using Super = CodeobjMap;
340
341public:
343 ~CodeobjAddressTranslate() override = default;
344
345 virtual void addDecoder(const char* filepath,
346 marker_id_t id,
347 uint64_t load_addr,
348 uint64_t memsize) override
349 {
350 this->Super::addDecoder(filepath, id, load_addr, memsize);
351 auto ptr = decoders.at(id);
352 table.insert({ptr->begin(), ptr->size(), id});
353 }
354
355 virtual void addDecoder(const void* data,
356 size_t memory_size,
357 marker_id_t id,
358 uint64_t load_addr,
359 uint64_t memsize) override
360 {
361 this->Super::addDecoder(data, memory_size, id, load_addr, memsize);
362 auto ptr = decoders.at(id);
363 table.insert({ptr->begin(), ptr->size(), id});
364 }
365
366 virtual bool removeDecoder(marker_id_t id, uint64_t load_addr)
367 {
368 return table.remove(load_addr) && this->Super::removeDecoderbyId(id);
369 }
370
371 std::unique_ptr<Instruction> get(uint64_t vaddr)
372 {
373 auto addr_range = table.find_codeobj_in_range(vaddr);
374 return this->Super::get(addr_range.id, vaddr - addr_range.addr);
375 }
376
377 std::unique_ptr<Instruction> get(marker_id_t id, uint64_t offset)
378 {
379 if(id == 0)
380 return get(offset);
381 else
382 return this->Super::get(id, offset);
383 }
384
385 const char* getSymbolName(uint64_t vaddr)
386 {
387 for(auto& [_, decoder] : decoders)
388 {
389 if(!decoder->inrange(vaddr)) continue;
390 return decoder->getSymbolName(vaddr);
391 }
392 return nullptr;
393 }
394
395 std::map<uint64_t, SymbolInfo> getSymbolMap() const
396 {
397 std::map<uint64_t, SymbolInfo> symbols;
398
399 for(auto& [_, dec] : decoders)
400 {
401 auto& smap = dec->getSymbolMap();
402 for(auto& [vaddr, sym] : smap)
403 symbols[vaddr + dec->load_addr] = sym;
404 }
405
406 return symbols;
407 }
408
409 std::map<uint64_t, SymbolInfo> getSymbolMap(marker_id_t id) const
410 {
411 if(decoders.find(id) == decoders.end()) return {};
412
413 try
414 {
415 return decoders.at(id)->getSymbolMap();
416 } catch(...)
417 {
418 return {};
419 }
420 }
421
422private:
424};
425
426} // namespace disassembly
427} // namespace codeobj
428} // namespace sdk
429} // namespace rocprofiler
Translates virtual addresses to elf file offsets.
virtual void addDecoder(const char *filepath, marker_id_t id, uint64_t load_addr, uint64_t memsize) override
std::unique_ptr< Instruction > get(uint64_t vaddr)
virtual void addDecoder(const void *data, unsigned long memory_size, marker_id_t id, uint64_t load_addr, uint64_t memsize) override
std::map< uint64_t, SymbolInfo > getSymbolMap(marker_id_t id) const
virtual bool removeDecoder(marker_id_t id, uint64_t load_addr)
std::unique_ptr< Instruction > get(marker_id_t id, uint64_t offset)
CodeobjDecoderComponent(const char *codeobj_data, uint64_t codeobj_size)
std::map< segment::address_range_t, std::string > m_line_number_map
std::unique_ptr< Instruction > disassemble_instruction(uint64_t faddr, uint64_t vaddr)
std::vector< std::shared_ptr< Instruction > > instructions
Maps ID and offsets into instructions.
std::unordered_map< marker_id_t, std::shared_ptr< LoadedCodeobjDecoder > > decoders
const char * getSymbolName(marker_id_t id, uint64_t offset)
virtual void addDecoder(const void *data, unsigned long memory_size, marker_id_t id, uint64_t load_addr, uint64_t memsize)
virtual void addDecoder(const char *filepath, marker_id_t id, uint64_t load_addr, uint64_t memsize)
std::unique_ptr< Instruction > get(marker_id_t id, uint64_t offset)
LoadedCodeobjDecoder(const void *data, uint64_t size, uint64_t _load_addr, unsigned long _memsize)
LoadedCodeobjDecoder(const char *filepath, uint64_t _load_addr, uint64_t _memsize)
std::map< uint64_t, SymbolInfo > & getSymbolMap() const
std::unique_ptr< Instruction > get(uint64_t ld_addr)
Finds a candidate codeobj for the given vaddr.
Definition segment.hpp:65
STL namespace.
Instruction(std::string &&_inst, unsigned long _size)