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

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

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