Skip to content

Commit e01a20a

Browse files
committed
[DSC] Resolve cross-image stub functions to their target image in context menu actions
This ensures the "Load /usr/lib/libFoo.dylib" context menu item will be offered when right-clicking on a stub function that calls into libFoo.dylib.
1 parent 2aba9ea commit e01a20a

1 file changed

Lines changed: 61 additions & 6 deletions

File tree

view/sharedcache/ui/SharedCacheUINotifications.cpp

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "SharedCacheUINotifications.h"
66
#include <sharedcacheapi.h>
7+
#include "mediumlevelilinstruction.h"
78
#include "ui/sidebar.h"
89
#include "ui/linearview.h"
910
#include "ui/viewframe.h"
@@ -15,6 +16,58 @@ using namespace SharedCacheAPI;
1516

1617
UINotifications* UINotifications::m_instance = nullptr;
1718

19+
// Resolve a stub function at `addr` to the constant target of its jump or tail call. Stub functions
20+
// are identified by the `j_` symbol prefix that the shared cache workflow applies when renaming them.
21+
static std::optional<uint64_t> ResolveStubTarget(BinaryView& view, uint64_t addr)
22+
{
23+
auto symbol = view.GetSymbolByAddress(addr);
24+
if (!symbol || symbol->GetShortName().rfind("j_", 0) != 0)
25+
return std::nullopt;
26+
27+
auto func = view.GetAnalysisFunction(view.GetDefaultPlatform(), addr);
28+
if (!func)
29+
return std::nullopt;
30+
31+
// Skip any function that is very clearly not a stub (not a single basic block with only a few instructions).
32+
const auto blocks = func->GetBasicBlocks();
33+
constexpr uint64_t maxStubLength = 0x20;
34+
if (blocks.size() != 1 || blocks[0]->GetLength() > maxStubLength)
35+
return std::nullopt;
36+
37+
auto mlil = func->GetMediumLevelIL();
38+
if (!mlil)
39+
return std::nullopt;
40+
41+
for (const auto& block : mlil->GetBasicBlocks())
42+
{
43+
for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i)
44+
{
45+
const auto instr = mlil->GetInstruction(i);
46+
if (instr.operation != MLIL_JUMP && instr.operation != MLIL_TAILCALL)
47+
continue;
48+
const auto dest = instr.GetDestExpr();
49+
if (dest.operation != MLIL_CONST_PTR && dest.operation != MLIL_CONST)
50+
continue;
51+
return dest.GetConstant();
52+
}
53+
}
54+
55+
return std::nullopt;
56+
}
57+
58+
// The address a token-based load action should operate on. A stub function's address is in an
59+
// already-loaded image, so resolve it to its target and offer to load what the stub jumps to.
60+
static uint64_t TokenAddress(const UIActionContext& ctx)
61+
{
62+
uint64_t addr = ctx.token.token.value;
63+
if (!ctx.binaryView->GetSectionsAt(addr).empty())
64+
{
65+
if (auto target = ResolveStubTarget(*ctx.binaryView, addr))
66+
return *target;
67+
}
68+
return addr;
69+
}
70+
1871
void UINotifications::init()
1972
{
2073
m_instance = new UINotifications;
@@ -95,19 +148,21 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
95148
};
96149

97150
auto loadRegionTokenAction = [](const UIActionContext& ctx) {
151+
uint64_t addr = TokenAddress(ctx);
98152
BackgroundThread::create(ctx.context->mainWindow())
99-
->thenBackground([ctx](){ loadRegionAtAddr(*ctx.binaryView, ctx.token.token.value); })
153+
->thenBackground([ctx, addr](){ loadRegionAtAddr(*ctx.binaryView, addr); })
100154
->start();
101155
};
102156

103157
auto loadImageTokenAction = [](const UIActionContext& ctx) {
158+
uint64_t addr = TokenAddress(ctx);
104159
BackgroundThread::create(ctx.context->mainWindow())
105-
->thenBackground([ctx](){ loadImageAtAddr(*ctx.binaryView, ctx.token.token.value); })
160+
->thenBackground([ctx, addr](){ loadImageAtAddr(*ctx.binaryView, addr); })
106161
->start();
107162
};
108163

109164
auto isValidUnloadedRegionAction = [](const UIActionContext& ctx) {
110-
uint64_t addr = ctx.token.token.value;
165+
uint64_t addr = TokenAddress(ctx);
111166
// Check if the region is already loaded in the view.
112167
if (!ctx.binaryView->GetSectionsAt(addr).empty())
113168
return false;
@@ -118,7 +173,7 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
118173
};
119174

120175
auto isValidUnloadedImageAction = [](const UIActionContext& ctx) {
121-
uint64_t addr = ctx.token.token.value;
176+
uint64_t addr = TokenAddress(ctx);
122177
// Check if the image is already loaded in the view.
123178
if (!ctx.binaryView->GetSectionsAt(addr).empty())
124179
return false;
@@ -139,7 +194,7 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
139194
auto controller = SharedCacheController::GetController(*ctx.binaryView);
140195
if (!controller)
141196
return QString("NO CONTROLLER");
142-
uint64_t addr = ctx.token.token.value;
197+
uint64_t addr = TokenAddress(ctx);
143198
auto region = controller->GetRegionContaining(addr);
144199
if (!region)
145200
return QString("NO REGION");
@@ -150,7 +205,7 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
150205
auto controller = SharedCacheController::GetController(*ctx.binaryView);
151206
if (!controller)
152207
return QString("NO CONTROLLER");
153-
uint64_t addr = ctx.token.token.value;
208+
uint64_t addr = TokenAddress(ctx);
154209
auto image = controller->GetImageContaining(addr);
155210
if (!image)
156211
return QString("NO IMAGE");

0 commit comments

Comments
 (0)