Merge remote-tracking branch

'origin/GP-6327_ryanmkurtz_msgSend--SQUASHED' into Ghidra_12.1
(Closes #5938)
This commit is contained in:
Ryan Kurtz
2026-03-06 04:55:58 -05:00
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()));
}
}
@@ -15,73 +15,76 @@
*/
package ghidra.app.plugin.core.analysis;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.parallel.*;
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.bin.format.objc.objc2.Objc2Constants;
import ghidra.app.util.bin.format.objc.objc2.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.MachoLoader;
import ghidra.app.util.opinion.MachoProgramBuilder;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.pcode.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
/**
* Analyzes {@code _objc_msgSend} information
*/
public class ObjcMessageAnalyzer extends AbstractAnalyzer {
private static final String NAME = "Objective-C Message Analyzer";
private static final String DESCRIPTION = "Analyzes _objc_msgSend information.";
private static final String NAME = "Objective-C 2 Decompiler Message";
private static final String DESCRIPTION =
"An analyzer for extracting Objective-C 2.0 message information.";
private static final String OPTION_NAME_CALL_OVERRIDE_REFS =
"Use CALL_OVERRIDE_UNCONDITIONAL references";
private static final String OPTION_DESCRIPTION_CALL_OVERRIDE_REFS =
"Applies CALL_OVERRIDE_UNCONDITIONAL references instead of UNCONDITIONAL_CALL references to _objc_msgSend calls. This makes the decompiler look nice.";
private static final String OPTION_NAME_LOG_MESSAGE_FAILURES = "Log message fix failures";
private static final String OPTION_DESCRIPTION_LOG_MESSAGE_FAILURES =
"Log message fix failures during analysis (useful for debugging).";
private final static String STUB_NAMESPACE = "objc_stub";
private final int MAX_RECURSION_DEPTH = 10;
/* ************************************************************************** */
/* ************************************************************************** */
public ObjectiveC2_DecompilerMessageAnalyzer() {
private boolean useCallOverrides = true;
private boolean logMessageFailures = false;
private Objc2TypeMetadata typeMetadata;
private DataTypes dataTypes;
private Map<String, List<Objc2Class>> classMap;
private Map<String, Integer> classExternalSymbolOffset = new HashMap<>();
private record DataTypes(DataType ptr, DataType id, DataType sel, DataType classT,
DataType messageRef, DataType messageRefPtr, DataType objcSuper,
DataType objcSuperPtr) {}
private record Message(String receiver, String selector, Function function, PcodeOpAST op,
int varargParamIndex, Address addr) {}
public ObjcMessageAnalyzer() {
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
setDefaultEnablement(true);
// The Objective-C 2.0 analyzer should always run after the class
// analyzer. And everything
// else apparently.
// It knows the deal!
setPriority(new AnalysisPriority(10000000));
}
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
monitor.initialize(set.getNumAddresses());
AddressIterator iterator = set.getAddresses(true);
ArrayList<Function> functions = new ArrayList<>();
while (iterator.hasNext()) {
if (monitor.isCancelled()) {
break;
}
monitor.incrementProgress(1);
Address address = iterator.next();
Function function = program.getListing().getFunctionAt(address);
if (isFunctionInTextSection(program, function)) {
functions.add(function);
}
}
try {
runDecompilerAnalysis(program, functions, monitor);
}
catch (Exception e) {
// Oh well.
}
return true;
setPriority(AnalysisPriority.DATA_ANALYSIS.before().before());
}
@Override
@@ -89,98 +92,375 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return Objc2Constants.isObjectiveC2(program);
}
/* ************************************************************************** */
/* ************************************************************************** */
private void runDecompilerAnalysis(Program program, List<Function> functions,
TaskMonitor monitor) throws InterruptedException, Exception {
DecompileConfigurer configurer = decompiler -> setupDecompiler(program, decompiler);
DecompilerCallback<Void> callback = new DecompilerCallback<Void>(program, configurer) {
@Override
public Void process(DecompileResults results, TaskMonitor m) throws Exception {
inspectFunction(program, results, monitor);
return null;
}
};
@Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException {
set = set.intersect(program.getMemory().getLoadedAndInitializedAddressSet());
try {
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
if (typeMetadata == null) {
typeMetadata = new Objc2TypeMetadata(program, monitor, log);
classMap = typeMetadata.getClasses()
.stream()
.filter(e -> e.getData() != null)
.collect(Collectors.groupingBy(e -> e.getData().getName()));
}
}
catch (IOException e) {
log.appendMsg("Failed to parse Objective-C type metadata: " + e.getMessage());
return false;
}
if (dataTypes == null) {
dataTypes = getDataTypes(program, log);
if (dataTypes == null) {
return false;
}
}
// Fix __objc_msgSend() function signatures
if (!fixMsgSendSignatures(program, monitor, log)) {
return false;
}
// Set up a standalone decompiler for later use
DecompileConfigurer configurer = d -> setupDecompiler(program, d);
DecompInterface decompiler = new DecompInterface();
configurer.configure(decompiler);
decompiler.openProgram(program);
// Use parallel decompiler to override _objc_msgSend() calls to their proper destinations
DecompilerCallback<Void> callback =
new DecompilerCallback<>(program, configurer) {
@Override
public Void process(DecompileResults results, TaskMonitor m) throws Exception {
fixMsgSendCalls(program, results.getHighFunction(), decompiler, log, monitor);
return null;
}
};
try {
ParallelDecompiler.decompileFunctions(callback, getFunctionsInTextSection(program, set),
monitor);
}
catch (Exception e) {
if (e.getCause() instanceof CancelledException ce) {
throw ce;
}
log.appendException(e);
}
finally {
callback.dispose();
decompiler.closeProgram();
}
return true;
}
@Override
public void registerOptions(Options options, Program program) {
options.registerOption(OPTION_NAME_CALL_OVERRIDE_REFS, OptionType.BOOLEAN_TYPE,
useCallOverrides, null, OPTION_DESCRIPTION_CALL_OVERRIDE_REFS);
options.registerOption(OPTION_NAME_LOG_MESSAGE_FAILURES, OptionType.BOOLEAN_TYPE,
logMessageFailures, null, OPTION_DESCRIPTION_LOG_MESSAGE_FAILURES);
}
@Override
public void optionsChanged(Options options, Program program) {
useCallOverrides = options.getBoolean(OPTION_NAME_CALL_OVERRIDE_REFS, useCallOverrides);
logMessageFailures =
options.getBoolean(OPTION_NAME_LOG_MESSAGE_FAILURES, logMessageFailures);
}
@Override
public void analysisEnded(Program program) {
if (typeMetadata != null) {
typeMetadata.close();
typeMetadata = null;
}
}
private void inspectFunction(Program program, DecompileResults results, TaskMonitor monitor) {
String currentClassName = null;
String currentMethodName = null;
private DataTypes getDataTypes(Program program, MessageLog log) {
// Get the data types that we'll need to use
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
CategoryPath cat = Objc2Constants.CATEGORY_PATH;
int ptrSize = program.getDefaultPointerSize();
DataType ptr = new PointerDataType(null, program.getDefaultPointerSize());
DataType id = dtm.getDataType(cat, "ID");
DataType sel = dtm.getDataType(cat, "SEL");
DataType classT = dtm.getDataType(cat, "class_t");
DataType messageRef = dtm.getDataType(cat, "message_ref");
if (messageRef == null) {
messageRef = id;
}
if (ObjectUtils.anyNull(id, sel, messageRef, classT)) {
log.appendMsg("ERROR: Required Objective-C data type not found in data type manager");
log.appendMsg("Try adding libobjc.dylib");
return null;
}
DataType messageRefPtr = new PointerDataType(messageRef, ptrSize);
StructureDataType objcSuper = new StructureDataType(cat, "objc_super", 0);
objcSuper.add(id, "receiver", null);
objcSuper.add(new PointerDataType(classT, ptrSize), "super_class", null);
DataType objcSuperPtr = new PointerDataType(objcSuper, program.getDefaultPointerSize());
HighFunction highFunction = results.getHighFunction();
if (highFunction == null) {
return;
return new DataTypes(ptr, id, sel, classT, messageRef, messageRefPtr, objcSuper,
objcSuperPtr);
}
private boolean fixMsgSendSignatures(Program program, TaskMonitor monitor, MessageLog log)
throws CancelledException {
for (Function func : program.getFunctionManager().getFunctions(program.getMemory(), true)) {
monitor.checkCancelled();
String name = func.getName();
Namespace global = program.getGlobalNamespace();
boolean isStub = isObjcMsgSendStub(program, func.getEntryPoint());
if (!name.startsWith(Objc1Constants.OBJC_MSG_SEND) && !isStub) {
continue;
}
try {
// Set up the parameter list
List<Parameter> params = new ArrayList<>();
switch (name) {
case "_objc_msgSend":
params.add(new ParameterImpl("self", dataTypes.id, program));
params.add(new ParameterImpl("op", dataTypes.sel, program));
break;
case "_objc_msgSend_fixup":
params.add(new ParameterImpl("self", dataTypes.id, program));
params.add(
new ParameterImpl("message_ref", dataTypes.messageRefPtr, program));
break;
case "_objc_msgSend_stret":
params.add(new ParameterImpl("stretAddr", dataTypes.ptr, program));
params.add(new ParameterImpl("self", dataTypes.id, program));
params.add(new ParameterImpl("op", dataTypes.sel, program));
break;
case "_objc_msgSendSuper":
case "_objc_msgSendSuper2":
params.add(new ParameterImpl("super", dataTypes.objcSuperPtr, program));
params.add(new ParameterImpl("op", dataTypes.sel, program));
break;
case "_objc_msgSendSuper_fixup":
case "_objc_msgSendSuper2_fixup":
params.add(new ParameterImpl("super", dataTypes.objcSuperPtr, program));
params.add(
new ParameterImpl("message_ref", dataTypes.messageRefPtr, program));
break;
case String s when isStub:
params.add(new ParameterImpl("self", dataTypes.id, program));
break;
default:
log.appendMsg("Unsupported _objc_msgSend variant: " + name);
}
// Set up the return value
Variable returnVar = new ReturnParameterImpl(dataTypes.id, program);
// Set up the calling convention
String cc = CompilerSpec.CALLING_CONVENTION_unknown;
if (isStub) {
if (program.getDataTypeManager()
.getCallingConvention(ObjcUtils.OBJC_MSGSEND_STUBS_CC) != null) {
cc = ObjcUtils.OBJC_MSGSEND_STUBS_CC;
}
}
// Update the namespace
func.setParentNamespace(isStub ? getStubsNamespace(program) : global);
// Update the function name
String stubPrefix = Objc1Constants.OBJC_MSG_SEND + "$";
if (isStub && name.startsWith(stubPrefix)) {
func.setName(name.substring(stubPrefix.length()), SourceType.ANALYSIS);
}
// Update the function
func.updateFunction(cc, returnVar, params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
func.setVarArgs(true);
}
catch (DuplicateNameException | InvalidInputException | CircularDependencyException e) {
log.appendMsg("Failed to fix up function signature function for: " + func);
}
}
Function function = results.getFunction();
Iterator<PcodeOpAST> pcodeOps = highFunction.getPcodeOps();
while (pcodeOps.hasNext()) {
if (monitor.isCancelled()) {
return;
}
currentClassName = null;
currentMethodName = null;
PcodeOpAST op = pcodeOps.next();
String mnemonic = op.getMnemonic();
if (mnemonic == null || (!mnemonic.equals("CALL") && !mnemonic.equals("CALLIND"))) {
return true;
}
private List<Message> findMessages(Program program, HighFunction highFunction,
DecompInterface decompiler, TaskMonitor monitor) throws CancelledException {
List<Message> messages = new ArrayList<>();
Function function = highFunction.getFunction();
for (PcodeOpAST op : CollectionUtils.asIterable(highFunction.getPcodeOps())) {
monitor.checkCancelled();
int opcode = op.getOpcode();
if (opcode != PcodeOp.CALL && opcode != PcodeOp.CALLIND) {
continue;
}
Varnode[] inputs = op.getInputs();
if (!isObjcCall(program, inputs[0], monitor)) {
continue;
}
boolean isStret = isStretCall(program, inputs[0], monitor);
for (int i = 1; i < inputs.length; i++) {
String name;
boolean isClass = isClass(i, isStret);
boolean isMessage = isMessage(i, isStret);
name = getNameForVarnode(program, function, inputs[i], isClass, isMessage, 0, 1,
monitor);
if (isClass) {
currentClassName = name;
}
else if (isMessage) {
currentMethodName = name;
}
if (currentClassName != null && currentMethodName != null) {
break;
}
}
if (currentClassName == null || currentMethodName == null) {
Address callTarget = getAddressFromVarnode(program, inputs[0], 0, monitor);
if (!isObjcMsgSendCall(program, inputs[0], callTarget, monitor)) {
continue;
}
int stretParamShift = isStructReturnCall(program, inputs[0], monitor) ? 1 : 0;
boolean isStub = isObjcMsgSendStub(program, callTarget);
Varnode receiverParam = inputs[1 + stretParamShift];
Varnode selectorParam = !isStub ? inputs[2 + stretParamShift] : null;
String receiver =
getNameForVarnode(program, function, receiverParam, true, false, 0, 1, monitor);
String selector = isStub ? processStub(program, callTarget, decompiler, monitor)
: getNameForVarnode(program, function, selectorParam, false, true, 0, 1,
monitor);
if (ObjectUtils.allNotNull(receiver, selector)) {
messages.add(new Message(ObjcUtils.stripClassPrefix(receiver), selector, function,
op, 3 + stretParamShift, callTarget));
}
}
return messages;
}
private String processStub(Program program, Address stubAddr, DecompInterface decompiler,
TaskMonitor monitor) throws CancelledException {
Function func = program.getFunctionManager().getFunctionAt(stubAddr);
DecompileResults results = decompiler.decompileFunction(func, 5, monitor);
HighFunction highFunction = results.getHighFunction();
if (highFunction == null) {
return null;
}
List<Message> messages = findMessages(program, highFunction, decompiler, monitor);
if (messages.isEmpty()) {
return null;
}
String selector = messages.getFirst().selector;
if (func.getName().startsWith("FUN_")) {
try {
func.setName(selector, SourceType.ANALYSIS);
}
catch (InvalidInputException | DuplicateNameException e) {
// oh well, just cosmetic
}
}
return messages.getFirst().selector;
}
private void fixMsgSendCalls(Program program, HighFunction highFunction,
DecompInterface decompiler, MessageLog log, TaskMonitor monitor)
throws CancelledException {
if (highFunction == null) {
return;
}
Function function = highFunction.getFunction();
List<Message> messages = findMessages(program, highFunction, decompiler, monitor);
for (Message msg : messages) {
monitor.checkCancelled();
List<String> parameters = new ArrayList<>();
int paramStart = isStret ? 4 : 3;
Varnode[] inputs = msg.op.getInputs();
int paramStart = msg.varargParamIndex;
for (int i = paramStart; i < inputs.length; i++) {
String paramValue =
getNameForVarnode(program, function, inputs[i], false, false, 0, 1, monitor);
parameters.add(getIvarNameFromQualifiedName(paramValue));
}
setCommentAndReference(program, currentClassName, currentMethodName, op, parameters);
updateExternalBlock(program, msg, log);
setCommentAndReference(program, msg, parameters, log);
}
}
private synchronized void updateExternalBlock(Program program, Message msg, MessageLog log) {
Memory mem = program.getMemory();
FunctionManager funcMgr = program.getFunctionManager();
ExternalManager extMgr = program.getExternalManager();
SymbolTable symbolTable = program.getSymbolTable();
String currentClassName = msg.receiver;
String currentMethodName = msg.selector;
String objcClassName = ObjcUtils.OBJC_CLASS_SYMBOL_PREFIX + currentClassName;
List<Symbol> objcClassSymbols = symbolTable.getGlobalSymbols(objcClassName);
if (objcClassSymbols.isEmpty()) {
objcClassName = ObjcUtils.OBJC_META_CLASS_SYMBOL_PREFIX + currentClassName;
if (objcClassSymbols.isEmpty()) {
if (logMessageFailures) {
log.appendMsg("Couldn't find class symbol for %s".formatted(msg));
}
return;
}
}
if (!mem.getBlock(objcClassSymbols.getFirst().getAddress()).isExternalBlock()) {
return;
}
try {
Symbol classSymbol = symbolTable.getClassSymbol(currentClassName, null);
if (classSymbol == null) {
classSymbol = symbolTable.createClass(null, currentClassName, SourceType.ANALYSIS)
.getSymbol();
}
Namespace classNamespace = (Namespace) classSymbol.getObject();
if (!symbolTable.getSymbols(currentMethodName, classNamespace).isEmpty()) {
return;
}
int offset = classExternalSymbolOffset.getOrDefault(currentClassName, 1);
int max = program.getExecutableFormat().equals(MachoLoader.MACH_O_NAME)
? MachoProgramBuilder.UNDEFINED_SYMBOL_SPACING
: program.getDefaultPointerSize();
if (offset >= max) {
log.appendMsg("No more space reserved in EXTERNAL block to create method: " + msg);
return;
}
Address funcAddr = objcClassSymbols.getFirst().getAddress().add(offset);
Function func = funcMgr.createFunction(currentMethodName, funcAddr,
new AddressSet(funcAddr), SourceType.ANALYSIS);
Symbol externalSymbol = symbolTable.getExternalSymbol(objcClassName);
if (externalSymbol != null) {
ExternalLocation loc = extMgr.addExtLocation(externalSymbol.getParentNamespace(),
currentMethodName, null, SourceType.IMPORTED);
func.setThunkedFunction(loc.createFunction());
}
List<Parameter> params = List.of(new ParameterImpl("self", dataTypes.id, program),
new ParameterImpl("op", dataTypes.sel, program));
Variable returnVar = new ReturnParameterImpl(dataTypes.id, program);
func.updateFunction(CompilerSpec.CALLING_CONVENTION_cdecl, returnVar, params,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
func.setVarArgs(true);
func.setParentNamespace(classNamespace);
classExternalSymbolOffset.put(currentClassName, offset + 1);
}
catch (Exception e) {
log.appendMsg("ERROR: Failed to update EXTERNAL block for %s.%s - %s"
.formatted(currentClassName, currentMethodName, e.getMessage()));
}
}
private void setCommentAndReference(Program program, String currentClassName,
String currentMethodName, PcodeOpAST op, List<String> parameters) {
Address objcCallAddress = op.getSeqnum().getTarget();
private Namespace getStubsNamespace(Program program) {
SymbolTable symTable = program.getSymbolTable();
Namespace global = program.getGlobalNamespace();
Namespace namespace = symTable.getNamespace(STUB_NAMESPACE, global);
if (namespace == null) {
try {
namespace = symTable.createNameSpace(global, STUB_NAMESPACE, SourceType.ANALYSIS);
}
catch (DuplicateNameException | InvalidInputException e) {
return null;
}
}
return namespace;
}
private void setCommentAndReference(Program program, Message msg, List<String> parameters,
MessageLog log) {
Address objcCallAddress = msg.op.getSeqnum().getTarget();
objcCallAddress = getAddressInProgram(program, objcCallAddress.getOffset());
Instruction instruction = program.getListing().getInstructionAt(objcCallAddress);
String currentClassName = msg.receiver;
String currentMethodName = msg.selector;
String fullyQualifiedName = currentClassName;
// If the target is an instance variable, we want to display the
@@ -189,7 +469,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
if (currentClassName.contains("::")) {
currentClassName = getClassNameFromQualifiedName(fullyQualifiedName);
}
setReference(objcCallAddress, program, currentClassName, currentMethodName);
setReference(objcCallAddress, program, currentClassName, currentMethodName, log);
if (instruction == null) {
return;
@@ -220,20 +500,39 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
instruction.setComment(CommentType.EOL, builder.toString());
}
private boolean isObjcCall(Program program, Varnode input, TaskMonitor monitor) {
Address address = getAddressFromVarnode(program, input, 0, monitor);
if (address == null) {
private boolean isObjcMsgSendCall(Program program, Varnode input, Address callTarget,
TaskMonitor monitor) throws CancelledException {
Symbol symbol = getSymbolFromVarnode(program, input, monitor);
if (symbol == null) {
return false;
}
String name = symbol.getName();
if (name.startsWith(Objc1Constants.OBJC_MSG_SEND) ||
name.equals(Objc1Constants.READ_UNIX2003) ||
name.startsWith("thunk" + Objc1Constants.OBJC_MSG_SEND) ||
name.startsWith("PTR_" + Objc1Constants.OBJC_MSG_SEND)) {
return true;
}
return isObjcMsgSendStub(program, callTarget);
}
private boolean isObjcMsgSendStub(Program program, Address addr) {
MemoryBlock block = program.getMemory().getBlock(addr);
return block != null && block.getName().equals(Objc2Constants.OBJC2_STUBS);
}
private boolean isObjcAllocCall(Program program, Varnode input, TaskMonitor monitor)
throws CancelledException {
Symbol symbol = getSymbolFromVarnode(program, input, monitor);
return isObjcNameMatch(symbol);
if (symbol == null) {
return false;
}
String name = symbol.getName();
return name.startsWith("_objc_alloc");
}
private Address getAddressFromVarnode(Program program, Varnode input, int depth,
TaskMonitor monitor) {
if (monitor.isCancelled()) {
return null;
}
TaskMonitor monitor) throws CancelledException {
if (input == null) {
return null;
}
@@ -247,9 +546,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
}
Varnode[] inputs = def.getInputs();
for (Varnode subInput : inputs) {
if (monitor.isCancelled()) {
return null;
}
monitor.checkCancelled();
Address address = getAddressFromVarnode(program, subInput, depth + 1, monitor);
if (address == null) {
continue;
@@ -263,14 +560,14 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return input.getAddress();
}
private Symbol getSymbolFromVarnode(Program program, Varnode input, TaskMonitor monitor) {
private Symbol getSymbolFromVarnode(Program program, Varnode input, TaskMonitor monitor)
throws CancelledException {
Address address = getAddressFromVarnode(program, input, 0, monitor);
if (address == null) {
return null;
}
SymbolTable symbolTable = program.getSymbolTable();
Symbol symbol = symbolTable.getPrimarySymbol(address);
return symbol;
return symbolTable.getPrimarySymbol(address);
}
private String getNameForVarnode(Program program, Function function, Varnode input,
@@ -299,8 +596,8 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return name;
}
Varnode[] inputs = def.getInputs();
if (isObjcCall(program, inputs[0], monitor)) {
Address addr = getAddressFromVarnode(program, inputs[0], 0, monitor);
if (isObjcMsgSendCall(program, inputs[0], addr, monitor)) {
Symbol objcSymbol = getSymbolFromVarnode(program, inputs[0], monitor);
int classIndex = 1;
if (objcSymbol.getName().contains("stret")) {
@@ -318,6 +615,10 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
}
numInputs = 1;
}
else if (isClass && isObjcAllocCall(program, inputs[0], monitor)) {
int classIndex = 1;
inputs = new Varnode[] { inputs[classIndex] };
}
int index = getIndexOfAddress(inputs);
if (index != -1) {
@@ -331,9 +632,6 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
// If a name was found, just unwind the recursion. If it is just
// a constant (ex. when determining parameters) keep looking
// to see if we can find an actual name.
if (name != null && !stringIsLong(name)) {
break;
}
name = getNameForVarnode(program, function, subInput, isClass, isMethod, depth + 1,
inputs.length, monitor);
}
@@ -383,6 +681,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
if (name != null && name.equals("param_1")) {
if (numInputs == 1) {
if (isClass) {
highVar.getDataType();
Namespace namespace = function.getParentNamespace();
if (namespace != null) {
name = namespace.getName();
@@ -400,19 +699,6 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return name;
}
private boolean stringIsLong(String value) {
if (value.startsWith("0x")) {
value = value.substring(2);
}
try {
Long.parseUnsignedLong(value, 16);
}
catch (NumberFormatException e) {
return false;
}
return true;
}
private String getNameFromOffset(Program program, long offset, Varnode input, boolean isClass,
boolean isMethod) {
String name;
@@ -512,7 +798,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
}
}
else {
name = getClassName(program, address);
name = getClassName2(program, address);
if (name == null) {
name = getValueAtAddress(program, address);
}
@@ -653,10 +939,9 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return address;
}
// Tries to lay down a reference to the function that is actually being
// called
// Tries to lay down a reference to the function that is actually being called
private void setReference(Address fromAddress, Program program, String currentClassName,
String currentMethodName) {
String currentMethodName, MessageLog log) {
SymbolTable symbolTable = program.getSymbolTable();
Symbol classSymbol = symbolTable.getClassSymbol(currentClassName, (Namespace) null);
if (classSymbol == null) {
@@ -664,12 +949,53 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
}
Namespace namespace = (Namespace) classSymbol.getObject();
List<Symbol> functionSymbols = symbolTable.getSymbols(currentMethodName, namespace);
if (functionSymbols.isEmpty()) {
// Walk up the superclass chain to see if the method is inherited
List<Objc2Class> classList = classMap.get(namespace.getName());
if (classList.size() == 1) {
Objc2Class superClass = classList.getFirst().getSuperClass();
if (superClass != null) {
Objc2ClassRW data = superClass.getData();
if (data != null) {
setReference(fromAddress, program, data.getName(), currentMethodName, log);
}
}
return;
}
}
if (functionSymbols.size() == 1) {
Address toAddress = functionSymbols.get(0).getAddress();
ReferenceManager referenceManager = program.getReferenceManager();
Reference reference = referenceManager.addMemoryReference(fromAddress, toAddress,
RefType.UNCONDITIONAL_CALL, SourceType.ANALYSIS, 0);
referenceManager.setPrimary(reference, true);
ReferenceManager refMgr = program.getReferenceManager();
FunctionManager funcMgr = program.getFunctionManager();
Reference[] origRefs = refMgr.getReferencesFrom(fromAddress);
Address originalToAddress = origRefs.length > 0
? origRefs[0].getToAddress()
: null;
Address newToAddress = functionSymbols.get(0).getAddress();
Reference reference = refMgr.addMemoryReference(fromAddress, newToAddress,
useCallOverrides ? RefType.CALL_OVERRIDE_UNCONDITIONAL : RefType.UNCONDITIONAL_CALL,
SourceType.ANALYSIS, 0);
refMgr.setPrimary(reference, true);
if (originalToAddress != null && isObjcMsgSendStub(program, originalToAddress)) {
Function func = funcMgr.getFunctionAt(newToAddress);
if (func != null) {
try {
FunctionDefinitionDataType signature =
new FunctionDefinitionDataType(func, true);
ParameterDefinition[] args = signature.getArguments();
if (args.length >= 2 && args[1].getDataType().equals(dataTypes.sel)) {
signature.setArguments(ArrayUtils.remove(args, 1));
}
signature.setCallingConvention(ObjcUtils.OBJC_MSGSEND_STUBS_CC);
HighFunctionDBUtil.writeOverride(funcMgr.getFunctionContaining(fromAddress),
fromAddress, signature);
}
catch (Exception e) {
log.appendException(e);
}
}
}
}
}
@@ -682,13 +1008,13 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
if (symbolName.contains("_OBJC_CLASS_$_")) {
symbolName = symbolName.substring("_OBJC_CLASS_$_".length());
}
else if (symbolName.contains("_objc_msgSend")) {
else if (symbolName.contains(Objc1Constants.OBJC_MSG_SEND)) {
return null;
}
return symbolName;
}
private String getClassName(Program program, Address toAddress) {
private String getClassName2(Program program, Address toAddress) {
try {
boolean is32Bit = false;
@@ -731,42 +1057,22 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return null;
}
private boolean isFunctionInTextSection(Program program, Function function) {
if (function == null) {
return false;
private List<Function> getFunctionsInTextSection(Program program, AddressSetView set) {
List<Function> ret = new ArrayList<>();
Memory mem = program.getMemory();
for (Function function : program.getFunctionManager().getFunctions(set, true)) {
Address address = function.getEntryPoint();
MemoryBlock block = mem.getBlock(address);
if (block != null && block.getName().equals(SectionNames.TEXT)) {
ret.add(function);
}
}
Address address = function.getEntryPoint();
Memory memory = program.getMemory();
MemoryBlock block = memory.getBlock(address);
if (block.getName().equals("__text")) {
return true;
}
return false;
return ret;
}
private boolean isClass(int index, boolean isStret) {
boolean isClass;
if (isStret) {
isClass = index == 2;
}
else {
isClass = index == 1;
}
return isClass;
}
private boolean isMessage(int index, boolean isStret) {
boolean isMessage;
if (isStret) {
isMessage = index == 3;
}
else {
isMessage = index == 2;
}
return isMessage;
}
private boolean isStretCall(Program program, Varnode input, TaskMonitor monitor) {
private boolean isStructReturnCall(Program program, Varnode input, TaskMonitor monitor)
throws CancelledException {
Address address = getAddressFromVarnode(program, input, 0, monitor);
if (address == null) {
return false;
@@ -785,22 +1091,13 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
return false;
}
Function function = program.getListing().getFunctionAt(address);
if (function.getName().equals("_objc_msgSendSuper2")) {
if (function.getName().startsWith("_objc_msgSendSuper2")) {
return true;
}
}
return false;
}
private boolean isObjcNameMatch(Symbol symbol) {
if (symbol == null) {
return false;
}
String name = symbol.getName();
return name.startsWith(Objc1Constants.OBJC_MSG_SEND) ||
name.equals(Objc1Constants.READ_UNIX2003);
}
private boolean isMessageRefsBlock(MemoryBlock block) {
return block.getName().equals(Objc2Constants.OBJC2_MESSAGE_REFS);
}
@@ -834,7 +1131,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
private boolean isDataBlock(MemoryBlock block) {
if (block != null) {
if (block.getName().equals("__data")) {
if (block.getName().equals(SectionNames.DATA)) {
return true;
}
}
@@ -843,7 +1140,8 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
private boolean isObjcDataBlock(MemoryBlock block) {
if (block != null) {
if (block.getName().equals("__objc_data")) {
if (block.getName().equals(Objc2Constants.OBJC2_DATA)) {
return true;
}
}
@@ -877,4 +1175,5 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
options.setEliminateUnreachable(false);
decompiler.setOptions(options);
}
}
@@ -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>