/* ###
 * 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.
 */
// This script propagates Windows external and library function parameter names and types
// It puts the parameter names and types in the comments next to the pushes before a function call.
// It currently does not check for branches in the middle of a series of parameters
//@category Analysis

import java.util.ArrayList;
import java.util.List;

import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.OperandType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;

public class PropagateExternalParametersScript extends GhidraScript {
	private List<PushedParamInfo> results = new ArrayList<>();

	private static final boolean PRINT_OPTYPE = false;

	@Override
	public void run() throws Exception {
		Listing listing = currentProgram.getListing();
		FunctionManager functionManager = currentProgram.getFunctionManager();
		//FunctionIterator externalFunctions = functionManager.getExternalFunctions();

		ReferenceManager refMan = currentProgram.getReferenceManager();

		// iterate over all external symbols
		SymbolTable symTab = currentProgram.getSymbolTable();
		SymbolIterator externalSymbols = symTab.getExternalSymbols();
		while (externalSymbols.hasNext()) {
			Symbol extSym = externalSymbols.next();
			if (extSym.getSymbolType() == SymbolType.FUNCTION) {
				Function extFunc = functionManager.getFunctionAt(extSym.getAddress());
				Parameter[] params = extFunc.getParameters();
				if (params.length == 0) {
					continue;
				}
				Reference[] references = extSym.getReferences();
				processExternalFunction(listing, refMan, references, extFunc, params,
					extSym.getName());
			}
		}

		// use the 'results' to propagate param info to the local variables, data, and params of
		// the calling function
		//println("Processing propagation results - count: " + results.size());
		for (int i = 0; i < results.size(); i++) {
			PushedParamInfo ppi = results.get(i);
			Instruction instr = listing.getInstructionAt(ppi.getAddress());
			int opType = instr.getOperandType(0);

			if (!instr.getOperandRefType(0).isData()) {
				continue;
			}

			//If operand of pushed parameter points to data make a symbol and comment at that location
			if (((opType & OperandType.ADDRESS) != 0) && (((opType & OperandType.DATA) != 0)) ||
				((opType & OperandType.SCALAR) != 0) || ((opType & OperandType.DYNAMIC) != 0)) {
				Reference[] refs = listing.getCodeUnitAt(ppi.getAddress()).getOperandReferences(0);

				if ((refs.length > 0) && (refs[0].isMemoryReference())) {
					Address dataAddress = refs[0].getToAddress();

					DataType dt = null;
					dt = ppi.getDataType();
					Data data = getDataAt(dataAddress);
					boolean isString = false;
					if ((data != null) && data.hasStringValue()) {
						isString = true;
					}

					String symbolName = new String(ppi.getName() + "_" + dataAddress.toString());
					String newComment = new String(
						ppi.getName() + " parameter of " + ppi.getCalledFunctionName() + "\n");

					List<Symbol> symbols = getSymbols(symbolName, null);

					if (symbols.isEmpty() && !isString) {
						createLabel(dataAddress, symbolName, true, SourceType.USER_DEFINED);
					}

					String currentComment = getPlateComment(dataAddress);
					if (currentComment == null) {
						setPlateComment(dataAddress, newComment);
					}
					else if (!currentComment.contains(ppi.getCalledFunctionName())) {
						setPlateComment(dataAddress, currentComment + newComment);
					}

					if ((data != null) &&
						(listing.getCodeUnitAt(dataAddress)
								.getMnemonicString()
								.startsWith(
									"undefined"))) {
						clearListing(dataAddress);
					}
					if (listing.isUndefined(dataAddress, dataAddress.add(dt.getLength() - 1))) {
						try {
							createData(dataAddress, dt);
							printf("Data Created at %s : %s ( %s )\n", dataAddress.toString(),
								newComment.replace("\n", ""), ppi.getAddress().toString());
						}
						catch (Exception e) {
							printf("Error making data: %s", e.toString());
						}
					}

				}

			}

		}

	} // end of run

