#include #include #include #include #include #include #include #include #include #include #include enum memory_view_columns { MEM_VIEW_COL_ADDRESS = 0, MEM_VIEW_COL_DATA, MEM_VIEW_COL_INTERPRETATION, MEM_VIEW_COLOR, MEM_VIEW_COLUMN_COUNT }; struct application_data { GtkWidget *borrowed_main_window; GtkTreeView *borrowed_tree_view; GtkHeaderBar *borrowed_header_bar; GtkInfoBar *borrowed_info_bar; const char *error_memory_data; size_t file_size; }; enum entry_state { ENTRY_STATE_VALID = 0, ENTRY_STATE_INVALID, ENTRY_STATE_NOT_PARSED, ENTRY_STATE_BLOCK }; static void append_data(GtkTreeStore *store, GtkTreeIter *parent, GtkTreeIter *iter, GString *interpretation, enum entry_state state, uint32_t addr, uint32_t val) { const GdkRGBA invalid_color = { .red = 1.0, .alpha = 1.0, .green = 0.0, .blue = 0.0, }; const GdkRGBA valid_color = { .red = 0.0, .alpha = 1.0, .green = 1.0, .blue = 0.0, }; const GdkRGBA warn_color = { .red = 1.0, .alpha = 1.0, .green = 1.0, .blue = 0.0, }; const GdkRGBA block_color = { .red = 1.0, .alpha = 1.0, .green = 0.3, .blue = 0.5, }; const GdkRGBA *col; switch (state) { case ENTRY_STATE_VALID: col = &valid_color; break; case ENTRY_STATE_NOT_PARSED: col = &warn_color; break; case ENTRY_STATE_BLOCK: col = &block_color; break; case ENTRY_STATE_INVALID: default: col = &invalid_color; break; } gtk_tree_store_append(store, iter, parent); gtk_tree_store_set(store, iter, MEM_VIEW_COL_ADDRESS, addr, MEM_VIEW_COLOR, col, MEM_VIEW_COL_INTERPRETATION, (interpretation ? interpretation->str : ""), MEM_VIEW_COL_DATA, val, -1); if (interpretation) g_string_free(interpretation, TRUE); } static void info_bar_show_message(GtkInfoBar *info_bar, GtkMessageType type, const char *format, ...) { GString *string; va_list args; GtkWidget *content; GList *children, *iter; GtkWidget *label; va_start(args, format); string = g_string_new(NULL); g_string_vprintf(string, format, args); va_end(args); content = gtk_info_bar_get_content_area(info_bar); children = gtk_container_get_children(GTK_CONTAINER(content)); for (iter = children; iter; iter = g_list_next(iter)) gtk_widget_destroy(GTK_WIDGET(iter->data)); g_list_free(children); label = gtk_label_new(string->str); gtk_container_add(GTK_CONTAINER(content), label); gtk_widget_show(label); gtk_info_bar_set_message_type(info_bar, type); gtk_widget_show(GTK_WIDGET(info_bar)); gtk_info_bar_set_revealed(info_bar, TRUE); g_string_free(string, TRUE); } static bool check_err_mem_header(struct safety_memory_header *header, uint32_t *expected) { uint32_t crc; crc = calculate_stm_crc((uint32_t *)header, sizeof(*header) / sizeof(uint32_t) - 1); if (expected) *expected = crc; if (crc == header->crc) return true; else return false; } static GString *new_string_printf(const char *fmt, ...) { GString *string; va_list args; va_start(args, fmt); string = g_string_new(NULL); g_string_vprintf(string, fmt, args); va_end(args); return string; } static void show_error_memory(GtkTreeView *tree_view, const unsigned char *memory, size_t len) { GtkTreeStore *store; GtkTreeIter iter; GtkTreeIter iter2; GtkTreeIter *parent_iter, *child_iter; enum entry_state state; GString *interpret; bool valid; bool analyze_further = true; struct safety_memory_header header; unsigned int i; uint32_t dat, expected_value; store = GTK_TREE_STORE(gtk_tree_view_get_model(tree_view)); gtk_tree_store_clear(store); for (i = 0; i < len / 4; i++) { valid = false; interpret = NULL; dat = memory[i*4]; dat |= ((uint32_t)memory[i*4+1]) << 8; dat |= ((uint32_t)memory[i*4+2]) << 16; dat |= ((uint32_t)memory[i*4+3]) << 24; state = ENTRY_STATE_NOT_PARSED; if (!analyze_further) { interpret = g_string_new("Not analyzed due to previous error"); state = ENTRY_STATE_NOT_PARSED; parent_iter = NULL; child_iter = &iter; goto print; } /* Parse header */ switch (i) { case 0: /* Magic */ parent_iter = NULL; child_iter = &iter; append_data(store, parent_iter, child_iter, new_string_printf("Header"), ENTRY_STATE_BLOCK, i * 4, dat); parent_iter = child_iter; child_iter = &iter2; if (dat == SAFETY_MEMORY_MAGIC) { valid = true; interpret = g_string_new("Valid Header Magic"); } else { interpret = g_string_new("Invalid Header Magic"); analyze_further = false; } header.magic = dat; break; case 1: header.boot_status_offset = dat; valid = true; interpret = new_string_printf("Boot status offset addr: %u", dat); break; case 2: header.config_overrides_offset = dat; interpret = new_string_printf("Config override offset addr: %u", dat); valid = true; break; case 3: header.config_overrides_len = dat; interpret = new_string_printf("Config override length: %u", dat); valid = true; break; case 4: header.firmware_update_filename = dat; interpret = new_string_printf("Offset of FW update filename: %u", dat); valid = true; break; case 5: header.err_memory_offset = dat; interpret = new_string_printf("Error memory offset addr: %u", dat); valid = true; break; case 6: header.err_memory_end = dat; interpret = new_string_printf("Error memory end ptr: %u", dat); valid = true; break; case 7: header.crc = dat; valid = check_err_mem_header(&header, &expected_value); if (valid) { interpret = new_string_printf("Header CRC: 0x%08X", dat); } else { interpret = new_string_printf("Invalid CRC: 0x%08X | Expected: 0x%08X", dat, expected_value); analyze_further = false; } break; } if (i <= 7) { state = ENTRY_STATE_INVALID; goto print; } /* Check if we're in the boot status structure */ if (i >= header.boot_status_offset && i < header.boot_status_offset + sizeof(struct safety_memory_boot_status) / 4) { if (i == header.boot_status_offset) { /* First entry of boot status struct */ parent_iter = NULL; child_iter = &iter; interpret = new_string_printf("Boot Status Struct"); append_data(store, parent_iter, child_iter, interpret, ENTRY_STATE_BLOCK, i * 4, dat); parent_iter = &iter; child_iter = &iter2; interpret = NULL; } switch (i - header.boot_status_offset) { case 0: interpret = new_string_printf("%s into bootloader", (dat ? "Boot" : "Do not boot")); valid = true; break; case 1: interpret = new_string_printf("Code %s been updated", (dat ? "has" : "hasn't")); valid = true; break; case 2: interpret = new_string_printf("Panic %s", (dat ? "occured" : "did not occur")); valid = true; break; default: valid = false; interpret = new_string_printf("Value undefined!"); break; } state = ENTRY_STATE_INVALID; goto print; } if (i >= header.config_overrides_offset && i < (header.config_overrides_offset + header.config_overrides_len)) { if (i == header.config_overrides_offset) { parent_iter = NULL; child_iter = &iter; append_data(store, parent_iter, child_iter, new_string_printf("Config Overrides"), ENTRY_STATE_BLOCK, i * 4, dat); parent_iter = child_iter; child_iter = &iter2; } interpret = new_string_printf("Config Overrides not yet implemented"); valid = true; state = ENTRY_STATE_INVALID; goto print; } if (i >= header.err_memory_offset && i < header.err_memory_end) { if (i == header.err_memory_offset) { parent_iter = NULL; child_iter = &iter; append_data(store, parent_iter, child_iter, new_string_printf("Error Memory (length: %d)", header.err_memory_end - header.err_memory_offset), ENTRY_STATE_BLOCK, i * 4, dat); parent_iter = child_iter; child_iter = &iter2; } switch (dat) { case SAFETY_MEMORY_NOP_ENTRY: valid = true; interpret = new_string_printf("Error Memory NOP"); break; default: if ((dat & 0xFFU) == 0x51U) { valid = true; interpret = new_string_printf("Err memory Entry"); } else interpret = new_string_printf("Invalid error memory entry"); break; } state = ENTRY_STATE_INVALID; goto print; } if (i == header.err_memory_end) { expected_value = calculate_stm_crc((uint32_t *)memory, header.err_memory_end); if (expected_value == dat) { interpret = new_string_printf("Valid Memory CRC"); valid = true; } else { interpret = new_string_printf("Invalid CRC. Expected: 0x%08X", expected_value); } state = ENTRY_STATE_INVALID; parent_iter = NULL; child_iter = &iter; goto print; } parent_iter = NULL; child_iter = &iter; print: if (state != ENTRY_STATE_NOT_PARSED) { state = (valid ? ENTRY_STATE_VALID : ENTRY_STATE_INVALID); } append_data(store, parent_iter, child_iter, interpret, state, i * 4, dat); } if (!store) return; } void setup_tree_view(GtkTreeView *tree_view) { GtkTreeStore *tree_store; GtkCellRenderer *string_renderer; GtkCellRenderer *hex_renderer; GtkTreeViewColumn *column; g_return_if_fail(GTK_IS_TREE_VIEW(tree_view)); tree_store = gtk_tree_store_new(MEM_VIEW_COLUMN_COUNT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, GDK_TYPE_RGBA); gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store)); string_renderer = gtk_cell_renderer_text_new(); hex_renderer = err_mem_view_hex_cell_renderer_new(); column = gtk_tree_view_column_new_with_attributes("Address", hex_renderer, "hex-num", MEM_VIEW_COL_ADDRESS, "foreground-rgba", MEM_VIEW_COLOR, NULL); gtk_tree_view_append_column(tree_view, column); column = gtk_tree_view_column_new_with_attributes("Data", hex_renderer, "hex-num", MEM_VIEW_COL_DATA, "foreground-rgba", MEM_VIEW_COLOR, NULL); gtk_tree_view_append_column(tree_view, column); column = gtk_tree_view_column_new_with_attributes("Interpretation", string_renderer, "text", MEM_VIEW_COL_INTERPRETATION, "foreground-rgba", MEM_VIEW_COLOR, NULL); gtk_tree_view_append_column(tree_view, column); } static ptrdiff_t get_file_size(const char *filename) { struct stat file_stat; int res; ptrdiff_t size; res = stat(filename, &file_stat); if (res) size = -1UL; else size = (ptrdiff_t)file_stat.st_size; return size; } /** * @brief Loads a file to memory and decodes it using base64 * @param name Filename * @param detected_size detected Size of the ouput generated * @return Buffer of decoded data with size \p detected_size, NULL in case of an error. * @note Return buffer is allocated on heap. Use free() to free the memory. */ static char *load_file_to_memory(const char *name, size_t *detected_size) { ptrdiff_t size; FILE *f; char *ret = NULL; size_t read_bytes; gsize decoded_size; size = get_file_size(name); if (size <= 0) goto exit; f = fopen(name, "rb"); ret = (char *)malloc(size + 1); if (!ret) goto exit_close_file; read_bytes = fread(ret, 1UL, (size_t)size, f); if (read_bytes != (size_t)size) { free(ret); ret = NULL; goto exit_close_file; } ret[size] = '\0'; g_base64_decode_inplace(ret, &decoded_size); if (decoded_size == 0) { free(ret); ret = NULL; goto exit_close_file; } if (detected_size) *detected_size = (size_t)decoded_size; exit_close_file: fclose(f); exit: return ret; } G_MODULE_EXPORT void info_bar_close_cb(GtkInfoBar *info_bar, gpointer user_data) { (void)user_data; gtk_info_bar_set_revealed(info_bar, FALSE); } G_MODULE_EXPORT void open_button_clicked_cb(GtkButton *button, gpointer *user_data) { struct application_data *data = (struct application_data *)user_data; (void)button; GtkDialog *dialog; gint res; gchar *filename; dialog = GTK_DIALOG(gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(data->borrowed_main_window), GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL)); res = gtk_dialog_run(dialog); if (res == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); if (data->error_memory_data) free((void *)data->error_memory_data); data->error_memory_data = load_file_to_memory(filename, &data->file_size); if (!data->error_memory_data) { g_warning("File could not be loaded"); g_free(filename); goto ret_destroy_dialog; } gtk_header_bar_set_subtitle(data->borrowed_header_bar, filename); g_free(filename); } else { goto ret_destroy_dialog; } if (data->file_size % 4 || data->file_size == 0) { free((void *)data->error_memory_data); data->error_memory_data = NULL; info_bar_show_message(data->borrowed_info_bar, GTK_MESSAGE_WARNING, "Data must be base64 encoded and must contain full 32 bit words of data."); goto ret_destroy_dialog; } show_error_memory(data->borrowed_tree_view, (const unsigned char *)data->error_memory_data, data->file_size); ret_destroy_dialog: gtk_widget_destroy(GTK_WIDGET(dialog)); } static void app_activated(GApplication *app, gpointer user_data) { struct application_data *data = (struct application_data *)user_data; GtkBuilder *builder; GtkWindow *main_window; builder = gtk_builder_new_from_resource("/gui/main"); main_window = GTK_WINDOW(gtk_builder_get_object(builder, "main-window")); data->borrowed_main_window = GTK_WIDGET(main_window); data->error_memory_data = NULL; data->borrowed_tree_view = GTK_TREE_VIEW(gtk_builder_get_object(builder, "error-mem-tree-view")); data->borrowed_header_bar = GTK_HEADER_BAR(gtk_builder_get_object(builder, "header-bar")); data->borrowed_info_bar = GTK_INFO_BAR(gtk_builder_get_object(builder, "info-bar")); setup_tree_view(data->borrowed_tree_view); gtk_builder_connect_signals(builder, data); g_object_unref(builder); gtk_application_add_window(GTK_APPLICATION(app), main_window); gtk_widget_show(GTK_WIDGET(main_window)); } static int start_gui(int argc, char **argv) { int ret = 0; GtkApplication *g_app; static struct application_data data; /* Create a new G application which will start a completely new process for each call to the program (NON_UNIQUE) instead * of creating an additional window in the same process */ g_app = gtk_application_new("de.shimatta.reflow.error-mem-viewer", G_APPLICATION_NON_UNIQUE); g_signal_connect(g_app, "activate", G_CALLBACK(app_activated), &data); ret = g_application_run(G_APPLICATION(g_app), argc, argv); g_object_unref(g_app); if (data.error_memory_data) free((void *)data.error_memory_data); return ret; } int main(int argc, char **argv) { return start_gui(argc, argv); }