2 * Copyright 2011-2016, Rene Gollent, rene@gollent.com. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 #include "InspectorWindow.h"
11 #include <Application.h>
12 #include <AutoLocker.h>
14 #include <ControlLook.h>
15 #include <LayoutBuilder.h>
16 #include <ScrollView.h>
17 #include <StringView.h>
18 #include <TextControl.h>
20 #include "Architecture.h"
21 #include "CppLanguage.h"
22 #include "GuiTeamUiSettings.h"
23 #include "MemoryView.h"
24 #include "MessageCodes.h"
26 #include "UserInterface.h"
31 MSG_NAVIGATE_PREVIOUS_BLOCK
= 'npbl',
32 MSG_NAVIGATE_NEXT_BLOCK
= 'npnl',
33 MSG_MEMORY_BLOCK_RETRIEVED
= 'mbre',
34 MSG_EDIT_CURRENT_BLOCK
= 'mecb',
35 MSG_COMMIT_MODIFIED_BLOCK
= 'mcmb',
36 MSG_REVERT_MODIFIED_BLOCK
= 'mrmb'
40 InspectorWindow::InspectorWindow(::Team
* team
, UserInterfaceListener
* listener
,
43 BWindow(BRect(100, 100, 700, 500), "Inspector", B_TITLED_WINDOW
,
44 B_ASYNCHRONOUS_CONTROLS
| B_AUTO_UPDATE_SIZE_LIMITS
),
49 fWritableBlockIndicator(NULL
),
55 fExpressionInfo(NULL
),
58 AutoLocker
< ::Team
> teamLocker(fTeam
);
59 fTeam
->AddListener(this);
63 InspectorWindow::~InspectorWindow()
65 _SetCurrentBlock(NULL
);
67 if (fLanguage
!= NULL
)
68 fLanguage
->ReleaseReference();
70 if (fExpressionInfo
!= NULL
) {
71 fExpressionInfo
->RemoveListener(this);
72 fExpressionInfo
->ReleaseReference();
75 AutoLocker
< ::Team
> teamLocker(fTeam
);
76 fTeam
->RemoveListener(this);
80 /* static */ InspectorWindow
*
81 InspectorWindow::Create(::Team
* team
, UserInterfaceListener
* listener
,
84 InspectorWindow
* self
= new InspectorWindow(team
, listener
, target
);
98 InspectorWindow::_Init()
100 fLanguage
= new CppLanguage();
101 fExpressionInfo
= new ExpressionInfo();
102 fExpressionInfo
->AddListener(this);
104 BScrollView
* scrollView
;
106 BMenu
* hexMenu
= new BMenu("Hex Mode");
107 BMessage
* message
= new BMessage(MSG_SET_HEX_MODE
);
108 message
->AddInt32("mode", HexModeNone
);
109 BMenuItem
* item
= new BMenuItem("<None>", message
, '0');
110 hexMenu
->AddItem(item
);
111 message
= new BMessage(*message
);
112 message
->ReplaceInt32("mode", HexMode8BitInt
);
113 item
= new BMenuItem("8-bit integer", message
, '1');
114 hexMenu
->AddItem(item
);
115 message
= new BMessage(*message
);
116 message
->ReplaceInt32("mode", HexMode16BitInt
);
117 item
= new BMenuItem("16-bit integer", message
, '2');
118 hexMenu
->AddItem(item
);
119 message
= new BMessage(*message
);
120 message
->ReplaceInt32("mode", HexMode32BitInt
);
121 item
= new BMenuItem("32-bit integer", message
, '3');
122 hexMenu
->AddItem(item
);
123 message
= new BMessage(*message
);
124 message
->ReplaceInt32("mode", HexMode64BitInt
);
125 item
= new BMenuItem("64-bit integer", message
, '4');
126 hexMenu
->AddItem(item
);
128 BMenu
* endianMenu
= new BMenu("Endian Mode");
129 message
= new BMessage(MSG_SET_ENDIAN_MODE
);
130 message
->AddInt32("mode", EndianModeLittleEndian
);
131 item
= new BMenuItem("Little Endian", message
, 'L');
132 endianMenu
->AddItem(item
);
133 message
= new BMessage(*message
);
134 message
->ReplaceInt32("mode", EndianModeBigEndian
);
135 item
= new BMenuItem("Big Endian", message
, 'B');
136 endianMenu
->AddItem(item
);
138 BMenu
* textMenu
= new BMenu("Text Mode");
139 message
= new BMessage(MSG_SET_TEXT_MODE
);
140 message
->AddInt32("mode", TextModeNone
);
141 item
= new BMenuItem("<None>", message
, 'N');
142 textMenu
->AddItem(item
);
143 message
= new BMessage(*message
);
144 message
->ReplaceInt32("mode", TextModeASCII
);
145 item
= new BMenuItem("ASCII", message
, 'A');
146 textMenu
->AddItem(item
);
148 BLayoutBuilder::Group
<>(this, B_VERTICAL
)
149 .SetInsets(B_USE_DEFAULT_SPACING
)
150 .AddGroup(B_HORIZONTAL
)
151 .Add(fAddressInput
= new BTextControl("addrInput",
152 "Target Address:", "",
153 new BMessage(MSG_INSPECT_ADDRESS
)))
154 .Add(fPreviousBlockButton
= new BButton("navPrevious", "<",
155 new BMessage(MSG_NAVIGATE_PREVIOUS_BLOCK
)))
156 .Add(fNextBlockButton
= new BButton("navNext", ">",
157 new BMessage(MSG_NAVIGATE_NEXT_BLOCK
)))
159 .AddGroup(B_HORIZONTAL
)
160 .Add(fHexMode
= new BMenuField("hexMode", "Hex Mode:",
163 .Add(fEndianMode
= new BMenuField("endianMode", "Endian Mode:",
166 .Add(fTextMode
= new BMenuField("viewMode", "Text Mode:",
169 .Add(scrollView
= new BScrollView("memory scroll",
170 NULL
, 0, false, true), 3.0f
)
171 .AddGroup(B_HORIZONTAL
)
172 .Add(fWritableBlockIndicator
= new BStringView("writableIndicator",
173 _GetCurrentWritableIndicator()))
175 .Add(fEditBlockButton
= new BButton("editBlock", "Edit",
176 new BMessage(MSG_EDIT_CURRENT_BLOCK
)))
177 .Add(fCommitBlockButton
= new BButton("commitBlock", "Commit",
178 new BMessage(MSG_COMMIT_MODIFIED_BLOCK
)))
179 .Add(fRevertBlockButton
= new BButton("revertBlock", "Revert",
180 new BMessage(MSG_REVERT_MODIFIED_BLOCK
)))
184 fHexMode
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
185 fEndianMode
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
186 fTextMode
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
188 int32 targetEndian
= fTeam
->GetArchitecture()->IsBigEndian()
189 ? EndianModeBigEndian
: EndianModeLittleEndian
;
191 scrollView
->SetTarget(fMemoryView
= MemoryView::Create(fTeam
, this));
193 fAddressInput
->SetTarget(this);
194 fPreviousBlockButton
->SetTarget(this);
195 fNextBlockButton
->SetTarget(this);
196 fPreviousBlockButton
->SetEnabled(false);
197 fNextBlockButton
->SetEnabled(false);
199 fEditBlockButton
->SetTarget(this);
200 fCommitBlockButton
->SetTarget(this);
201 fRevertBlockButton
->SetTarget(this);
203 fEditBlockButton
->SetEnabled(false);
204 fCommitBlockButton
->Hide();
205 fRevertBlockButton
->Hide();
207 hexMenu
->SetLabelFromMarked(true);
208 hexMenu
->SetTargetForItems(fMemoryView
);
209 endianMenu
->SetLabelFromMarked(true);
210 endianMenu
->SetTargetForItems(fMemoryView
);
211 textMenu
->SetLabelFromMarked(true);
212 textMenu
->SetTargetForItems(fMemoryView
);
214 // default to 8-bit format w/ text display
215 hexMenu
->ItemAt(1)->SetMarked(true);
216 textMenu
->ItemAt(1)->SetMarked(true);
218 if (targetEndian
== EndianModeBigEndian
)
219 endianMenu
->ItemAt(1)->SetMarked(true);
221 endianMenu
->ItemAt(0)->SetMarked(true);
223 fAddressInput
->TextView()->MakeFocus(true);
225 AddShortcut(B_LEFT_ARROW
, B_COMMAND_KEY
, new BMessage(
226 MSG_NAVIGATE_PREVIOUS_BLOCK
));
227 AddShortcut(B_RIGHT_ARROW
, B_COMMAND_KEY
, new BMessage(
228 MSG_NAVIGATE_NEXT_BLOCK
));
233 InspectorWindow::MessageReceived(BMessage
* message
)
235 switch (message
->what
) {
236 case MSG_THREAD_STATE_CHANGED
:
239 if (message
->FindPointer("thread",
240 reinterpret_cast<void**>(&thread
)) != B_OK
) {
244 BReference
< ::Thread
> threadReference(thread
, true);
245 if (thread
->State() == THREAD_STATE_STOPPED
) {
246 if (fCurrentBlock
!= NULL
) {
247 _SetCurrentBlock(NULL
);
248 _SetToAddress(fCurrentAddress
);
253 case MSG_INSPECT_ADDRESS
:
255 target_addr_t address
= 0;
256 if (message
->FindUInt64("address", &address
) != B_OK
) {
257 if (fAddressInput
->TextView()->TextLength() == 0)
260 fExpressionInfo
->SetTo(fAddressInput
->Text());
262 fListener
->ExpressionEvaluationRequested(fLanguage
,
265 _SetToAddress(address
);
268 case MSG_EXPRESSION_EVALUATED
:
270 BString errorMessage
;
271 BReference
<ExpressionResult
> reference
;
272 ExpressionResult
* value
= NULL
;
273 if (message
->FindPointer("value",
274 reinterpret_cast<void**>(&value
)) == B_OK
) {
275 reference
.SetTo(value
, true);
276 if (value
->Kind() == EXPRESSION_RESULT_KIND_PRIMITIVE
) {
277 Value
* primitive
= value
->PrimitiveValue();
278 BVariant variantValue
;
279 primitive
->ToVariant(variantValue
);
280 if (variantValue
.Type() == B_STRING_TYPE
) {
281 errorMessage
.SetTo(variantValue
.ToString());
283 _SetToAddress(variantValue
.ToUInt64());
288 status_t result
= message
->FindInt32("result");
289 errorMessage
.SetToFormat("Failed to evaluate expression: %s",
293 BAlert
* alert
= new(std::nothrow
) BAlert("Inspect Address",
294 errorMessage
.String(), "Close");
300 case MSG_NAVIGATE_PREVIOUS_BLOCK
:
301 case MSG_NAVIGATE_NEXT_BLOCK
:
303 if (fCurrentBlock
!= NULL
) {
304 target_addr_t address
= fCurrentBlock
->BaseAddress();
305 if (message
->what
== MSG_NAVIGATE_PREVIOUS_BLOCK
)
306 address
-= fCurrentBlock
->Size();
308 address
+= fCurrentBlock
->Size();
310 BMessage
setMessage(MSG_INSPECT_ADDRESS
);
311 setMessage
.AddUInt64("address", address
);
312 PostMessage(&setMessage
);
316 case MSG_MEMORY_BLOCK_RETRIEVED
:
318 TeamMemoryBlock
* block
= NULL
;
320 if (message
->FindPointer("block",
321 reinterpret_cast<void **>(&block
)) != B_OK
322 || message
->FindInt32("result", &result
) != B_OK
) {
326 if (result
== B_OK
) {
327 _SetCurrentBlock(block
);
328 fPreviousBlockButton
->SetEnabled(true);
329 fNextBlockButton
->SetEnabled(true);
331 BString errorMessage
;
332 errorMessage
.SetToFormat("Unable to read address 0x%" B_PRIx64
333 ": %s", block
->BaseAddress(), strerror(result
));
335 BAlert
* alert
= new(std::nothrow
) BAlert("Inspect address",
336 errorMessage
.String(), "Close");
341 block
->ReleaseReference();
345 case MSG_EDIT_CURRENT_BLOCK
:
350 case MSG_MEMORY_DATA_CHANGED
:
352 if (fCurrentBlock
== NULL
)
355 target_addr_t address
;
356 if (message
->FindUInt64("address", &address
) == B_OK
357 && address
>= fCurrentBlock
->BaseAddress()
358 && address
< fCurrentBlock
->BaseAddress()
359 + fCurrentBlock
->Size()) {
360 fCurrentBlock
->Invalidate();
362 fListener
->InspectRequested(address
, this);
366 case MSG_COMMIT_MODIFIED_BLOCK
:
368 // TODO: this could conceivably be extended to detect the
369 // individual modified regions and only write those back.
370 // That would require potentially submitting multiple separate
371 // write requests, and thus require tracking all the writes being
372 // waited upon for completion.
373 fListener
->MemoryWriteRequested(fCurrentBlock
->BaseAddress(),
374 fMemoryView
->GetEditedData(), fCurrentBlock
->Size());
377 case MSG_REVERT_MODIFIED_BLOCK
:
384 BWindow::MessageReceived(message
);
392 InspectorWindow::QuitRequested()
394 BMessage
settings(MSG_INSPECTOR_WINDOW_CLOSED
);
395 SaveSettings(settings
);
397 BMessenger(fTarget
).SendMessage(&settings
);
403 InspectorWindow::ThreadStateChanged(const Team::ThreadEvent
& event
)
405 BMessage
message(MSG_THREAD_STATE_CHANGED
);
406 BReference
< ::Thread
> threadReference(event
.GetThread());
407 message
.AddPointer("thread", threadReference
.Get());
409 if (PostMessage(&message
) == B_OK
)
410 threadReference
.Detach();
415 InspectorWindow::MemoryChanged(const Team::MemoryChangedEvent
& event
)
417 BMessage
message(MSG_MEMORY_DATA_CHANGED
);
418 message
.AddUInt64("address", event
.GetTargetAddress());
419 message
.AddUInt64("size", event
.GetSize());
421 PostMessage(&message
);
426 InspectorWindow::MemoryBlockRetrieved(TeamMemoryBlock
* block
)
428 BMessage
message(MSG_MEMORY_BLOCK_RETRIEVED
);
429 message
.AddPointer("block", block
);
430 message
.AddInt32("result", B_OK
);
431 PostMessage(&message
);
436 InspectorWindow::MemoryBlockRetrievalFailed(TeamMemoryBlock
* block
,
439 BMessage
message(MSG_MEMORY_BLOCK_RETRIEVED
);
440 message
.AddPointer("block", block
);
441 message
.AddInt32("result", result
);
442 PostMessage(&message
);
447 InspectorWindow::TargetAddressChanged(target_addr_t address
)
449 AutoLocker
<BLooper
> lock(this);
450 if (lock
.IsLocked()) {
451 fCurrentAddress
= address
;
452 BString computedAddress
;
453 computedAddress
.SetToFormat("0x%" B_PRIx64
, address
);
454 fAddressInput
->SetText(computedAddress
.String());
460 InspectorWindow::HexModeChanged(int32 newMode
)
462 AutoLocker
<BLooper
> lock(this);
463 if (lock
.IsLocked()) {
464 BMenu
* menu
= fHexMode
->Menu();
465 if (newMode
< 0 || newMode
> menu
->CountItems())
467 BMenuItem
* item
= menu
->ItemAt(newMode
);
468 item
->SetMarked(true);
474 InspectorWindow::EndianModeChanged(int32 newMode
)
476 AutoLocker
<BLooper
> lock(this);
477 if (lock
.IsLocked()) {
478 BMenu
* menu
= fEndianMode
->Menu();
479 if (newMode
< 0 || newMode
> menu
->CountItems())
481 BMenuItem
* item
= menu
->ItemAt(newMode
);
482 item
->SetMarked(true);
488 InspectorWindow::TextModeChanged(int32 newMode
)
490 AutoLocker
<BLooper
> lock(this);
491 if (lock
.IsLocked()) {
492 BMenu
* menu
= fTextMode
->Menu();
493 if (newMode
< 0 || newMode
> menu
->CountItems())
495 BMenuItem
* item
= menu
->ItemAt(newMode
);
496 item
->SetMarked(true);
502 InspectorWindow::ExpressionEvaluated(ExpressionInfo
* info
, status_t result
,
503 ExpressionResult
* value
)
505 BMessage
message(MSG_EXPRESSION_EVALUATED
);
506 message
.AddInt32("result", result
);
507 BReference
<ExpressionResult
> reference
;
509 reference
.SetTo(value
);
510 message
.AddPointer("value", value
);
513 if (PostMessage(&message
) == B_OK
)
519 InspectorWindow::LoadSettings(const GuiTeamUiSettings
& settings
)
521 AutoLocker
<BLooper
> lock(this);
522 if (!lock
.IsLocked())
525 BMessage inspectorSettings
;
526 if (settings
.Settings("inspectorWindow", inspectorSettings
) != B_OK
)
530 if (inspectorSettings
.FindRect("frame", &frameRect
) == B_OK
) {
531 ResizeTo(frameRect
.Width(), frameRect
.Height());
532 MoveTo(frameRect
.left
, frameRect
.top
);
535 _LoadMenuFieldMode(fHexMode
, "Hex", inspectorSettings
);
536 _LoadMenuFieldMode(fEndianMode
, "Endian", inspectorSettings
);
537 _LoadMenuFieldMode(fTextMode
, "Text", inspectorSettings
);
544 InspectorWindow::SaveSettings(BMessage
& settings
)
546 AutoLocker
<BLooper
> lock(this);
547 if (!lock
.IsLocked())
550 settings
.MakeEmpty();
552 status_t error
= settings
.AddRect("frame", Frame());
556 error
= _SaveMenuFieldMode(fHexMode
, "Hex", settings
);
560 error
= _SaveMenuFieldMode(fEndianMode
, "Endian", settings
);
564 error
= _SaveMenuFieldMode(fTextMode
, "Text", settings
);
573 InspectorWindow::_LoadMenuFieldMode(BMenuField
* field
, const char* name
,
574 const BMessage
& settings
)
578 fieldName
.SetToFormat("%sMode", name
);
579 if (settings
.FindInt32(fieldName
.String(), &mode
) == B_OK
) {
580 BMenu
* menu
= field
->Menu();
581 for (int32 i
= 0; i
< menu
->CountItems(); i
++) {
582 BInvoker
* item
= menu
->ItemAt(i
);
583 if (item
->Message()->FindInt32("mode") == mode
) {
593 InspectorWindow::_SaveMenuFieldMode(BMenuField
* field
, const char* name
,
596 BMenuItem
* item
= field
->Menu()->FindMarked();
597 if (item
&& item
->Message()) {
598 int32 mode
= item
->Message()->FindInt32("mode");
600 fieldName
.SetToFormat("%sMode", name
);
601 return settings
.AddInt32(fieldName
.String(), mode
);
609 InspectorWindow::_SetToAddress(target_addr_t address
)
611 fCurrentAddress
= address
;
612 if (fCurrentBlock
== NULL
613 || !fCurrentBlock
->Contains(address
)) {
614 fListener
->InspectRequested(address
, this);
616 fMemoryView
->SetTargetAddress(fCurrentBlock
, address
);
621 InspectorWindow::_SetCurrentBlock(TeamMemoryBlock
* block
)
623 AutoLocker
< ::Team
> teamLocker(fTeam
);
624 if (fCurrentBlock
!= NULL
) {
625 fCurrentBlock
->RemoveListener(this);
626 fCurrentBlock
->ReleaseReference();
629 fCurrentBlock
= block
;
630 fMemoryView
->SetTargetAddress(fCurrentBlock
, fCurrentAddress
);
631 _UpdateWritableOptions();
636 InspectorWindow::_GetWritableState() const
638 return fCurrentBlock
!= NULL
? fCurrentBlock
->IsWritable() : false;
643 InspectorWindow::_SetEditMode(bool enabled
)
645 if (enabled
== fMemoryView
->GetEditMode())
648 status_t error
= fMemoryView
->SetEditMode(enabled
);
653 fEditBlockButton
->Hide();
654 fCommitBlockButton
->Show();
655 fRevertBlockButton
->Show();
657 fEditBlockButton
->Show();
658 fCommitBlockButton
->Hide();
659 fRevertBlockButton
->Hide();
662 fHexMode
->SetEnabled(!enabled
);
663 fEndianMode
->SetEnabled(!enabled
);
665 // while the block is being edited, disable block navigation controls.
666 fAddressInput
->SetEnabled(!enabled
);
667 fPreviousBlockButton
->SetEnabled(!enabled
);
668 fNextBlockButton
->SetEnabled(!enabled
);
675 InspectorWindow::_UpdateWritableOptions()
677 fEditBlockButton
->SetEnabled(_GetWritableState());
678 _UpdateWritableIndicator();
683 InspectorWindow::_UpdateWritableIndicator()
685 fWritableBlockIndicator
->SetText(_GetCurrentWritableIndicator());
690 InspectorWindow::_GetCurrentWritableIndicator() const
692 static char buffer
[32];
693 snprintf(buffer
, sizeof(buffer
), "Writable: %s", fCurrentBlock
== NULL
694 ? "N/A" : fCurrentBlock
->IsWritable() ? "Yes" : "No");