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

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

ROCprofiler-SDK developer API: rocprofiler-sdk/cxx/codeobj/disassembly.hpp Source File
ROCprofiler-SDK developer API 1.0.0
ROCm Profiling API and tools
disassembly.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 <amd_comgr/amd_comgr.h>
26#include <hsa/amd_hsa_elf.h>
27
28#include <fcntl.h>
29#include <sys/mman.h>
30#include <sys/stat.h>
31#include <sys/types.h>
32#include <unistd.h>
33
34#include <algorithm>
35#include <cstring>
36#include <fstream>
37#include <iostream>
38#include <limits>
39#include <map>
40#include <memory>
41#include <optional>
42#include <string>
43#include <unordered_map>
44#include <vector>
45
46#define THROW_COMGR(call) \
47 if(amd_comgr_status_s status = call) \
48 { \
49 const char* reason = ""; \
50 amd_comgr_status_string(status, &reason); \
51 std::cerr << __FILE__ << ':' << __LINE__ << " code: " << status << " failed: " << reason \
52 << "\n"; \
53 throw std::exception(); \
54 }
55
56#define RETURN_COMGR(call) \
57 if(amd_comgr_status_s status = call) \
58 { \
59 const char* reason = ""; \
60 amd_comgr_status_string(status, &reason); \
61 std::cerr << __FILE__ << ':' << __LINE__ << " code: " << status << " failed: " << reason \
62 << "\n"; \
63 return AMD_COMGR_STATUS_ERROR; \
64 }
65
66namespace rocprofiler
67{
68namespace sdk
69{
70namespace codeobj
71{
72namespace disassembly
73{
74class CodeObjectBinary
75{
76public:
77 CodeObjectBinary(std::string _uri)
78 : m_uri(std::move(_uri))
79 {
80 const std::string protocol_delim{"://"};
81
82 size_t protocol_end = m_uri.find(protocol_delim);
83 std::string protocol = m_uri.substr(0, protocol_end);
84 protocol_end += protocol_delim.length();
85
86 std::transform(protocol.begin(), protocol.end(), protocol.begin(), [](unsigned char c) {
87 return std::tolower(c);
88 });
89
90 std::string path;
91 size_t path_end = m_uri.find_first_of("#?", protocol_end);
92 if(path_end != std::string::npos)
93 {
94 path = m_uri.substr(protocol_end, path_end++ - protocol_end);
95 }
96 else
97 {
98 path = m_uri.substr(protocol_end);
99 }
100
101 /* %-decode the string. */
102 std::string decoded_path;
103 decoded_path.reserve(path.length());
104 for(size_t i = 0; i < path.length(); ++i)
105 {
106 if(path[i] == '%' && std::isxdigit(path[i + 1]) != 0 && std::isxdigit(path[i + 2]) != 0)
107 {
108 decoded_path += std::stoi(path.substr(i + 1, 2), nullptr, 16);
109 i += 2;
110 }
111 else
112 {
113 decoded_path += path[i];
114 }
115 }
116
117 /* Tokenize the query/fragment. */
118 std::vector<std::string> tokens;
119 size_t pos, last = path_end;
120 while((pos = m_uri.find('&', last)) != std::string::npos)
121 {
122 tokens.emplace_back(m_uri.substr(last, pos - last));
123 last = pos + 1;
124 }
125 if(last != std::string::npos)
126 {
127 tokens.emplace_back(m_uri.substr(last));
128 }
129
130 /* Create a tag-value map from the tokenized query/fragment. */
131 std::unordered_map<std::string, std::string> params;
132 std::for_each(tokens.begin(), tokens.end(), [&](std::string& token) {
133 size_t delim = token.find('=');
134 if(delim != std::string::npos)
135 {
136 params.emplace(token.substr(0, delim), token.substr(delim + 1));
137 }
138 });
139
140 buffer = std::vector<char>{};
141 size_t offset = 0;
142 size_t size = 0;
143
144 if(auto offset_it = params.find("offset"); offset_it != params.end())
145 {
146 offset = std::stoul(offset_it->second, nullptr, 0);
147 }
148
149 if(auto size_it = params.find("size"); size_it != params.end())
150 {
151 if((size = std::stoul(size_it->second, nullptr, 0)) == 0) return;
152 }
153
154 if(protocol == "memory") throw std::runtime_error(protocol + " protocol not supported!");
155
156 std::ifstream file(decoded_path, std::ios::in | std::ios::binary);
157 if(!file || !file.is_open()) throw std::runtime_error("could not open " + decoded_path);
158
159 if(size == 0)
160 {
161 file.ignore(std::numeric_limits<std::streamsize>::max());
162 size_t bytes = file.gcount();
163 file.clear();
164
165 if(bytes < offset) throw std::runtime_error("invalid uri " + decoded_path);
166
167 size = bytes - offset;
168 }
169
170 file.seekg(offset, std::ios_base::beg);
171 buffer.resize(size);
172 file.read(buffer.data(), size);
173 }
174
175 std::string m_uri;
176 std::vector<char> buffer;
177};
178
179struct SymbolInfo
180{
181 std::string name{};
182 uint64_t faddr = 0;
183 uint64_t vaddr = 0;
184 uint64_t mem_size = 0;
185};
186
187class DisassemblyInstance
188{
189public:
190 DisassemblyInstance(const char* codeobj_data, uint64_t codeobj_size)
191 {
192 buffer = std::vector<char>(codeobj_size, 0);
193 std::memcpy(buffer.data(), codeobj_data, codeobj_size);
194
195 THROW_COMGR(amd_comgr_create_data(AMD_COMGR_DATA_KIND_EXECUTABLE, &data));
196 THROW_COMGR(amd_comgr_set_data(data, buffer.size(), buffer.data()));
197
198 size_t isa_size = 128;
199 std::string input_isa{};
200 input_isa.resize(isa_size);
201 THROW_COMGR(amd_comgr_get_data_isa_name(data, &isa_size, input_isa.data()));
202
203 THROW_COMGR(amd_comgr_create_disassembly_info(
204 input_isa.data(),
205 &DisassemblyInstance::memory_callback,
206 &DisassemblyInstance::inst_callback,
207 [](uint64_t, void*) {},
208 &info));
209 }
210 ~DisassemblyInstance()
211 {
212 amd_comgr_release_data(data);
213 amd_comgr_destroy_disassembly_info(info);
214 }
215
216 std::pair<std::string, size_t> ReadInstruction(uint64_t faddr)
217 {
218 uint64_t size_read;
219 uint64_t addr_in_buffer = reinterpret_cast<uint64_t>(buffer.data()) + faddr;
220
221 THROW_COMGR(
222 amd_comgr_disassemble_instruction(info, addr_in_buffer, (void*) this, &size_read));
223 return {std::move(this->last_instruction), size_read};
224 }
225
226 std::map<uint64_t, SymbolInfo>& GetKernelMap()
227 {
228 symbol_map = {};
229 THROW_COMGR(amd_comgr_iterate_symbols(data, &DisassemblyInstance::symbol_callback, this));
230
231 return symbol_map;
232 }
233
234 static amd_comgr_status_t symbol_callback(amd_comgr_symbol_t symbol, void* user_data)
235 {
236 amd_comgr_symbol_type_t type;
237 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_TYPE, &type));
238
239 if(type != AMD_COMGR_SYMBOL_TYPE_FUNC) return AMD_COMGR_STATUS_SUCCESS;
240
241 uint64_t vaddr = 0;
242 uint64_t mem_size = 0;
243 uint64_t name_size = 0;
244 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_VALUE, &vaddr));
245 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_SIZE, &mem_size));
246 RETURN_COMGR(
247 amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME_LENGTH, &name_size));
248
249 std::string name;
250 name.resize(name_size);
251
252 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME, name.data()));
253
254 DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
255 std::optional<uint64_t> faddr = instance.va2fo(vaddr);
256
257 if(faddr) instance.symbol_map[vaddr] = {name, *faddr, vaddr, mem_size};
258 return AMD_COMGR_STATUS_SUCCESS;
259 }
260
261 static uint64_t memory_callback(uint64_t from, char* to, uint64_t size, void* user_data)
262 {
263 DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
264 int64_t copysize = reinterpret_cast<int64_t>(instance.buffer.data()) +
265 instance.buffer.size() - static_cast<int64_t>(from);
266 copysize = std::min<int64_t>(size, copysize);
267 // NOLINTNEXTLINE(performance-no-int-to-ptr)
268 std::memcpy(to, (char*) from, copysize);
269 return copysize;
270 }
271
272 static void inst_callback(const char* instruction, void* user_data)
273 {
274 DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
275
276 if(!instruction) return;
277
278 while(*instruction == '\t' || *instruction == ' ')
279 instruction++;
280 instance.last_instruction = instruction;
281 }
282
283 std::optional<uint64_t> va2fo(uint64_t va) const
284 {
285 uint64_t offset = 0;
286 uint64_t slicesize = 0;
287 bool nobits = false;
288
289 auto status = amd_comgr_map_elf_virtual_address_to_code_object_offset(
290 data, va, &offset, &slicesize, &nobits);
291
292 if(status != AMD_COMGR_STATUS_SUCCESS || nobits)
293 return std::nullopt;
294 else
295 return offset;
296 }
297
298 std::vector<char> buffer{};
299 std::string last_instruction{};
300 amd_comgr_disassembly_info_t info{};
301 amd_comgr_data_t data{};
302 std::map<uint64_t, SymbolInfo> symbol_map{};
303};
304
305} // namespace disassembly
306} // namespace codeobj
307} // namespace sdk
308} // namespace rocprofiler
STL namespace.