From 47f5f487159e63723ae431fa6d55bbc08ad443fb Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Tue, 4 May 2021 18:50:00 -0400 Subject: search UI --- base.h | 81 +++++++ data.c | 163 +++++++++++++ main.c | 458 +++++++++--------------------------- memory.c | 104 +++++++++ ui.glade | 777 +++++++++++++++++++++++++++++++++++++++++++++----------------- unicode.h | 1 + 6 files changed, 1023 insertions(+), 561 deletions(-) create mode 100644 base.h create mode 100644 data.c create mode 100644 memory.c diff --git a/base.h b/base.h new file mode 100644 index 0000000..a533ce0 --- /dev/null +++ b/base.h @@ -0,0 +1,81 @@ +// types and stuff needed everywhere + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef pid_t PID; +typedef uint64_t Address; +#define SCNxADDR SCNx64 +#define PRIdADDR PRId64 +#define PRIxADDR PRIx64 + +typedef enum { + TYPE_U8, + TYPE_S8, + TYPE_U16, + TYPE_S16, + TYPE_U32, + TYPE_S32, + TYPE_U64, + TYPE_S64, + TYPE_ASCII, + TYPE_UTF16, + TYPE_UTF32, + TYPE_F32, + TYPE_F64 +} DataType; + +typedef enum { + SEARCH_ENTER_VALUE, + SEARCH_SAME_DIFFERENT +} SearchType; + +// a memory map +typedef struct { + Address lo, size; +} Map; + +typedef struct { + GtkWindow *window; + GtkBuilder *builder; + bool stop_while_accessing_memory; + long editing_memory; // index of memory value being edited, or -1 if none is + PID pid; + Address total_memory; // total amount of memory used by process, in bytes + Map *maps; + Address memory_view_address; + uint32_t memory_view_entries; // # of entries to show + unsigned nmaps; + DataType data_type; + SearchType search_type; +} State; + +static void display_dialog_box_nofmt(State *state, GtkMessageType type, char const *message) { + GtkWidget *box = gtk_message_dialog_new(state->window, + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + type, GTK_BUTTONS_OK, "%s", message); + // make sure dialog box is closed when OK is clicked. + g_signal_connect_swapped(box, "response", G_CALLBACK(gtk_widget_destroy), box); + gtk_widget_show_all(box); +} + +// this is a macro so we get -Wformat warnings +#define display_dialog_box(state, type, fmt, ...) do { \ + char _buf[1024]; \ + snprintf(_buf, sizeof _buf, fmt, __VA_ARGS__); \ + display_dialog_box_nofmt(state, type, _buf); \ +} while (0) +#define display_error(state, fmt, ...) display_dialog_box(state, GTK_MESSAGE_ERROR, fmt, __VA_ARGS__) +#define display_info(state, fmt, ...) display_dialog_box(state, GTK_MESSAGE_INFO, fmt, __VA_ARGS__) +#define display_info_nofmt(state, message) display_dialog_box_nofmt(state, GTK_MESSAGE_INFO, message) + + diff --git a/data.c b/data.c new file mode 100644 index 0000000..6afb100 --- /dev/null +++ b/data.c @@ -0,0 +1,163 @@ +// stuff for dealing with various data types. + +static DataType data_type_from_name(char const *name) { + switch (name[0]) { + case 'u': + switch (atoi(&name[1])) { + case 8: return TYPE_U8; + case 16: return TYPE_U16; + case 32: return TYPE_U32; + case 64: return TYPE_U64; + } + if (strncmp(name, "utf", 3) == 0) { + switch (atoi(&name[3])) { + case 16: return TYPE_UTF16; + case 32: return TYPE_UTF32; + } + } + break; + case 's': + switch (atoi(&name[1])) { + case 8: return TYPE_S8; + case 16: return TYPE_S16; + case 32: return TYPE_S32; + case 64: return TYPE_S64; + } + break; + case 'f': + switch (atoi(&name[1])) { + case 32: return TYPE_F32; + case 64: return TYPE_F64; + } + break; + case 'a': + if (strcmp(name, "ascii") == 0) + return TYPE_ASCII; + break; + } + assert(0); + return TYPE_U8; +} + +static size_t data_type_size(DataType type) { + switch (type) { + case TYPE_U8: + case TYPE_S8: + case TYPE_ASCII: + return 1; + case TYPE_U16: + case TYPE_S16: + case TYPE_UTF16: + return 2; + case TYPE_U32: + case TYPE_S32: + case TYPE_F32: + case TYPE_UTF32: + return 4; + case TYPE_U64: + case TYPE_S64: + case TYPE_F64: + return 8; + } + return (size_t)-1; +} + +// set str to "a" for 'a', "\\n" for '\n', "\\xff" for (wchar_t)255, etc. +static void char_to_str(uint32_t c, char *str, size_t str_size) { + if (c <= WINT_MAX && iswgraph((wint_t)c)) { + snprintf(str, str_size, "%lc", (wint_t)c); + } else { + switch (c) { + case ' ': snprintf(str, str_size, "(space)"); break; + case '\n': snprintf(str, str_size, "\\n"); break; + case '\t': snprintf(str, str_size, "\\t"); break; + case '\r': snprintf(str, str_size, "\\r"); break; + case '\v': snprintf(str, str_size, "\\v"); break; + case '\0': snprintf(str, str_size, "\\0"); break; + default: + if (c < 256) + snprintf(str, str_size, "\\x%02x", (unsigned)c); + else + snprintf(str, str_size, "\\x%05lx", (unsigned long)c); + } + } +} + +static bool char_from_str(char const *str, uint32_t *c) { + if (str[0] == '\0') return false; + if (str[0] == '\\') { + switch (str[1]) { + case 'n': *c = '\n'; return str[2] == '\0'; + case 't': *c = '\t'; return str[2] == '\0'; + case 'r': *c = '\r'; return str[2] == '\0'; + case 'v': *c = '\v'; return str[2] == '\0'; + case '0': *c = '\0'; return str[2] == '\0'; + case 'x': { + unsigned long v = 0; + int w = 0; + if (sscanf(&str[2], "%lx%n", &v, &w) != 1 || + (size_t)w != strlen(&str[2]) || v > UINT32_MAX) + return false; + *c = (uint32_t)v; + return true; + } + } + } + return unicode_utf8_to_utf32(c, str, strlen(str)) == strlen(str); +} + +static void data_to_str(void const *value, DataType type, char *str, size_t str_size) { + switch (type) { + case TYPE_U8: snprintf(str, str_size, "%" PRIu8, *(uint8_t *)value); break; + case TYPE_U16: snprintf(str, str_size, "%" PRIu16, *(uint16_t *)value); break; + case TYPE_U32: snprintf(str, str_size, "%" PRIu32, *(uint32_t *)value); break; + case TYPE_U64: snprintf(str, str_size, "%" PRIu64, *(uint64_t *)value); break; + case TYPE_S8: snprintf(str, str_size, "%" PRId8, *(int8_t *)value); break; + case TYPE_S16: snprintf(str, str_size, "%" PRId16, *(int16_t *)value); break; + case TYPE_S32: snprintf(str, str_size, "%" PRId32, *(int32_t *)value); break; + case TYPE_S64: snprintf(str, str_size, "%" PRId64, *(int64_t *)value); break; + case TYPE_F32: snprintf(str, str_size, "%g", *(float *)value); break; + case TYPE_F64: snprintf(str, str_size, "%g", *(double *)value); break; + case TYPE_UTF16: char_to_str(*(uint16_t *)value, str, str_size); break; + case TYPE_UTF32: char_to_str(*(uint32_t *)value, str, str_size); break; + case TYPE_ASCII: + char_to_str((uint8_t)*(char *)value, str, str_size); + break; + + } +} + +// returns true on success, false if str is not a well-formatted value +static bool data_from_str(char const *str, DataType type, void *value) { + int len = (int)strlen(str); + int w = 0; + uint32_t c = 0; + switch (type) { + case TYPE_U8: return sscanf(str, "%" SCNu8 "%n", (uint8_t *)value, &w) == 1 && w == len; + case TYPE_S8: return sscanf(str, "%" SCNd8 "%n", ( int8_t *)value, &w) == 1 && w == len; + case TYPE_U16: return sscanf(str, "%" SCNu16 "%n", (uint16_t *)value, &w) == 1 && w == len; + case TYPE_S16: return sscanf(str, "%" SCNd16 "%n", ( int16_t *)value, &w) == 1 && w == len; + case TYPE_U32: return sscanf(str, "%" SCNu32 "%n", (uint32_t *)value, &w) == 1 && w == len; + case TYPE_S32: return sscanf(str, "%" SCNd32 "%n", ( int32_t *)value, &w) == 1 && w == len; + case TYPE_U64: return sscanf(str, "%" SCNu64 "%n", (uint64_t *)value, &w) == 1 && w == len; + case TYPE_S64: return sscanf(str, "%" SCNd64 "%n", ( int64_t *)value, &w) == 1 && w == len; + case TYPE_F32: return sscanf(str, "%f%n", (float *)value, &w) == 1 && w == len; + case TYPE_F64: return sscanf(str, "%lf%n", (double *)value, &w) == 1 && w == len; + case TYPE_ASCII: + if (!char_from_str(str, &c)) return false; + if (c > 127) return false; + *(uint8_t *)value = (uint8_t)c; + return true; + case TYPE_UTF16: + if (!char_from_str(str, &c)) return false; + if (c > 65535) return false; + *(uint16_t *)value = (uint16_t)c; + return true; + case TYPE_UTF32: + if (!char_from_str(str, &c)) return false; + *(uint32_t *)value = c; + return true; + } + assert(0); + return false; +} diff --git a/main.c b/main.c index 6052a26..bc304e1 100644 --- a/main.c +++ b/main.c @@ -1,341 +1,16 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef pid_t PID; -typedef uint64_t Address; -#define SCNxADDR SCNx64 -#define PRIdADDR PRId64 -#define PRIxADDR PRIx64 - +#include "base.h" #include "unicode.h" - -// a memory map -typedef struct { - Address lo, size; -} Map; - -typedef enum { - TYPE_U8, - TYPE_S8, - TYPE_U16, - TYPE_S16, - TYPE_U32, - TYPE_S32, - TYPE_U64, - TYPE_S64, - TYPE_ASCII, - TYPE_UTF16, - TYPE_UTF32, - TYPE_F32, - TYPE_F64 -} DataType; - -static DataType data_type_from_name(char const *name) { - switch (name[0]) { - case 'u': - switch (atoi(&name[1])) { - case 8: return TYPE_U8; - case 16: return TYPE_U16; - case 32: return TYPE_U32; - case 64: return TYPE_U64; - } - if (strncmp(name, "utf", 3) == 0) { - switch (atoi(&name[3])) { - case 16: return TYPE_UTF16; - case 32: return TYPE_UTF32; - } - } - break; - case 's': - switch (atoi(&name[1])) { - case 8: return TYPE_S8; - case 16: return TYPE_S16; - case 32: return TYPE_S32; - case 64: return TYPE_S64; - } - break; - case 'f': - switch (atoi(&name[1])) { - case 32: return TYPE_F32; - case 64: return TYPE_F64; - } - break; - case 'a': - if (strcmp(name, "ascii") == 0) - return TYPE_ASCII; - break; - } - assert(0); - return TYPE_U8; -} - -static size_t data_type_size(DataType type) { - switch (type) { - case TYPE_U8: - case TYPE_S8: - case TYPE_ASCII: - return 1; - case TYPE_U16: - case TYPE_S16: - case TYPE_UTF16: - return 2; - case TYPE_U32: - case TYPE_S32: - case TYPE_F32: - case TYPE_UTF32: - return 4; - case TYPE_U64: - case TYPE_S64: - case TYPE_F64: - return 8; - } - return (size_t)-1; -} - -// set str to "a" for 'a', "\\n" for '\n', "\\xff" for (wchar_t)255, etc. -static void char_to_str(uint32_t c, char *str, size_t str_size) { - if (c <= WINT_MAX && iswgraph((wint_t)c)) { - snprintf(str, str_size, "%lc", (wint_t)c); - } else { - switch (c) { - case ' ': snprintf(str, str_size, "(space)"); break; - case '\n': snprintf(str, str_size, "\\n"); break; - case '\t': snprintf(str, str_size, "\\t"); break; - case '\r': snprintf(str, str_size, "\\r"); break; - case '\v': snprintf(str, str_size, "\\v"); break; - case '\0': snprintf(str, str_size, "\\0"); break; - default: - if (c < 256) - snprintf(str, str_size, "\\x%02x", (unsigned)c); - else - snprintf(str, str_size, "\\x%05lx", (unsigned long)c); - } - } -} - -static bool char_from_str(char const *str, uint32_t *c) { - if (str[0] == '\0') return false; - if (str[0] == '\\') { - switch (str[1]) { - case 'n': *c = '\n'; return str[2] == '\0'; - case 't': *c = '\t'; return str[2] == '\0'; - case 'r': *c = '\r'; return str[2] == '\0'; - case 'v': *c = '\v'; return str[2] == '\0'; - case '0': *c = '\0'; return str[2] == '\0'; - case 'x': { - unsigned long v = 0; - int w = 0; - if (sscanf(&str[2], "%lx%n", &v, &w) != 1 || - (size_t)w != strlen(&str[2]) || v > UINT32_MAX) - return false; - *c = (uint32_t)v; - return true; - } - } - } - return unicode_utf8_to_utf32(c, str, strlen(str)) == strlen(str); -} - -static void data_to_str(void const *value, DataType type, char *str, size_t str_size) { - switch (type) { - case TYPE_U8: snprintf(str, str_size, "%" PRIu8, *(uint8_t *)value); break; - case TYPE_U16: snprintf(str, str_size, "%" PRIu16, *(uint16_t *)value); break; - case TYPE_U32: snprintf(str, str_size, "%" PRIu32, *(uint32_t *)value); break; - case TYPE_U64: snprintf(str, str_size, "%" PRIu64, *(uint64_t *)value); break; - case TYPE_S8: snprintf(str, str_size, "%" PRId8, *(int8_t *)value); break; - case TYPE_S16: snprintf(str, str_size, "%" PRId16, *(int16_t *)value); break; - case TYPE_S32: snprintf(str, str_size, "%" PRId32, *(int32_t *)value); break; - case TYPE_S64: snprintf(str, str_size, "%" PRId64, *(int64_t *)value); break; - case TYPE_F32: snprintf(str, str_size, "%g", *(float *)value); break; - case TYPE_F64: snprintf(str, str_size, "%g", *(double *)value); break; - case TYPE_UTF16: char_to_str(*(uint16_t *)value, str, str_size); break; - case TYPE_UTF32: char_to_str(*(uint32_t *)value, str, str_size); break; - case TYPE_ASCII: - char_to_str((uint8_t)*(char *)value, str, str_size); - break; - - } -} - -// returns true on success, false if str is not a well-formatted value -static bool data_from_str(char const *str, DataType type, void *value) { - int len = (int)strlen(str); - int w = 0; - uint32_t c = 0; - switch (type) { - case TYPE_U8: return sscanf(str, "%" SCNu8 "%n", (uint8_t *)value, &w) == 1 && w == len; - case TYPE_S8: return sscanf(str, "%" SCNd8 "%n", ( int8_t *)value, &w) == 1 && w == len; - case TYPE_U16: return sscanf(str, "%" SCNu16 "%n", (uint16_t *)value, &w) == 1 && w == len; - case TYPE_S16: return sscanf(str, "%" SCNd16 "%n", ( int16_t *)value, &w) == 1 && w == len; - case TYPE_U32: return sscanf(str, "%" SCNu32 "%n", (uint32_t *)value, &w) == 1 && w == len; - case TYPE_S32: return sscanf(str, "%" SCNd32 "%n", ( int32_t *)value, &w) == 1 && w == len; - case TYPE_U64: return sscanf(str, "%" SCNu64 "%n", (uint64_t *)value, &w) == 1 && w == len; - case TYPE_S64: return sscanf(str, "%" SCNd64 "%n", ( int64_t *)value, &w) == 1 && w == len; - case TYPE_F32: return sscanf(str, "%f%n", (float *)value, &w) == 1 && w == len; - case TYPE_F64: return sscanf(str, "%lf%n", (double *)value, &w) == 1 && w == len; - case TYPE_ASCII: - if (!char_from_str(str, &c)) return false; - if (c > 127) return false; - *(uint8_t *)value = (uint8_t)c; - return true; - case TYPE_UTF16: - if (!char_from_str(str, &c)) return false; - if (c > 65535) return false; - *(uint16_t *)value = (uint16_t)c; - return true; - case TYPE_UTF32: - if (!char_from_str(str, &c)) return false; - *(uint32_t *)value = c; - return true; +#include "data.c" +#include "memory.c" + +static SearchType search_type_from_str(char const *str) { + if (strcmp(str, "enter-value") == 0) { + return SEARCH_ENTER_VALUE; + } else if (strcmp(str, "same-different") == 0) { + return SEARCH_SAME_DIFFERENT; } assert(0); - return false; -} - -typedef struct { - GtkWindow *window; - GtkBuilder *builder; - bool stop_while_accessing_memory; - long editing_memory; // index of memory value being edited, or -1 if none is - PID pid; - Map *maps; - Address memory_view_address; - uint32_t memory_view_entries; // # of entries to show - unsigned nmaps; - DataType data_type; -} State; - -static void display_dialog_box_nofmt(State *state, GtkMessageType type, char const *message) { - GtkWidget *box = gtk_message_dialog_new(state->window, - GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, - type, GTK_BUTTONS_OK, "%s", message); - // make sure dialog box is closed when OK is clicked. - g_signal_connect_swapped(box, "response", G_CALLBACK(gtk_widget_destroy), box); - gtk_widget_show_all(box); -} - -// this is a macro so we get -Wformat warnings -#define display_dialog_box(state, type, fmt, ...) do { \ - char _buf[1024]; \ - snprintf(_buf, sizeof _buf, fmt, __VA_ARGS__); \ - display_dialog_box_nofmt(state, type, _buf); \ -} while (0) -#define display_error(state, fmt, ...) display_dialog_box(state, GTK_MESSAGE_ERROR, fmt, __VA_ARGS__) -#define display_info(state, fmt, ...) display_dialog_box(state, GTK_MESSAGE_INFO, fmt, __VA_ARGS__) -#define display_info_nofmt(state, message) display_dialog_box_nofmt(state, GTK_MESSAGE_INFO, message) - - - -static void close_process(State *state, char const *reason) { - GtkBuilder *builder = state->builder; - free(state->maps); state->maps = NULL; - state->nmaps = 0; - state->pid = 0; - { - GtkListStore *store = GTK_LIST_STORE(gtk_builder_get_object(builder, "memory")); - gtk_list_store_clear(store); - } - if (reason) - display_info(state, "Can't access process anymore: %s", reason); - else - display_info_nofmt(state, "Can't access process anymore."); -} - -// don't use this function; use one of the ones below -static int memory_open(State *state, int flags) { - if (state->pid) { - if (state->stop_while_accessing_memory) { - if (kill(state->pid, SIGSTOP) == -1) { - close_process(state, strerror(errno)); - return 0; - } - } - char name[64]; - sprintf(name, "/proc/%lld/mem", (long long)state->pid); - int fd = open(name, flags); - if (fd == -1) { - close_process(state, strerror(errno)); - return 0; - } - return fd; - } - return 0; -} - -static void memory_close(State *state, int fd) { - if (state->stop_while_accessing_memory) { - kill(state->pid, SIGCONT); - } - if (fd) close(fd); -} - - -// get a file descriptor for reading memory from the process -// returns 0 on failure -static int memory_reader_open(State *state) { - return memory_open(state, O_RDONLY); -} - -static void memory_reader_close(State *state, int reader) { - memory_close(state, reader); -} - -// like memory_reader_open, but for writing to memory -static int memory_writer_open(State *state) { - return memory_open(state, O_WRONLY); -} - -static void memory_writer_close(State *state, int writer) { - memory_close(state, writer); -} - -static uint8_t memory_read_byte(int reader, Address addr) { - lseek(reader, (off_t)addr, SEEK_SET); - uint8_t byte = 0; - read(reader, &byte, 1); - return byte; -} - -// returns number of bytes successfully read -static Address memory_read_bytes(int reader, Address addr, uint8_t *memory, Address nbytes) { - lseek(reader, (off_t)addr, SEEK_SET); - Address idx = 0; - while (idx < nbytes) { - ssize_t n = read(reader, &memory[idx], (size_t)(nbytes - idx)); - if (n <= 0) break; - idx += (Address)n; - } - return idx; -} - -// returns # of bytes written (so either 0 or 1) -static Address memory_write_byte(int writer, Address addr, uint8_t byte) { - lseek(writer, (off_t)addr, SEEK_SET); - if (write(writer, &byte, 1) == 1) - return 1; - else - return 0; -} - -// returns # of bytes written -static Address memory_write_bytes(int writer, Address addr, uint8_t const *bytes, Address nbytes) { - lseek(writer, (off_t)addr, SEEK_SET); - Address idx = 0; - while (idx < nbytes) { - ssize_t n = write(writer, &bytes[idx], (size_t)(nbytes - idx)); - if (n < 0) break; - idx += (Address)n; - } - return idx; + return 0xff; } // pass config_potentially_changed = false if there definitely hasn't been an update to the target address @@ -383,7 +58,34 @@ static void update_memory_view(State *state, bool config_potentially_changed) { } } -G_MODULE_EXPORT void update_configuration(GtkWidget *widget, gpointer user_data) { +static void bytes_to_text(uint64_t nbytes, char *out, size_t out_size) { + if (nbytes == 0) + snprintf(out, out_size, "0"); + else if (nbytes < (1ul<<10)) + snprintf(out, out_size, "%uB", (unsigned)nbytes); + else if (nbytes < (1ul<<20)) + snprintf(out, out_size, "%.1fKB", (double)nbytes / (double)(1ul<<10)); + else if (nbytes < (1ul<<30)) + snprintf(out, out_size, "%.1fMB", (double)nbytes / (double)(1ul<<20)); + else + snprintf(out, out_size, "%.1fGB", (double)nbytes / (double)(1ul<<30)); +} + +// returns the name of the selected GtkRadioButton in the group whose leader's name is group_name +// (or NULL if none is, which shouldn't usually happen) +static char const *radio_group_get_selected(State *state, char const *group_name) { + GtkRadioButton *leader = GTK_RADIO_BUTTON(gtk_builder_get_object(state->builder, group_name)); + for (GSList *l = gtk_radio_button_get_group(leader); + l; l = l->next) { + GtkToggleButton *button = l->data; + if (gtk_toggle_button_get_active(button)) { + return gtk_widget_get_name(GTK_WIDGET(button)); + } + } + return NULL; +} + +G_MODULE_EXPORT void update_configuration(GtkWidget *_widget, gpointer user_data) { State *state = user_data; GtkBuilder *builder = state->builder; state->stop_while_accessing_memory = gtk_toggle_button_get_active( @@ -404,19 +106,48 @@ G_MODULE_EXPORT void update_configuration(GtkWidget *widget, gpointer user_data) state->memory_view_address = address; update_memview = true; } - GtkRadioButton *data_type_u8 = GTK_RADIO_BUTTON( - gtk_builder_get_object(builder, "type-u8")); - for (GSList *l = gtk_radio_button_get_group(data_type_u8); - l; l = l->next) { - GtkToggleButton *button = l->data; - if (gtk_toggle_button_get_active(button)) { - char const *type_name = gtk_widget_get_name(GTK_WIDGET(button)); - DataType type = data_type_from_name(type_name); - if (state->data_type != type) { - state->data_type = type; - update_memview = true; - } + char const *data_type_str = radio_group_get_selected(state, "type-u8"); + DataType data_type = data_type_from_name(data_type_str); + if (state->data_type != data_type) { + state->data_type = data_type; + update_memview = true; + } + + // update memory/disk estimates for search + GtkLabel *memory_label = GTK_LABEL(gtk_builder_get_object(builder, "required-memory")); + GtkLabel *disk_label = GTK_LABEL(gtk_builder_get_object(builder, "required-disk")); + char const *search_type_str = radio_group_get_selected(state, "enter-value"); + SearchType search_type = search_type_from_str(search_type_str); + state->search_type = search_type; + if (state->pid) { + size_t item_size = data_type_size(data_type); + Address total_memory = state->total_memory; + Address total_items = total_memory / item_size; + Address memory_usage = total_items / 8; // 1 bit per item + { + char text[32]; + bytes_to_text(memory_usage, text, sizeof text); + gtk_label_set_text(memory_label, text); + } + Address disk_usage = 0; + switch (search_type) { + case SEARCH_ENTER_VALUE: + // no disk space needed + break; + case SEARCH_SAME_DIFFERENT: + // we need to store all of memory on disk for this kind of search + disk_usage = total_memory; + break; } + + { + char text[32]; + bytes_to_text(disk_usage, text, sizeof text); + gtk_label_set_text(disk_label, text); + } + } else { + gtk_label_set_text(memory_label, "N/A"); + gtk_label_set_text(disk_label, "N/A"); } if (update_memview) { @@ -439,6 +170,7 @@ static void update_maps(State *state) { rewind(maps_file); Map *maps = state->maps = calloc(capacity, sizeof *maps); unsigned nmaps = 0; + state->total_memory = 0; if (maps) { while (fgets(line, sizeof line, maps_file)) { Address addr_lo, addr_hi; @@ -448,6 +180,7 @@ static void update_maps(State *state) { Map *map = &maps[nmaps++]; map->lo = addr_lo; map->size = addr_hi - addr_lo; + state->total_memory += map->size; } } } @@ -461,7 +194,7 @@ static void update_maps(State *state) { } // the user entered a PID. -G_MODULE_EXPORT void select_pid(GtkButton *button, gpointer user_data) { +G_MODULE_EXPORT void select_pid(GtkButton *_button, gpointer user_data) { State *state = user_data; GtkBuilder *builder = state->builder; GtkEntry *pid = GTK_ENTRY(gtk_builder_get_object(builder, "pid")); @@ -491,6 +224,7 @@ G_MODULE_EXPORT void select_pid(GtkButton *button, gpointer user_data) { state->pid = (PID)pid_number; close(dir); update_maps(state); + update_configuration(NULL, state); // we need to do this to update the search resource usage estimates if (state->nmaps) { // display whatever's in the first mapping Map *first_map = &state->maps[0]; @@ -502,7 +236,7 @@ G_MODULE_EXPORT void select_pid(GtkButton *button, gpointer user_data) { } // a value in memory was edited -G_MODULE_EXPORT void memory_edited(GtkCellRendererText *renderer, char *path, char *new_text, gpointer user_data) { +G_MODULE_EXPORT void memory_edited(GtkCellRendererText *_renderer, char *path, char *new_text, gpointer user_data) { State *state = user_data; GtkBuilder *builder = state->builder; DataType data_type = state->data_type; @@ -514,12 +248,14 @@ G_MODULE_EXPORT void memory_edited(GtkCellRendererText *renderer, char *path, ch if (data_from_str(new_text, data_type, &value)) { int writer = memory_writer_open(state); if (writer) { + // write the value bool success = memory_write_bytes(writer, addr, (uint8_t const *)&value, item_size) == item_size; memory_writer_close(state, writer); if (success) { GtkListStore *store = GTK_LIST_STORE(gtk_builder_get_object(builder, "memory")); GtkTreeIter iter; char value_str[32]; + // convert back to a string (so new_text = "0.10" becomes value_str = "0.1", etc.) data_to_str(&value, data_type, value_str, sizeof value_str); gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path); gtk_list_store_set(store, &iter, 2, value_str, -1); @@ -529,21 +265,37 @@ G_MODULE_EXPORT void memory_edited(GtkCellRendererText *renderer, char *path, ch } } -G_MODULE_EXPORT void refresh_memory(GtkWidget *widget, gpointer user_data) { +G_MODULE_EXPORT void refresh_memory(GtkWidget *_widget, gpointer user_data) { State *state = user_data; update_memory_view(state, true); } -G_MODULE_EXPORT void memory_editing_started(GtkCellRenderer *renderer, GtkCellEditable *editable, char *path, gpointer user_data) { +G_MODULE_EXPORT void memory_editing_started(GtkCellRenderer *_renderer, GtkCellEditable *editable, char *path, gpointer user_data) { State *state = user_data; state->editing_memory = atol(path); } -G_MODULE_EXPORT void memory_editing_canceled(GtkCellRenderer *renderer, gpointer user_data) { +G_MODULE_EXPORT void memory_editing_canceled(GtkCellRenderer *_renderer, gpointer user_data) { State *state = user_data; state->editing_memory = -1; } +G_MODULE_EXPORT void search_start(GtkWidget *_widget, gpointer user_data) { + State *state = user_data; + GtkBuilder *builder = state->builder; + SearchType search_type = state->search_type; + gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, "pre-search"))); + gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "search-common"))); + switch (search_type) { + case SEARCH_ENTER_VALUE: + gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "search-enter-value"))); + break; + case SEARCH_SAME_DIFFERENT: + gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "search-same-different"))); + break; + } +} + // this function is run once per frame static gboolean frame_callback(gpointer user_data) { State *state = user_data; diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..37b9983 --- /dev/null +++ b/memory.c @@ -0,0 +1,104 @@ +// low-level stuff for reading/writing the memory of another process + +static void close_process(State *state, char const *reason) { + GtkBuilder *builder = state->builder; + free(state->maps); state->maps = NULL; + state->nmaps = 0; + state->pid = 0; + { + GtkListStore *store = GTK_LIST_STORE(gtk_builder_get_object(builder, "memory")); + gtk_list_store_clear(store); + } + if (reason) + display_info(state, "Can't access process anymore: %s", reason); + else + display_info_nofmt(state, "Can't access process anymore."); +} + +// don't use this function; use one of the ones below +static int memory_open(State *state, int flags) { + if (state->pid) { + if (state->stop_while_accessing_memory) { + if (kill(state->pid, SIGSTOP) == -1) { + close_process(state, strerror(errno)); + return 0; + } + } + char name[64]; + sprintf(name, "/proc/%lld/mem", (long long)state->pid); + int fd = open(name, flags); + if (fd == -1) { + close_process(state, strerror(errno)); + return 0; + } + return fd; + } + return 0; +} + +static void memory_close(State *state, int fd) { + if (state->stop_while_accessing_memory) { + kill(state->pid, SIGCONT); + } + if (fd) close(fd); +} + + +// get a file descriptor for reading memory from the process +// returns 0 on failure +static int memory_reader_open(State *state) { + return memory_open(state, O_RDONLY); +} + +static void memory_reader_close(State *state, int reader) { + memory_close(state, reader); +} + +// like memory_reader_open, but for writing to memory +static int memory_writer_open(State *state) { + return memory_open(state, O_WRONLY); +} + +static void memory_writer_close(State *state, int writer) { + memory_close(state, writer); +} + +static uint8_t memory_read_byte(int reader, Address addr) { + lseek(reader, (off_t)addr, SEEK_SET); + uint8_t byte = 0; + read(reader, &byte, 1); + return byte; +} + +// returns number of bytes successfully read +static Address memory_read_bytes(int reader, Address addr, uint8_t *memory, Address nbytes) { + lseek(reader, (off_t)addr, SEEK_SET); + Address idx = 0; + while (idx < nbytes) { + ssize_t n = read(reader, &memory[idx], (size_t)(nbytes - idx)); + if (n <= 0) break; + idx += (Address)n; + } + return idx; +} + +// returns # of bytes written (so either 0 or 1) +static Address memory_write_byte(int writer, Address addr, uint8_t byte) { + lseek(writer, (off_t)addr, SEEK_SET); + if (write(writer, &byte, 1) == 1) + return 1; + else + return 0; +} + +// returns # of bytes written +static Address memory_write_bytes(int writer, Address addr, uint8_t const *bytes, Address nbytes) { + lseek(writer, (off_t)addr, SEEK_SET); + Address idx = 0; + while (idx < nbytes) { + ssize_t n = write(writer, &bytes[idx], (size_t)(nbytes - idx)); + if (n < 0) break; + idx += (Address)n; + } + return idx; +} diff --git a/ui.glade b/ui.glade index 94d37ae..515755f 100644 --- a/ui.glade +++ b/ui.glade @@ -273,7 +273,7 @@ - + True False How to interpret the process' memory. @@ -287,226 +287,587 @@ - - 8-bit unsigned integer - u8 + True - True - False - True - True - - - - False - True - 3 - - - - - 8-bit signed integer - s8 - True - True - False - True - True - type-u8 - - - - False - True - 4 - - - - - 16-bit unsigned integer - u16 - True - True - False - True - True - type-u8 - - - - False - True - 5 - - - - - 16-bit signed integer - s16 - True - True - False - True - type-u8 - - - - False - True - 6 - - - - - 32-bit unsigned integer - u32 - True - True - False - True - type-u8 - - - - False - True - 7 - - - - - 32-bit signed integer - s32 - True - True - False - True - type-u8 - - - - False - True - 8 - - - - - 64-bit unsigned integer - u64 - True - True - False - True - type-u8 - - - - False - True - 9 - - - - - 64-bit signed integer - s64 - True - True - False - True - type-u8 - - - - False - True - 10 - - - - - ASCII text - ascii - True - True - False - True - type-u8 - - - - False - True - 11 - - - - - UTF-16 text - utf16 - True - True - False - True - True - type-u8 - - - - False - True - 12 - - - - - UTF-32 text - utf32 - True - True - False - True - type-u8 - - - False - True - 13 - - - - - 32-bit floating-point - f32 - True - True - False - True - type-u8 - + False + vertical + + + 8-bit unsigned integer + u8 + True + True + False + True + True + + + + False + True + 0 + + + + + 8-bit signed integer + s8 + True + True + False + True + True + type-u8 + + + + False + True + 1 + + + + + 16-bit unsigned integer + u16 + True + True + False + True + True + type-u8 + + + + False + True + 2 + + + + + 16-bit signed integer + s16 + True + True + False + True + type-u8 + + + + False + True + 3 + + + + + 32-bit unsigned integer + u32 + True + True + False + True + type-u8 + + + + False + True + 4 + + + + + 32-bit signed integer + s32 + True + True + False + True + type-u8 + + + + False + True + 5 + + + + + 64-bit unsigned integer + u64 + True + True + False + True + type-u8 + + + + False + True + 6 + + + + + 64-bit signed integer + s64 + True + True + False + True + type-u8 + + + + False + True + 7 + + + + + 32-bit floating-point + f32 + True + True + False + True + type-u8 + + + + False + True + 8 + + + + + 64-bit floating-point + f64 + True + True + False + True + type-u8 + + + + False + True + 9 + + + + + ASCII text + ascii + True + True + False + True + type-u8 + + + + False + True + 10 + + + + + UTF-16 text + utf16 + True + True + False + True + True + type-u8 + + + + False + True + 11 + + + + + UTF-32 text + utf32 + True + True + False + True + type-u8 + + + False + True + 12 + + False True - 14 + 16 - - 64-bit floating-point - f64 + True - True - False - True - type-u8 - + False + vertical + + + True + False + Search for a value + + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + Search type: + + + False + True + 0 + + + + + Enter a value + enter-value + True + True + False + At every step in the search, enter the current value of what you're looking for (or an approximate value for floating-point numbers). + True + True + + + + False + True + 1 + + + + + Same/different + same-different + True + True + False + At every step in the search, enter whether or not the value has changed since the last step. This will use quite a bit of disk space, to record all the process' memory. + True + True + enter-value + + + + False + True + 2 + + + + + True + False + + + True + False + Memory required (approx): + + + False + True + 0 + + + + + True + False + N/A + + + False + True + 1 + + + + + False + True + 3 + + + + + True + False + + + True + False + Disk space required (approx): + + + False + True + 0 + + + + + True + False + N/A + + + False + True + 1 + + + + + False + True + 4 + + + + + Begin search + True + True + True + Start searching for a value in memory. Make sure the value is stored somewhere in memory before you start. + + + + False + True + 5 + + + + + False + True + 1 + + + + + False + True + + + True + False + Current value: + + + False + True + 0 + + + + + True + True + + + False + True + 1 + + + + + False + True + 2 + + + + + False + True + + + Same + same + True + True + False + 5 + True + True + + + False + True + 0 + + + + + Different + different + True + True + False + True + True + same + + + False + True + 1 + + + + + False + True + 3 + + + + + False + True + vertical + + + True + False + + + True + False + 0 + + + False + True + 0 + + + + + True + False + steps completed + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + Number of possible places where this value could be + + + True + False + 0 + + + False + True + 0 + + + + + True + False + candidates left + + + False + True + 1 + + + + + False + True + 1 + + + + + Update + True + True + True + Click this to indicate that the value in the box above is the current value of the thing you're looking for. + + + False + True + 2 + + + + + False + True + 4 + + False True - 15 + 18 diff --git a/unicode.h b/unicode.h index c327006..926f426 100644 --- a/unicode.h +++ b/unicode.h @@ -1,3 +1,4 @@ +// Functions for handling unicode text. #ifndef UNICODE_H_ #define UNICODE_H_ #define UNICODE_BOX_CHARACTER 0x2610 -- cgit v1.2.3