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