1/*
2 * Copyright 2011-2016, Rene Gollent, rene@gollent.com. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "InspectorWindow.h"
7
8#include <stdio.h>
9
10#include <Alert.h>
11#include <Application.h>
12#include <AutoLocker.h>
13#include <Button.h>
14#include <ControlLook.h>
15#include <LayoutBuilder.h>
16#include <ScrollView.h>
17#include <StringView.h>
18#include <TextControl.h>
19
20#include "AppMessageCodes.h"
21#include "Architecture.h"
22#include "CppLanguage.h"
23#include "GuiTeamUiSettings.h"
24#include "MemoryView.h"
25#include "MessageCodes.h"
26#include "Team.h"
27#include "UserInterface.h"
28#include "Value.h"
29
30
31enum {
32	MSG_NAVIGATE_PREVIOUS_BLOCK 		= 'npbl',
33	MSG_NAVIGATE_NEXT_BLOCK				= 'npnl',
34	MSG_MEMORY_BLOCK_RETRIEVED			= 'mbre',
35	MSG_EDIT_CURRENT_BLOCK				= 'mecb',
36	MSG_COMMIT_MODIFIED_BLOCK			= 'mcmb',
37	MSG_REVERT_MODIFIED_BLOCK			= 'mrmb'
38};
39
40
41InspectorWindow::InspectorWindow(::Team* team, UserInterfaceListener* listener,
42	BHandler* target)
43	:
44	BWindow(BRect(100, 100, 700, 500), "Inspector", B_TITLED_WINDOW,
45		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
46	fListener(listener),
47	fAddressInput(NULL),
48	fHexMode(NULL),
49	fTextMode(NULL),
50	fWritableBlockIndicator(NULL),
51	fMemoryView(NULL),
52	fCurrentBlock(NULL),
53	fCurrentAddress(0LL),
54	fTeam(team),
55	fLanguage(NULL),
56	fExpressionInfo(NULL),
57	fTarget(target)
58{
59	AutoLocker< ::Team> teamLocker(fTeam);
60	fTeam->AddListener(this);
61}
62
63
64InspectorWindow::~InspectorWindow()
65{
66	_SetCurrentBlock(NULL);
67
68	if (fLanguage != NULL)
69		fLanguage->ReleaseReference();
70
71	if (fExpressionInfo != NULL) {
72		fExpressionInfo->RemoveListener(this);
73		fExpressionInfo->ReleaseReference();
74	}
75
76	AutoLocker< ::Team> teamLocker(fTeam);
77	fTeam->RemoveListener(this);
78}
79
80
81/* static */ InspectorWindow*
82InspectorWindow::Create(::Team* team, UserInterfaceListener* listener,
83	BHandler* target)
84{
85	InspectorWindow* self = new InspectorWindow(team, listener, target);
86
87	try {
88		self->_Init();
89	} catch (...) {
90		delete self;
91		throw;
92	}
93
94	return self;
95}
96
97
98void
99InspectorWindow::_Init()
100{
101	fLanguage = new CppLanguage();
102	fExpressionInfo = new ExpressionInfo();
103	fExpressionInfo->AddListener(this);
104
105	BScrollView* scrollView;
106
107	BMenu* hexMenu = new BMenu("Hex Mode");
108	BMessage* message = new BMessage(MSG_SET_HEX_MODE);
109	message->AddInt32("mode", HexModeNone);
110	BMenuItem* item = new BMenuItem("<None>", message, '0');
111	hexMenu->AddItem(item);
112	message = new BMessage(*message);
113	message->ReplaceInt32("mode", HexMode8BitInt);
114	item = new BMenuItem("8-bit integer", message, '1');
115	hexMenu->AddItem(item);
116	message = new BMessage(*message);
117	message->ReplaceInt32("mode", HexMode16BitInt);
118	item = new BMenuItem("16-bit integer", message, '2');
119	hexMenu->AddItem(item);
120	message = new BMessage(*message);
121	message->ReplaceInt32("mode", HexMode32BitInt);
122	item = new BMenuItem("32-bit integer", message, '3');
123	hexMenu->AddItem(item);
124	message = new BMessage(*message);
125	message->ReplaceInt32("mode", HexMode64BitInt);
126	item = new BMenuItem("64-bit integer", message, '4');
127	hexMenu->AddItem(item);
128
129	BMenu* endianMenu = new BMenu("Endian Mode");
130	message = new BMessage(MSG_SET_ENDIAN_MODE);
131	message->AddInt32("mode", EndianModeLittleEndian);
132	item = new BMenuItem("Little Endian", message, 'L');
133	endianMenu->AddItem(item);
134	message = new BMessage(*message);
135	message->ReplaceInt32("mode", EndianModeBigEndian);
136	item = new BMenuItem("Big Endian", message, 'B');
137	endianMenu->AddItem(item);
138
139	BMenu* textMenu = new BMenu("Text Mode");
140	message = new BMessage(MSG_SET_TEXT_MODE);
141	message->AddInt32("mode", TextModeNone);
142	item = new BMenuItem("<None>", message, 'N');
143	textMenu->AddItem(item);
144	message = new BMessage(*message);
145	message->ReplaceInt32("mode", TextModeASCII);
146	item = new BMenuItem("ASCII", message, 'A');
147	textMenu->AddItem(item);
148
149	BLayoutBuilder::Group<>(this, B_VERTICAL)
150		.SetInsets(B_USE_DEFAULT_SPACING)
151		.AddGroup(B_HORIZONTAL)
152			.Add(fAddressInput = new BTextControl("addrInput",
153			"Target Address:", "",
154			new BMessage(MSG_INSPECT_ADDRESS)))
155			.Add(fPreviousBlockButton = new BButton("navPrevious", "<",
156				new BMessage(MSG_NAVIGATE_PREVIOUS_BLOCK)))
157			.Add(fNextBlockButton = new BButton("navNext", ">",
158				new BMessage(MSG_NAVIGATE_NEXT_BLOCK)))
159		.End()
160		.AddGroup(B_HORIZONTAL)
161			.Add(fHexMode = new BMenuField("hexMode", "Hex Mode:",
162				hexMenu))
163			.AddGlue()
164			.Add(fEndianMode = new BMenuField("endianMode", "Endian Mode:",
165				endianMenu))
166			.AddGlue()
167			.Add(fTextMode = new BMenuField("viewMode",  "Text Mode:",
168				textMenu))
169		.End()
170		.Add(scrollView = new BScrollView("memory scroll",
171			NULL, 0, false, true), 3.0f)
172		.AddGroup(B_HORIZONTAL)
173			.Add(fWritableBlockIndicator = new BStringView("writableIndicator",
174				_GetCurrentWritableIndicator()))
175			.AddGlue()
176			.Add(fEditBlockButton = new BButton("editBlock", "Edit",
177				new BMessage(MSG_EDIT_CURRENT_BLOCK)))
178			.Add(fCommitBlockButton = new BButton("commitBlock", "Commit",
179				new BMessage(MSG_COMMIT_MODIFIED_BLOCK)))
180			.Add(fRevertBlockButton = new BButton("revertBlock", "Revert",
181				new BMessage(MSG_REVERT_MODIFIED_BLOCK)))
182		.End()
183	.End();
184
185	fHexMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
186	fEndianMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
187	fTextMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
188
189	int32 targetEndian = fTeam->GetArchitecture()->IsBigEndian()
190		? EndianModeBigEndian : EndianModeLittleEndian;
191
192	scrollView->SetTarget(fMemoryView = MemoryView::Create(fTeam, this));
193
194	fAddressInput->SetTarget(this);
195	fPreviousBlockButton->SetTarget(this);
196	fNextBlockButton->SetTarget(this);
197	fPreviousBlockButton->SetEnabled(false);
198	fNextBlockButton->SetEnabled(false);
199
200	fEditBlockButton->SetTarget(this);
201	fCommitBlockButton->SetTarget(this);
202	fRevertBlockButton->SetTarget(this);
203
204	fEditBlockButton->SetEnabled(false);
205	fCommitBlockButton->Hide();
206	fRevertBlockButton->Hide();
207
208	hexMenu->SetLabelFromMarked(true);
209	hexMenu->SetTargetForItems(fMemoryView);
210	endianMenu->SetLabelFromMarked(true);
211	endianMenu->SetTargetForItems(fMemoryView);
212	textMenu->SetLabelFromMarked(true);
213	textMenu->SetTargetForItems(fMemoryView);
214
215	// default to 8-bit format w/ text display
216	hexMenu->ItemAt(1)->SetMarked(true);
217	textMenu->ItemAt(1)->SetMarked(true);
218
219	if (targetEndian == EndianModeBigEndian)
220		endianMenu->ItemAt(1)->SetMarked(true);
221	else
222		endianMenu->ItemAt(0)->SetMarked(true);
223
224	fAddressInput->TextView()->MakeFocus(true);
225
226	AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, new BMessage(
227			MSG_NAVIGATE_PREVIOUS_BLOCK));
228	AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, new BMessage(
229			MSG_NAVIGATE_NEXT_BLOCK));
230}
231
232
233void
234InspectorWindow::MessageReceived(BMessage* message)
235{
236	switch (message->what) {
237		case MSG_THREAD_STATE_CHANGED:
238		{
239			::Thread* thread;
240			if (message->FindPointer("thread",
241					reinterpret_cast<void**>(&thread)) != B_OK) {
242				break;
243			}
244
245			BReference< ::Thread> threadReference(thread, true);
246			if (thread->State() == THREAD_STATE_STOPPED) {
247				if (fCurrentBlock != NULL) {
248					_SetCurrentBlock(NULL);
249					_SetToAddress(fCurrentAddress);
250				}
251			}
252			break;
253		}
254		case MSG_INSPECT_ADDRESS:
255		{
256			target_addr_t address = 0;
257			if (message->FindUInt64("address", &address) != B_OK) {
258				if (fAddressInput->TextView()->TextLength() == 0)
259					break;
260
261				fExpressionInfo->SetTo(fAddressInput->Text());
262
263				fListener->ExpressionEvaluationRequested(fLanguage,
264					fExpressionInfo);
265			} else
266				_SetToAddress(address);
267			break;
268		}
269		case MSG_EXPRESSION_EVALUATED:
270		{
271			BString errorMessage;
272			BReference<ExpressionResult> reference;
273			ExpressionResult* value = NULL;
274			if (message->FindPointer("value",
275					reinterpret_cast<void**>(&value)) == B_OK) {
276				reference.SetTo(value, true);
277				if (value->Kind() == EXPRESSION_RESULT_KIND_PRIMITIVE) {
278					Value* primitive = value->PrimitiveValue();
279					BVariant variantValue;
280					primitive->ToVariant(variantValue);
281					if (variantValue.Type() == B_STRING_TYPE) {
282						errorMessage.SetTo(variantValue.ToString());
283					} else {
284						_SetToAddress(variantValue.ToUInt64());
285						break;
286					}
287				}
288			} else {
289				status_t result = message->FindInt32("result");
290				errorMessage.SetToFormat("Failed to evaluate expression: %s",
291					strerror(result));
292			}
293
294			BAlert* alert = new(std::nothrow) BAlert("Inspect Address",
295				errorMessage.String(), "Close");
296			if (alert != NULL)
297				alert->Go();
298			break;
299		}
300
301		case MSG_NAVIGATE_PREVIOUS_BLOCK:
302		case MSG_NAVIGATE_NEXT_BLOCK:
303		{
304			if (fCurrentBlock != NULL) {
305				target_addr_t address = fCurrentBlock->BaseAddress();
306				if (message->what == MSG_NAVIGATE_PREVIOUS_BLOCK)
307					address -= fCurrentBlock->Size();
308				else
309					address += fCurrentBlock->Size();
310
311				BMessage setMessage(MSG_INSPECT_ADDRESS);
312				setMessage.AddUInt64("address", address);
313				PostMessage(&setMessage);
314			}
315			break;
316		}
317		case MSG_MEMORY_BLOCK_RETRIEVED:
318		{
319			TeamMemoryBlock* block = NULL;
320			status_t result;
321			if (message->FindPointer("block",
322					reinterpret_cast<void **>(&block)) != B_OK
323				|| message->FindInt32("result", &result) != B_OK) {
324				break;
325			}
326
327			if (result == B_OK) {
328				_SetCurrentBlock(block);
329				fPreviousBlockButton->SetEnabled(true);
330				fNextBlockButton->SetEnabled(true);
331			} else {
332				BString errorMessage;
333				errorMessage.SetToFormat("Unable to read address 0x%" B_PRIx64
334					": %s", block->BaseAddress(), strerror(result));
335
336				BAlert* alert = new(std::nothrow) BAlert("Inspect address",
337					errorMessage.String(), "Close");
338				if (alert == NULL)
339					break;
340
341				alert->Go(NULL);
342				block->ReleaseReference();
343			}
344			break;
345		}
346		case MSG_EDIT_CURRENT_BLOCK:
347		{
348			_SetEditMode(true);
349			break;
350		}
351		case MSG_MEMORY_DATA_CHANGED:
352		{
353			if (fCurrentBlock == NULL)
354				break;
355
356			target_addr_t address;
357			if (message->FindUInt64("address", &address) == B_OK
358				&& address >= fCurrentBlock->BaseAddress()
359				&& address < fCurrentBlock->BaseAddress()
360					+ fCurrentBlock->Size()) {
361				fCurrentBlock->Invalidate();
362				_SetEditMode(false);
363				fListener->InspectRequested(address, this);
364			}
365			break;
366		}
367		case MSG_COMMIT_MODIFIED_BLOCK:
368		{
369			// TODO: this could conceivably be extended to detect the
370			// individual modified regions and only write those back.
371			// That would require potentially submitting multiple separate
372			// write requests, and thus require tracking all the writes being
373			// waited upon for completion.
374			fListener->MemoryWriteRequested(fCurrentBlock->BaseAddress(),
375				fMemoryView->GetEditedData(), fCurrentBlock->Size());
376			break;
377		}
378		case MSG_REVERT_MODIFIED_BLOCK:
379		{
380			_SetEditMode(false);
381			break;
382		}
383		default:
384		{
385			BWindow::MessageReceived(message);
386			break;
387		}
388	}
389}
390
391
392bool
393InspectorWindow::QuitRequested()
394{
395	BMessage settings(MSG_INSPECTOR_WINDOW_CLOSED);
396	SaveSettings(settings);
397
398	BMessenger(fTarget).SendMessage(&settings);
399	return true;
400}
401
402
403void
404InspectorWindow::ThreadStateChanged(const Team::ThreadEvent& event)
405{
406	BMessage message(MSG_THREAD_STATE_CHANGED);
407	BReference< ::Thread> threadReference(event.GetThread());
408	message.AddPointer("thread", threadReference.Get());
409
410	if (PostMessage(&message) == B_OK)
411		threadReference.Detach();
412}
413
414
415void
416InspectorWindow::MemoryChanged(const Team::MemoryChangedEvent& event)
417{
418	BMessage message(MSG_MEMORY_DATA_CHANGED);
419	message.AddUInt64("address", event.GetTargetAddress());
420	message.AddUInt64("size", event.GetSize());
421
422	PostMessage(&message);
423}
424
425
426void
427InspectorWindow::MemoryBlockRetrieved(TeamMemoryBlock* block)
428{
429	BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
430	message.AddPointer("block", block);
431	message.AddInt32("result", B_OK);
432	PostMessage(&message);
433}
434
435
436void
437InspectorWindow::MemoryBlockRetrievalFailed(TeamMemoryBlock* block,
438	status_t result)
439{
440	BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
441	message.AddPointer("block", block);
442	message.AddInt32("result", result);
443	PostMessage(&message);
444}
445
446
447void
448InspectorWindow::TargetAddressChanged(target_addr_t address)
449{
450	AutoLocker<BLooper> lock(this);
451	if (lock.IsLocked()) {
452		fCurrentAddress = address;
453		BString computedAddress;
454		computedAddress.SetToFormat("0x%" B_PRIx64, address);
455		fAddressInput->SetText(computedAddress.String());
456	}
457}
458
459
460void
461InspectorWindow::HexModeChanged(int32 newMode)
462{
463	AutoLocker<BLooper> lock(this);
464	if (lock.IsLocked()) {
465		BMenu* menu = fHexMode->Menu();
466		if (newMode < 0 || newMode > menu->CountItems())
467			return;
468		BMenuItem* item = menu->ItemAt(newMode);
469		item->SetMarked(true);
470	}
471}
472
473
474void
475InspectorWindow::EndianModeChanged(int32 newMode)
476{
477	AutoLocker<BLooper> lock(this);
478	if (lock.IsLocked()) {
479		BMenu* menu = fEndianMode->Menu();
480		if (newMode < 0 || newMode > menu->CountItems())
481			return;
482		BMenuItem* item = menu->ItemAt(newMode);
483		item->SetMarked(true);
484	}
485}
486
487
488void
489InspectorWindow::TextModeChanged(int32 newMode)
490{
491	AutoLocker<BLooper> lock(this);
492	if (lock.IsLocked()) {
493		BMenu* menu = fTextMode->Menu();
494		if (newMode < 0 || newMode > menu->CountItems())
495			return;
496		BMenuItem* item = menu->ItemAt(newMode);
497		item->SetMarked(true);
498	}
499}
500
501
502void
503InspectorWindow::ExpressionEvaluated(ExpressionInfo* info, status_t result,
504	ExpressionResult* value)
505{
506	BMessage message(MSG_EXPRESSION_EVALUATED);
507	message.AddInt32("result", result);
508	BReference<ExpressionResult> reference;
509	if (value != NULL) {
510		reference.SetTo(value);
511		message.AddPointer("value", value);
512	}
513
514	if (PostMessage(&message) == B_OK)
515		reference.Detach();
516}
517
518
519status_t
520InspectorWindow::LoadSettings(const GuiTeamUiSettings& settings)
521{
522	AutoLocker<BLooper> lock(this);
523	if (!lock.IsLocked())
524		return B_ERROR;
525
526	BMessage inspectorSettings;
527	if (settings.Settings("inspectorWindow", inspectorSettings) != B_OK)
528		return B_OK;
529
530	BRect frameRect;
531	if (inspectorSettings.FindRect("frame", &frameRect) == B_OK) {
532		ResizeTo(frameRect.Width(), frameRect.Height());
533		MoveTo(frameRect.left, frameRect.top);
534	}
535
536	_LoadMenuFieldMode(fHexMode, "Hex", inspectorSettings);
537	_LoadMenuFieldMode(fEndianMode, "Endian", inspectorSettings);
538	_LoadMenuFieldMode(fTextMode, "Text", inspectorSettings);
539
540	return B_OK;
541}
542
543
544status_t
545InspectorWindow::SaveSettings(BMessage& settings)
546{
547	AutoLocker<BLooper> lock(this);
548	if (!lock.IsLocked())
549		return B_ERROR;
550
551	settings.MakeEmpty();
552
553	status_t error = settings.AddRect("frame", Frame());
554	if (error != B_OK)
555		return error;
556
557	error = _SaveMenuFieldMode(fHexMode, "Hex", settings);
558	if (error != B_OK)
559		return error;
560
561	error = _SaveMenuFieldMode(fEndianMode, "Endian", settings);
562	if (error != B_OK)
563		return error;
564
565	error = _SaveMenuFieldMode(fTextMode, "Text", settings);
566	if (error != B_OK)
567		return error;
568
569	return B_OK;
570}
571
572
573void
574InspectorWindow::_LoadMenuFieldMode(BMenuField* field, const char* name,
575	const BMessage& settings)
576{
577	BString fieldName;
578	int32 mode;
579	fieldName.SetToFormat("%sMode", name);
580	if (settings.FindInt32(fieldName.String(), &mode) == B_OK) {
581		BMenu* menu = field->Menu();
582		for (int32 i = 0; i < menu->CountItems(); i++) {
583			BInvoker* item = menu->ItemAt(i);
584			if (item->Message()->FindInt32("mode") == mode) {
585				item->Invoke();
586				break;
587			}
588		}
589	}
590}
591
592
593status_t
594InspectorWindow::_SaveMenuFieldMode(BMenuField* field, const char* name,
595	BMessage& settings)
596{
597	BMenuItem* item = field->Menu()->FindMarked();
598	if (item && item->Message()) {
599		int32 mode = item->Message()->FindInt32("mode");
600		BString fieldName;
601		fieldName.SetToFormat("%sMode", name);
602		return settings.AddInt32(fieldName.String(), mode);
603	}
604
605	return B_OK;
606}
607
608
609void
610InspectorWindow::_SetToAddress(target_addr_t address)
611{
612	fCurrentAddress = address;
613	if (fCurrentBlock == NULL
614		|| !fCurrentBlock->Contains(address)) {
615		fListener->InspectRequested(address, this);
616	} else
617		fMemoryView->SetTargetAddress(fCurrentBlock, address);
618}
619
620
621void
622InspectorWindow::_SetCurrentBlock(TeamMemoryBlock* block)
623{
624	AutoLocker< ::Team> teamLocker(fTeam);
625	if (fCurrentBlock != NULL) {
626		fCurrentBlock->RemoveListener(this);
627		fCurrentBlock->ReleaseReference();
628	}
629
630	fCurrentBlock = block;
631	fMemoryView->SetTargetAddress(fCurrentBlock, fCurrentAddress);
632	_UpdateWritableOptions();
633}
634
635
636bool
637InspectorWindow::_GetWritableState() const
638{
639	return fCurrentBlock != NULL ? fCurrentBlock->IsWritable() : false;
640}
641
642
643void
644InspectorWindow::_SetEditMode(bool enabled)
645{
646	if (enabled == fMemoryView->GetEditMode())
647		return;
648
649	status_t error = fMemoryView->SetEditMode(enabled);
650	if (error != B_OK)
651		return;
652
653	if (enabled) {
654		fEditBlockButton->Hide();
655		fCommitBlockButton->Show();
656		fRevertBlockButton->Show();
657	} else {
658		fEditBlockButton->Show();
659		fCommitBlockButton->Hide();
660		fRevertBlockButton->Hide();
661	}
662
663	fHexMode->SetEnabled(!enabled);
664	fEndianMode->SetEnabled(!enabled);
665
666	// while the block is being edited, disable block navigation controls.
667	fAddressInput->SetEnabled(!enabled);
668	fPreviousBlockButton->SetEnabled(!enabled);
669	fNextBlockButton->SetEnabled(!enabled);
670
671	InvalidateLayout();
672}
673
674
675void
676InspectorWindow::_UpdateWritableOptions()
677{
678	fEditBlockButton->SetEnabled(_GetWritableState());
679	_UpdateWritableIndicator();
680}
681
682
683void
684InspectorWindow::_UpdateWritableIndicator()
685{
686	fWritableBlockIndicator->SetText(_GetCurrentWritableIndicator());
687}
688
689
690const char*
691InspectorWindow::_GetCurrentWritableIndicator() const
692{
693	static char buffer[32];
694	snprintf(buffer, sizeof(buffer), "Writable: %s", fCurrentBlock == NULL
695			? "N/A" : fCurrentBlock->IsWritable() ? "Yes" : "No");
696
697	return buffer;
698}
699