	private void processExternalFunction(Listing listing, ReferenceManager refMan,
			Reference[] extRefs, Function extFunc, Parameter[] params, String extFuncName) {

		for (Reference extRef : extRefs) {

			Address refAddr = extRef.getFromAddress();
			String refMnemonic = listing.getCodeUnitAt(refAddr).getMnemonicString();

			Function calledFromFunc = listing.getFunctionContaining(refAddr);
			if (calledFromFunc == null) {
				continue;
			}

			if ((refMnemonic.equals(new String("JMP")) && (calledFromFunc.isThunk()))) {
				//println(calledFromFunc.getName() + " is a thunk. Refs are:");
				ReferenceIterator tempIter = refMan.getReferencesTo(calledFromFunc.getEntryPoint());
				while (tempIter.hasNext()) {
					Reference thunkRef = tempIter.next();
					Address thunkRefAddr = thunkRef.getFromAddress();

					CodeUnit cu = listing.getCodeUnitAt(thunkRefAddr);
					if (cu == null) {
						// println("Referenced CodeUnit is null: " + thunkRefAddr);
						continue;
					}
					String thunkRefMnemonic = cu.getMnemonicString();

					Function thunkRefFunc = listing.getFunctionContaining(thunkRefAddr);
					if ((thunkRefMnemonic.equals(new String("CALL")) && (thunkRefFunc != null))) {
						CodeUnitIterator cuIt =
							getCodeUnitsFromFunctionStartToRef(thunkRefFunc, thunkRefAddr);
						if (checkEnoughPushes(cuIt, params.length)) {
							CodeUnitIterator codeUnitsToRef =
								getCodeUnitsFromFunctionStartToRef(thunkRefFunc, thunkRefAddr);
							propogateParams(params, codeUnitsToRef, extFunc.getName());
							println("Processing external function: " + extFuncName + " at " +
								thunkRefAddr.toString());
						}
					}
				}
			}
			else if ((refMnemonic.equals(new String("CALL")))) {// not a thunk

				CodeUnitIterator cuIt = getCodeUnitsFromFunctionStartToRef(calledFromFunc, refAddr);
				if (checkEnoughPushes(cuIt, params.length)) {
					CodeUnitIterator codeUnitsToRef =
						getCodeUnitsFromFunctionStartToRef(calledFromFunc, refAddr);
					propogateParams(params, codeUnitsToRef, extFunc.getName());
					println("Processing external function: " + extFuncName + " at " +
						refAddr.toString());
				}
			}
		}//end of extRef loop
	}

	/*
	 * Function to skip the parameters of a call that is in the middle of the parameters I am
	 * trying to populate. For example:
	 * PUSH arg 4 to call func1           ; put arg 4 of func1 here
	 * PUSH arg 3 to call func1           ; put arg 3 of func1 here
	 * PUSH arg 3 to call func2 ---|
	 * PUSH arg 2 to call func2    |
	 * PUSH arg 1 to call func2	   | -- want to bypass these
	 * CALL func2               ___|
	 * PUSH arg 2 to call func1           ; put arg2 of func1 here
	 * PUSH arg 1 to call func1           ; put arg1 of func1 here
	 * CALL func1
	 */

	// get the number of pushes for a code unit if it is a call
	int numParams(CodeUnit cu) {
		FunctionManager functionManager = currentProgram.getFunctionManager();
		Reference[] opref = cu.getReferencesFrom();

		Address toAddr = null;
		Function f = null;
		int numParams = 0;
		if (opref.length != 0) {
			toAddr = opref[0].getToAddress();

			//f = listing.getFunctionAt(toAddr);
			f = functionManager.getReferencedFunction(toAddr);
			if (f != null) {
				//println("Call in middle at " + cu.getMinAddress().toString() + " " + f.getName());
				Parameter[] prms = f.getParameters();
				numParams = prms.length;

			}
		}
		return numParams;
	}

