Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/ImDebugger/ImStructViewer.cpp
3185 views
1
#include <algorithm>
2
#include <regex>
3
#include <sstream>
4
#include <unordered_map>
5
#include <cctype>
6
7
#include "ext/imgui/imgui.h"
8
9
#include "Common/System/Request.h"
10
#include "Core/MemMap.h"
11
#include "Core/Debugger/Breakpoints.h"
12
#include "Core/MIPS/MIPSDebugInterface.h"
13
14
#include "UI/ImDebugger/ImStructViewer.h"
15
#include "UI/ImDebugger/ImDebugger.h"
16
17
static auto COLOR_GRAY = ImVec4(0.45f, 0.45f, 0.45f, 1);
18
static auto COLOR_RED = ImVec4(1, 0, 0, 1);
19
20
enum class BuiltInType {
21
Bool,
22
Char,
23
Int8,
24
Int16,
25
Int32,
26
Int64,
27
TerminatedString,
28
Float,
29
Void,
30
};
31
32
struct BuiltIn {
33
BuiltInType type;
34
ImGuiDataType imGuiType;
35
const char *hexFormat;
36
};
37
38
static const std::unordered_map<std::string, BuiltIn> knownBuiltIns = {
39
{"/bool", {BuiltInType::Bool, ImGuiDataType_U8, "%hhx"}},
40
41
{"/char", {BuiltInType::Char, ImGuiDataType_S8, "%hhx"}},
42
{"/uchar", {BuiltInType::Char, ImGuiDataType_U8, "%hhx"}},
43
44
{"/byte", {BuiltInType::Int8, ImGuiDataType_U8, "%hhx"}},
45
{"/sbyte", {BuiltInType::Int8, ImGuiDataType_S8, "%hhx"}},
46
{"/undefined1", {BuiltInType::Int8, ImGuiDataType_U8, "%hhx"}},
47
48
{"/word", {BuiltInType::Int16, ImGuiDataType_U16, "%hx"}},
49
{"/sword", {BuiltInType::Int16, ImGuiDataType_S16, "%hx"}},
50
{"/ushort", {BuiltInType::Int16, ImGuiDataType_U16, "%hx"}},
51
{"/short", {BuiltInType::Int16, ImGuiDataType_S16, "%hx"}},
52
{"/undefined2", {BuiltInType::Int16, ImGuiDataType_U16, "%hx"}},
53
54
{"/dword", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
55
{"/sdword", {BuiltInType::Int32, ImGuiDataType_S32, "%lx"}},
56
{"/uint", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
57
{"/int", {BuiltInType::Int32, ImGuiDataType_S32, "%lx"}},
58
{"/ulong", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
59
{"/long", {BuiltInType::Int32, ImGuiDataType_S32, "%lx"}},
60
{"/undefined4", {BuiltInType::Int32, ImGuiDataType_U32, "%lx"}},
61
62
{"/qword", {BuiltInType::Int64, ImGuiDataType_U64, "%llx"}},
63
{"/sqword", {BuiltInType::Int64, ImGuiDataType_S64, "%llx"}},
64
{"/ulonglong", {BuiltInType::Int64, ImGuiDataType_U64, "%llx"}},
65
{"/longlong", {BuiltInType::Int64, ImGuiDataType_S64, "%llx"}},
66
{"/undefined8", {BuiltInType::Int64, ImGuiDataType_U64, "%llx"}},
67
68
{"/TerminatedCString", {BuiltInType::TerminatedString, -1, nullptr}},
69
70
{"/float", {BuiltInType::Float, ImGuiDataType_Float, nullptr}},
71
{"/float4", {BuiltInType::Float, ImGuiDataType_Float, nullptr}},
72
73
{"/void", {BuiltInType::Void, -1, nullptr}},
74
};
75
76
static void DrawBuiltInEditPopup(const BuiltIn &builtIn, const u32 address) {
77
if (builtIn.imGuiType == -1) {
78
return;
79
}
80
ImGui::OpenPopupOnItemClick("edit", ImGuiPopupFlags_MouseButtonRight);
81
if (ImGui::BeginPopup("edit")) {
82
if (ImGui::Selectable("Set to zero")) {
83
switch (builtIn.type) {
84
case BuiltInType::Bool:
85
case BuiltInType::Char:
86
case BuiltInType::Int8:
87
Memory::Write_U8(0, address);
88
break;
89
case BuiltInType::Int16:
90
Memory::Write_U16(0, address);
91
break;
92
case BuiltInType::Int32:
93
Memory::Write_U32(0, address);
94
break;
95
case BuiltInType::Int64:
96
Memory::Write_U64(0, address);
97
break;
98
case BuiltInType::Float:
99
Memory::Write_Float(0, address);
100
break;
101
default:
102
break;
103
}
104
}
105
void *data = Memory::GetPointerWriteUnchecked(address);
106
if (builtIn.hexFormat) {
107
ImGui::DragScalar("Value (hex)", builtIn.imGuiType, data, 0.2f, nullptr, nullptr, builtIn.hexFormat);
108
}
109
ImGui::DragScalar("Value", builtIn.imGuiType, data, 0.2f);
110
ImGui::EndPopup();
111
}
112
}
113
114
static void DrawIntBuiltInEditPopup(const u32 address, const u32 length) {
115
switch (length) {
116
case 1:
117
DrawBuiltInEditPopup(knownBuiltIns.at("/byte"), address);
118
break;
119
case 2:
120
DrawBuiltInEditPopup(knownBuiltIns.at("/word"), address);
121
break;
122
case 4:
123
DrawBuiltInEditPopup(knownBuiltIns.at("/dword"), address);
124
break;
125
case 8:
126
DrawBuiltInEditPopup(knownBuiltIns.at("/qword"), address);
127
break;
128
default:
129
break;
130
}
131
}
132
133
static void DrawBuiltInContent(const BuiltIn &builtIn, const u32 address) {
134
switch (builtIn.type) {
135
case BuiltInType::Bool:
136
ImGui::Text("= %s", Memory::Read_U8(address) ? "true" : "false");
137
break;
138
case BuiltInType::Char: {
139
const u8 value = Memory::Read_U8(address);
140
if (std::isprint(value)) {
141
ImGui::Text("= %x '%c'", value, value);
142
} else {
143
ImGui::Text("= %x", value);
144
}
145
break;
146
}
147
case BuiltInType::Int8:
148
ImGui::Text("= %x", Memory::Read_U8(address));
149
break;
150
case BuiltInType::Int16:
151
ImGui::Text("= %x", Memory::Read_U16(address));
152
break;
153
case BuiltInType::Int32:
154
ImGui::Text("= %x", Memory::Read_U32(address));
155
break;
156
case BuiltInType::Int64:
157
ImGui::Text("= %llx", Memory::Read_U64(address));
158
break;
159
case BuiltInType::TerminatedString:
160
if (Memory::IsValidNullTerminatedString(address)) {
161
ImGui::Text("= \"%s\"", Memory::GetCharPointerUnchecked(address));
162
} else {
163
ImGui::Text("= %x <invalid string @ %x>", Memory::Read_U8(address), address);
164
}
165
break;
166
case BuiltInType::Float:
167
ImGui::Text("= %f", Memory::Read_Float(address));
168
break;
169
case BuiltInType::Void:
170
ImGui::Text("<void type>");
171
default:
172
return;
173
}
174
DrawBuiltInEditPopup(builtIn, address);
175
}
176
177
static u64 ReadMemoryInt(const u32 address, const u32 length) {
178
switch (length) {
179
case 1:
180
return Memory::Read_U8(address);
181
case 2:
182
return Memory::Read_U16(address);
183
case 4:
184
return Memory::Read_U32(address);
185
case 8:
186
return Memory::Read_U64(address);
187
default:
188
return 0;
189
}
190
}
191
192
void ImStructViewer::WatchForm::Clear() {
193
memset(name, 0, sizeof(name));
194
memset(expression, 0, sizeof(expression));
195
dynamic = false;
196
error = "";
197
typeFilter.Clear();
198
// Not clearing the actual selected type on purpose here, user will have to reselect one anyway and
199
// maybe there is a chance they will reuse the current one
200
}
201
202
void ImStructViewer::WatchForm::SetFrom(const std::unordered_map<std::string, GhidraType> &types, const Watch &watch) {
203
if (!types.count(watch.typePathName)) {
204
return;
205
}
206
snprintf(name, sizeof(name), "%s", watch.name.c_str());
207
typeDisplayName = types.at(watch.typePathName).displayName;
208
typePathName = watch.typePathName;
209
if (watch.expression.empty()) {
210
snprintf(expression, sizeof(expression), "0x%x", watch.address);
211
} else {
212
snprintf(expression, sizeof(expression), "%s", watch.expression.c_str());
213
}
214
dynamic = !watch.expression.empty();
215
error = "";
216
}
217
218
static constexpr int COLUMN_NAME = 0;
219
static constexpr int COLUMN_TYPE = 1;
220
static constexpr int COLUMN_CONTENT = 2;
221
222
// Those numbers are rather arbitrary, they prevent drawing too many elements at once
223
static constexpr int MAX_POINTER_ELEMENTS = 0x100000;
224
static constexpr u32 INDEXED_MEMBERS_CHUNK_SIZE = 0x1000;
225
226
void ImStructViewer::Draw(ImConfig &cfg, ImControl &control, MIPSDebugInterface *mipsDebug) {
227
control_ = &control;
228
mipsDebug_ = mipsDebug;
229
ImGui::SetNextWindowSize(ImVec2(750, 550), ImGuiCond_FirstUseEver);
230
if (!ImGui::Begin("Struct viewer", &cfg.structViewerOpen) || !mipsDebug->isAlive() || !Memory::IsActive()) {
231
ImGui::End();
232
return;
233
}
234
if (ghidraClient_.Ready()) {
235
ghidraClient_.UpdateResult();
236
if (!fetchedAtLeastOnce_ && !ghidraClient_.Failed()) {
237
fetchedAtLeastOnce_ = true;
238
}
239
}
240
241
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
242
if (fetchedAtLeastOnce_) {
243
DrawStructViewer();
244
} else {
245
DrawConnectionSetup();
246
}
247
ImGui::PopStyleVar();
248
249
ImGui::End();
250
}
251
252
void ImStructViewer::DrawConnectionSetup() {
253
ImGui::TextWrapped(R"(Struct viewer visualizes data in game memory using types from your Ghidra project.
254
It also allows to set memory breakpoints and edit field values which is helpful when reverse engineering unknown types.
255
To get started:
256
1. In Ghidra install the ghidra-rest-api extension by Kotcrab.
257
2. In a CodeBrowser window do File -> Configure, then select Miscellaneous and enable the RestApiPlugin.
258
3. Click "Start Rest API Server" in the "Tools" menu bar.
259
4. Press the connect button below.
260
)");
261
ImGui::Dummy(ImVec2(1, 6));
262
263
ImGui::BeginDisabled(!ghidraClient_.Idle());
264
ImGui::PushItemWidth(120);
265
ImGui::InputText("Host", ghidraHost_, IM_ARRAYSIZE(ghidraHost_));
266
ImGui::SameLine();
267
ImGui::InputInt("Port", &ghidraPort_, 0);
268
ImGui::SameLine();
269
if (ImGui::Button("Connect")) {
270
ghidraClient_.FetchAll(ghidraHost_, ghidraPort_);
271
}
272
ImGui::PopItemWidth();
273
ImGui::EndDisabled();
274
275
if (ghidraClient_.Idle() && ghidraClient_.Failed()) {
276
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
277
ImGui::TextWrapped("Error: %s", ghidraClient_.result.error.c_str());
278
ImGui::PopStyleColor();
279
}
280
}
281
282
void ImStructViewer::DrawStructViewer() {
283
ImGui::BeginDisabled(!ghidraClient_.Idle());
284
if (ImGui::Button("Refresh data types")) {
285
ghidraClient_.FetchAll(ghidraHost_, ghidraPort_);
286
}
287
ImGui::EndDisabled();
288
289
if (ghidraClient_.Idle() && ghidraClient_.Failed()) {
290
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
291
ImGui::SameLine();
292
ImGui::TextWrapped("Error: %s", ghidraClient_.result.error.c_str());
293
ImGui::PopStyleColor();
294
}
295
296
// Handle any pending updates to the watches vector
297
if (addWatch_.address != 0) {
298
watches_.push_back(addWatch_);
299
addWatch_ = Watch();
300
}
301
if (removeWatchId_ != -1) {
302
auto pred = [&](const Watch &watch) { return watch.id == removeWatchId_; };
303
watches_.erase(std::remove_if(watches_.begin(), watches_.end(), pred), watches_.end());
304
if (editWatchId_ == removeWatchId_) {
305
ClearWatchForm();
306
}
307
removeWatchId_ = -1;
308
}
309
310
if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_Reorderable)) {
311
if (ImGui::BeginTabItem("Globals")) {
312
DrawGlobals();
313
ImGui::EndTabItem();
314
}
315
if (ImGui::BeginTabItem("Watch")) {
316
DrawWatch();
317
ImGui::EndTabItem();
318
}
319
ImGui::EndTabBar();
320
}
321
}
322
323
void ImStructViewer::DrawGlobals() {
324
globalFilter_.Draw();
325
if (ImGui::BeginTable("##globals", 3,
326
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
327
ImGuiTableFlags_RowBg)) {
328
ImGui::TableSetupScrollFreeze(0, 1);
329
ImGui::TableSetupColumn("Field");
330
ImGui::TableSetupColumn("Type");
331
ImGui::TableSetupColumn("Content");
332
ImGui::TableHeadersRow();
333
334
for (const auto &symbol : ghidraClient_.result.symbols) {
335
if (!symbol.label || !symbol.userDefined || symbol.dataTypePathName.empty()) {
336
continue;
337
}
338
if (!globalFilter_.PassFilter(symbol.name.c_str())) {
339
continue;
340
}
341
DrawType(symbol.address, 0, symbol.dataTypePathName, nullptr, symbol.name.c_str(), -1);
342
}
343
344
ImGui::EndTable();
345
}
346
}
347
348
void ImStructViewer::DrawWatch() {
349
DrawWatchForm();
350
ImGui::Dummy(ImVec2(1, 6));
351
352
watchFilter_.Draw();
353
if (ImGui::BeginTable("##watch", 3,
354
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
355
ImGuiTableFlags_RowBg)) {
356
ImGui::TableSetupScrollFreeze(0, 1);
357
ImGui::TableSetupColumn("Field");
358
ImGui::TableSetupColumn("Type");
359
ImGui::TableSetupColumn("Content");
360
ImGui::TableHeadersRow();
361
362
for (const auto &watch : watches_) {
363
if (!watchFilter_.PassFilter(watch.name.c_str())) {
364
continue;
365
}
366
u32 address = 0;
367
if (!watch.expression.empty()) {
368
u32 val;
369
PostfixExpression postfix;
370
if (initExpression(mipsDebug_, watch.expression.c_str(), postfix)
371
&& parseExpression(mipsDebug_, postfix, val)) {
372
address = val;
373
}
374
} else {
375
address = watch.address;
376
}
377
DrawType(address, 0, watch.typePathName, nullptr, watch.name.c_str(), watch.id);
378
}
379
ImGui::EndTable();
380
}
381
}
382
383
void ImStructViewer::DrawWatchForm() {
384
ImGui::PushItemWidth(150);
385
ImGui::InputText("Name", watchForm_.name, IM_ARRAYSIZE(watchForm_.name));
386
387
ImGui::SameLine();
388
if (ImGui::BeginCombo("Type", watchForm_.typeDisplayName.c_str())) {
389
if (ImGui::IsWindowAppearing()) {
390
ImGui::SetKeyboardFocusHere(0);
391
}
392
watchForm_.typeFilter.Draw();
393
for (const auto &entry : ghidraClient_.result.types) {
394
const auto &type = entry.second;
395
if (watchForm_.typeFilter.PassFilter(type.displayName.c_str())) {
396
ImGui::PushID(type.pathName.c_str());
397
if (ImGui::Selectable(type.displayName.c_str(), watchForm_.typePathName == type.pathName)) {
398
watchForm_.typePathName = type.pathName;
399
watchForm_.typeDisplayName = type.displayName;
400
}
401
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip) && ImGui::BeginTooltip()) {
402
ImGui::Text("%s (%s)", type.displayName.c_str(), type.pathName.c_str());
403
ImGui::Text("Length: %x (aligned: %x)", type.length, type.alignedLength);
404
ImGui::EndTooltip();
405
}
406
ImGui::PopID();
407
}
408
}
409
ImGui::EndCombo();
410
}
411
412
ImGui::SameLine();
413
ImGui::InputText("Expression", watchForm_.expression, IM_ARRAYSIZE(watchForm_.expression));
414
ImGui::SameLine();
415
ImGui::Checkbox("Dynamic", &watchForm_.dynamic);
416
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal)) {
417
ImGui::SetTooltip("When checked the expression will be\nre-evaluated on each frame.");
418
}
419
420
ImGui::PopItemWidth();
421
422
ImGui::SameLine();
423
if (editWatchId_ == -1 ? ImGui::Button("Add watch") : ImGui::Button("Edit watch")) {
424
u32 val;
425
PostfixExpression postfix;
426
if (watchForm_.typePathName.empty()) {
427
watchForm_.error = "type can't be empty";
428
} else if (!initExpression(mipsDebug_, watchForm_.expression, postfix)
429
|| !parseExpression(mipsDebug_, postfix, val)) {
430
watchForm_.error = "invalid expression";
431
} else {
432
std::string watchName = watchForm_.name;
433
if (watchName.empty()) {
434
watchName = "<watch>";
435
}
436
if (watchForm_.dynamic) {
437
watchName = watchName + " (" + watchForm_.expression + ")";
438
}
439
if (editWatchId_ == -1) {
440
watches_.emplace_back(Watch{
441
nextWatchId_++,
442
watchForm_.dynamic ? watchForm_.expression : "",
443
watchForm_.dynamic ? 0 : val,
444
watchForm_.typePathName,
445
watchName
446
});
447
} else {
448
for (auto &watch : watches_) {
449
if (watch.id == editWatchId_) {
450
watch.expression = watchForm_.dynamic ? watchForm_.expression : "";
451
watch.address = watchForm_.dynamic ? 0 : val;
452
watch.typePathName = watchForm_.typePathName;
453
watch.name = watchName;
454
break;
455
}
456
}
457
}
458
ClearWatchForm();
459
}
460
}
461
if (editWatchId_ != -1) {
462
ImGui::SameLine();
463
if (ImGui::Button("Cancel")) {
464
ClearWatchForm();
465
}
466
}
467
if (!watchForm_.error.empty()) {
468
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
469
ImGui::TextWrapped("Error: %s", watchForm_.error.c_str());
470
ImGui::PopStyleColor();
471
}
472
}
473
474
void ImStructViewer::ClearWatchForm() {
475
watchForm_.Clear();
476
editWatchId_ = -1;
477
}
478
479
static void DrawTypeColumn(
480
const std::string &format,
481
const std::string &typeDisplayName,
482
const u32 base,
483
const u32 offset
484
) {
485
ImGui::TableSetColumnIndex(COLUMN_TYPE);
486
ImGui::Text(format.c_str(), typeDisplayName.c_str());
487
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
488
ImGui::SameLine();
489
if (offset != 0) {
490
ImGui::Text("@ %x+%x", base, offset);
491
} else {
492
ImGui::Text("@ %x", base);
493
}
494
ImGui::PopStyleColor();
495
}
496
497
static void DrawArrayContent(
498
const std::unordered_map<std::string, GhidraType> &types,
499
const GhidraType &type,
500
const u32 address
501
) {
502
if (type.arrayElementLength != 1 || !types.count(type.arrayTypePathName)) {
503
return;
504
}
505
const auto &arrayType = types.at(type.arrayTypePathName);
506
bool charElement = false;
507
if (arrayType.kind == TYPEDEF && types.count(arrayType.typedefBaseTypePathName)) {
508
const auto &baseArrayType = types.at(arrayType.typedefBaseTypePathName);
509
charElement = baseArrayType.pathName == "/char";
510
} else {
511
charElement = arrayType.pathName == "/char";
512
}
513
if (!charElement) {
514
return;
515
}
516
const char *charPointer = Memory::GetCharPointerUnchecked(address);
517
std::string text(charPointer, charPointer + type.arrayElementCount);
518
text = std::regex_replace(text, std::regex("\n"), "\\n");
519
ImGui::Text("= \"%s\"", text.c_str());
520
}
521
522
static void DrawPointerText(const u32 value) {
523
if (Memory::IsValidAddress(value)) {
524
ImGui::Text("* %x", value);
525
return;
526
}
527
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
528
if (value == 0) {
529
ImGui::Text("* NULL");
530
} else {
531
ImGui::Text("* <invalid pointer: %x>", value);
532
}
533
ImGui::PopStyleColor();
534
}
535
536
static void DrawPointerContent(
537
const std::unordered_map<std::string, GhidraType> &types,
538
const GhidraType &type,
539
const u32 value
540
) {
541
if (!types.count(type.pointerTypePathName)) {
542
DrawPointerText(value);
543
return;
544
}
545
const auto &pointedType = types.at(type.pointerTypePathName);
546
bool charPointerElement = false;
547
if (pointedType.kind == TYPEDEF && types.count(pointedType.typedefBaseTypePathName)) {
548
const auto &basePointedType = types.at(pointedType.typedefBaseTypePathName);
549
charPointerElement = basePointedType.pathName == "/char";
550
} else {
551
charPointerElement = pointedType.pathName == "/char";
552
}
553
if (!charPointerElement || !Memory::IsValidNullTerminatedString(value)) {
554
DrawPointerText(value);
555
return;
556
}
557
const char *charPointer = Memory::GetCharPointerUnchecked(value);
558
std::string text(charPointer);
559
text = std::regex_replace(text, std::regex("\n"), "\\n");
560
ImGui::Text("= \"%s\"", text.c_str());
561
}
562
563
// If enum is a bitfield then format as it would look in code with 'or' operator (e.g. "ALIGN_TOP | ALIGN_LEFT")
564
// otherwise just try to find exact matching member
565
static std::string FormatEnumValue(
566
const std::vector<GhidraEnumMember> &enumMembers,
567
const u64 value,
568
const bool bitfield
569
) {
570
if (bitfield) {
571
std::stringstream ss;
572
bool hasPrevious = false;
573
574
// The value for the yet unknown enum members, it will be non-zero if the enum definition is incomplete
575
u64 remainderValue = value;
576
for (const auto &member : enumMembers) {
577
remainderValue &= ~member.value;
578
}
579
if (remainderValue != 0) {
580
ss << std::hex << remainderValue;
581
hasPrevious = true;
582
}
583
584
for (const auto &member : enumMembers) {
585
if ((value & member.value) != 0 || (value == 0 && member.value == 0)) {
586
if (hasPrevious) {
587
ss << " | ";
588
}
589
ss << member.name;
590
hasPrevious = true;
591
}
592
}
593
return ss.str();
594
}
595
596
for (const auto &member : enumMembers) {
597
if (value == member.value) {
598
return member.name;
599
}
600
}
601
return "?";
602
}
603
604
void ImStructViewer::DrawType(
605
const u32 base,
606
const u32 offset,
607
const std::string &typePathName,
608
const char *typeDisplayNameOverride,
609
const char *name,
610
const int watchId,
611
const ImGuiTreeNodeFlags extraTreeNodeFlags
612
) {
613
const auto &types = ghidraClient_.result.types;
614
// Generic pointer is not included in the type listing, need to resolve it manually to void*
615
if (typePathName == "/pointer") {
616
DrawType(base, offset, "/void *", "pointer", name, watchId);
617
return;
618
}
619
// Undefined itself doesn't exist as a type, let's just display first byte in that case
620
if (typePathName == "/undefined") {
621
DrawType(base, offset, "/undefined1", "undefined", name, watchId);
622
return;
623
}
624
625
const bool hasType = types.count(typePathName) != 0;
626
627
// Resolve typedefs as early as possible
628
if (hasType) {
629
const auto &type = types.at(typePathName);
630
if (type.kind == TYPEDEF) {
631
DrawType(base, offset, type.typedefBaseTypePathName, type.displayName.c_str(), name, watchId);
632
return;
633
}
634
}
635
636
const u32 address = base + offset;
637
ImGui::PushID(static_cast<int>(address));
638
ImGui::PushID(watchId); // We push watch id too as it's possible to have multiple watches on the same address
639
640
// Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing
641
// to make the tree lines equal high.
642
ImGui::TableNextRow();
643
ImGui::TableSetColumnIndex(COLUMN_NAME);
644
ImGui::AlignTextToFramePadding();
645
// Flags used for nodes that can't be further opened
646
const ImGuiTreeNodeFlags leafFlags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen |
647
extraTreeNodeFlags;
648
649
// Type is missing in fetched types, this can happen e.g. if type used for watch is removed from Ghidra
650
if (!hasType) {
651
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
652
DrawContextMenu(base, offset, 0, typePathName, name, watchId, nullptr);
653
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
654
DrawTypeColumn("<missing type: %s>", typePathName, base, offset);
655
ImGui::PopStyleColor();
656
ImGui::PopID();
657
ImGui::PopID();
658
return;
659
}
660
661
const auto &type = types.at(typePathName);
662
const std::string typeDisplayName =
663
typeDisplayNameOverride == nullptr ? type.displayName : typeDisplayNameOverride;
664
665
// Handle cases where pointers or expressions point to invalid memory
666
if (!Memory::IsValidAddress(address)) {
667
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
668
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, nullptr);
669
DrawTypeColumn("%s", typeDisplayName, base, offset);
670
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
671
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY);
672
ImGui::Text("<invalid address: %x>", address);
673
ImGui::PopStyleColor();
674
ImGui::PopID();
675
ImGui::PopID();
676
return;
677
}
678
679
// For each type we create tree node with the field name and fill type column
680
// Content column and edit popup is only set for types where it makes sense
681
switch (type.kind) {
682
case ENUM: {
683
ImGui::TreeNodeEx("Enum", leafFlags, "%s", name);
684
const u64 enumValue = ReadMemoryInt(address, type.length);
685
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, &enumValue);
686
DrawTypeColumn("%s", typeDisplayName, base, offset);
687
688
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
689
const std::string enumString = FormatEnumValue(type.enumMembers, enumValue, type.enumBitfield);
690
ImGui::Text("= %llx (%s)", enumValue, enumString.c_str());
691
DrawIntBuiltInEditPopup(address, type.length);
692
break;
693
}
694
case POINTER: {
695
const bool nodeOpen = ImGui::TreeNodeEx("Pointer", extraTreeNodeFlags, "%s", name);
696
const u32 pointer = Memory::Read_U32(address);
697
const u64 pointer64 = pointer;
698
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, &pointer64);
699
DrawTypeColumn("%s", typeDisplayName, base, offset);
700
701
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
702
DrawPointerContent(types, type, pointer);
703
704
if (nodeOpen) {
705
int pointerElementAlignedLength = -1;
706
const auto countStateId = ImGui::GetID("PointerElementCount");
707
const int pointerElementCount = ImGui::GetStateStorage()->GetInt(countStateId, 1);
708
709
// To draw more pointer elements the "pointed to" type must exist
710
// It also can't be an unsized type (e.g. it can't be a function or void)
711
if (types.count(type.pointerTypePathName)) {
712
pointerElementAlignedLength = types.at(type.pointerTypePathName).alignedLength;
713
if (pointerElementAlignedLength > 0) {
714
ImGui::TableNextRow();
715
ImGui::TableSetColumnIndex(COLUMN_NAME);
716
int inputElementCount = pointerElementCount;
717
ImGui::PushItemWidth(110);
718
ImGui::InputScalar("Elements", ImGuiDataType_S32, &inputElementCount, NULL, NULL, "%x");
719
if (ImGui::IsItemDeactivatedAfterEdit()) {
720
const int newValue = std::clamp(inputElementCount, 1, MAX_POINTER_ELEMENTS);
721
ImGui::GetStateStorage()->SetInt(countStateId, newValue);
722
}
723
ImGui::PopItemWidth();
724
}
725
}
726
727
// A pointer always creates at least one extra node in the tree
728
// We want to auto open first element here to spare user one extra click
729
DrawIndexedMembers(pointer, 0, type.pointerTypePathName, name, pointerElementCount,
730
pointerElementAlignedLength, true);
731
ImGui::TreePop();
732
}
733
break;
734
}
735
case ARRAY: {
736
const bool nodeOpen = ImGui::TreeNodeEx("Array", extraTreeNodeFlags, "%s", name);
737
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, nullptr);
738
DrawTypeColumn("%s", typeDisplayName, base, offset);
739
740
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
741
DrawArrayContent(types, type, address);
742
743
if (nodeOpen) {
744
DrawIndexedMembers(base, offset, type.arrayTypePathName, name, type.arrayElementCount,
745
type.arrayElementLength, false);
746
ImGui::TreePop();
747
}
748
break;
749
}
750
case STRUCTURE:
751
case UNION: {
752
const bool nodeOpen = ImGui::TreeNodeEx("Composite", extraTreeNodeFlags, "%s", name);
753
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, nullptr);
754
DrawTypeColumn("%s", typeDisplayName, base, offset);
755
756
if (nodeOpen) {
757
for (const auto &member : type.compositeMembers) {
758
DrawType(base, offset + member.offset, member.typePathName, nullptr,
759
member.fieldName.c_str(), -1);
760
}
761
ImGui::TreePop();
762
}
763
break;
764
}
765
case FUNCTION_DEFINITION:
766
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
767
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, nullptr);
768
DrawTypeColumn("%s", typeDisplayName, base, offset);
769
770
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
771
ImGui::Text("<function definition>");
772
break;
773
case BUILT_IN: {
774
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
775
776
if (knownBuiltIns.count(typePathName)) {
777
// This will copy float as int, but we can live with that for now
778
const u64 value = ReadMemoryInt(address, type.alignedLength);
779
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, &value);
780
DrawTypeColumn("%s", typeDisplayName, base, offset);
781
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
782
DrawBuiltInContent(knownBuiltIns.at(typePathName), address);
783
} else {
784
// Some built in types are rather obscure so we don't handle every possible one
785
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
786
DrawTypeColumn("<unsupported built in: %s>", typePathName, base, offset);
787
ImGui::PopStyleColor();
788
}
789
break;
790
}
791
default: {
792
// At this point there is most likely some issue in the Ghidra extension and the type wasn't
793
// classified to any category
794
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
795
DrawContextMenu(base, offset, type.alignedLength, typePathName, name, watchId, nullptr);
796
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
797
DrawTypeColumn("<not implemented type: %s>", typeDisplayName, base, offset);
798
ImGui::PopStyleColor();
799
break;
800
}
801
}
802
803
ImGui::PopID();
804
ImGui::PopID();
805
}
806
807
void ImStructViewer::DrawIndexedMembers(
808
const u32 base,
809
const u32 offset,
810
const std::string &typePathName,
811
const char *name,
812
const u32 elementCount,
813
const int elementLength,
814
const bool openFirst
815
) {
816
auto drawChunk = [&](const u32 chunkOffset) {
817
for (u32 i = chunkOffset; i < std::min(elementCount, chunkOffset + INDEXED_MEMBERS_CHUNK_SIZE); i++) {
818
char nameBuffer[256];
819
snprintf(nameBuffer, sizeof(nameBuffer), "%s[%x]", name, i);
820
// Element length might be non-positive here, e.g. for pointers which point to types that
821
// don't exist (e.g. undefined type) or when pointing to an unsized type (e.g. function, void types)
822
// However the first element is always at offset 0 so we can draw it even if we don't know type length
823
const u32 elementOffset = i == 0 ? 0 : i * elementLength;
824
const ImGuiTreeNodeFlags elementTreeFlags = i == 0 && openFirst ? ImGuiTreeNodeFlags_DefaultOpen : 0;
825
DrawType(base, offset + elementOffset, typePathName, nullptr, nameBuffer, -1, elementTreeFlags);
826
}
827
};
828
829
if (elementCount <= INDEXED_MEMBERS_CHUNK_SIZE) {
830
drawChunk(0);
831
return;
832
}
833
const u32 chunks = 1 + (elementCount - 1) / INDEXED_MEMBERS_CHUNK_SIZE; // Round up div
834
for (u32 i = 0; i < chunks; i++) {
835
ImGui::PushID(static_cast<int>(i));
836
ImGui::TableNextRow();
837
ImGui::TableSetColumnIndex(COLUMN_NAME);
838
const u32 chunkOffset = i * INDEXED_MEMBERS_CHUNK_SIZE;
839
const u32 chunkEnd = std::min(elementCount, chunkOffset + INDEXED_MEMBERS_CHUNK_SIZE) - 1;
840
const ImGuiTreeNodeFlags chunkTreeFlags = i == 0 && openFirst ? ImGuiTreeNodeFlags_DefaultOpen : 0;
841
char nameBuffer[256];
842
snprintf(nameBuffer, sizeof(nameBuffer), "%s[%x..%x]", name, chunkOffset, chunkEnd);
843
if (ImGui::TreeNodeEx("Chunk", chunkTreeFlags, "%s", nameBuffer)) {
844
drawChunk(chunkOffset);
845
ImGui::TreePop();
846
}
847
ImGui::PopID();
848
}
849
}
850
851
static void CopyHexNumberToClipboard(const u64 value) {
852
std::stringstream ss;
853
ss << std::hex << value;
854
const std::string valueString = ss.str();
855
System_CopyStringToClipboard(valueString);
856
}
857
858
void ImStructViewer::DrawContextMenu(
859
const u32 base,
860
const u32 offset,
861
const int length,
862
const std::string &typePathName,
863
const char *name,
864
const int watchId,
865
const u64 *value
866
) {
867
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
868
if (ImGui::BeginPopup("context")) {
869
const u32 address = base + offset;
870
871
if (ImGui::MenuItem("Copy address")) {
872
CopyHexNumberToClipboard(address);
873
}
874
if (value && ImGui::MenuItem("Copy value")) {
875
CopyHexNumberToClipboard(*value);
876
}
877
ImGui::Separator();
878
879
// This might be called when iterating over existing watches so can't modify the watch vector directly here
880
if (watchId < 0) {
881
if (ImGui::MenuItem("Add watch")) {
882
addWatch_.id = nextWatchId_++;
883
addWatch_.address = address;
884
addWatch_.typePathName = typePathName;
885
addWatch_.name = name;
886
}
887
} else {
888
if (ImGui::MenuItem("Remove watch")) {
889
removeWatchId_ = watchId;
890
}
891
if (ImGui::MenuItem("Edit watch")) {
892
for (const auto &watch : watches_) {
893
if (watch.id == watchId) {
894
editWatchId_ = watchId;
895
watchForm_.SetFrom(ghidraClient_.result.types, watch);
896
break;
897
}
898
}
899
}
900
}
901
902
ImGui::Separator();
903
ShowInWindowMenuItems(address, *control_);
904
ImGui::Separator();
905
906
// Memory breakpoints are only possible for sized types
907
if (length > 0) {
908
const u32 end = address + length;
909
MemCheck memCheck;
910
const bool hasMemCheck = g_breakpoints.GetMemCheck(address, end, &memCheck);
911
if (hasMemCheck) {
912
if (ImGui::MenuItem("Remove memory breakpoint")) {
913
g_breakpoints.RemoveMemCheck(address, end);
914
}
915
}
916
const bool canAddRead = !hasMemCheck || !(memCheck.cond & MEMCHECK_READ);
917
const bool canAddWrite = !hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE);
918
const bool canAddWriteOnChange = !hasMemCheck || !(memCheck.cond & MEMCHECK_WRITE_ONCHANGE);
919
if ((canAddRead || canAddWrite || canAddWriteOnChange) && ImGui::BeginMenu("Add memory breakpoint")) {
920
if (canAddRead && canAddWrite && ImGui::MenuItem("Read/Write")) {
921
constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_READ | MEMCHECK_WRITE);
922
g_breakpoints.AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE);
923
}
924
if (canAddRead && ImGui::MenuItem("Read")) {
925
g_breakpoints.AddMemCheck(address, end, MEMCHECK_READ, BREAK_ACTION_PAUSE);
926
}
927
if (canAddWrite && ImGui::MenuItem("Write")) {
928
g_breakpoints.AddMemCheck(address, end, MEMCHECK_WRITE, BREAK_ACTION_PAUSE);
929
}
930
if (canAddWriteOnChange && ImGui::MenuItem("Write Change")) {
931
constexpr auto cond = static_cast<MemCheckCondition>(MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE);
932
g_breakpoints.AddMemCheck(address, end, cond, BREAK_ACTION_PAUSE);
933
}
934
ImGui::EndMenu();
935
}
936
}
937
938
ImGui::EndPopup();
939
}
940
}
941
942