Module: Mesa Branch: main Commit: 33fd93f3b182a9421b863d9155ca3429090e5a3f URL: http://cgit.freedesktop.org/mesa/mesa/commit/?id=33fd93f3b182a9421b863d9155ca3429090e5a3f
Author: Lionel Landwerlin <lionel.g.landwer...@intel.com> Date: Tue May 2 16:41:33 2023 +0300 intel/tools: hang viewer/editor Acked-by: Tapani Pälli <tapani.pa...@intel.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/21167> --- src/intel/common/intel_hang_dump.h | 8 + src/intel/tools/intel_hang_viewer.cpp | 698 ++++++++++++++++++++++++++++++++++ src/intel/tools/meson.build | 12 + 3 files changed, 718 insertions(+) diff --git a/src/intel/common/intel_hang_dump.h b/src/intel/common/intel_hang_dump.h index a7fe1f1ebf8..7ddc339a23d 100644 --- a/src/intel/common/intel_hang_dump.h +++ b/src/intel/common/intel_hang_dump.h @@ -23,6 +23,10 @@ #ifndef INTEL_HANG_DUMP_H #define INTEL_HANG_DUMP_H +#ifdef __cplusplus +extern "C" { +#endif + #include <stdint.h> /** @@ -111,4 +115,8 @@ union intel_hang_dump_block_all { struct intel_hang_dump_block_hw_image hw_img; }; +#ifdef __cplusplus +} +#endif + #endif /* INTEL_HANG_DUMP_H */ diff --git a/src/intel/tools/intel_hang_viewer.cpp b/src/intel/tools/intel_hang_viewer.cpp new file mode 100644 index 00000000000..18217b9e233 --- /dev/null +++ b/src/intel/tools/intel_hang_viewer.cpp @@ -0,0 +1,698 @@ +/* + * Copyright © 2023 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <getopt.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <ctype.h> + +#include <memory> +#include <string> +#include <vector> + +#include "util/list.h" +#include "util/macros.h" + +#include "common/intel_disasm.h" +#include "common/intel_hang_dump.h" + +/* Data */ + +struct hang_bo { + void *map = NULL; + uint64_t offset = 0; + uint64_t size = 0; +}; + +struct hang_map { + uint64_t offset = 0; + uint64_t size = 0; +}; + +struct hang_exec { + uint64_t offset = 0; +}; + +/* UI */ + +#include <epoxy/gl.h> + +#include "imgui/imgui.h" +#include "imgui/imgui_memory_editor.h" +#include "imgui_impl_gtk3.h" +#include "imgui_impl_opengl3.h" + +#include "aubinator_viewer.h" + +static int +map_key(int k) +{ + return ImGuiKey_COUNT + k; +} + +static bool +has_ctrl_key(int key) +{ + return ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(map_key(key)); +} + +static bool +window_has_ctrl_key(int key) +{ + return ImGui::IsRootWindowOrAnyChildFocused() && has_ctrl_key(key); +} + +class window { +public: + virtual void display() = 0; + virtual void destroy() = 0; + + virtual ~window() {} + + const char *name() const { return m_name; } + + bool m_opened = true; + + ImVec2 m_position = ImVec2(-1, -1); + ImVec2 m_size = ImVec2(700, 300); + +protected: + window() {} + + char m_name[128]; +}; + +static struct Context { + /* Hang file descriptor */ + int file_fd = -1; + void *file_map = NULL; + + /* Map hang file in RW for edition */ + bool edit = false; + + struct intel_device_info devinfo; + struct intel_spec *spec = NULL; + struct brw_isa_info isa; + + /* Result of parsing the hang file */ + std::vector<hang_bo> bos; + std::vector<hang_map> maps; + std::vector<hang_exec> execs; + + hang_bo hw_image; + + GtkWidget *gtk_window; + + /* UI state*/ + bool show_commands_window; + bool show_registers_window; + + struct aub_viewer_cfg cfg; + + std::vector<std::shared_ptr<window>> windows; +} context; + +thread_local ImGuiContext* __MesaImGui; + +/**/ + +static uint8_t +read_edit_window(const uint8_t *data, size_t off) +{ + return data[off]; +} + +static void +write_edit_window(uint8_t *data, size_t off, uint8_t d) +{ + data[off] = d; +} + +class edit_window : public window { +public: + struct hang_bo m_bo; + + struct intel_batch_decode_bo m_aub_bo; + uint64_t m_aub_offset; + + struct intel_batch_decode_bo m_gtt_bo; + uint64_t m_gtt_offset; + + struct MemoryEditor m_editor; + + edit_window(const struct hang_bo &bo) + : m_bo(bo) { + m_editor.OptShowDataPreview = true; + m_editor.OptShowAscii = false; + m_editor.ReadFn = read_edit_window; + m_editor.WriteFn = write_edit_window; + + snprintf(m_name, sizeof(m_name), "Memory view 0x%016" PRIx64 "##%p", + bo.offset, this); + } + + void display() { + if (m_bo.map) { + ImGui::BeginChild(ImGui::GetID("##block")); + m_editor.DrawContents((uint8_t *) m_bo.map, m_bo.size, m_bo.offset); + ImGui::EndChild(); + } else { + ImGui::Text("Memory view at 0x%" PRIx64 " not available", m_bo.offset); + } + } + + void destroy() {} +}; + +class shader_window : public window { +public: + std::string m_description; + uint64_t m_address; + std::string m_shader; + + shader_window(const char *description, uint64_t address) + : m_description(description) + , m_address(address) { + snprintf(m_name, sizeof(m_name), + "%s (0x%" PRIx64 ")##%p", m_description.c_str(), m_address, this); + + for (auto &bo : context.bos) { + if (address >= bo.offset && + address < (bo.offset + bo.size)) { + char *shader_txt = NULL; + size_t shader_txt_size = 0; + FILE *f = open_memstream(&shader_txt, &shader_txt_size); + if (f) { + intel_disassemble(&context.isa, + (const uint8_t *) bo.map + + (address - bo.offset), 0, f); + fclose(f); + } + + m_shader = std::string(shader_txt); + } + } + } + + void display() { + ImGui::InputTextMultiline("Assembly", + (char *) m_shader.c_str(), m_shader.size(), + ImGui::GetContentRegionAvail(), + ImGuiInputTextFlags_ReadOnly); + } + + void destroy() {} +}; + +static struct intel_batch_decode_bo +batch_get_bo(void *user_data, bool ppgtt, uint64_t address) +{ + intel_batch_decode_bo ret_bo; + ret_bo.map = NULL; + ret_bo.addr = 0; + + if (!ppgtt) + return ret_bo; + + for (const auto &bo : context.bos) { + if (address >= bo.offset && + address < (bo.offset + bo.size)) { + ret_bo.map = bo.map; + ret_bo.addr = bo.offset; + ret_bo.size = bo.size; + } + } + + return ret_bo; +} + +static void +batch_display_shader(void *user_data, const char *shader_desc, uint64_t address) +{ + context.windows.push_back(std::shared_ptr<window>(new shader_window(shader_desc, address))); +} + +class batch_window : public window { +public: + batch_window(const struct hang_bo &bo) + : m_bo(bo) + , m_collapsed(true) { + aub_viewer_decode_ctx_init(&m_decode_ctx, + &context.cfg, + &m_decode_cfg, + &context.devinfo, + context.spec, + batch_get_bo, + NULL, + NULL); + m_decode_ctx.display_shader = batch_display_shader; + // window->decode_ctx.display_urb = batch_display_urb; + // window->decode_ctx.edit_address = batch_edit_address; + + snprintf(m_name, sizeof(m_name), "Batch view 0x%016" PRIx64 "##%p", + bo.offset, this); + } + ~batch_window() {} + + void display() { + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() / (2 * 2)); + decode_options(); + if (ImGui::Button("Edit commands")) + context.windows.push_back(std::shared_ptr<window>(new edit_window(m_bo))); + ImGui::PopItemWidth(); + + ImGui::BeginChild(ImGui::GetID("##block")); + + aub_viewer_render_batch(&m_decode_ctx, + m_bo.map, + m_bo.size, + m_bo.offset, + false /* from_ring */); + + ImGui::EndChild(); + } + + void destroy() {} + +private: + + void decode_options() { + char name[40]; + snprintf(name, sizeof(name), "command filter##%p", &m_decode_cfg.command_filter); + m_decode_cfg.command_filter.Draw(name); ImGui::SameLine(); + snprintf(name, sizeof(name), "field filter##%p", &m_decode_cfg.field_filter); + m_decode_cfg.field_filter.Draw(name); ImGui::SameLine(); + if (ImGui::Button("Dwords")) m_decode_cfg.show_dwords ^= 1; + } + + struct hang_bo m_bo; + + bool m_collapsed; + + struct aub_viewer_decode_cfg m_decode_cfg; + struct aub_viewer_decode_ctx m_decode_ctx; + + char edit_address[20]; +}; + +/* Main window */ + +static const char * +human_size(size_t size) +{ + unsigned divisions = 0; + double v = size; + double divider = 1024; + while (v >= divider) { + v /= divider; + divisions++; + } + + static const char *units[] = { "Bytes", "Kilobytes", "Megabytes", "Gigabytes" }; + static char result[20]; + snprintf(result, sizeof(result), "%.2f %s", + v, divisions >= ARRAY_SIZE(units) ? "Too much!" : units[divisions]); + return result; +} + +static void +display_hang_stats() +{ + ImGui::Begin("Hang stats"); + + ImGuiColorEditFlags cflags = (ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoLabel | + ImGuiColorEditFlags_NoInputs); + struct aub_viewer_cfg *cfg = &context.cfg; + + ImGui::ColorEdit3("background", (float *)&cfg->clear_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("missing", (float *)&cfg->missing_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("error", (float *)&cfg->error_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("highlight", (float *)&cfg->highlight_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("dwords", (float *)&cfg->dwords_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("booleans", (float *)&cfg->boolean_color, cflags); ImGui::SameLine(); + + if (ImGui::Button("Help") || has_ctrl_key('h')) { ImGui::OpenPopup("Help"); } + + ImGui::Text("BOs: %lu", context.bos.size()); + ImGui::Text("Execs %lu", context.execs.size()); + ImGui::Text("Maps: %lu", context.maps.size()); + ImGui::Text("PCI ID: 0x%x", context.devinfo.pci_device_id); + + ImGui::SetNextWindowContentWidth(500); + if (ImGui::BeginPopupModal("Help", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Some global keybindings:"); + ImGui::Separator(); + + static const char *texts[] = { + "Ctrl-h", "show this screen", + "Ctrl-c", "show commands list", + "Ctrl-r", "show registers list", + "Ctrl-b", "new batch window", + "Ctrl-p/n", "switch to previous/next batch buffer", + "Ctrl-Tab", "switch focus between window", + "Ctrl-left/right", "align window to the side of the screen", + }; + float align = 0.0f; + for (uint32_t i = 0; i < ARRAY_SIZE(texts); i += 2) + align = MAX2(align, ImGui::CalcTextSize(texts[i]).x); + align += ImGui::GetStyle().WindowPadding.x + 10; + + for (uint32_t i = 0; i < ARRAY_SIZE(texts); i += 2) { + ImGui::Text("%s", texts[i]); ImGui::SameLine(align); ImGui::Text("%s", texts[i + 1]); + } + + if (ImGui::Button("Done") || ImGui::IsKeyPressed(ImGuiKey_Escape)) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + uint64_t exec_buf_addr = 0; + if (!context.execs.empty()) + exec_buf_addr = context.execs.front().offset; + + ImGui::BeginChild(ImGui::GetID("BO list:")); + for (const auto &bo : context.bos) { + char bo_name[80]; + snprintf(bo_name, sizeof(bo_name), "BO 0x%012" PRIx64 " size=%" PRIu64 "(%s) %s", + bo.offset, bo.size, human_size(bo.size), + bo.offset == exec_buf_addr ? "BATCH BUFFER" : ""); + + if (ImGui::Selectable(bo_name, false)) + context.windows.push_back(std::shared_ptr<window>(new batch_window(bo))); + } + if (context.hw_image.size != 0 && ImGui::Selectable("HW IMAGE", false)) + context.windows.push_back(std::shared_ptr<window>(new batch_window(context.hw_image))); + ImGui::EndChild(); + + ImGui::End(); +} + +/* Main redrawing */ + +static void +display_windows(void) +{ + display_hang_stats(); + + /* Start by disposing closed windows, we don't want to destroy windows that + * have already been scheduled to be painted. So destroy always happens on + * the next draw cycle, prior to any drawing. + */ + auto it = context.windows.begin(); + while (it != context.windows.end()) { + if (!(*it)->m_opened) { + (*it)->destroy(); + it = context.windows.erase(it); + } else { + it++; + } + } + + for (uint32_t i = 0; i < context.windows.size(); i++) { + std::shared_ptr<window> window = context.windows[i]; + ImGui::SetNextWindowPos(window->m_position, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(window->m_size, ImGuiCond_FirstUseEver); + if (ImGui::Begin(window->name(), &window->m_opened)) { + window->display(); + window->m_position = ImGui::GetWindowPos(); + window->m_size = ImGui::GetWindowSize(); + } + if (window_has_ctrl_key('w')) + window->m_opened = false; + ImGui::End(); + } +} + +static void +repaint_area(GtkGLArea *area, GdkGLContext *gdk_gl_context) +{ + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGtk3_NewFrame(); + ImGui::NewFrame(); + + display_windows(); + + ImGui::EndFrame(); + ImGui::Render(); + + glClearColor(context.cfg.clear_color.Value.x, + context.cfg.clear_color.Value.y, + context.cfg.clear_color.Value.z, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} + +static void +realize_area(GtkGLArea *area) +{ + ImGui::CreateContext(); + ImGui_ImplGtk3_Init(GTK_WIDGET(area), true); + ImGui_ImplOpenGL3_Init("#version 130"); + + ImGui::StyleColorsDark(); + context.cfg = aub_viewer_cfg(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; +} + +static void +unrealize_area(GtkGLArea *area) +{ + gtk_gl_area_make_current(area); + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGtk3_Shutdown(); + ImGui::DestroyContext(); +} + +static void +size_allocate_area(GtkGLArea *area, + GdkRectangle *allocation, + gpointer user_data) +{ + if (!gtk_widget_get_realized(GTK_WIDGET(area))) + return; + + /* We want to catch only initial size allocate. */ + g_signal_handlers_disconnect_by_func(area, + (gpointer) size_allocate_area, + user_data); + // TODO +} + +static void +print_help(const char *progname, FILE *file) +{ + fprintf(file, + "Usage: %s -p platform HANG_FILE\n" + "\n" + " -p, --platform platform platform to use for decoding\n" + " -e, --edit map the hang file read/write for edition\n" + , progname); +} + +static void +add_bo(void *map, uint64_t addr, uint64_t size) +{ + hang_bo bo; + bo.map = map; + bo.offset = addr; + bo.size = size; + context.bos.push_back(bo); +} + +static void +add_map(uint64_t addr, uint64_t size) +{ + hang_map map; + map.offset = addr; + map.size = size; + context.maps.push_back(map); +} + +static void +add_exec(uint64_t addr) +{ + hang_exec exec; + exec.offset = addr; + context.execs.push_back(exec); +} + +static size_t +get_block_size(uint32_t type) +{ + switch (type) { + case INTEL_HANG_DUMP_BLOCK_TYPE_HEADER: return sizeof(struct intel_hang_dump_block_header); + case INTEL_HANG_DUMP_BLOCK_TYPE_BO: return sizeof(struct intel_hang_dump_block_bo); + case INTEL_HANG_DUMP_BLOCK_TYPE_MAP: return sizeof(struct intel_hang_dump_block_map); + case INTEL_HANG_DUMP_BLOCK_TYPE_EXEC: return sizeof(struct intel_hang_dump_block_exec); + case INTEL_HANG_DUMP_BLOCK_TYPE_HW_IMAGE: return sizeof(struct intel_hang_dump_block_hw_image); + default: unreachable("invalid block"); + } +} + +static void +parse_hang_file(const char *filename) +{ + context.file_fd = open(filename, context.edit ? O_RDWR : O_RDONLY); + if (context.file_fd < 0) + exit(EXIT_FAILURE); + + struct stat file_stats; + if (fstat(context.file_fd, &file_stats) != 0) + exit(EXIT_FAILURE); + + context.file_map = mmap(NULL, file_stats.st_size, + PROT_READ | PROT_WRITE, + context.edit ? MAP_SHARED : MAP_PRIVATE, + context.file_fd, 0); + if (context.file_map == MAP_FAILED) + exit(EXIT_FAILURE); + + uint8_t *current_file_ptr = (uint8_t *) context.file_map; + uint8_t *last_file_ptr = current_file_ptr + file_stats.st_size; + + while (current_file_ptr < last_file_ptr) { + union intel_hang_dump_block_all *block_header = + (union intel_hang_dump_block_all *)current_file_ptr; + size_t block_size = get_block_size(block_header->base.type); + + switch (block_header->base.type) { + case INTEL_HANG_DUMP_BLOCK_TYPE_HEADER: + assert(block_header->header.magic == INTEL_HANG_DUMP_MAGIC); + assert(block_header->header.version == INTEL_HANG_DUMP_VERSION); + break; + + case INTEL_HANG_DUMP_BLOCK_TYPE_BO: { + add_bo((uint8_t *) current_file_ptr + block_size, + block_header->bo.offset, + block_header->bo.size); + current_file_ptr = (uint8_t *) current_file_ptr + block_size + block_header->bo.size; + break; + } + + case INTEL_HANG_DUMP_BLOCK_TYPE_HW_IMAGE: { + context.hw_image.offset = block_header->bo.offset; + context.hw_image.size = block_header->hw_img.size; + context.hw_image.map = (uint8_t *) current_file_ptr + block_size; + current_file_ptr = (uint8_t *) current_file_ptr + block_size + block_header->hw_img.size; + break; + } + + case INTEL_HANG_DUMP_BLOCK_TYPE_MAP: { + add_map(block_header->map.offset, + block_header->map.size); + current_file_ptr = (uint8_t *) current_file_ptr + block_size; + break; + } + + case INTEL_HANG_DUMP_BLOCK_TYPE_EXEC: { + add_exec(block_header->exec.offset); + current_file_ptr = (uint8_t *) current_file_ptr + block_size; + break; + } + + default: + unreachable("Invalid block type"); + } + } +} + +int +main(int argc, char *argv[]) +{ + int c, i; + bool help = false, edit = false; + const char *platform = NULL; + const struct option aubinator_opts[] = { + { "platform", required_argument, NULL, 0 }, + { "edit", no_argument, (int *) &edit, true }, + { "help", no_argument, (int *) &help, true }, + { NULL, 0, NULL, 0 }, + }; + + i = 0; + while ((c = getopt_long(argc, argv, "p:e", aubinator_opts, &i)) != -1) { + switch (c) { + case 'p': + platform = optarg; + break; + case 'e': + edit = true; + break; + default: + break; + } + } + + context = {}; + context.edit = edit; + + const char *filename = NULL; + if (optind < argc) + filename = argv[optind]; + + if (help || !platform || !filename) { + print_help(argv[0], stderr); + exit(0); + } + + intel_get_device_info_from_pci_id( + intel_device_name_to_pci_device_id(platform), + &context.devinfo); + + brw_init_isa_info(&context.isa, &context.devinfo); + context.spec = intel_spec_load(&context.devinfo); + + parse_hang_file(filename); + + gtk_init(NULL, NULL); + + context.gtk_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(context.gtk_window), "Hang Viewer"); + g_signal_connect(context.gtk_window, "delete-event", G_CALLBACK(gtk_main_quit), NULL); + gtk_window_resize(GTK_WINDOW(context.gtk_window), 1280, 720); + + GtkWidget* gl_area = gtk_gl_area_new(); + g_signal_connect(gl_area, "render", G_CALLBACK(repaint_area), NULL); + g_signal_connect(gl_area, "realize", G_CALLBACK(realize_area), NULL); + g_signal_connect(gl_area, "unrealize", G_CALLBACK(unrealize_area), NULL); + g_signal_connect(gl_area, "size_allocate", G_CALLBACK(size_allocate_area), NULL); + gtk_container_add(GTK_CONTAINER(context.gtk_window), gl_area); + + gtk_widget_show_all(context.gtk_window); + + gtk_main(); + + return EXIT_SUCCESS; +} diff --git a/src/intel/tools/meson.build b/src/intel/tools/meson.build index 80db1525b58..759a8906c9a 100644 --- a/src/intel/tools/meson.build +++ b/src/intel/tools/meson.build @@ -179,6 +179,18 @@ if with_tools.contains('intel-ui') cpp_args : ['-fpermissive', '-Wno-parentheses'], install : true ) + + intel_hang_viewer = executable( + 'intel_hang_viewer', + files('intel_hang_viewer.cpp', 'aubinator_viewer_decoder.cpp'), + dependencies : [idep_mesautil, dep_zlib, dep_dl, dep_thread, dep_m, libintel_imgui_gtk_dep], + include_directories : [inc_include, inc_src, inc_intel], + link_with : [libintel_common, libintel_compiler, libintel_dev], + c_args : [no_override_init_args], + gnu_symbol_visibility : 'hidden', + cpp_args : ['-fpermissive', '-Wno-parentheses'], + install : true + ) endif bison_command = []