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(
109 dwarf_nextcu(
110 dbg.get(), cu_offset, &next_offset, &header_size, nullptr, nullptr, nullptr) ==
111 0)
112 {
113 Dwarf_Die die;
114 if(!dwarf_offdie(dbg.get(), cu_offset + header_size, &die)) continue;
115
116 Dwarf_Lines* lines;
117 size_t line_count;
118 if(dwarf_getsrclines(&die, &lines, &line_count) != 0) continue;
119
120 for(size_t i = 0; i < line_count; ++i)
121 {
122 Dwarf_Addr addr;
123 int line_number;
124 Dwarf_Line* line = dwarf_onesrcline(lines, i);
125
126 if(line && dwarf_lineaddr(line, &addr) == 0 &&
127 dwarf_lineno(line, &line_number) == 0 && line_number != 0)
128 {
129 std::string src = dwarf_linesrc(line, nullptr, nullptr);
130 auto dwarf_line = src + ':' + std::to_string(line_number);
131
132 if(line_addrs.find(addr) != line_addrs.end())
133 {
134 line_addrs.at(addr) += ' ' + dwarf_line;
135 continue;
136 }
137
138 line_addrs.emplace(addr, std::move(dwarf_line));
139 }
140 }
141 cu_offset = next_offset;
142 }
143
144 auto it = line_addrs.begin();
145 if(it != line_addrs.end())
146 {
147 while(std::next(it) != line_addrs.end())
148 {
149 uint64_t delta = std::next(it)->first - it->first;
150 auto segment = segment::address_range_t{it->first, delta, 0};
151 m_line_number_map.emplace(segment, std::move(it->second));
152 it++;
153 }
154 auto segment = segment::address_range_t{it->first, codeobj_size - it->first, 0};
155 m_line_number_map.emplace(segment, std::move(it->second));
156 }
157 }
158
159 // Can throw
160 disassembly = std::make_unique<DisassemblyInstance>(codeobj_data, codeobj_size);
161 try
162 {
163 m_symbol_map = disassembly->GetKernelMap(); // Can throw
164 } catch(...)
165 {}
166 }
168
169 std::optional<uint64_t> va2fo(uint64_t vaddr) const
170 {
171 if(disassembly) return disassembly->va2fo(vaddr);
172 return {};
173 };
174
175 std::unique_ptr<Instruction> disassemble_instruction(uint64_t faddr, uint64_t vaddr)
176 {
177 if(!disassembly) throw std::exception();
178
179 auto pair = disassembly->ReadInstruction(faddr);
180 auto inst = std::make_unique<Instruction>(std::move(pair.first), pair.second);
181 inst->faddr = faddr;
182 inst->vaddr = vaddr;
183
184 auto it = m_line_number_map.find({vaddr, 0, 0});
185 if(it != m_line_number_map.end()) inst->comment = it->second;
186
187 return inst;
188 }
189
190 std::map<uint64_t, SymbolInfo> m_symbol_map{};
191 std::vector<std::shared_ptr<Instruction>> instructions{};
192 std::unique_ptr<DisassemblyInstance> disassembly{};
193
194 std::map<segment::address_range_t, std::string> m_line_number_map{};
195};
196
198{
199public:
200 LoadedCodeobjDecoder(const char* filepath, uint64_t _load_addr, uint64_t _memsize)
201 : load_addr(_load_addr)
202 , load_end(_load_addr + _memsize)
203 {
204 if(!filepath) throw std::runtime_error("Empty filepath.");
205
206 std::string_view fpath(filepath);
207
208 if(fpath.rfind(".out") + 4 == fpath.size())
209 {
210 std::ifstream file(filepath, std::ios::in | std::ios::binary);
211
212 if(!file.is_open()) throw std::runtime_error("Invalid file " + std::string(filepath));
213
214 std::vector<char> buffer;
215 file.seekg(0, file.end);
216 buffer.resize(file.tellg());
217 file.seekg(0, file.beg);
218 file.read(buffer.data(), buffer.size());
219
220 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
221 }
222 else
223 {
224 std::unique_ptr<CodeObjectBinary> binary = std::make_unique<CodeObjectBinary>(filepath);
225 auto& buffer = binary->buffer;
226 decoder = std::make_unique<CodeobjDecoderComponent>(buffer.data(), buffer.size());
227 }
228 }
229 LoadedCodeobjDecoder(const void* data, uint64_t size, uint64_t _load_addr, size_t _memsize)
230 : load_addr(_load_addr)
231 , load_end(load_addr + _memsize)
232 {
233 decoder =
234 std::make_unique<CodeobjDecoderComponent>(reinterpret_cast<const char*>(data), size);
235 }
236 std::unique_ptr<Instruction> get(uint64_t ld_addr)
237 {
238 if(!decoder || ld_addr < load_addr) return nullptr;
239
240 uint64_t voffset = ld_addr - load_addr;
241 auto faddr = decoder->va2fo(voffset);
242 if(!faddr) return nullptr;
243
244 auto unique = decoder->disassemble_instruction(*faddr, voffset);
245 if(unique == nullptr || unique->size == 0) return nullptr;
246 unique->ld_addr = ld_addr;
247 return unique;
248 }
249
250 uint64_t begin() const { return load_addr; };
251 uint64_t end() const { return load_end; }
252 uint64_t size() const { return load_end - load_addr; }
253 bool inrange(uint64_t addr) const { return addr >= begin() && addr < end(); }
254
255 const char* getSymbolName(uint64_t addr) const
256 {
257 if(!decoder) return nullptr;
258
259 auto it = decoder->m_symbol_map.find(addr - load_addr);
260 if(it != decoder->m_symbol_map.end()) return it->second.name.data();
261
262 return nullptr;
263 }
264
265 std::map<uint64_t, SymbolInfo>& getSymbolMap() const
266 {
267 if(!decoder) throw std::exception();
268 return decoder->m_symbol_map;
269 }
270 const uint64_t load_addr;
271
272private:
273 uint64_t load_end{0};
274
275 std::unique_ptr<CodeobjDecoderComponent> decoder{nullptr};
276};
277
278/**
279 * @brief Maps ID and offsets into instructions
280 */
282{
283public:
284 CodeobjMap() = default;
285 virtual ~CodeobjMap() = default;
286
287 virtual void addDecoder(const char* filepath,
288 marker_id_t id,
289 uint64_t load_addr,
290 uint64_t memsize)
291 {
292 decoders[id] = std::make_shared<LoadedCodeobjDecoder>(filepath, load_addr, memsize);
293 }
294
295 virtual void addDecoder(const void* data,
296 size_t memory_size,
297 marker_id_t id,
298 uint64_t load_addr,
299 uint64_t memsize)
300 {
301 decoders[id] =
302 std::make_shared<LoadedCodeobjDecoder>(data, memory_size, load_addr, memsize);
303 }
304
305 virtual bool removeDecoderbyId(marker_id_t id) { return decoders.erase(id) != 0; }
306
307 std::unique_ptr<Instruction> get(marker_id_t id, uint64_t offset)
308 {
309 try
310 {
311 auto& decoder = decoders.at(id);
312 auto inst = decoder->get(decoder->begin() + offset);
313 if(inst != nullptr) inst->codeobj_id = id;
314 return inst;
315 } catch(std::out_of_range&)
316 {}
317 return nullptr;
318 }
319
320 const char* getSymbolName(marker_id_t id, uint64_t offset)
321 {
322 try
323 {
324 auto& decoder = decoders.at(id);
325 uint64_t vaddr = decoder->begin() + offset;
326 if(decoder->inrange(vaddr)) return decoder->getSymbolName(vaddr);
327 } catch(std::out_of_range&)
328 {}
329 return nullptr;
330 }
331
332protected:
333 std::unordered_map<marker_id_t, std::shared_ptr<LoadedCodeobjDecoder>> decoders{};
334};
335
336/**
337 * @brief Translates virtual addresses to elf file offsets
338 */
340{
341 using Super = CodeobjMap;
342
343public:
345 ~CodeobjAddressTranslate() override = default;
346
347 void addDecoder(const char* filepath,
348 marker_id_t id,
349 uint64_t load_addr,
350 uint64_t memsize) override
351 {
352 this->Super::addDecoder(filepath, id, load_addr, memsize);
353 auto ptr = decoders.at(id);
354 table.insert({ptr->begin(), ptr->size(), id});
355 }
356
357 void addDecoder(const void* data,
358 size_t memory_size,
359 marker_id_t id,
360 uint64_t load_addr,
361 uint64_t memsize) override
362 {
363 this->Super::addDecoder(data, memory_size, id, load_addr, memsize);
364 auto ptr = decoders.at(id);
365 table.insert({ptr->begin(), ptr->size(), id});
366 }
367
368 virtual bool removeDecoder(marker_id_t id, uint64_t load_addr)
369 {
370 return table.remove(load_addr) && this->Super::removeDecoderbyId(id);
371 }
372
373 std::unique_ptr<Instruction> get(uint64_t vaddr)
374 {
375 auto addr_range = table.find_codeobj_in_range(vaddr);
376 return this->Super::get(addr_range.id, vaddr - addr_range.addr);
377 }
378
379 std::unique_ptr<Instruction> get(marker_id_t id, uint64_t offset)
380 {
381 if(id == 0)
382 return get(offset);
383 else
384 return this->Super::get(id, offset);
385 }
386
387 const char* getSymbolName(uint64_t vaddr)
388 {
389 for(auto& [_, decoder] : decoders)
390 {
391 if(!decoder->inrange(vaddr)) continue;
392 return decoder->getSymbolName(vaddr);
393 }
394 return nullptr;
395 }
396
397 std::map<uint64_t, SymbolInfo> getSymbolMap() const
398 {
399 std::map<uint64_t, SymbolInfo> symbols;
400
401 for(const auto& [_, dec] : decoders)
402 {
403 auto& smap = dec->getSymbolMap();
404 for(auto& [vaddr, sym] : smap)
405 symbols[vaddr + dec->load_addr] = sym;
406 }
407
408 return symbols;
409 }
410
411 std::map<uint64_t, SymbolInfo> getSymbolMap(marker_id_t id) const
412 {
413 if(decoders.find(id) == decoders.end()) return {};
414
415 try
416 {
417 return decoders.at(id)->getSymbolMap();
418 } catch(...)
419 {
420 return {};
421 }
422 }
423
424private:
426};
427
428} // namespace disassembly
429} // namespace codeobj
430} // namespace sdk
431} // namespace rocprofiler
Translates virtual addresses to elf file offsets.
std::unique_ptr< Instruction > get(uint64_t vaddr)
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)
void addDecoder(const char *filepath, marker_id_t id, uint64_t load_addr, uint64_t memsize) override
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
std::optional< uint64_t > va2fo(uint64_t vaddr) const
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)