/*
* Copyright 2011-2016, Rene Gollent, rene@gollent.com. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "InspectorWindow.h"
#include <stdio.h>
#include <Alert.h>
#include <Application.h>
#include <AutoLocker.h>
#include <Button.h>
#include <ControlLook.h>
#include <LayoutBuilder.h>
#include <ScrollView.h>
#include <StringView.h>
#include <TextControl.h>
#include "AppMessageCodes.h"
#include "Architecture.h"
#include "CppLanguage.h"
#include "GuiTeamUiSettings.h"
#include "MemoryView.h"
#include "MessageCodes.h"
#include "Team.h"
#include "UserInterface.h"
#include "Value.h"
enum {
MSG_NAVIGATE_PREVIOUS_BLOCK = 'npbl',
MSG_NAVIGATE_NEXT_BLOCK = 'npnl',
MSG_MEMORY_BLOCK_RETRIEVED = 'mbre',
MSG_EDIT_CURRENT_BLOCK = 'mecb',
MSG_COMMIT_MODIFIED_BLOCK = 'mcmb',
MSG_REVERT_MODIFIED_BLOCK = 'mrmb'
};
InspectorWindow::InspectorWindow(::Team* team, UserInterfaceListener* listener,
BHandler* target)
:
BWindow(BRect(100, 100, 700, 500), "Inspector", B_TITLED_WINDOW,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
fListener(listener),
fAddressInput(NULL),
fHexMode(NULL),
fTextMode(NULL),
fWritableBlockIndicator(NULL),
fMemoryView(NULL),
fCurrentBlock(NULL),
fCurrentAddress(0LL),
fTeam(team),
fLanguage(NULL),
fExpressionInfo(NULL),
fTarget(target)
{
AutoLocker< ::Team> teamLocker(fTeam);
fTeam->AddListener(this);
}
InspectorWindow::~InspectorWindow()
{
_SetCurrentBlock(NULL);
if (fLanguage != NULL)
fLanguage->ReleaseReference();
if (fExpressionInfo != NULL) {
fExpressionInfo->RemoveListener(this);
fExpressionInfo->ReleaseReference();
}
AutoLocker< ::Team> teamLocker(fTeam);
fTeam->RemoveListener(this);
}
/* static */ InspectorWindow*
InspectorWindow::Create(::Team* team, UserInterfaceListener* listener,
BHandler* target)
{
InspectorWindow* self = new InspectorWindow(team, listener, target);
try {
self->_Init();
} catch (...) {
delete self;
throw;
}
return self;
}
void
InspectorWindow::_Init()
{
fLanguage = new CppLanguage();
fExpressionInfo = new ExpressionInfo();
fExpressionInfo->AddListener(this);
BScrollView* scrollView;
BMenu* hexMenu = new BMenu("Hex Mode");
BMessage* message = new BMessage(MSG_SET_HEX_MODE);
message->AddInt32("mode", HexModeNone);
BMenuItem* item = new BMenuItem("<None>", message, '0');
hexMenu->AddItem(item);
message = new BMessage(*message);
message->ReplaceInt32("mode", HexMode8BitInt);
item = new BMenuItem("8-bit integer", message, '1');
hexMenu->AddItem(item);
message = new BMessage(*message);
message->ReplaceInt32("mode", HexMode16BitInt);
item = new BMenuItem("16-bit integer", message, '2');
hexMenu->AddItem(item);
message = new BMessage(*message);
message->ReplaceInt32("mode", HexMode32BitInt);
item = new BMenuItem("32-bit integer", message, '3');
hexMenu->AddItem(item);
message = new BMessage(*message);
message->ReplaceInt32("mode", HexMode64BitInt);
item = new BMenuItem("64-bit integer", message, '4');
hexMenu->AddItem(item);
BMenu* endianMenu = new BMenu("Endian Mode");
message = new BMessage(MSG_SET_ENDIAN_MODE);
message->AddInt32("mode", EndianModeLittleEndian);
item = new BMenuItem("Little Endian", message, 'L');
endianMenu->AddItem(item);
message = new BMessage(*message);
message->ReplaceInt32("mode", EndianModeBigEndian);
item = new BMenuItem("Big Endian", message, 'B');
endianMenu->AddItem(item);
BMenu* textMenu = new BMenu("Text Mode");
message = new BMessage(MSG_SET_TEXT_MODE);
message->AddInt32("mode", TextModeNone);
item = new BMenuItem("<None>", message, 'N');
textMenu->AddItem(item);
message = new BMessage(*message);
message->ReplaceInt32("mode", TextModeASCII);
item = new BMenuItem("ASCII", message, 'A');
textMenu->AddItem(item);
BLayoutBuilder::Group<>(this, B_VERTICAL)
.SetInsets(B_USE_DEFAULT_SPACING)
.AddGroup(B_HORIZONTAL)
.Add(fAddressInput = new BTextControl("addrInput",
"Target Address:", "",
new BMessage(MSG_INSPECT_ADDRESS)))
.Add(fPreviousBlockButton = new BButton("navPrevious", "<",
new BMessage(MSG_NAVIGATE_PREVIOUS_BLOCK)))
.Add(fNextBlockButton = new BButton("navNext", ">",
new BMessage(MSG_NAVIGATE_NEXT_BLOCK)))
.End()
.AddGroup(B_HORIZONTAL)
.Add(fHexMode = new BMenuField("hexMode", "Hex Mode:",
hexMenu))
.AddGlue()
.Add(fEndianMode = new BMenuField("endianMode", "Endian Mode:",
endianMenu))
.AddGlue()
.Add(fTextMode = new BMenuField("viewMode", "Text Mode:",
textMenu))
.End()
.Add(scrollView = new BScrollView("memory scroll",
NULL, 0, false, true), 3.0f)
.AddGroup(B_HORIZONTAL)
.Add(fWritableBlockIndicator = new BStringView("writableIndicator",
_GetCurrentWritableIndicator()))
.AddGlue()
.Add(fEditBlockButton = new BButton("editBlock", "Edit",
new BMessage(MSG_EDIT_CURRENT_BLOCK)))
.Add(fCommitBlockButton = new BButton("commitBlock", "Commit",
new BMessage(MSG_COMMIT_MODIFIED_BLOCK)))
.Add(fRevertBlockButton = new BButton("revertBlock", "Revert",
new BMessage(MSG_REVERT_MODIFIED_BLOCK)))
.End()
.End();
fHexMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
fEndianMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
fTextMode->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
int32 targetEndian = fTeam->GetArchitecture()->IsBigEndian()
? EndianModeBigEndian : EndianModeLittleEndian;
scrollView->SetTarget(fMemoryView = MemoryView::Create(fTeam, this));
fAddressInput->SetTarget(this);
fPreviousBlockButton->SetTarget(this);
fNextBlockButton->SetTarget(this);
fPreviousBlockButton->SetEnabled(false);
fNextBlockButton->SetEnabled(false);
fEditBlockButton->SetTarget(this);
fCommitBlockButton->SetTarget(this);
fRevertBlockButton->SetTarget(this);
fEditBlockButton->SetEnabled(false);
fCommitBlockButton->Hide();
fRevertBlockButton->Hide();
hexMenu->SetLabelFromMarked(true);
hexMenu->SetTargetForItems(fMemoryView);
endianMenu->SetLabelFromMarked(true);
endianMenu->SetTargetForItems(fMemoryView);
textMenu->SetLabelFromMarked(true);
textMenu->SetTargetForItems(fMemoryView);
// default to 8-bit format w/ text display
hexMenu->ItemAt(1)->SetMarked(true);
textMenu->ItemAt(1)->SetMarked(true);
if (targetEndian == EndianModeBigEndian)
endianMenu->ItemAt(1)->SetMarked(true);
else
endianMenu->ItemAt(0)->SetMarked(true);
fAddressInput->TextView()->MakeFocus(true);
AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, new BMessage(
MSG_NAVIGATE_PREVIOUS_BLOCK));
AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, new BMessage(
MSG_NAVIGATE_NEXT_BLOCK));
}
void
InspectorWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_THREAD_STATE_CHANGED:
{
::Thread* thread;
if (message->FindPointer("thread",
reinterpret_cast<void**>(&thread)) != B_OK) {
break;
}
BReference< ::Thread> threadReference(thread, true);
if (thread->State() == THREAD_STATE_STOPPED) {
if (fCurrentBlock != NULL) {
_SetCurrentBlock(NULL);
_SetToAddress(fCurrentAddress);
}
}
break;
}
case MSG_INSPECT_ADDRESS:
{
target_addr_t address = 0;
if (message->FindUInt64("address", &address) != B_OK) {
if (fAddressInput->TextView()->TextLength() == 0)
break;
fExpressionInfo->SetTo(fAddressInput->Text());
fListener->ExpressionEvaluationRequested(fLanguage,
fExpressionInfo);
} else
_SetToAddress(address);
break;
}
case MSG_EXPRESSION_EVALUATED:
{
BString errorMessage;
BReference<ExpressionResult> reference;
ExpressionResult* value = NULL;
if (message->FindPointer("value",
reinterpret_cast<void**>(&value)) == B_OK) {
reference.SetTo(value, true);
if (value->Kind() == EXPRESSION_RESULT_KIND_PRIMITIVE) {
Value* primitive = value->PrimitiveValue();
BVariant variantValue;
primitive->ToVariant(variantValue);
if (variantValue.Type() == B_STRING_TYPE) {
errorMessage.SetTo(variantValue.ToString());
} else {
_SetToAddress(variantValue.ToUInt64());
break;
}
}
} else {
status_t result = message->FindInt32("result");
errorMessage.SetToFormat("Failed to evaluate expression: %s",
strerror(result));
}
BAlert* alert = new(std::nothrow) BAlert("Inspect Address",
errorMessage.String(), "Close");
if (alert != NULL)
alert->Go();
break;
}
case MSG_NAVIGATE_PREVIOUS_BLOCK:
case MSG_NAVIGATE_NEXT_BLOCK:
{
if (fCurrentBlock != NULL) {
target_addr_t address = fCurrentBlock->BaseAddress();
if (message->what == MSG_NAVIGATE_PREVIOUS_BLOCK)
address -= fCurrentBlock->Size();
else
address += fCurrentBlock->Size();
BMessage setMessage(MSG_INSPECT_ADDRESS);
setMessage.AddUInt64("address", address);
PostMessage(&setMessage);
}
break;
}
case MSG_MEMORY_BLOCK_RETRIEVED:
{
TeamMemoryBlock* block = NULL;
status_t result;
if (message->FindPointer("block",
reinterpret_cast<void **>(&block)) != B_OK
|| message->FindInt32("result", &result) != B_OK) {
break;
}
if (result == B_OK) {
_SetCurrentBlock(block);
fPreviousBlockButton->SetEnabled(true);
fNextBlockButton->SetEnabled(true);
} else {
BString errorMessage;
errorMessage.SetToFormat("Unable to read address 0x%" B_PRIx64
": %s", block->BaseAddress(), strerror(result));
BAlert* alert = new(std::nothrow) BAlert("Inspect address",
errorMessage.String(), "Close");
if (alert == NULL)
break;
alert->Go(NULL);
block->ReleaseReference();
}
break;
}
case MSG_EDIT_CURRENT_BLOCK:
{
_SetEditMode(true);
break;
}
case MSG_MEMORY_DATA_CHANGED:
{
if (fCurrentBlock == NULL)
break;
target_addr_t address;
if (message->FindUInt64("address", &address) == B_OK
&& address >= fCurrentBlock->BaseAddress()
&& address < fCurrentBlock->BaseAddress()
+ fCurrentBlock->Size()) {
fCurrentBlock->Invalidate();
_SetEditMode(false);
fListener->InspectRequested(address, this);
}
break;
}
case MSG_COMMIT_MODIFIED_BLOCK:
{
// TODO: this could conceivably be extended to detect the
// individual modified regions and only write those back.
// That would require potentially submitting multiple separate
// write requests, and thus require tracking all the writes being
// waited upon for completion.
fListener->MemoryWriteRequested(fCurrentBlock->BaseAddress(),
fMemoryView->GetEditedData(), fCurrentBlock->Size());
break;
}
case MSG_REVERT_MODIFIED_BLOCK:
{
_SetEditMode(false);
break;
}
default:
{
BWindow::MessageReceived(message);
break;
}
}
}
bool
InspectorWindow::QuitRequested()
{
BMessage settings(MSG_INSPECTOR_WINDOW_CLOSED);
SaveSettings(settings);
BMessenger(fTarget).SendMessage(&settings);
return true;
}
void
InspectorWindow::ThreadStateChanged(const Team::ThreadEvent& event)
{
BMessage message(MSG_THREAD_STATE_CHANGED);
BReference< ::Thread> threadReference(event.GetThread());
message.AddPointer("thread", threadReference.Get());
if (PostMessage(&message) == B_OK)
threadReference.Detach();
}
void
InspectorWindow::MemoryChanged(const Team::MemoryChangedEvent& event)
{
BMessage message(MSG_MEMORY_DATA_CHANGED);
message.AddUInt64("address", event.GetTargetAddress());
message.AddUInt64("size", event.GetSize());
PostMessage(&message);
}
void
InspectorWindow::MemoryBlockRetrieved(TeamMemoryBlock* block)
{
BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
message.AddPointer("block", block);
message.AddInt32("result", B_OK);
PostMessage(&message);
}
void
InspectorWindow::MemoryBlockRetrievalFailed(TeamMemoryBlock* block,
status_t result)
{
BMessage message(MSG_MEMORY_BLOCK_RETRIEVED);
message.AddPointer("block", block);
message.AddInt32("result", result);
PostMessage(&message);
}
void
InspectorWindow::TargetAddressChanged(target_addr_t address)
{
AutoLocker<BLooper> lock(this);
if (lock.IsLocked()) {
fCurrentAddress = address;
BString computedAddress;
computedAddress.SetToFormat("0x%" B_PRIx64, address);
fAddressInput->SetText(computedAddress.String());
}
}
void
InspectorWindow::HexModeChanged(int32 newMode)
{
AutoLocker<BLooper> lock(this);
if (lock.IsLocked()) {
BMenu* menu = fHexMode->Menu();
if (newMode < 0 || newMode > menu->CountItems())
return;
BMenuItem* item = menu->ItemAt(newMode);
item->SetMarked(true);
}
}
void
InspectorWindow::EndianModeChanged(int32 newMode)
{
AutoLocker<BLooper> lock(this);
if (lock.IsLocked()) {
BMenu* menu = fEndianMode->Menu();
if (newMode < 0 || newMode > menu->CountItems())
return;
BMenuItem* item = menu->ItemAt(newMode);
item->SetMarked(true);
}
}
void
InspectorWindow::TextModeChanged(int32 newMode)
{
AutoLocker<BLooper> lock(this);
if (lock.IsLocked()) {
BMenu* menu = fTextMode->Menu();
if (newMode < 0 || newMode > menu->CountItems())
return;
BMenuItem* item = menu->ItemAt(newMode);
item->SetMarked(true);
}
}
void
InspectorWindow::ExpressionEvaluated(ExpressionInfo* info, status_t result,
ExpressionResult* value)
{
BMessage message(MSG_EXPRESSION_EVALUATED);
message.AddInt32("result", result);
BReference<ExpressionResult> reference;
if (value != NULL) {
reference.SetTo(value);
message.AddPointer("value", value);
}
if (PostMessage(&message) == B_OK)
reference.Detach();
}
status_t
InspectorWindow::LoadSettings(const GuiTeamUiSettings& settings)
{
AutoLocker<BLooper> lock(this);
if (!lock.IsLocked())
return B_ERROR;
BMessage inspectorSettings;
if (settings.Settings("inspectorWindow", inspectorSettings) != B_OK)
return B_OK;
BRect frameRect;
if (inspectorSettings.FindRect("frame", &frameRect) == B_OK) {
ResizeTo(frameRect.Width(), frameRect.Height());
MoveTo(frameRect.left, frameRect.top);
}
_LoadMenuFieldMode(fHexMode, "Hex", inspectorSettings);
_LoadMenuFieldMode(fEndianMode, "Endian", inspectorSettings);
_LoadMenuFieldMode(fTextMode, "Text", inspectorSettings);
return B_OK;
}
status_t
InspectorWindow::SaveSettings(BMessage& settings)
{
AutoLocker<BLooper> lock(this);
if (!lock.IsLocked())
return B_ERROR;
settings.MakeEmpty();
status_t error = settings.AddRect("frame", Frame());
if (error != B_OK)
return error;
error = _SaveMenuFieldMode(fHexMode, "Hex", settings);
if (error != B_OK)
return error;
error = _SaveMenuFieldMode(fEndianMode, "Endian", settings);
if (error != B_OK)
return error;
error = _SaveMenuFieldMode(fTextMode, "Text", settings);
if (error != B_OK)
return error;
return B_OK;
}
void
InspectorWindow::_LoadMenuFieldMode(BMenuField* field, const char* name,
const BMessage& settings)
{
BString fieldName;
int32 mode;
fieldName.SetToFormat("%sMode", name);
if (settings.FindInt32(fieldName.String(), &mode) == B_OK) {
BMenu* menu = field->Menu();
for (int32 i = 0; i < menu->CountItems(); i++) {
BInvoker* item = menu->ItemAt(i);
if (item->Message()->FindInt32("mode") == mode) {
item->Invoke();
break;
}
}
}
}
status_t
InspectorWindow::_SaveMenuFieldMode(BMenuField* field, const char* name,
BMessage& settings)
{
BMenuItem* item = field->Menu()->FindMarked();
if (item && item->Message()) {
int32 mode = item->Message()->FindInt32("mode");
BString fieldName;
fieldName.SetToFormat("%sMode", name);
return settings.AddInt32(fieldName.String(), mode);
}
return B_OK;
}
void
InspectorWindow::_SetToAddress(target_addr_t address)
{
fCurrentAddress = address;
if (fCurrentBlock == NULL
|| !fCurrentBlock->Contains(address)) {
fListener->InspectRequested(address, this);
} else
fMemoryView->SetTargetAddress(fCurrentBlock, address);
}
void
InspectorWindow::_SetCurrentBlock(TeamMemoryBlock* block)
{
AutoLocker< ::Team> teamLocker(fTeam);
if (fCurrentBlock != NULL) {
fCurrentBlock->RemoveListener(this);
fCurrentBlock->ReleaseReference();
}
fCurrentBlock = block;
fMemoryView->SetTargetAddress(fCurrentBlock, fCurrentAddress);
_UpdateWritableOptions();
}
bool
InspectorWindow::_GetWritableState() const
{
return fCurrentBlock != NULL ? fCurrentBlock->IsWritable() : false;
}
void
InspectorWindow::_SetEditMode(bool enabled)
{
if (enabled == fMemoryView->GetEditMode())
return;
status_t error = fMemoryView->SetEditMode(enabled);
if (error != B_OK)
return;
if (enabled) {
fEditBlockButton->Hide();
fCommitBlockButton->Show();
fRevertBlockButton->Show();
} else {
fEditBlockButton->Show();
fCommitBlockButton->Hide();
fRevertBlockButton->Hide();
}
fHexMode->SetEnabled(!enabled);
fEndianMode->SetEnabled(!enabled);
// while the block is being edited, disable block navigation controls.
fAddressInput->SetEnabled(!enabled);
fPreviousBlockButton->SetEnabled(!enabled);
fNextBlockButton->SetEnabled(!enabled);
InvalidateLayout();
}
void
InspectorWindow::_UpdateWritableOptions()
{
fEditBlockButton->SetEnabled(_GetWritableState());
_UpdateWritableIndicator();
}
void
InspectorWindow::_UpdateWritableIndicator()
{
fWritableBlockIndicator->SetText(_GetCurrentWritableIndicator());
}
const char*
InspectorWindow::_GetCurrentWritableIndicator() const
{
static char buffer[32];
snprintf(buffer, sizeof(buffer), "Writable: %s", fCurrentBlock == NULL
? "N/A" : fCurrentBlock->IsWritable() ? "Yes" : "No");
return buffer;
}
↑ V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.