summaryrefslogtreecommitdiff
path: root/view
diff options
context:
space:
mode:
authorMark Rowe <mark@vector35.com>2025-08-11 10:42:07 -0700
committerMark Rowe <mark@vector35.com>2025-08-27 19:15:49 -0700
commit2303f75b080f6dd0c9a5c669a71f64ce830f5650 (patch)
treea49d80ea0a8131a50c16f85e767722679ff08c85 /view
parentb302d7ba796f41b1102ee61feed8b8e212299997 (diff)
Rewrite Obj-C workflow in Rust
This is functionally equivalent to the previous workflow_objc, with the following changes: 1. It mutates the `core.function.metaAnalysis` workflow rather than registering a new named workflow. The activities now all check for the presence of the Objective-C metadata added by `ObjCProcessor` to determine whether they should do work, rather than relying on `MachoView` to override the function workflow when Objective-C metadata is present. This fixes https://github.com/Vector35/binaryninja-api/issues/6779. 2. The auto-inlining of `objc_msgSend` selector stub functions is performed in a separate activity from the processing of `objc_msgSend` call sites. The selector stub inlining activity is configured so that it does not run in `DSCView` as the shared cache needs different behavior for stub functions more generally that `SharedCacheWorkflow` already provides. 3. The way that types like `id` and `SEL` are referenced is fixed so that they show up as `id` rather than `objc_struct*`. This also replaces the Objective-C portion of the shared cache's workflow, and incorporates several bug fixes that had been applied to it but not the standalone Objective-C workflow.
Diffstat (limited to 'view')
-rw-r--r--view/macho/machoview.cpp33
-rw-r--r--view/sharedcache/workflow/ObjCActivity.cpp184
-rw-r--r--view/sharedcache/workflow/ObjCActivity.h9
-rw-r--r--view/sharedcache/workflow/SharedCacheWorkflow.cpp3
4 files changed, 11 insertions, 218 deletions
diff --git a/view/macho/machoview.cpp b/view/macho/machoview.cpp
index 1da2bda7..1c65629a 100644
--- a/view/macho/machoview.cpp
+++ b/view/macho/machoview.cpp
@@ -1845,6 +1845,17 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_
BinaryReader virtualReader(this);
virtualReader.SetEndianness(m_endian);
+ if (m_file->IsBackedByDatabase(GetTypeName()))
+ {
+ // The `core.function.objectiveC` workflow has been removed in favor of the meta-analysis
+ // workflow. If the file is backed by a database, we need to update any references to the
+ // old workflow to refer to the meta-analysis workflow.
+ Ref<Settings> analysisSettings = Settings::Instance();
+ auto workflow = analysisSettings->Get<string>("analysis.workflows.functionWorkflow", this);
+ if (workflow == "core.function.objectiveC")
+ analysisSettings->Set("analysis.workflows.functionWorkflow", "core.function.metaAnalysis", this);
+ }
+
bool parseObjCStructs = true;
bool parseCFStrings = true;
if (settings && settings->Contains("loader.macho.processObjectiveC"))
@@ -1861,20 +1872,6 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_
{
objcProcessor = std::make_unique<MachoObjCProcessor>(this);
}
- if (parseObjCStructs)
- {
- if (!settings) // Add our defaults
- {
- Ref<Settings> programSettings = Settings::Instance();
- if (programSettings->Contains("corePlugins.workflows.objc"))
- {
- if (programSettings->Get<bool>("corePlugins.workflows.objc"))
- {
- programSettings->Set("analysis.workflows.functionWorkflow", "core.function.objectiveC", this);
- }
- }
- }
- }
// parse thread starts section if available
bool rebaseThreadStarts = false;
@@ -4133,14 +4130,6 @@ Ref<Settings> MachoViewType::GetLoadSettingsForData(BinaryView* data)
"default" : true,
"description" : "Processes Objective-C structures, applying method names and types from encoded metadata"
})");
- Ref<Settings> programSettings = Settings::Instance();
- if (programSettings->Contains("corePlugins.workflows.objc"))
- {
- if (programSettings->Get<bool>("corePlugins.workflows.objc"))
- {
- programSettings->Set("analysis.workflows.functionWorkflow", "core.function.objectiveC", viewRef);
- }
- }
}
if (viewRef->GetSectionByName("__cfstring"))
{
diff --git a/view/sharedcache/workflow/ObjCActivity.cpp b/view/sharedcache/workflow/ObjCActivity.cpp
deleted file mode 100644
index f3efbccd..00000000
--- a/view/sharedcache/workflow/ObjCActivity.cpp
+++ /dev/null
@@ -1,184 +0,0 @@
-#include "ObjCActivity.h"
-#include "lowlevelilinstruction.h"
-
-// TODO: Consolidate this with the Obj-C workflow at some point https://github.com/Vector35/workflow_objc
-
-using namespace BinaryNinja;
-
-void ObjCActivity::Register(Workflow &workflow)
-{
- workflow.RegisterActivity(new Activity(R"({
- "name": "core.analysis.sharedCache.objc.adjustCallType",
- "eligibility": {
- "predicates": [
- {
- "type": "viewType",
- "operator": "in",
- "value": [
- "DSCView"
- ]
- }
- ]
- }
- })", &AdjustCallType));
- workflow.Insert("core.function.analyzeTailCalls", "core.analysis.sharedCache.objc.adjustCallType");
-}
-
-std::vector<std::string> splitSelector(const std::string& selector) {
- std::vector<std::string> components;
- std::istringstream stream(selector);
- std::string component;
-
- while (std::getline(stream, component, ':')) {
- if (!component.empty()) {
- components.push_back(component);
- }
- }
-
- return components;
-}
-
-std::vector<std::string> generateArgumentNames(const std::vector<std::string>& components) {
- std::vector<std::string> argumentNames;
-
- for (const std::string& component : components) {
- size_t startPos = component.find_last_of(' ');
- std::string argumentName = (startPos == std::string::npos) ? component : component.substr(startPos + 1);
- argumentNames.push_back(argumentName);
- }
-
- return argumentNames;
-}
-
-void ObjCActivity::AdjustCallType(Ref<AnalysisContext> ctx)
-{
- const auto func = ctx->GetFunction();
- const auto arch = func->GetArchitecture();
- const auto bv = func->GetView();
- const auto baseAddr = bv->GetStart();
-
- const auto llil = ctx->GetLowLevelILFunction();
- if (!llil) {
- return;
- }
- const auto ssa = llil->GetSSAForm();
- if (!ssa) {
- return;
- }
-
- const auto rewriteIfEligible = [bv, ssa, baseAddr](size_t insnIndex) {
- auto insn = ssa->GetInstruction(insnIndex);
- if (insn.operation != LLIL_CALL_SSA && insn.operation != LLIL_TAILCALL_SSA)
- return;
-
- enum class MessageSendType {
- Normal,
- Super,
- };
-
- MessageSendType messageSendType = MessageSendType::Normal;
- // Filter out calls that aren't to `objc_msgSend`, `objc_msgSendSuper`, or `objc_msgSendSuper2`.
- auto callExpr = insn.GetDestExpr();
- if (auto symbol = bv->GetSymbolByAddress(callExpr.GetValue().value))
- {
- std::string_view symbolName = symbol->GetRawNameRef();
- if (symbolName == "_objc_msgSend")
- messageSendType = MessageSendType::Normal;
- else if (symbolName == "_objc_msgSendSuper2" || symbolName == "_objc_msgSendSuper")
- messageSendType = MessageSendType::Super;
- else
- return;
- }
-
- const auto params = insn.GetParameterExprs();
- // The second parameter passed to the objc_msgSend call is the address of
- // either the selector reference or the method's name, which in both cases
- // is dereferenced to retrieve a selector.
- if (params.size() < 2)
- return;
- uint64_t rawSelector = 0;
- if (params[1].operation == LLIL_REG_SSA)
- {
- const auto selectorRegister = params[1].GetSourceSSARegister<LLIL_REG_SSA>();
- rawSelector = ssa->GetSSARegisterValue(selectorRegister).value;
- }
- else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA)
- {
- if (params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>().size() == 0)
- return;
- const auto selectorRegister = params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>()[1].GetSourceSSARegister<LLIL_REG_SSA>();
- rawSelector = ssa->GetSSARegisterValue(selectorRegister).value;
- }
-
- // Skip if we don't have a
- if (!rawSelector || rawSelector < baseAddr)
- return;
-
- std::string selector;
- if (bv->IsValidOffset(rawSelector)) {
- BinaryReader reader(bv);
- reader.Seek(rawSelector);
- selector = reader.ReadCString(500);
- } else {
- // Look for the `sel_` symbols that ObjCProcessor adds to represent selectors
- // whose backing regions have not yet been loaded into the view.
- constexpr std::string_view SelectorPrefix = "sel_";
-
- auto symbol = bv->GetSymbolByAddress(rawSelector);
- if (!symbol)
- return;
-
- std::string_view name = symbol->GetRawNameRef();
- if (name.find(SelectorPrefix) != 0)
- return;
-
- selector = name.substr(SelectorPrefix.length());
- }
-
- // -- Do callsite override
- auto additionalArgumentCount = std::count(selector.begin(), selector.end(), ':');
-
- auto retType = bv->GetTypeByName({ "id" });
- if (!retType)
- retType = Type::PointerType(ssa->GetArchitecture(), Type::VoidType());
-
- std::vector<FunctionParameter> callTypeParams;
- auto cc = bv->GetDefaultPlatform()->GetDefaultCallingConvention();
-
- if (messageSendType == MessageSendType::Normal)
- callTypeParams.emplace_back("self", retType, true, Variable());
- else
- {
- auto superType = bv->GetTypeByName({ "objc_super" });
- if (!superType)
- superType = Type::PointerType(ssa->GetArchitecture(), Type::VoidType());
- callTypeParams.emplace_back("super", Type::PointerType(ssa->GetArchitecture(), superType), true, Variable());
- }
-
- auto selType = bv->GetTypeByName({ "SEL" });
- if (!selType)
- selType = Type::PointerType(ssa->GetArchitecture(), Type::IntegerType(1, true));
- callTypeParams.emplace_back("sel", selType, true, Variable());
-
- std::vector<std::string> selectorComponents = splitSelector(selector);
- std::vector<std::string> argumentNames = generateArgumentNames(selectorComponents);
-
- for (size_t i = 0; i < additionalArgumentCount; i++)
- {
- auto argType = Type::IntegerType(bv->GetAddressSize(), true);
- if (argumentNames.size() > i && !argumentNames[i].empty())
- callTypeParams.emplace_back(argumentNames[i], argType, true, Variable());
- else
- callTypeParams.emplace_back("arg" + std::to_string(i), argType, true, Variable());
- }
-
- auto funcType = Type::FunctionType(retType, cc, callTypeParams);
- ssa->GetFunction()->SetAutoCallTypeAdjustment(ssa->GetFunction()->GetArchitecture(), insn.address, {funcType, BN_DEFAULT_CONFIDENCE});
- // --
- };
-
- for (const auto& block : ssa->GetBasicBlocks())
- for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i)
- rewriteIfEligible(i);
-}
-
diff --git a/view/sharedcache/workflow/ObjCActivity.h b/view/sharedcache/workflow/ObjCActivity.h
deleted file mode 100644
index 05a0d1b8..00000000
--- a/view/sharedcache/workflow/ObjCActivity.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-#include "binaryninjaapi.h"
-
-class ObjCActivity
-{
- static void AdjustCallType(BinaryNinja::Ref<BinaryNinja::AnalysisContext> ctx);
-public:
- static void Register(BinaryNinja::Workflow& workflow);
-}; \ No newline at end of file
diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.cpp b/view/sharedcache/workflow/SharedCacheWorkflow.cpp
index 3844516f..dcdf6c23 100644
--- a/view/sharedcache/workflow/SharedCacheWorkflow.cpp
+++ b/view/sharedcache/workflow/SharedCacheWorkflow.cpp
@@ -15,8 +15,6 @@
#include "thread"
#include <shared_mutex>
-#include "ObjCActivity.h"
-
using namespace BinaryNinja;
using namespace SharedCacheAPI;
@@ -373,7 +371,6 @@ void SharedCacheWorkflow::Register()
Ref<Workflow> workflow = Workflow::Get("core.function.metaAnalysis")->Clone("core.function.metaAnalysis");
// Register and insert activities here.
- ObjCActivity::Register(*workflow);
workflow->RegisterActivity(new Activity(R"({
"name": "core.analysis.sharedCache.analysis",
"eligibility": {