mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-06 21:09:44 -05:00
Merge remote-tracking branch
'origin/GP-6327_ryanmkurtz_msgSend--SQUASHED' into Ghidra_12.1 (Closes #5938)
This commit is contained in:
-324
@@ -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;
|
||||
}
|
||||
}
|
||||
-371
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+54
-49
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+494
-195
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+10
@@ -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>
|
||||
Reference in New Issue
Block a user