	CodeUnitIterator getCodeUnitsFromFunctionStartToRef(Function func, Address refAddr) {
		if (func == null) {
			return null;
		}

		Listing listing = currentProgram.getListing();
		AddressSetView funcAddresses = func.getBody();
		CodeUnit referenceCodeUnit = listing.getCodeUnitAt(refAddr);
		Address referenceMinAddress = referenceCodeUnit.getMinAddress();

		CodeUnit previousCodeUnit = listing.getCodeUnitBefore(referenceMinAddress);
		Address previousMinAddress = previousCodeUnit.getMinAddress();
		AddressIterator it = funcAddresses.getAddresses(previousMinAddress, false);
		AddressSet addrSet = new AddressSet();
		while (it.hasNext()) {
			Address addr = it.next();
			addrSet.addRange(addr, addr);
		}
		return listing.getCodeUnits(addrSet, false);
	}

	// this will return true if enough pushes before top of function
	// this will return false if not enough pushes or if not a function
	boolean checkEnoughPushes(CodeUnitIterator cuIterator, int numParams) {

		if (cuIterator == null) {
			return false;
		}

		int numPushes = 0;
		int numSkips = 0;
		while ((cuIterator.hasNext()) && (numPushes < numParams)) {
			CodeUnit cu = cuIterator.next();
			if (numSkips > 0) {
				numSkips--;
			}
			else if (cu.getMnemonicString().equals(new String("CALL"))) {
				numParams += numParams(cu);
			}
			else if (cu.getMnemonicString().equals(new String("PUSH"))) {
				numPushes++;
			}
		}

		if (numPushes >= numParams) {
			return true;
		}
		return false; // not enough params between ref and top of function
	}

	void propogateParams(Parameter[] params, CodeUnitIterator cuIt, String extFuncName) {

		int index = 0;
		int numSkips = 0;
		boolean hasBranch = false;

		while (cuIt.hasNext() && (index < params.length) && !hasBranch) {
			CodeUnit cu = cuIt.next();

			// need to take into account calls between the pushes and skip the pushes for those calls
			// skip pushes that are used for another call

			// if label, then probably a branch, allow current push to be commented and
			// next time through stop
			// can also be a branch if not label there but this case should still have parameters set
			// before it as long as not an unconditional jump - this wouldn't make sense so it shouldn't happen

			if (cu.getLabel() != null) {
				hasBranch = true;
			}

			if (cu.getMnemonicString().equals(new String("CALL"))) {
				numSkips += numParams(cu);
				//printf("numSkips = %d", numSkips);
			}
			else if (cu.getMnemonicString().equals(new String("PUSH"))) {
				if (numSkips > 0) {
					numSkips--;
				}
				else {

					// if option is true add the value of the optype to the EOL comment
					String opType = "";
					if (PRINT_OPTYPE) {
						opType = " " + toHexString(currentProgram.getListing()
								.getInstructionAt(cu.getMinAddress())
								.getOperandType(0),
							false, true);
					}
					setEOLComment(cu.getMinAddress(), params[index].getDataType().getDisplayName() +
						" " + params[index].getName() + " for " + extFuncName + opType);

					addResult(params[index].getName(), params[index].getDataType(),
						cu.getMinAddress(), extFuncName);
					index++;
				}
			}

		}
	}

	// for now all calledFuncNames are extFuncNames
	void addResult(String name, DataType dataType, Address addr, String calledFuncName) {
		PushedParamInfo param = new PushedParamInfo(name, dataType, addr, calledFuncName);
		results.add(param);
	}

	// info about the pushed parameter that gets applied to the calling functions params and locals and referenced data
	private class PushedParamInfo {
		private String name;
		private DataType dataType;
		private Address addr;
		private String calledFunctionName;

		PushedParamInfo(String name, DataType dataType, Address addr, String calledFunctionName) {
			this.name = name;
			this.dataType = dataType;
			this.addr = addr;
			this.calledFunctionName = calledFunctionName;
		}

		String getName() {
			return name;
		}

		DataType getDataType() {
			return dataType;
		}

		Address getAddress() {
			return addr;
		}

		String getCalledFunctionName() {
			return calledFunctionName;
		}
	}
}
