1 /*
2  *  memory.c
3  *
4  *  Copyright 2013 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gdk/gdkkeysyms.h>
28 
29 #include "common.h"
30 
31 enum
32 {
33 	MEMORY_ADDR,
34 	MEMORY_BYTES,
35 	MEMORY_ASCII
36 };
37 
38 static ScpTreeStore *store;
39 static GtkTreeSelection *selection;
40 
on_memory_bytes_edited(G_GNUC_UNUSED GtkCellRendererText * renderer,gchar * path_str,gchar * new_text,G_GNUC_UNUSED gpointer gdata)41 static void on_memory_bytes_edited(G_GNUC_UNUSED GtkCellRendererText *renderer, gchar *path_str,
42 	gchar *new_text, G_GNUC_UNUSED gpointer gdata)
43 {
44 	if (*new_text && (debug_state() & DS_VARIABLE))
45 	{
46 		GtkTreeIter iter;
47 		const char *addr, *bytes;
48 		guint i;
49 
50 		scp_tree_store_get_iter_from_string(store, &iter, path_str);
51 		scp_tree_store_get(store, &iter, MEMORY_ADDR, &addr, MEMORY_BYTES, &bytes, -1);
52 
53 		for (i = 0; bytes[i]; i++)
54 			if (!(isxdigit(bytes[i]) ? isxdigit(new_text[i]) : new_text[i] == ' '))
55 				break;
56 
57 		if (bytes[i] || new_text[i])
58 			dc_error("memory: invalid format");
59 		else
60 		{
61 			utils_strchrepl(new_text, ' ', '\0');
62 			debug_send_format(T, "07-data-write-memory-bytes 0x%s%s", addr, new_text);
63 		}
64 	}
65 	else
66 		plugin_blink();
67 }
68 
on_memory_entry_key_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventKey * event,GtkEditable * editable)69 static gboolean on_memory_entry_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
70 	GtkEditable *editable)
71 {
72 	const char *text = gtk_entry_get_text(GTK_ENTRY(editable));
73 	gint pos = gtk_editable_get_position(editable);
74 
75 	if (event->keyval <= 0x7F && ((isxdigit(event->keyval) && isxdigit(text[pos])) ||
76 		(event->keyval == ' ' && text[pos] == ' ')) && event->state <= GDK_SHIFT_MASK)
77 	{
78 		char c = event->keyval;
79 
80 		gtk_editable_set_editable(editable, TRUE);
81 		gtk_editable_delete_text(editable, pos, pos + 1);
82 		gtk_editable_insert_text(editable, &c, 1, &pos);
83 		gtk_editable_set_position(editable, pos);
84 		gtk_editable_set_editable(editable, FALSE);
85 		return TRUE;
86 	}
87 
88 	return event->keyval == GDK_Insert || event->keyval == GDK_KP_Insert ||
89 		event->keyval == GDK_space || event->keyval == GDK_KP_Space;
90 }
91 
92 static const char *memory_font;
93 
on_memory_bytes_editing_started(G_GNUC_UNUSED GtkCellRenderer * cell,GtkCellEditable * cell_editable,G_GNUC_UNUSED const gchar * path,G_GNUC_UNUSED gpointer gdata)94 static void on_memory_bytes_editing_started(G_GNUC_UNUSED GtkCellRenderer *cell,
95 	GtkCellEditable *cell_editable, G_GNUC_UNUSED const gchar *path,
96 	G_GNUC_UNUSED gpointer gdata)
97 {
98 	iff (GTK_IS_ENTRY(cell_editable), "memory_bytes: not an entry")
99 	{
100 		GtkEditable *editable = GTK_EDITABLE(cell_editable);
101 
102 		ui_widget_modify_font_from_string(GTK_WIDGET(editable), memory_font);
103 		gtk_entry_set_overwrite_mode(GTK_ENTRY(editable), TRUE);
104 		gtk_editable_set_editable(editable, FALSE);
105 		gtk_editable_set_position(editable, 0);
106 		g_signal_connect(editable, "key-press-event", G_CALLBACK(on_memory_entry_key_press),
107 			editable);
108 	}
109 }
110 
111 static const TreeCell memory_cells[] =
112 {
113 	{ "memory_bytes", G_CALLBACK(on_memory_bytes_edited) },
114 	{ NULL, NULL }
115 };
116 
117 static guint pointer_size;
118 static char *addr_format;
119 #define MAX_BYTES_PER_LINE 128
120 #define MAX_POINTER_SIZE 8
121 
122 static gint back_bytes_per_line;
123 static gint bytes_per_line;
124 static gint bytes_per_group = 1;
125 
memory_configure(void)126 static void memory_configure(void)
127 {
128 	gint groups_per_line;
129 
130 	back_bytes_per_line = pref_memory_bytes_per_line;
131 	bytes_per_line = pref_memory_bytes_per_line;
132 	if ((unsigned) (bytes_per_line - 8) > MAX_BYTES_PER_LINE - 8)
133 		bytes_per_line = 16;
134 
135 	groups_per_line = bytes_per_line / bytes_per_group;
136 	bytes_per_line = groups_per_line * bytes_per_group;
137 }
138 
139 static guint64 memory_start;
140 static guint memory_count = 0;
141 #define MAX_BYTES (124 * MAX_BYTES_PER_LINE)
142 
write_block(guint64 start,const char * contents,guint count,const char * maddr)143 static void write_block(guint64 start, const char *contents, guint count, const char *maddr)
144 {
145 	if (!memory_count)
146 		memory_start = start;
147 	else if (memory_count < MAX_BYTES)
148 		memory_count = start - memory_start;
149 
150 	while (memory_count < MAX_BYTES)
151 	{
152 		GtkTreeIter iter;
153 		char *addr = g_strdup_printf(addr_format, start);
154 		GString *bytes = g_string_sized_new(bytes_per_line * 3);
155 		GString *ascii = g_string_new(" ");
156 		gint n = 0;
157 
158 		while (n < bytes_per_line)
159 		{
160 			char locale;
161 			gchar *utf8;
162 
163 			g_string_append_len(bytes, contents, 2);
164 			contents += 2;
165 			memory_count++;
166 			locale = strtol(bytes->str + bytes->len - 2, NULL, 16);
167 			utf8 = locale >= 0x20 ? g_locale_to_utf8(&locale, 1, NULL, NULL, NULL) : NULL;
168 
169 			if (utf8)
170 			{
171 				g_string_append(ascii, utf8);
172 				g_free(utf8);
173 			}
174 			else
175 				g_string_append_c(ascii, '.');  /* 0xfffd? */
176 
177 			if (++n % bytes_per_group == 0)
178 				g_string_append_c(bytes, ' ');
179 
180 			if (!--count)
181 				break;
182 		}
183 
184 		while (n < bytes_per_line)
185 		{
186 			g_string_append(bytes, "  ");
187 
188 			if (++n % bytes_per_group == 0)
189 				g_string_append_c(bytes, ' ');
190 		}
191 
192 		scp_tree_store_append_with_values(store, &iter, NULL, MEMORY_ADDR, addr,
193 			MEMORY_BYTES, bytes->str, MEMORY_ASCII, ascii->str, -1);
194 		if (!g_strcmp0(addr, maddr))
195 			gtk_tree_selection_select_iter(selection, &iter);
196 
197 		g_free(addr);
198 		g_string_free(bytes, TRUE);
199 		g_string_free(ascii, TRUE);
200 
201 		if (!count)
202 			break;
203 
204 		start += bytes_per_line;
205 	}
206 
207 	if (count)
208 		dc_error("memory: too much data");
209 }
210 
memory_node_read(const ParseNode * node,const char * maddr)211 static void memory_node_read(const ParseNode *node, const char *maddr)
212 {
213 	iff (node->type == PT_ARRAY, "memory: contains value")
214 	{
215 		GArray *nodes = (GArray *) node->value;
216 		const char *begin = parse_find_value(nodes, "begin");
217 		const char *offset = parse_find_value(nodes, "offset");
218 		const char *contents = parse_find_value(nodes, "contents");
219 
220 		iff (begin && contents, "memory: no begin or contents")
221 		{
222 			guint64 start = g_ascii_strtoull(begin, NULL, 0);
223 			guint64 count = strlen(contents) / 2;
224 
225 			if (offset)
226 				start += g_ascii_strtoull(offset, NULL, 0);
227 
228 			iff (count, "memory: contents too short")
229 				write_block(start, contents, count, maddr);
230 		}
231 	}
232 }
233 
on_memory_read_bytes(GArray * nodes)234 void on_memory_read_bytes(GArray *nodes)
235 {
236 	if (pointer_size <= MAX_POINTER_SIZE)
237 	{
238 		GtkTreeIter iter;
239 		char *maddr = NULL;
240 
241 		if (gtk_tree_selection_get_selected(selection, NULL, &iter))
242 			gtk_tree_model_get((GtkTreeModel *) store, &iter, MEMORY_ADDR, &maddr, -1);
243 
244 		store_clear(store);
245 		memory_count = 0;
246 
247 		if (pref_memory_bytes_per_line != back_bytes_per_line)
248 		{
249 			memory_configure();
250 			gtk_tree_view_column_queue_resize(get_column("memory_bytes_column"));
251 			gtk_tree_view_column_queue_resize(get_column("memory_ascii_column"));
252 		}
253 
254 		parse_foreach(parse_lead_array(nodes), (GFunc) memory_node_read, maddr);
255 		g_free(maddr);
256 	}
257 }
258 
memory_clear(void)259 void memory_clear(void)
260 {
261 	store_clear(store);
262 }
263 
memory_update(void)264 gboolean memory_update(void)
265 {
266 	if (memory_count)
267 	{
268 		debug_send_format(T, "04-data-read-memory-bytes 0x%" G_GINT64_MODIFIER "x %u",
269 			memory_start, memory_count);
270 	}
271 	return TRUE;
272 }
273 
on_memory_refresh(G_GNUC_UNUSED const MenuItem * menu_item)274 static void on_memory_refresh(G_GNUC_UNUSED const MenuItem *menu_item)
275 {
276 	debug_send_format(T, "-data-read-memory-bytes 0x%" G_GINT64_MODIFIER "x %u",
277 		memory_start, memory_count);
278 }
279 
on_memory_read(G_GNUC_UNUSED const MenuItem * menu_item)280 static void on_memory_read(G_GNUC_UNUSED const MenuItem *menu_item)
281 {
282 	GString *command = g_string_new("-data-read-memory-bytes ");
283 	gchar *expr = utils_get_default_selection();
284 
285 	if (expr)
286 	{
287 		g_string_append(command, expr);
288 		g_free(expr);
289 	}
290 	else if (memory_count)
291 	{
292 		g_string_append_printf(command, "0x%" G_GINT64_MODIFIER "x %u", memory_start,
293 			memory_count);
294 	}
295 
296 	view_command_line(command->str, _("Read Memory"), " ", TRUE);
297 	g_string_free(command, TRUE);
298 }
299 
on_memory_copy(G_GNUC_UNUSED const MenuItem * menu_item)300 static void on_memory_copy(G_GNUC_UNUSED const MenuItem *menu_item)
301 {
302 	GtkTreeIter iter;
303 	const char *addr, *bytes;
304 	const gchar *ascii;
305 	gchar *string;
306 
307 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
308 	{
309 		scp_tree_store_get(store, &iter, MEMORY_ADDR, &addr, MEMORY_BYTES, &bytes,
310 			MEMORY_ASCII, &ascii, -1);
311 		string = g_strdup_printf("%s%s%s", addr, bytes, ascii);
312 		gtk_clipboard_set_text(gtk_widget_get_clipboard(menu_item->widget,
313 			GDK_SELECTION_CLIPBOARD), string, -1);
314 		g_free(string);
315 	}
316 }
317 
on_memory_clear(G_GNUC_UNUSED const MenuItem * menu_item)318 static void on_memory_clear(G_GNUC_UNUSED const MenuItem *menu_item)
319 {
320 	store_clear(store);
321 	memory_count = 0;
322 }
323 
on_memory_group_display(const MenuItem * menu_item)324 static void on_memory_group_display(const MenuItem *menu_item)
325 {
326 	guint i;
327 
328 	for (i = 0; (1 << i) < bytes_per_group; i++);
329 	menu_item_set_active(menu_item + i + 1, TRUE);
330 }
331 
on_memory_group_update(const MenuItem * menu_item)332 static void on_memory_group_update(const MenuItem *menu_item)
333 {
334 	bytes_per_group = 1 << GPOINTER_TO_INT(menu_item->gdata);
335 	back_bytes_per_line = 0;
336 
337 	if (memory_count)
338 		on_memory_refresh(menu_item);
339 }
340 
341 #define DS_FRESHABLE (DS_VRIABLE | DS_EXTRA_2)
342 #define DS_COPYABLE (DS_BASICS | DS_EXTRA_1)
343 #define DS_CLEARABLE (DS_ACTIVE | DS_EXTRA_2)
344 
345 #define GROUP_ITEM(count, POWER) \
346 	{ ("memory_group_"count), on_memory_group_update, DS_VARIABLE, NULL, \
347 		GINT_TO_POINTER(POWER) }
348 
349 static MenuItem memory_menu_items[] =
350 {
351 	{ "memory_refresh", on_memory_refresh,       DS_VARIABLE,  NULL, NULL },
352 	{ "memory_read",    on_memory_read,          DS_VARIABLE,  NULL, NULL },
353 	{ "memory_copy",    on_memory_copy,          DS_COPYABLE,  NULL, NULL },
354 	{ "memory_clear",   on_memory_clear,         DS_CLEARABLE, NULL, NULL },
355 	{ "memory_group",   on_memory_group_display, DS_VARIABLE,  NULL, NULL },
356 	GROUP_ITEM("1", 0),
357 	GROUP_ITEM("2", 1),
358 	GROUP_ITEM("4", 2),
359 	GROUP_ITEM("8", 3),
360 	{ NULL, NULL, 0, NULL, NULL }
361 };
362 
memory_menu_extra_state(void)363 static guint memory_menu_extra_state(void)
364 {
365 	return (gtk_tree_selection_get_selected(selection, NULL, NULL) << DS_INDEX_1) |
366 		(memory_count != 0) << DS_INDEX_2;
367 }
368 
369 static MenuInfo memory_menu_info = { memory_menu_items, memory_menu_extra_state, 0 };
370 
on_memory_key_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventKey * event,gpointer gdata)371 static gboolean on_memory_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event,
372 	gpointer gdata)
373 {
374 	if (event->keyval == GDK_Insert || event->keyval == GDK_KP_Insert)
375 	{
376 		menu_item_execute(&memory_menu_info, (const MenuItem *) gdata, FALSE);
377 		return TRUE;
378 	}
379 
380 	return FALSE;
381 }
382 
memory_init(void)383 void memory_init(void)
384 {
385 	GtkWidget *tree = GTK_WIDGET(view_connect("memory_view", &store, &selection,
386 		memory_cells, "memory_window", NULL));
387 
388 	memory_font = *pref_memory_font ? pref_memory_font : pref_vte_font;
389 	ui_widget_modify_font_from_string(tree, memory_font);
390 	g_signal_connect(get_object("memory_bytes"), "editing-started",
391 		G_CALLBACK(on_memory_bytes_editing_started), NULL);
392 	g_signal_connect(tree, "key-press-event", G_CALLBACK(on_memory_key_press),
393 		(gpointer) menu_item_find(memory_menu_items, "memory_read"));
394 
395 	pointer_size = MAX(sizeof(void *), sizeof(&memory_init));
396 	addr_format = g_strdup_printf("%%0%u" G_GINT64_MODIFIER "x  ", pointer_size * 2);
397 	memory_configure();
398 
399 	if (pointer_size > MAX_POINTER_SIZE)
400 	{
401 		msgwin_status_add(_("Scope: pointer size > %d, Data disabled."), MAX_POINTER_SIZE);
402 		gtk_widget_hide(tree);
403 	}
404 	else
405 		menu_connect("memory_menu", &memory_menu_info, tree);
406 }
407 
memory_finalize(void)408 void memory_finalize(void)
409 {
410 	g_free(addr_format);
411 }
412