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.3.2
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{
74struct FallbackOp
75{
76 const char* str{};
77 size_t size{};
78};
79
80inline FallbackOp
81get_fallback_op_gfx1250(uint32_t header)
82{
83 if((header >> 7) == 0) return {"v_vop_generic ", 4};
84
85 switch(header)
86 {
87 case 0b11001100: return {"v_vop3p_generic ", 8};
88 case 0b11001111: return {"v_vopd3_generic ", 12};
89 default: break;
90 }
91
92 header >>= 2;
93
94 switch(header)
95 {
96 case 0b110001: return {"buffer_load_generic ", 12};
97 case 0b111011: return {"global_load_generic ", 12};
98 case 0b110100: return {"image_load_generic ", 12};
99 case 0b110010: return {"v_vopd_generic ", 8};
100 case 0b110101: return {"v_vopsd_generic ", 8};
101 case 0b110110: return {"ds_generic ", 8};
102 case 0b111101: return {"s_load_generic ", 8};
103 default: break;
104 }
105
106 header >>= 4;
107 if(header == 0b10) return {"s_sop ", 4};
108
109 return {nullptr, 0};
110}
111
112class CodeObjectBinary
113{
114public:
115 CodeObjectBinary(std::string _uri)
116 : m_uri(std::move(_uri))
117 {
118 const std::string protocol_delim{"://"};
119
120 size_t protocol_end = m_uri.find(protocol_delim);
121 std::string protocol = m_uri.substr(0, protocol_end);
122 protocol_end += protocol_delim.length();
123
124 std::transform(protocol.begin(), protocol.end(), protocol.begin(), [](unsigned char c) {
125 return std::tolower(c);
126 });
127
128 std::string path;
129 size_t path_end = m_uri.find_first_of("#?", protocol_end);
130 if(path_end != std::string::npos)
131 {
132 path = m_uri.substr(protocol_end, path_end++ - protocol_end);
133 }
134 else
135 {
136 path = m_uri.substr(protocol_end);
137 }
138
139 /* %-decode the string. */
140 std::string decoded_path;
141 decoded_path.reserve(path.length());
142 for(size_t i = 0; i < path.length(); ++i)
143 {
144 if(path[i] == '%' && std::isxdigit(path[i + 1]) != 0 && std::isxdigit(path[i + 2]) != 0)
145 {
146 decoded_path += std::stoi(path.substr(i + 1, 2), nullptr, 16);
147 i += 2;
148 }
149 else
150 {
151 decoded_path += path[i];
152 }
153 }
154
155 /* Tokenize the query/fragment. */
156 std::vector<std::string> tokens;
157 size_t pos, last = path_end;
158 while((pos = m_uri.find('&', last)) != std::string::npos)
159 {
160 tokens.emplace_back(m_uri.substr(last, pos - last));
161 last = pos + 1;
162 }
163 if(last != std::string::npos)
164 {
165 tokens.emplace_back(m_uri.substr(last));
166 }
167
168 /* Create a tag-value map from the tokenized query/fragment. */
169 std::unordered_map<std::string, std::string> params;
170 std::for_each(tokens.begin(), tokens.end(), [&](std::string& token) {
171 size_t delim = token.find('=');
172 if(delim != std::string::npos)
173 {
174 params.emplace(token.substr(0, delim), token.substr(delim + 1));
175 }
176 });
177
178 buffer = std::vector<char>{};
179 size_t offset = 0;
180 size_t size = 0;
181
182 if(auto offset_it = params.find("offset"); offset_it != params.end())
183 {
184 offset = std::stoul(offset_it->second, nullptr, 0);
185 }
186
187 if(auto size_it = params.find("size"); size_it != params.end())
188 {
189 if((size = std::stoul(size_it->second, nullptr, 0)) == 0) return;
190 }
191
192 if(protocol == "memory") throw std::runtime_error(protocol + " protocol not supported!");
193
194 std::ifstream file(decoded_path, std::ios::in | std::ios::binary);
195 if(!file || !file.is_open()) throw std::runtime_error("could not open " + decoded_path);
196
197 if(size == 0)
198 {
199 file.ignore(std::numeric_limits<std::streamsize>::max());
200 size_t bytes = file.gcount();
201 file.clear();
202
203 if(bytes < offset) throw std::runtime_error("invalid uri " + decoded_path);
204
205 size = bytes - offset;
206 }
207
208 file.seekg(offset, std::ios_base::beg);
209 buffer.resize(size);
210 file.read(buffer.data(), size);
211 }
212
213 std::string m_uri;
214 std::vector<char> buffer;
215};
216
217struct SymbolInfo
218{
219 std::string name{};
220 uint64_t faddr = 0;
221 uint64_t vaddr = 0;
222 uint64_t mem_size = 0;
223};
224
225class DisassemblyInstance
226{
227public:
228 DisassemblyInstance(const char* codeobj_data, uint64_t codeobj_size)
229 {
230 buffer = std::vector<char>(codeobj_size, 0);
231 std::memcpy(buffer.data(), codeobj_data, codeobj_size);
232
233 THROW_COMGR(amd_comgr_create_data(AMD_COMGR_DATA_KIND_EXECUTABLE, &data));
234 THROW_COMGR(amd_comgr_set_data(data, buffer.size(), buffer.data()));
235
236 size_t isa_size = 128;
237 std::string input_isa{};
238 input_isa.resize(isa_size);
239 THROW_COMGR(amd_comgr_get_data_isa_name(data, &isa_size, input_isa.data()));
240
241 THROW_COMGR(amd_comgr_create_disassembly_info(
242 input_isa.data(),
243 &DisassemblyInstance::memory_callback,
244 &DisassemblyInstance::inst_callback,
245 [](uint64_t, void*) {},
246 &info));
247
248 if(input_isa.find("gfx1250") != std::string::npos) gfxip = 1250;
249 }
250 ~DisassemblyInstance()
251 {
252 amd_comgr_release_data(data);
253 amd_comgr_destroy_disassembly_info(info);
254 }
255
256 std::pair<std::string, size_t> ReadInstruction(uint64_t faddr)
257 {
258 uint64_t size_read;
259 uint64_t addr_in_buffer = reinterpret_cast<uint64_t>(buffer.data()) + faddr;
260
261 auto _status =
262 amd_comgr_disassemble_instruction(info, addr_in_buffer, (void*) this, &size_read);
263 if(_status != AMD_COMGR_STATUS_SUCCESS)
264 {
265 if(faddr + 4 > buffer.size()) THROW_COMGR(_status);
266
267 uint32_t read = 0;
268 std::memcpy(&read, buffer.data() + faddr, 4);
269
270 FallbackOp fallback{};
271
272 if(gfxip == 1250) fallback = get_fallback_op_gfx1250(read >> 24);
273
274 if(fallback.str == nullptr || fallback.size == 0) THROW_COMGR(_status);
275
276 this->last_instruction = fallback.str;
277 size_read = fallback.size;
278 }
279
280 return {std::move(this->last_instruction), size_read};
281 }
282
283 std::map<uint64_t, SymbolInfo>& GetKernelMap()
284 {
285 symbol_map = {};
286 THROW_COMGR(amd_comgr_iterate_symbols(data, &DisassemblyInstance::symbol_callback, this));
287
288 return symbol_map;
289 }
290
291 static amd_comgr_status_t symbol_callback(amd_comgr_symbol_t symbol, void* user_data)
292 {
293 amd_comgr_symbol_type_t type;
294 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_TYPE, &type));
295
296 if(type != AMD_COMGR_SYMBOL_TYPE_FUNC) return AMD_COMGR_STATUS_SUCCESS;
297
298 uint64_t vaddr = 0;
299 uint64_t mem_size = 0;
300 uint64_t name_size = 0;
301 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_VALUE, &vaddr));
302 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_SIZE, &mem_size));
303 RETURN_COMGR(
304 amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME_LENGTH, &name_size));
305
306 std::string name;
307 // amd_comgr_symbol_get_info(NAME) writes a C-string: name_size payload
308 // bytes + a trailing '\0'. Reserve name_size + 1 so the NUL has a
309 // legal slot to land in (writing past end() would be UB), then shrink
310 // back to name_size to drop the NUL from std::string's logical size.
311 name.resize(name_size + 1);
312
313 RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME, name.data()));
314 name.resize(name_size);
315
316 DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
317 std::optional<uint64_t> faddr = instance.va2fo(vaddr);
318
319 if(faddr) instance.symbol_map[vaddr] = {name, *faddr, vaddr, mem_size};
320 return AMD_COMGR_STATUS_SUCCESS;
321 }
322
323 static uint64_t memory_callback(uint64_t from, char* to, uint64_t size, void* user_data)
324 {
325 DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
326 int64_t copysize = reinterpret_cast<int64_t>(instance.buffer.data()) +
327 instance.buffer.size() - static_cast<int64_t>(from);
328 copysize = std::min<int64_t>(size, copysize);
329 // NOLINTNEXTLINE(performance-no-int-to-ptr)
330 std::memcpy(to, (char*) from, copysize);
331 return copysize;
332 }
333
334 static void inst_callback(const char* instruction, void* user_data)
335 {
336 DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
337
338 if(!instruction) return;
339
340 while(*instruction == '\t' || *instruction == ' ')
341 instruction++;
342 instance.last_instruction = instruction;
343 }
344
345 std::optional<uint64_t> va2fo(uint64_t va) const
346 {
347 uint64_t offset = 0;
348 uint64_t slicesize = 0;
349 bool nobits = false;
350
351 auto status = amd_comgr_map_elf_virtual_address_to_code_object_offset(
352 data, va, &offset, &slicesize, &nobits);
353
354 if(status != AMD_COMGR_STATUS_SUCCESS || nobits)
355 return std::nullopt;
356 else
357 return offset;
358 }
359
360 int gfxip{};
361 std::vector<char> buffer{};
362 std::string last_instruction{};
363 amd_comgr_disassembly_info_t info{};
364 amd_comgr_data_t data{};
365 std::map<uint64_t, SymbolInfo> symbol_map{};
366};
367
368} // namespace disassembly
369} // namespace codeobj
370} // namespace sdk
371} // namespace rocprofiler
STL namespace.