vfs: check userland buffers before reading them.
[haiku.git] / src / apps / debugger / user_interface / gui / inspector_window / InspectorWindow.cpp
blob4f43ef9981da5d16dde4443055544a2335c2374d
1 /*
2 * Copyright 2011-2016, Rene Gollent, rene@gollent.com. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
6 #include "InspectorWindow.h"
8 #include <stdio.h>
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>
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"
31 enum {
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,
42 BHandler* target)
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)
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,
83 BHandler* target)
85 InspectorWindow* self = new InspectorWindow(team, listener, target);
87 try {
88 self->_Init();
89 } catch (...) {
90 delete self;
91 throw;
94 return self;
98 void
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)))
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();
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);
221 else
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));
233 void
234 InspectorWindow::MessageReceived(BMessage* message)
236 switch (message->what) {
237 case MSG_THREAD_STATE_CHANGED:
239 ::Thread* thread;
240 if (message->FindPointer("thread",
241 reinterpret_cast<void**>(&thread)) != B_OK) {
242 break;
245 BReference< ::Thread> threadReference(thread, true);
246 if (thread->State() == THREAD_STATE_STOPPED) {
247 if (fCurrentBlock != NULL) {
248 _SetCurrentBlock(NULL);
249 _SetToAddress(fCurrentAddress);
252 break;
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)
259 break;
261 fExpressionInfo->SetTo(fAddressInput->Text());
263 fListener->ExpressionEvaluationRequested(fLanguage,
264 fExpressionInfo);
265 } else
266 _SetToAddress(address);
267 break;
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());
283 } else {
284 _SetToAddress(variantValue.ToUInt64());
285 break;
288 } else {
289 status_t result = message->FindInt32("result");
290 errorMessage.SetToFormat("Failed to evaluate expression: %s",
291 strerror(result));
294 BAlert* alert = new(std::nothrow) BAlert("Inspect Address",
295 errorMessage.String(), "Close");
296 if (alert != NULL)
297 alert->Go();
298 break;
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();
308 else
309 address += fCurrentBlock->Size();
311 BMessage setMessage(MSG_INSPECT_ADDRESS);
312 setMessage.AddUInt64("address", address);
313 PostMessage(&setMessage);
315 break;
317 case MSG_MEMORY_BLOCK_RETRIEVED:
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;
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));
336 BAlert* alert = new(std::nothrow) BAlert("Inspect address",
337 errorMessage.String(), "Close");
338 if (alert == NULL)
339 break;
341 alert->Go(NULL);
342 block->ReleaseReference();
344 break;
346 case MSG_EDIT_CURRENT_BLOCK:
348 _SetEditMode(true);
349 break;
351 case MSG_MEMORY_DATA_CHANGED:
353 if (fCurrentBlock == NULL)
354 break;
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);
365 break;
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());
376 break;
378 case MSG_REVERT_MODIFIED_BLOCK:
380 _SetEditMode(false);
381 break;
383 default:
385 BWindow::MessageReceived(message);
386 break;
392 bool
393 InspectorWindow::QuitRequested()
395 BMessage settings(MSG_INSPECTOR_WINDOW_CLOSED);
396 SaveSettings(settings);
398 BMessenger(fTarget).SendMessage(&settings);
399 return true;
403 void
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();
415 void
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);
426 void
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);
436 void
437 InspectorWindow::MemoryBlockRetrievalFailed(TeamMemoryBlock* block,
438 status_t result)
440 BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
441 message.AddPointer("block", block);
442 message.AddInt32("result", result);
443 PostMessage(&message);
447 void
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());
460 void
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())
467 return;
468 BMenuItem* item = menu->ItemAt(newMode);
469 item->SetMarked(true);
474 void
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())
481 return;
482 BMenuItem* item = menu->ItemAt(newMode);
483 item->SetMarked(true);
488 void
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())
495 return;
496 BMenuItem* item = menu->ItemAt(newMode);
497 item->SetMarked(true);
502 void
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;
509 if (value != NULL) {
510 reference.SetTo(value);
511 message.AddPointer("value", value);
514 if (PostMessage(&message) == B_OK)
515 reference.Detach();
519 status_t
520 InspectorWindow::LoadSettings(const GuiTeamUiSettings& settings)
522 AutoLocker<BLooper> lock(this);
523 if (!lock.IsLocked())
524 return B_ERROR;
526 BMessage inspectorSettings;
527 if (settings.Settings("inspectorWindow", inspectorSettings) != B_OK)
528 return B_OK;
530 BRect frameRect;
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);
540 return B_OK;
544 status_t
545 InspectorWindow::SaveSettings(BMessage& settings)
547 AutoLocker<BLooper> lock(this);
548 if (!lock.IsLocked())
549 return B_ERROR;
551 settings.MakeEmpty();
553 status_t error = settings.AddRect("frame", Frame());
554 if (error != B_OK)
555 return error;
557 error = _SaveMenuFieldMode(fHexMode, "Hex", settings);
558 if (error != B_OK)
559 return error;
561 error = _SaveMenuFieldMode(fEndianMode, "Endian", settings);
562 if (error != B_OK)
563 return error;
565 error = _SaveMenuFieldMode(fTextMode, "Text", settings);
566 if (error != B_OK)
567 return error;
569 return B_OK;
573 void
574 InspectorWindow::_LoadMenuFieldMode(BMenuField* field, const char* name,
575 const BMessage& settings)
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;
593 status_t
594 InspectorWindow::_SaveMenuFieldMode(BMenuField* field, const char* name,
595 BMessage& settings)
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);
605 return B_OK;
609 void
610 InspectorWindow::_SetToAddress(target_addr_t address)
612 fCurrentAddress = address;
613 if (fCurrentBlock == NULL
614 || !fCurrentBlock->Contains(address)) {
615 fListener->InspectRequested(address, this);
616 } else
617 fMemoryView->SetTargetAddress(fCurrentBlock, address);
621 void
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();
636 bool
637 InspectorWindow::_GetWritableState() const
639 return fCurrentBlock != NULL ? fCurrentBlock->IsWritable() : false;
643 void
644 InspectorWindow::_SetEditMode(bool enabled)
646 if (enabled == fMemoryView->GetEditMode())
647 return;
649 status_t error = fMemoryView->SetEditMode(enabled);
650 if (error != B_OK)
651 return;
653 if (enabled) {
654 fEditBlockButton->Hide();
655 fCommitBlockButton->Show();
656 fRevertBlockButton->Show();
657 } else {
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);
671 InvalidateLayout();
675 void
676 InspectorWindow::_UpdateWritableOptions()
678 fEditBlockButton->SetEnabled(_GetWritableState());
679 _UpdateWritableIndicator();
683 void
684 InspectorWindow::_UpdateWritableIndicator()
686 fWritableBlockIndicator->SetText(_GetCurrentWritableIndicator());
690 const char*
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");
697 return buffer;