GP-6327: Improving Objective-C msgSend() support

This commit is contained in:
Ryan Kurtz
2026-03-06 04:53:12 -05:00
parent 41e7ac82ed
commit b90adfdfdd
17 changed files with 1016 additions and 943 deletions
@@ -1,324 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.analysis.objc;
import java.util.List;
import ghidra.app.services.*;
import ghidra.app.util.bin.format.macho.SectionNames;
import ghidra.app.util.bin.format.objc.ObjcUtils;
import ghidra.app.util.bin.format.objc.objc1.Objc1Constants;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class Objc1MessageAnalyzer extends AbstractAnalyzer {
private static final String DESCRIPTION =
"An analyzer for extracting _objc_msgSend information.";
private static final String NAME = "Objective-C Message";
public Objc1MessageAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
setDefaultEnablement(true);
setPriority(new AnalysisPriority(10000000));
}
/* ************************************************************************** */
/* ************************************************************************** */
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
CurrentState state = new CurrentState(program);
monitor.initialize(set.getNumAddresses());
int progress = 0;
AddressIterator iterator = set.getAddresses(true);
while (iterator.hasNext()) {
if (monitor.isCancelled()) {
break;
}
monitor.setProgress(++progress);
Address address = iterator.next();
Function function = program.getListing().getFunctionAt(address);
try {
inspectFunction(program, function, state, monitor);
}
catch (Exception e) {
// do nothing
}
}
return true;
}
@Override
public boolean canAnalyze(Program program) {
return Objc1Constants.isObjectiveC(program);
}
/* ************************************************************************** */
/* ************************************************************************** */
private void inspectFunction(Program program, Function function, CurrentState state,
TaskMonitor monitor) {
if (function == null) {
return;
}
InstructionIterator instructionIterator =
program.getListing().getInstructions(function.getBody(), true);
while (instructionIterator.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Instruction instruction = instructionIterator.next();
if (isCallingObjcMsgSend(instruction)) {
String eolComment = instruction.getComment(CommentType.EOL);
if (eolComment != null) {//if a comment already exists, ignore...
continue;
}
markupInstruction(instruction, state, monitor);
}
}
}
private boolean isCallingObjcMsgSend(Instruction instruction) {
if (instruction.getNumOperands() != 1) {
return false;
}
Reference reference = instruction.getPrimaryReference(0);
if (reference == null) {
return false;
}
if (!reference.getReferenceType().isCall() && !reference.getReferenceType().isJump()) {
return false;
}
SymbolTable symbolTable = instruction.getProgram().getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(reference.getToAddress());
return isObjcNameMatch(symbol);
}
private boolean isObjcNameMatch(Symbol symbol) {
String name = symbol.getName();
return name.startsWith(Objc1Constants.OBJC_MSG_SEND) ||
name.equals(Objc1Constants.READ_UNIX2003) ||
name.startsWith("thunk" + Objc1Constants.OBJC_MSG_SEND);
}
private class CurrentState {
Program program;
Namespace globalNamespace;
Namespace selectorNamespace;
Namespace idNamespace;
String currentClassName = null;
String currentMethodName = null;
//Function currentFunction = null;
CurrentState(Program program) {
this.program = program;
globalNamespace = program.getGlobalNamespace();
SymbolTable symbolTable = program.getSymbolTable();
selectorNamespace = findMatchingChildNamespace("@sel", globalNamespace, symbolTable);
idNamespace = findMatchingChildNamespace("@id", globalNamespace, symbolTable);
}
boolean isValid() {
return currentMethodName != null && currentClassName != null;
}
void reset() {
currentClassName = null;
currentMethodName = null;
}
@Override
public String toString() {
return "[" + currentClassName + " " + currentMethodName + "]";
}
private Namespace findMatchingChildNamespace(String namespaceName,
Namespace parentNamespace, SymbolTable symbolTable) {
SymbolIterator it = symbolTable.getSymbols(parentNamespace);
while (it.hasNext()) {
Symbol s = it.next();
if (s.getSymbolType() == SymbolType.NAMESPACE) {
if (namespaceName.equals(s.getName())) {
return (Namespace) s.getObject();
}
}
}
try {
return symbolTable.createNameSpace(parentNamespace, namespaceName,
SourceType.ANALYSIS);
}
catch (InvalidInputException | DuplicateNameException e) {
return null;
}
}
}
private void markupInstruction(Instruction instruction, CurrentState state,
TaskMonitor monitor) {
Address fromAddress = instruction.getMinAddress();
Function function = state.program.getListing().getFunctionContaining(fromAddress);
if (function == null) {
return;
}
state.reset();
InstructionIterator iter = state.program.getListing().getInstructions(fromAddress, false);
while (iter.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Instruction instructionBefore = iter.next();
if (!function.getBody().contains(instructionBefore.getMinAddress())) {
break;//don't look outside of the function
}
if (!isValidInstruction(instructionBefore)) {
continue;
}
Reference[] opRefs = instructionBefore.getOperandReferences(1);
if (opRefs.length != 1) {
continue;
}
Address toAddress = opRefs[0].getToAddress();
MemoryBlock block = state.program.getMemory().getBlock(toAddress);
if (block == null) {
continue;
}
pullNameThrough(state, toAddress, null);
if (state.isValid()) {
instruction.setComment(CommentType.EOL, state.toString());
setReference(fromAddress, state);
break;
}
}
}
// Tries to lay down a reference to the function that is actually being called
private void setReference(Address fromAddress, CurrentState state) {
SymbolTable symbolTable = state.program.getSymbolTable();
Symbol classSymbol = symbolTable.getClassSymbol(state.currentClassName, (Namespace) null);
if (classSymbol == null) {
return;
}
Namespace namespace = (Namespace) classSymbol.getObject();
List<Symbol> functionSymbols = symbolTable.getSymbols(state.currentMethodName, namespace);
if (functionSymbols.size() >= 1) {
Address toAddress = functionSymbols.get(0).getAddress();
ReferenceManager referenceManager = state.program.getReferenceManager();
Reference reference = referenceManager.addMemoryReference(fromAddress, toAddress,
RefType.UNCONDITIONAL_CALL, SourceType.ANALYSIS, 0);
referenceManager.setPrimary(reference, true);
}
}
/**
* Objective-C class and method names are stored in the
* "__cstring" memory block. The strings are referenced
* by either the "class" block or the "message" block.
* The references are through n-levels of pointer indirection
* based on the specific target (x86 vs ppc vs arm).
* This method will pull the string through the pointer indirection
* and set the appropriate value in the current state.
*/
String pullNameThrough(CurrentState state, Address address, Namespace space) {
MemoryBlock block = state.program.getMemory().getBlock(address);
if (block == null) {
return null;
}
if (block.getName().equals(SectionNames.TEXT_CSTRING)) {
return ObjcUtils.createString(state.program, address);
}
Data data = state.program.getListing().getDataAt(address);
if (data == null) {
data = state.program.getListing().getDataContaining(address);
if (data == null) {
return null;
}
data = data.getComponentContaining((int) address.subtract(data.getAddress()));
if (data == null) {
return null;
}
}
Reference[] references = data.getValueReferences();
if (references.length == 0) {
return null;
}
if (address.equals(references[0].getToAddress())) {
return null;//self reference
}
if (isClassBlock(block)) {
space = state.idNamespace;
}
else if (isMessageBlock(block)) {
space = state.selectorNamespace;
}
String name = pullNameThrough(state, references[0].getToAddress(), space);
if (isClassBlock(block)) {
if (state.currentClassName == null) {
state.currentClassName = name;
}
}
else if (isMessageBlock(block)) {
if (state.currentMethodName == null) {
state.currentMethodName = name;
}
}
return name;
}
private boolean isMessageBlock(MemoryBlock block) {
return block.getName().equals(Objc1Constants.OBJC_SECTION_MESSAGE_REFS);
}
private boolean isClassBlock(MemoryBlock block) {
return block.getName().equals(Objc1Constants.OBJC_SECTION_CLASS_REFS) ||
block.getName().equals(Objc1Constants.OBJC_SECTION_CLASS);
}
private boolean isValidInstruction(Instruction instruction) {
if (instruction.getNumOperands() != 2) {
return false;
}
boolean isMOV = instruction.getMnemonicString().equals("MOV");//intel
boolean isLWZ = instruction.getMnemonicString().equals("lwz");//powerpc
boolean isLDR = instruction.getMnemonicString().equals("ldr");//arm
return isMOV || isLWZ || isLDR;
}
}
@@ -1,371 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.analysis.objc;
import java.math.BigInteger;
import ghidra.app.services.*;
import ghidra.app.util.bin.format.macho.SectionNames;
import ghidra.app.util.bin.format.objc.objc1.Objc1Constants;
import ghidra.app.util.bin.format.objc.objc2.Objc2Constants;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class Objc2MessageAnalyzer extends AbstractAnalyzer {
private static final String NAME = "Objective-C 2 Message";
private static final String DESCRIPTION =
"An analyzer for extracting Objective-C 2.0 message information.";
/* ************************************************************************** */
/* ************************************************************************** */
public Objc2MessageAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
setPrototype();
//The Objective-C 2.0 analyzer should always run after the class analyzer.
//It knows the deal!
setPriority(AnalysisPriority.FORMAT_ANALYSIS.after());
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
AddressIterator iterator = set.getAddresses(true);
while (iterator.hasNext()) {
Address address = iterator.next();
Function function = program.getListing().getFunctionAt(address);
try {
inspectFunction(program, function, monitor);
}
catch (Exception e) {
// ignore
}
}
return true;
}
@Override
public boolean canAnalyze(Program program) {
return Objc2Constants.isObjectiveC2(program);
}
/* ************************************************************************** */
/* ************************************************************************** */
private void inspectFunction(Program program, Function function, TaskMonitor monitor) {
if (function == null) {
return;
}
InstructionIterator instructionIterator =
program.getListing().getInstructions(function.getBody(), true);
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
if (isCallingObjcMsgSend(instruction)) {
String eolComment = instruction.getComment(CommentType.EOL);
if (eolComment != null) {//if a comment already exists, ignore...
continue;
}
markupInstruction(program, instruction, monitor);
}
}
}
private boolean isCallingObjcMsgSend(Instruction instruction) {
if (instruction.getNumOperands() != 1) {
return false;
}
Reference reference = instruction.getPrimaryReference(0);
if (reference == null) {
return false;
}
// if (!reference.getReferenceType().isCall() && !reference.getReferenceType().isJump()) {
// return false;
// }
SymbolTable symbolTable = instruction.getProgram().getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(reference.getToAddress());
return isObjcNameMatch(symbol);
}
private boolean isObjcNameMatch(Symbol symbol) {
String name = symbol.getName();
return name.startsWith(Objc1Constants.OBJC_MSG_SEND) ||
name.equals(Objc1Constants.READ_UNIX2003);
}
private void markupInstruction(Program program, Instruction instruction, TaskMonitor monitor) {
Address fromAddress = instruction.getMinAddress();
Function function = program.getListing().getFunctionContaining(fromAddress);
if (function == null) {
return;
}
String currentClass = null;
String currentMethod = null;
InstructionIterator iter = program.getListing().getInstructions(fromAddress, false);
while (iter.hasNext()) {
if (monitor.isCancelled()) {
break;
}
Instruction instructionBefore = iter.next();
if (!function.getBody().contains(instructionBefore.getMinAddress())) {
break;//don't look outside of the function
}
boolean is64bit = program.getDefaultPointerSize() == 8;
boolean isX86 = program.getLanguageID().getIdAsString().equals("x86");
final String CLASS_REGISTER = is64bit ? "x0" : "r0";
final String METHOD_REGISTER = is64bit ? "x1" : "r1";
boolean isRegisterModified = false;
if (isRegisterModified(instructionBefore, CLASS_REGISTER)) {
currentClass = null;
isRegisterModified = true;
}
if (isRegisterModified(instructionBefore, METHOD_REGISTER)) {
currentClass = null;
isRegisterModified = true;
}
if (!isValidInstruction(instructionBefore)) {
if (isRegisterModified) {
break;
}
continue;
}
Object[] firstOperandObjects = instructionBefore.getOpObjects(0);
if (firstOperandObjects.length != 1) {
continue;
}
if (!(firstOperandObjects[0] instanceof Register)) {
continue;
}
Register register = (Register) firstOperandObjects[0];
if (!register.getName().equals(CLASS_REGISTER) &&
!register.getName().equals(METHOD_REGISTER)) {
continue;
}
Object[] secondOperandObjects = instructionBefore.getOpObjects(1);
if (secondOperandObjects.length < 1) {
continue;
}
Address toAddress = null;
if (secondOperandObjects.length == 1 &&
secondOperandObjects[0] instanceof Address addr) {
toAddress = addr;
}
else if (secondOperandObjects.length == 2 &&
secondOperandObjects[0] instanceof Register reg &&
secondOperandObjects[1] instanceof Scalar scalar) {
Address instrAddr = instructionBefore.getAddress();
ProgramContext programContext = program.getProgramContext();
BigInteger registerValue = programContext.getValue(reg, instrAddr, false);
toAddress = instrAddr.getNewAddress(registerValue.longValue() + scalar.getValue());
}
if (toAddress == null) {
continue;
}
MemoryBlock block = program.getMemory().getBlock(toAddress);
if (block == null) {
continue;
}
if (register.getName().equals(CLASS_REGISTER)) {
currentClass = getClassName(program, toAddress);
}
else if (register.getName().equals(METHOD_REGISTER)) {
currentMethod = getMethodName(program, toAddress);
}
if (currentClass != null && currentMethod != null) {
instruction.setComment(CommentType.EOL,
"[" + currentClass + " " + currentMethod + "]");
break;
}
}
}
private boolean isRegisterModified(Instruction instruction, String registerName) {
Object[] destinationOperandObjects = instruction.getOpObjects(0);
if (destinationOperandObjects.length != 1) {
return false;
}
if (!(destinationOperandObjects[0] instanceof Register)) {
return false;
}
Register register = (Register) destinationOperandObjects[0];
if (register.getName().equals(registerName)) {
return true;
}
return false;
}
private String getClassName(Program program, Address toAddress) {
try {
int classPointerValue = program.getMemory().getInt(toAddress);
Address classPointerAddress = toAddress.getNewAddress(classPointerValue);
if (!isObjcClassRefBlock(program, classPointerAddress)) {
return null;
}
Data classPointerData = program.getListing().getDefinedDataAt(classPointerAddress);
Address classAddress = (Address) classPointerData.getValue();
if (!isObjcDataBlock(program, classAddress)) {
return null;
}
Data classData = program.getListing().getDefinedDataAt(classAddress);
Data classRwPointerData = classData.getComponent(4);
Address classRwPointerAddress = (Address) classRwPointerData.getValue();
if (!isObjcConstBlock(program, classRwPointerAddress)) {
return null;
}
Data classRwData = program.getListing().getDefinedDataAt(classRwPointerAddress);
Data classNamePointerData = classRwData.getComponent(4);
Address classNameAddress = (Address) classNamePointerData.getValue();
if (!isCStringBlock(program, classNameAddress)) {
return null;
}
Data classNameData = program.getListing().getDefinedDataAt(classNameAddress);
String className = (String) classNameData.getValue();
return className;
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String getMethodName(Program program, Address toAddress) {
try {
int methodNamePointerValue = program.getMemory().getInt(toAddress);
Address methodNamePointerAddress = toAddress.getNewAddress(methodNamePointerValue);
if (!isObjcSelectorRefBlock(program, methodNamePointerAddress)) {
return null;
}
Data methodNamePointerData =
program.getListing().getDefinedDataAt(methodNamePointerAddress);
Address methodNameAddress = (Address) methodNamePointerData.getValue();
if (!isCStringBlock(program, methodNameAddress)) {
return null;
}
Data methodNameData = program.getListing().getDefinedDataAt(methodNameAddress);
String methodName = (String) methodNameData.getValue();
return methodName;
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
private boolean isValidInstruction(Instruction instruction) {
if (instruction.getNumOperands() != 2) {
return false;
}
boolean isMOV = instruction.getMnemonicString().equals("MOV");//intel
boolean isLWZ = instruction.getMnemonicString().equals("lwz");//powerpc
boolean isLDR = instruction.getMnemonicString().equals("ldr");//arm
return isMOV || isLWZ || isLDR;
}
private boolean isCStringBlock(Program program, Address address) {
MemoryBlock block = program.getMemory().getBlock(address);
if (block != null) {
if (block.getName().equals(SectionNames.TEXT_CSTRING)) {
return true;
}
}
return false;
}
private boolean isObjcSelectorRefBlock(Program program, Address address) {
MemoryBlock block = program.getMemory().getBlock(address);
if (block != null) {
if (block.getName().equals(Objc2Constants.OBJC2_SELECTOR_REFS)) {
return true;
}
}
return false;
}
private boolean isObjcClassRefBlock(Program program, Address address) {
MemoryBlock block = program.getMemory().getBlock(address);
if (block != null) {
if (block.getName().equals(Objc2Constants.OBJC2_CLASS_REFS)) {
return true;
}
}
return false;
}
private boolean isObjcConstBlock(Program program, Address address) {
MemoryBlock block = program.getMemory().getBlock(address);
if (block != null) {
if (block.getName().equals(Objc2Constants.OBJC2_CONST)) {
return true;
}
}
return false;
}
private boolean isObjcDataBlock(Program program, Address address) {
MemoryBlock block = program.getMemory().getBlock(address);
if (block != null) {
if (block.getName().equals(Objc2Constants.OBJC2_DATA)) {
return true;
}
}
return false;
}
}
@@ -15,24 +15,32 @@
*/
package ghidra.app.util.bin.format.objc;
import java.io.IOException;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import org.xml.sax.SAXException;
import generic.jar.ResourceFile;
import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.cmd.function.CreateFunctionCmd;
import ghidra.app.cmd.register.SetRegisterCmd;
import ghidra.app.plugin.processors.sleigh.SleighException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.objc.objc2.Objc2Constants;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.Application;
import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.cmd.Command;
import ghidra.framework.store.LockException;
import ghidra.program.database.SpecExtension;
import ghidra.program.database.SpecExtension.DocInfo;
import ghidra.program.database.symbol.ClassSymbol;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataUtilities.ClearDataMode;
import ghidra.program.model.lang.Processor;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
@@ -43,9 +51,34 @@ import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
import ghidra.xml.XmlParseException;
/**
* Objective-C utilities
*/
public final class ObjcUtils {
/**
* The Objective-C compiler name, used by {@link Program#setCompiler(String)}
*/
public static final String OBJC_COMPILER = "objc";
/**
* The Objective-C {@code _objc_msgSend} stub calling convention name (added with processor
* extension)
*/
public static final String OBJC_MSGSEND_STUBS_CC = "__objc_msgSend_stub";
/**
* String that prefixes Objective-C class symbols
*/
public static final String OBJC_CLASS_SYMBOL_PREFIX = "_OBJC_CLASS_$_";
/**
* String that prefixes Objective-C meta-class symbols
*/
public static final String OBJC_META_CLASS_SYMBOL_PREFIX = "_OBJC_METACLASS_$_";
/**
* {@return the next read index value}
* <p>
@@ -370,4 +403,99 @@ public final class ObjcUtils {
.filter(b -> b.getName().equals(section))
.toList();
}
/**
* {@return true if the given {@link Program} is an Objective-C program; otherwise, false}
* <p>
* NOTE: This method only identifies Mach-O Objective-C programs. ELF Objective-C programs
* produced with GCC use different section names.
*
* @param program The {@link Program} to check
*/
public static boolean isObjc(Program program) {
return isObjc(
Arrays.stream(program.getMemory().getBlocks()).map(MemoryBlock::getName).toList());
}
/**
* {@return true if the given {@link List} of section names contains an Objective-C section
* name; otherwise, false}
* <p>
* NOTE: This method only identifies Mach-O Objective-C programs. ELF Objective-C programs
* produced with GCC use different section names.
*
* @param sectionNames The {@link List} of section names to check
*/
public static boolean isObjc(List<String> sectionNames) {
return sectionNames.stream().anyMatch(n -> n.startsWith(Objc2Constants.OBJC2_PREFIX));
}
/**
* {@return the given name with any Objective-C class prefixes stripped off}
*
* @param name The name to strip
* @see #OBJC_CLASS_SYMBOL_PREFIX
* @see #OBJC_META_CLASS_SYMBOL_PREFIX
*/
public static String stripClassPrefix(String name) {
if (name.startsWith(ObjcUtils.OBJC_CLASS_SYMBOL_PREFIX)) {
return name.substring(ObjcUtils.OBJC_CLASS_SYMBOL_PREFIX.length());
}
if (name.startsWith(ObjcUtils.OBJC_META_CLASS_SYMBOL_PREFIX)) {
return name.substring(ObjcUtils.OBJC_META_CLASS_SYMBOL_PREFIX.length());
}
return name;
}
/**
* Adds Objective-C processor extensions to the {@link Program}, which include:
* <ul>
* <li>A special calling convention used by objc_msgSend stubs</li>
* <li>Call fixups to clear out a lot of Objective-C Automatic Reference Counting (ARC) clutter</li>
* </ul>
*
* @param program The {@link Program} to add the extensions to
* @param monitor A cancelable task monitor
* @return The number of extensions successfully added
* @throws IOException if an IO-related error occurred
* @see <a href="https://doi.org/10.1109/STATIC66697.2025.00005">Heros in Action: Analyzing Objective-C Binaries through Decompilation and IFDS</a>
* @see <a href="https://youtu.be/ojXI7Gio8Pg?si=zcAaZ2KGeBFcAabn">RE//verse 2025: Langs Beyond The C</a>
*/
public static int addExtensions(Program program, TaskMonitor monitor) throws IOException {
Language language = program.getLanguageCompilerSpecPair().getLanguage();
Processor processor = language.getProcessor();
String spath = "extensions/" + OBJC_COMPILER;
int extensionCount = 0;
try {
ResourceFile module =
Application.getModuleDataSubDirectory(processor.toString(), spath);
ResourceFile[] files = module.listFiles();
if (files != null) {
for (ResourceFile file : files) {
InputStream stream = file.getInputStream();
byte[] bytes = stream.readAllBytes();
String xml = new String(bytes);
try {
SpecExtension extension = new SpecExtension(program);
DocInfo docInfo = extension.testExtensionDocument(xml);
if (SpecExtension.getCompilerSpecExtension(program, docInfo) == null) {
extension.addReplaceCompilerSpecExtension(xml, monitor);
extensionCount++;
}
}
catch (SleighException | SAXException | XmlParseException | LockException e) {
Msg.error(ObjcUtils.class,
"Failed to load Objective-C cspec extension: " + file, e);
}
}
}
}
catch (FileNotFoundException e) {
// fall thru
}
return extensionCount;
}
}
@@ -248,7 +248,8 @@ public final class Objc1TypeEncodings {
}
case _C_SEL: {
buffer.deleteCharAt(0);
return createTypeDef("SEL");
return new TypedefDataType("SEL",
PointerDataType.getPointer(new CharDataType(), pointerSize));
}
case _C_CHR: {
buffer.deleteCharAt(0);
@@ -82,6 +82,7 @@ public class Objc2MessageReference extends ObjcTypeMetadataStructure {
Structure struct = new StructureDataType(NAME, 0);
struct.add(new PointerDataType(VOID), pointerSize, "imp", null);
struct.add(new PointerDataType(ASCII), pointerSize, "sel", null);
struct.setCategoryPath(Objc2Constants.CATEGORY_PATH);
return struct;
}
}
@@ -40,6 +40,7 @@ import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
import ghidra.app.util.bin.format.macho.dyld.DyldFixup;
import ghidra.app.util.bin.format.macho.relocation.*;
import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand;
import ghidra.app.util.bin.format.objc.ObjcUtils;
import ghidra.app.util.bin.format.objc.objc1.Objc1Constants;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.options.Options;
@@ -68,6 +69,13 @@ public class MachoProgramBuilder {
public static final String HEADER_SYMBOL = "MACH_HEADER";
/**
* The spacing between symbols that get created in the
* {@link MemoryBlock#EXTERNAL_BLOCK_NAME EXTERNAL block}. This will allow some room for
* the recreation of external method thunks as they are discovered during analysis.
*/
public static final int UNDEFINED_SYMBOL_SPACING = 0x100;
protected MachHeader machoHeader;
protected Program program;
protected ByteProvider provider;
@@ -168,7 +176,7 @@ public class MachoProgramBuilder {
// Perform additional actions
renameObjMsgSendRtpSymbol();
fixupProgramTree(null); // should be done last to account for new memory blocks
setCompiler();
setCompiler(provider.getName());
}
/**
@@ -708,37 +716,37 @@ public class MachoProgramBuilder {
if (monitor.isCancelled()) {
return;
}
if (!(command instanceof SymbolTableCommand)) {
if (!(command instanceof SymbolTableCommand symbolTableCommand)) {
continue;
}
SymbolTableCommand symbolTableCommand = (SymbolTableCommand) command;
List<NList> symbols = symbolTableCommand.getSymbols();
monitor.initialize(symbols.size(), "Collectiing undefined symbols...");
monitor.initialize(symbols.size(), "Collecting undefined symbols...");
for (NList symbol : symbols) {
monitor.increment();
if (symbol.isSymbolicDebugging()) {
continue;
}
if (symbol.isTypeUndefined()) {
List<Symbol> globalSymbols = program.getSymbolTable()
.getLabelOrFunctionSymbols(symbol.getString(), null);
if (globalSymbols.isEmpty()) {//IF IT DOES NOT ALREADY EXIST...
String name = symbol.getString();
List<Symbol> globalSymbols =
program.getSymbolTable().getLabelOrFunctionSymbols(name, null);
if (globalSymbols.isEmpty()) { //IF IT DOES NOT ALREADY EXIST...
undefinedSymbols.add(symbol);
}
}
}
}
if (undefinedSymbols.size() == 0) {
if (undefinedSymbols.isEmpty()) {
return;
}
try {
Address addr = MemoryBlockUtils.addExternalBlock(program,
undefinedSymbols.size() * machoHeader.getAddressSize(), log);
long blockSize = undefinedSymbols.size() * UNDEFINED_SYMBOL_SPACING;
Address addr = MemoryBlockUtils.addExternalBlock(program, blockSize, log);
monitor.initialize(undefinedSymbols.size(), "Processing undefined symbols...");
for (NList symbol : undefinedSymbols) {
monitor.increment();
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
if (name != null && name.length() > 0) {
program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
program.getExternalManager()
@@ -748,7 +756,7 @@ public class MachoProgramBuilder {
catch (Exception e) {
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
}
addr = addr.add(machoHeader.getAddressSize());
addr = addr.add(UNDEFINED_SYMBOL_SPACING);
}
}
catch (Exception e) {
@@ -761,10 +769,9 @@ public class MachoProgramBuilder {
List<LoadCommand> commands = machoHeader.getLoadCommands();
for (LoadCommand command : commands) {
monitor.checkCancelled();
if (!(command instanceof SymbolTableCommand)) {
if (!(command instanceof SymbolTableCommand symbolTableCommand)) {
continue;
}
SymbolTableCommand symbolTableCommand = (SymbolTableCommand) command;
List<NList> symbols = symbolTableCommand.getSymbols();
monitor.initialize(symbols.size(), "Collecting absolute symbols...");
for (NList symbol : symbols) {
@@ -995,8 +1002,7 @@ public class MachoProgramBuilder {
listing.setComment(loadCommandAddr, CommentType.PRE,
LoadCommandTypes.getLoadCommandName(loadCommand.getCommandType()));
if (loadCommand instanceof SegmentCommand) {
SegmentCommand segmentCommand = (SegmentCommand) loadCommand;
if (loadCommand instanceof SegmentCommand segmentCommand) {
listing.setComment(loadCommandAddr, CommentType.EOL,
segmentCommand.getSegmentName());
@@ -1011,51 +1017,43 @@ public class MachoProgramBuilder {
sectionOffset += sectionDataType.getLength();
}
}
else if (loadCommand instanceof DynamicLinkerCommand) {
DynamicLinkerCommand dynamicLinkerCommand = (DynamicLinkerCommand) loadCommand;
else if (loadCommand instanceof DynamicLinkerCommand dynamicLinkerCommand) {
LoadCommandString name = dynamicLinkerCommand.getLoadCommandString();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof DynamicLibraryCommand) {
DynamicLibraryCommand dynamicLibraryCommand =
(DynamicLibraryCommand) loadCommand;
else if (loadCommand instanceof DynamicLibraryCommand dynamicLibraryCommand) {
LoadCommandString name = dynamicLibraryCommand.getDynamicLibrary().getName();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof RunPathCommand) {
RunPathCommand runPathCommand = (RunPathCommand) loadCommand;
else if (loadCommand instanceof RunPathCommand runPathCommand) {
LoadCommandString path = runPathCommand.getPath();
DataUtilities.createData(program, loadCommandAddr.add(path.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - path.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof SubFrameworkCommand) {
SubFrameworkCommand subFrameworkCommand = (SubFrameworkCommand) loadCommand;
else if (loadCommand instanceof SubFrameworkCommand subFrameworkCommand) {
LoadCommandString name = subFrameworkCommand.getUmbrellaFrameworkName();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof SubClientCommand) {
SubClientCommand subClientCommand = (SubClientCommand) loadCommand;
else if (loadCommand instanceof SubClientCommand subClientCommand) {
LoadCommandString name = subClientCommand.getClientName();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof SubLibraryCommand) {
SubLibraryCommand subLibraryCommand = (SubLibraryCommand) loadCommand;
else if (loadCommand instanceof SubLibraryCommand subLibraryCommand) {
LoadCommandString name = subLibraryCommand.getSubLibraryName();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof SubUmbrellaCommand) {
SubUmbrellaCommand subUmbrellaCommand = (SubUmbrellaCommand) loadCommand;
else if (loadCommand instanceof SubUmbrellaCommand subUmbrellaCommand) {
LoadCommandString name = subUmbrellaCommand.getSubUmbrellaFrameworkName();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
@@ -1064,15 +1062,13 @@ public class MachoProgramBuilder {
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof FileSetEntryCommand) {
FileSetEntryCommand fileSetEntryCommand = (FileSetEntryCommand) loadCommand;
else if (loadCommand instanceof FileSetEntryCommand fileSetEntryCommand) {
LoadCommandString name = fileSetEntryCommand.getFileSetEntryId();
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
else if (loadCommand instanceof LinkerOptionCommand) {
LinkerOptionCommand linkerOptionCommand = (LinkerOptionCommand) loadCommand;
else if (loadCommand instanceof LinkerOptionCommand linkerOptionCommand) {
List<String> linkerOptions = linkerOptionCommand.getLinkerOptions();
int offset = linkerOptionCommand.toDataType().getLength();
for (int i = 0; i < linkerOptions.size(); i++) {
@@ -1813,27 +1809,36 @@ public class MachoProgramBuilder {
}
}
protected void setCompiler() throws CancelledException {
// Check for Rust
protected void setCompiler(String source) throws CancelledException {
if (ObjcUtils.isObjc(program)) {
try {
program.setCompiler(ObjcUtils.OBJC_COMPILER);
int count = ObjcUtils.addExtensions(program, monitor);
if (count > 0) {
log.appendMsg("%s: installed %d objc SpecExtensions".formatted(source, count));
}
}
catch (IOException e) {
log.appendMsg("%s: objc error - %s".formatted(source, e.getMessage()));
}
return;
}
try {
SegmentCommand segment = machoHeader.getSegment(SegmentNames.SEG_TEXT);
if (segment == null) {
return;
}
Section section = segment.getSectionByName(SectionNames.TEXT_CONST);
if (section == null) {
return;
}
if (RustUtilities.isRust(program,
Section section =
machoHeader.getSection(SegmentNames.SEG_TEXT, SectionNames.TEXT_CONST);
if (section != null && RustUtilities.isRust(program,
memory.getBlock(space.getAddress(section.getAddress())), monitor)) {
program.setCompiler(RustConstants.RUST_COMPILER);
int extensionCount = RustUtilities.addExtensions(program, monitor,
int count = RustUtilities.addExtensions(program, monitor,
RustConstants.RUST_EXTENSIONS_UNIX);
log.appendMsg("Installed " + extensionCount + " Rust cspec extensions");
if (count > 0) {
log.appendMsg("%s: installed %d rust SpecExtensions".formatted(source, count));
}
}
}
catch (IOException e) {
log.appendMsg("Rust error: " + e.getMessage());
log.appendMsg("%s: Rust error - %s".formatted(source, e.getMessage()));
}
}
@@ -307,6 +307,16 @@ public class SpecExtension {
return options.getString(optionName, null);
}
/**
* Get the raw string making up an extension, given its {@link DocInfo}
* @param program is the program to extract the extension from
* @param docInfo is extension's {@link DocInfo}
* @return the extension string or null
*/
public static String getCompilerSpecExtension(Program program, DocInfo docInfo) {
return getCompilerSpecExtension(program, docInfo.getType(), docInfo.getFormalName());
}
/**
* Check the format version for spec extensions for a given program.
* If the program reports a version that does not match the current
@@ -2,6 +2,14 @@
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/aarch64-pltThunks.xml||GHIDRA||||END|
data/extensions/objc/chkstk_darwin_fixup.xml||GHIDRA||||END|
data/extensions/objc/objc_getProperty_fixup.xml||GHIDRA||||END|
data/extensions/objc/objc_load_fixup.xml||GHIDRA||||END|
data/extensions/objc/objc_msgSend_stub.xml||GHIDRA||||END|
data/extensions/objc/objc_release_fixup.xml||GHIDRA||||END|
data/extensions/objc/objc_retain_fixup.xml||GHIDRA||||END|
data/extensions/objc/objc_setProperty_fixup.xml||GHIDRA||||END|
data/extensions/objc/objc_store_fixup.xml||GHIDRA||||END|
data/languages/AARCH64.cspec||GHIDRA||||END|
data/languages/AARCH64.dwarf||GHIDRA||||END|
data/languages/AARCH64.ldefs||GHIDRA||||END|
@@ -0,0 +1,8 @@
<callfixup name="___chkstk_darwin_fixup">
<target name="___chkstk_darwin"/>
<pcode>
<body><![CDATA[
x0 = x0;
]]></body>
</pcode>
</callfixup>
@@ -0,0 +1,8 @@
<callfixup name="_objc_getProperty_fixup">
<target name="_objc_getProperty"/>
<pcode>
<body><![CDATA[
x0 = *(x0 + x2);
]]></body>
</pcode>
</callfixup>
@@ -0,0 +1,9 @@
<callfixup name="_objc_load_fixup">
<target name="_objc_loadWeak"/>
<target name="_objc_loadWeakRetained"/>
<pcode>
<body><![CDATA[
x0 = *x0;
]]></body>
</pcode>
</callfixup>
@@ -0,0 +1,187 @@
<prototype name="__objc_msgSend_stub" extrapop="0" stackshift="0">
<!-- Used for "_objc_msgSend$methodName" stubs, which don't consider x1 a parameter register-->
<input>
<pentry minsize="8" maxsize="8" storage="hiddenret">
<register name="x8"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q0"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q1"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q2"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q3"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q4"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q5"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q6"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q7"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x0"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x2"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x3"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x4"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x5"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x6"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x7"/>
</pentry>
<pentry minsize="1" maxsize="500" align="8">
<addr offset="0" space="stack"/>
</pentry>
<rule>
<datatype name="homogeneous-float-aggregate"/>
<join_per_primitive storage="float"/>
</rule>
<rule>
<datatype name="homogeneous-float-aggregate"/>
<goto_stack/> <!-- Don't consume general purpose registers -->
<consume_extra storage="float"/> <!-- Once the stack has been used, don't go back to registers -->
</rule>
<rule>
<datatype name="struct" minsize="17"/>
<convert_to_ptr/>
</rule>
<rule>
<datatype name="union" minsize="17"/>
<convert_to_ptr/>
</rule>
<!-- Variadic arguments are passed differently than in the AARCH64 standard -->
<!-- See "Writing ARM64 Code for Apple Platforms" -->
<rule>
<datatype name="any"/>
<varargs first="0"/>
<goto_stack/>
</rule>
<rule>
<datatype name="float"/>
<consume storage="float"/>
</rule>
<rule>
<datatype name="float"/>
<goto_stack/> <!-- Don't consume general purpose registers -->
</rule>
<rule>
<datatype name="any"/>
<join align="true"/> <!-- Chunk from general purpose registers -->
</rule>
</input>
<output>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q0"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q1"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q2"/>
</pentry>
<pentry minsize="1" maxsize="16" storage="float">
<register name="q3"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x0"/>
</pentry>
<pentry minsize="1" maxsize="8" extension="zero">
<register name="x1"/>
</pentry>
<rule>
<datatype name="homogeneous-float-aggregate"/>
<join_per_primitive storage="float"/>
</rule>
<rule>
<datatype name="float"/>
<consume storage="float"/>
</rule>
<rule>
<datatype name="any" minsize="17"/>
<hidden_return voidlock="true"/>
</rule>
<rule>
<datatype name="any"/>
<join/>
</rule>
</output>
<unaffected>
<register name="x19"/>
<register name="x20"/>
<register name="x21"/>
<register name="x22"/>
<register name="x23"/>
<register name="x24"/>
<register name="x25"/>
<register name="x26"/>
<register name="x27"/>
<register name="x28"/>
<register name="x29"/>
<register name="x30"/>
<register name="sp"/>
<!-- vectors -->
<register name="d8"/>
<register name="d9"/>
<register name="d10"/>
<register name="d11"/>
<register name="d12"/>
<register name="d13"/>
<register name="d14"/>
<register name="d15"/>
</unaffected>
<killedbycall>
<register name="x0"/>
<register name="x1"/>
<register name="q0"/>
<!-- x8: indirect result location register, which is not
reflected in the pentry list -->
<register name="x8"/>
<register name="x9"/>
<register name="x10"/>
<register name="x11"/>
<register name="x12"/>
<register name="x13"/>
<register name="x14"/>
<register name="x15"/>
<register name="x16"/>
<register name="x17"/>
<register name="x18"/>
<!-- vectors -->
<register name="d16"/>
<register name="d17"/>
<register name="d18"/>
<register name="d19"/>
<register name="d20"/>
<register name="d21"/>
<register name="d22"/>
<register name="d23"/>
<register name="d24"/>
<register name="d25"/>
<register name="d26"/>
<register name="d27"/>
<register name="d28"/>
<register name="d29"/>
<register name="d30"/>
<register name="d31"/>
</killedbycall>
</prototype>
@@ -0,0 +1,37 @@
<callfixup name="_objc_release_fixup">
<target name="_objc_release"/>
<target name="_objc_release_x0"/>
<target name="_objc_release_x1"/>
<target name="_objc_release_x2"/>
<target name="_objc_release_x3"/>
<target name="_objc_release_x4"/>
<target name="_objc_release_x5"/>
<target name="_objc_release_x6"/>
<target name="_objc_release_x7"/>
<target name="_objc_release_x8"/>
<target name="_objc_release_x9"/>
<target name="_objc_release_x10"/>
<target name="_objc_release_x11"/>
<target name="_objc_release_x12"/>
<target name="_objc_release_x13"/>
<target name="_objc_release_x14"/>
<target name="_objc_release_x15"/>
<target name="_objc_release_x16"/>
<target name="_objc_release_x17"/>
<target name="_objc_release_x18"/>
<target name="_objc_release_x19"/>
<target name="_objc_release_x20"/>
<target name="_objc_release_x21"/>
<target name="_objc_release_x22"/>
<target name="_objc_release_x23"/>
<target name="_objc_release_x24"/>
<target name="_objc_release_x25"/>
<target name="_objc_release_x26"/>
<target name="_objc_release_x27"/>
<target name="_objc_release_x28"/>
<pcode>
<body><![CDATA[
x0 = 0;
]]></body>
</pcode>
</callfixup>
@@ -0,0 +1,46 @@
<callfixup name="_objc_retain_fixup">
<target name="_objc_retain"/>
<target name="_objc_retainAutoreleasedReturnValue"/>
<target name="_objc_retainAutoreleaseReturnValue"/>
<target name="_objc_autoreleaseReturnValue"/>
<target name="_objc_retainAutoRelease"/>
<target name="_objc_autorelease"/>
<target name="_objc_claimAutoreleasedReturnValue"/>
<target name="_objc_opt_self"/>
<target name="_objc_unsafeClaimAutoreleasedReturnValue"/>
<target name="_objc_retainBlock"/>
<target name="_objc_retain_x0"/>
<target name="_objc_retain_x1"/>
<target name="_objc_retain_x2"/>
<target name="_objc_retain_x3"/>
<target name="_objc_retain_x4"/>
<target name="_objc_retain_x5"/>
<target name="_objc_retain_x6"/>
<target name="_objc_retain_x7"/>
<target name="_objc_retain_x8"/>
<target name="_objc_retain_x9"/>
<target name="_objc_retain_x10"/>
<target name="_objc_retain_x11"/>
<target name="_objc_retain_x12"/>
<target name="_objc_retain_x13"/>
<target name="_objc_retain_x14"/>
<target name="_objc_retain_x15"/>
<target name="_objc_retain_x16"/>
<target name="_objc_retain_x17"/>
<target name="_objc_retain_x18"/>
<target name="_objc_retain_x19"/>
<target name="_objc_retain_x20"/>
<target name="_objc_retain_x21"/>
<target name="_objc_retain_x22"/>
<target name="_objc_retain_x23"/>
<target name="_objc_retain_x24"/>
<target name="_objc_retain_x25"/>
<target name="_objc_retain_x26"/>
<target name="_objc_retain_x27"/>
<target name="_objc_retain_x28"/>
<pcode>
<body><![CDATA[
x0 = x0;
]]></body>
</pcode>
</callfixup>
@@ -0,0 +1,12 @@
<callfixup name="_objc_setProperty_fixup">
<target name="_objc_setProperty"/>
<target name="_objc_setProperty_atomic"/>
<target name="_objc_setProperty_atomic_copy"/>
<target name="_objc_setProperty_nonatomic"/>
<target name="_objc_setProperty_nonatomic_copy"/>
<pcode>
<body><![CDATA[
*(x0 + x3) = x2;
]]></body>
</pcode>
</callfixup>
@@ -0,0 +1,9 @@
<callfixup name="_objc_store_fixup">
<target name="_objc_storeStrong"/>
<target name="_objc_storeWeak"/>
<pcode>
<body><![CDATA[
*x0 = x1;
]]></body>
</pcode>
</callfixup>