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 "AppMessageCodes.h"
21 #include "Architecture.h"
22 #include "CppLanguage.h"
23 #include "GuiTeamUiSettings.h"
24 #include "MemoryView.h"
25 #include "MessageCodes.h"
27 #include "UserInterface.h"
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'
41 InspectorWindow::InspectorWindow(::Team
* team
, UserInterfaceListener
* listener
,
44 BWindow(BRect(100, 100, 700, 500), "Inspector", B_TITLED_WINDOW
,
45 B_ASYNCHRONOUS_CONTROLS
| B_AUTO_UPDATE_SIZE_LIMITS
),
50 fWritableBlockIndicator(NULL
),
56 fExpressionInfo(NULL
),
59 AutoLocker
< ::Team
> teamLocker(fTeam
);
60 fTeam
->AddListener(this);
64 InspectorWindow::~InspectorWindow()
66 _SetCurrentBlock(NULL
);
68 if (fLanguage
!= NULL
)
69 fLanguage
->ReleaseReference();
71 if (fExpressionInfo
!= NULL
) {
72 fExpressionInfo
->RemoveListener(this);
73 fExpressionInfo
->ReleaseReference();
76 AutoLocker
< ::Team
> teamLocker(fTeam
);
77 fTeam
->RemoveListener(this);
81 /* static */ InspectorWindow
*
82 InspectorWindow::Create(::Team
* team
, UserInterfaceListener
* listener
,
85 InspectorWindow
* self
= new InspectorWindow(team
, listener
, target
);
99 InspectorWindow::_Init()
101 fLanguage
= new CppLanguage();
102 fExpressionInfo
= new ExpressionInfo();
103 fExpressionInfo
->AddListener(this);
105 BScrollView
* scrollView
;
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
);
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
);
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
);
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
)))
160 .AddGroup(B_HORIZONTAL
)
161 .Add(fHexMode
= new BMenuField("hexMode", "Hex Mode:",
164 .Add(fEndianMode
= new BMenuField("endianMode", "Endian Mode:",
167 .Add(fTextMode
= new BMenuField("viewMode", "Text Mode:",
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()))
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
)))
185 fHexMode
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
186 fEndianMode
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
187 fTextMode
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
189 int32 targetEndian
= fTeam
->GetArchitecture()->IsBigEndian()
190 ? EndianModeBigEndian
: EndianModeLittleEndian
;
192 scrollView
->SetTarget(fMemoryView
= MemoryView::Create(fTeam
, this));
194 fAddressInput
->SetTarget(this);
195 fPreviousBlockButton
->SetTarget(this);
196 fNextBlockButton
->SetTarget(this);
197 fPreviousBlockButton
->SetEnabled(false);
198 fNextBlockButton
->SetEnabled(false);
200 fEditBlockButton
->SetTarget(this);
201 fCommitBlockButton
->SetTarget(this);
202 fRevertBlockButton
->SetTarget(this);
204 fEditBlockButton
->SetEnabled(false);
205 fCommitBlockButton
->Hide();
206 fRevertBlockButton
->Hide();
208 hexMenu
->SetLabelFromMarked(true);
209 hexMenu
->SetTargetForItems(fMemoryView
);
210 endianMenu
->SetLabelFromMarked(true);
211 endianMenu
->SetTargetForItems(fMemoryView
);
212 textMenu
->SetLabelFromMarked(true);
213 textMenu
->SetTargetForItems(fMemoryView
);
215 // default to 8-bit format w/ text display
216 hexMenu
->ItemAt(1)->SetMarked(true);
217 textMenu
->ItemAt(1)->SetMarked(true);
219 if (targetEndian
== EndianModeBigEndian
)
220 endianMenu
->ItemAt(1)->SetMarked(true);
222 endianMenu
->ItemAt(0)->SetMarked(true);
224 fAddressInput
->TextView()->MakeFocus(true);
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
));
234 InspectorWindow::MessageReceived(BMessage
* message
)
236 switch (message
->what
) {
237 case MSG_THREAD_STATE_CHANGED
:
240 if (message
->FindPointer("thread",
241 reinterpret_cast<void**>(&thread
)) != B_OK
) {
245 BReference
< ::Thread
> threadReference(thread
, true);
246 if (thread
->State() == THREAD_STATE_STOPPED
) {
247 if (fCurrentBlock
!= NULL
) {
248 _SetCurrentBlock(NULL
);
249 _SetToAddress(fCurrentAddress
);
254 case MSG_INSPECT_ADDRESS
:
256 target_addr_t address
= 0;
257 if (message
->FindUInt64("address", &address
) != B_OK
) {
258 if (fAddressInput
->TextView()->TextLength() == 0)
261 fExpressionInfo
->SetTo(fAddressInput
->Text());
263 fListener
->ExpressionEvaluationRequested(fLanguage
,
266 _SetToAddress(address
);
269 case MSG_EXPRESSION_EVALUATED
:
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());
284 _SetToAddress(variantValue
.ToUInt64());
289 status_t result
= message
->FindInt32("result");
290 errorMessage
.SetToFormat("Failed to evaluate expression: %s",
294 BAlert
* alert
= new(std::nothrow
) BAlert("Inspect Address",
295 errorMessage
.String(), "Close");
301 case MSG_NAVIGATE_PREVIOUS_BLOCK
:
302 case MSG_NAVIGATE_NEXT_BLOCK
:
304 if (fCurrentBlock
!= NULL
) {
305 target_addr_t address
= fCurrentBlock
->BaseAddress();
306 if (message
->what
== MSG_NAVIGATE_PREVIOUS_BLOCK
)
307 address
-= fCurrentBlock
->Size();
309 address
+= fCurrentBlock
->Size();
311 BMessage
setMessage(MSG_INSPECT_ADDRESS
);
312 setMessage
.AddUInt64("address", address
);
313 PostMessage(&setMessage
);
317 case MSG_MEMORY_BLOCK_RETRIEVED
:
319 TeamMemoryBlock
* block
= NULL
;
321 if (message
->FindPointer("block",
322 reinterpret_cast<void **>(&block
)) != B_OK
323 || message
->FindInt32("result", &result
) != B_OK
) {
327 if (result
== B_OK
) {
328 _SetCurrentBlock(block
);
329 fPreviousBlockButton
->SetEnabled(true);
330 fNextBlockButton
->SetEnabled(true);
332 BString errorMessage
;
333 errorMessage
.SetToFormat("Unable to read address 0x%" B_PRIx64
334 ": %s", block
->BaseAddress(), strerror(result
));
336 BAlert
* alert
= new(std::nothrow
) BAlert("Inspect address",
337 errorMessage
.String(), "Close");
342 block
->ReleaseReference();
346 case MSG_EDIT_CURRENT_BLOCK
:
351 case MSG_MEMORY_DATA_CHANGED
:
353 if (fCurrentBlock
== NULL
)
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();
363 fListener
->InspectRequested(address
, this);
367 case MSG_COMMIT_MODIFIED_BLOCK
:
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());
378 case MSG_REVERT_MODIFIED_BLOCK
:
385 BWindow::MessageReceived(message
);
393 InspectorWindow::QuitRequested()
395 BMessage
settings(MSG_INSPECTOR_WINDOW_CLOSED
);
396 SaveSettings(settings
);
398 BMessenger(fTarget
).SendMessage(&settings
);
404 InspectorWindow::ThreadStateChanged(const Team::ThreadEvent
& event
)
406 BMessage
message(MSG_THREAD_STATE_CHANGED
);
407 BReference
< ::Thread
> threadReference(event
.GetThread());
408 message
.AddPointer("thread", threadReference
.Get());
410 if (PostMessage(&message
) == B_OK
)
411 threadReference
.Detach();
416 InspectorWindow::MemoryChanged(const Team::MemoryChangedEvent
& event
)
418 BMessage
message(MSG_MEMORY_DATA_CHANGED
);
419 message
.AddUInt64("address", event
.GetTargetAddress());
420 message
.AddUInt64("size", event
.GetSize());
422 PostMessage(&message
);
427 InspectorWindow::MemoryBlockRetrieved(TeamMemoryBlock
* block
)
429 BMessage
message(MSG_MEMORY_BLOCK_RETRIEVED
);
430 message
.AddPointer("block", block
);
431 message
.AddInt32("result", B_OK
);
432 PostMessage(&message
);
437 InspectorWindow::MemoryBlockRetrievalFailed(TeamMemoryBlock
* block
,
440 BMessage
message(MSG_MEMORY_BLOCK_RETRIEVED
);
441 message
.AddPointer("block", block
);
442 message
.AddInt32("result", result
);
443 PostMessage(&message
);
448 InspectorWindow::TargetAddressChanged(target_addr_t address
)
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());
461 InspectorWindow::HexModeChanged(int32 newMode
)
463 AutoLocker
<BLooper
> lock(this);
464 if (lock
.IsLocked()) {
465 BMenu
* menu
= fHexMode
->Menu();
466 if (newMode
< 0 || newMode
> menu
->CountItems())
468 BMenuItem
* item
= menu
->ItemAt(newMode
);
469 item
->SetMarked(true);
475 InspectorWindow::EndianModeChanged(int32 newMode
)
477 AutoLocker
<BLooper
> lock(this);
478 if (lock
.IsLocked()) {
479 BMenu
* menu
= fEndianMode
->Menu();
480 if (newMode
< 0 || newMode
> menu
->CountItems())
482 BMenuItem
* item
= menu
->ItemAt(newMode
);
483 item
->SetMarked(true);
489 InspectorWindow::TextModeChanged(int32 newMode
)
491 AutoLocker
<BLooper
> lock(this);
492 if (lock
.IsLocked()) {
493 BMenu
* menu
= fTextMode
->Menu();
494 if (newMode
< 0 || newMode
> menu
->CountItems())
496 BMenuItem
* item
= menu
->ItemAt(newMode
);
497 item
->SetMarked(true);
503 InspectorWindow::ExpressionEvaluated(ExpressionInfo
* info
, status_t result
,
504 ExpressionResult
* value
)
506 BMessage
message(MSG_EXPRESSION_EVALUATED
);
507 message
.AddInt32("result", result
);
508 BReference
<ExpressionResult
> reference
;
510 reference
.SetTo(value
);
511 message
.AddPointer("value", value
);
514 if (PostMessage(&message
) == B_OK
)
520 InspectorWindow::LoadSettings(const GuiTeamUiSettings
& settings
)
522 AutoLocker
<BLooper
> lock(this);
523 if (!lock
.IsLocked())
526 BMessage inspectorSettings
;
527 if (settings
.Settings("inspectorWindow", inspectorSettings
) != B_OK
)
531 if (inspectorSettings
.FindRect("frame", &frameRect
) == B_OK
) {
532 ResizeTo(frameRect
.Width(), frameRect
.Height());
533 MoveTo(frameRect
.left
, frameRect
.top
);
536 _LoadMenuFieldMode(fHexMode
, "Hex", inspectorSettings
);
537 _LoadMenuFieldMode(fEndianMode
, "Endian", inspectorSettings
);
538 _LoadMenuFieldMode(fTextMode
, "Text", inspectorSettings
);
545 InspectorWindow::SaveSettings(BMessage
& settings
)
547 AutoLocker
<BLooper
> lock(this);
548 if (!lock
.IsLocked())
551 settings
.MakeEmpty();
553 status_t error
= settings
.AddRect("frame", Frame());
557 error
= _SaveMenuFieldMode(fHexMode
, "Hex", settings
);
561 error
= _SaveMenuFieldMode(fEndianMode
, "Endian", settings
);
565 error
= _SaveMenuFieldMode(fTextMode
, "Text", settings
);
574 InspectorWindow::_LoadMenuFieldMode(BMenuField
* field
, const char* name
,
575 const BMessage
& settings
)
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
) {
594 InspectorWindow::_SaveMenuFieldMode(BMenuField
* field
, const char* name
,
597 BMenuItem
* item
= field
->Menu()->FindMarked();
598 if (item
&& item
->Message()) {
599 int32 mode
= item
->Message()->FindInt32("mode");
601 fieldName
.SetToFormat("%sMode", name
);
602 return settings
.AddInt32(fieldName
.String(), mode
);
610 InspectorWindow::_SetToAddress(target_addr_t address
)
612 fCurrentAddress
= address
;
613 if (fCurrentBlock
== NULL
614 || !fCurrentBlock
->Contains(address
)) {
615 fListener
->InspectRequested(address
, this);
617 fMemoryView
->SetTargetAddress(fCurrentBlock
, address
);
622 InspectorWindow::_SetCurrentBlock(TeamMemoryBlock
* block
)
624 AutoLocker
< ::Team
> teamLocker(fTeam
);
625 if (fCurrentBlock
!= NULL
) {
626 fCurrentBlock
->RemoveListener(this);
627 fCurrentBlock
->ReleaseReference();
630 fCurrentBlock
= block
;
631 fMemoryView
->SetTargetAddress(fCurrentBlock
, fCurrentAddress
);
632 _UpdateWritableOptions();
637 InspectorWindow::_GetWritableState() const
639 return fCurrentBlock
!= NULL
? fCurrentBlock
->IsWritable() : false;
644 InspectorWindow::_SetEditMode(bool enabled
)
646 if (enabled
== fMemoryView
->GetEditMode())
649 status_t error
= fMemoryView
->SetEditMode(enabled
);
654 fEditBlockButton
->Hide();
655 fCommitBlockButton
->Show();
656 fRevertBlockButton
->Show();
658 fEditBlockButton
->Show();
659 fCommitBlockButton
->Hide();
660 fRevertBlockButton
->Hide();
663 fHexMode
->SetEnabled(!enabled
);
664 fEndianMode
->SetEnabled(!enabled
);
666 // while the block is being edited, disable block navigation controls.
667 fAddressInput
->SetEnabled(!enabled
);
668 fPreviousBlockButton
->SetEnabled(!enabled
);
669 fNextBlockButton
->SetEnabled(!enabled
);
676 InspectorWindow::_UpdateWritableOptions()
678 fEditBlockButton
->SetEnabled(_GetWritableState());
679 _UpdateWritableIndicator();
684 InspectorWindow::_UpdateWritableIndicator()
686 fWritableBlockIndicator
->SetText(_GetCurrentWritableIndicator());
691 InspectorWindow::_GetCurrentWritableIndicator() const
693 static char buffer
[32];
694 snprintf(buffer
, sizeof(buffer
), "Writable: %s", fCurrentBlock
== NULL
695 ? "N/A" : fCurrentBlock
->IsWritable() ? "Yes" : "No");