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