summaryrefslogtreecommitdiff
path: root/plugins/workflow_objc/src/metadata/selector.rs
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 /plugins/workflow_objc/src/metadata/selector.rs
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 'plugins/workflow_objc/src/metadata/selector.rs')
-rw-r--r--plugins/workflow_objc/src/metadata/selector.rs52
1 files changed, 52 insertions, 0 deletions
diff --git a/plugins/workflow_objc/src/metadata/selector.rs b/plugins/workflow_objc/src/metadata/selector.rs
new file mode 100644
index 00000000..a48be14d
--- /dev/null
+++ b/plugins/workflow_objc/src/metadata/selector.rs
@@ -0,0 +1,52 @@
+use crate::Error;
+use binaryninja::binary_view::{BinaryView, BinaryViewBase as _, BinaryViewExt};
+
+pub struct Selector {
+ pub name: String,
+ pub addr: u64,
+}
+
+impl Selector {
+ pub fn from_address(bv: &BinaryView, addr: u64) -> Result<Self, Error> {
+ let name = if bv.offset_valid(addr) {
+ // Read the selector name from the binary view
+ read_cstring(bv, addr, 500)
+ } else {
+ // Look for the `sel_` symbols that ObjCProcessor adds to represent selectors
+ // whose backing regions have not yet been loaded into the view.
+ bv.symbol_by_address(addr)
+ .and_then(|sym| sym.raw_name().to_str().ok().map(|name| name.to_owned()))
+ .filter(|name| name.starts_with("sel_"))
+ .map(|name| name["sel_".len()..].to_string())
+ }
+ .ok_or(Error::InvalidSelector { address: addr })?;
+ Ok(Selector { name, addr })
+ }
+
+ pub fn argument_labels(&self) -> Vec<String> {
+ if !self.name.contains(':') {
+ return vec![];
+ }
+
+ self.name
+ .split(':')
+ .filter(|s| !s.is_empty())
+ .map(|s| s.to_string())
+ .collect()
+ }
+}
+
+// Read a null-terminated string from the view
+fn read_cstring(bv: &BinaryView, address: u64, max_len: usize) -> Option<String> {
+ let mut buffer = vec![0u8; max_len];
+ let bytes_read = bv.read(&mut buffer, address);
+ if bytes_read == 0 {
+ return None;
+ }
+
+ // Find the null terminator
+ let null_pos = buffer.iter().position(|&b| b == 0).unwrap_or(bytes_read);
+ buffer.truncate(null_pos);
+
+ String::from_utf8(buffer).ok()
+}