public static List<MatchedFunctions> matchFunctions(Program aProgram, AddressSetView setA,
			Program bProgram, AddressSetView setB, int minimumFunctionSize,
			boolean includeOneToOne, boolean includeNonOneToOne, FunctionHasher hasher,
			TaskMonitor monitor) throws CancelledException {

		Map<Long, Match> functionHashes = new HashMap<>();
		List<MatchedFunctions> functionMatches = new ArrayList<MatchedFunctions>();
		FunctionIterator aProgfIter = aProgram.getFunctionManager().getFunctions(setA, true);
		FunctionIterator bProgfIter = bProgram.getFunctionManager().getFunctions(setB, true);
		monitor.setIndeterminate(false);
		monitor.initialize(2 * (aProgram.getFunctionManager().getFunctionCount() +
			bProgram.getFunctionManager().getFunctionCount()));
		monitor.setMessage("Hashing functions in " + aProgram.getName());

		// Hash functions in program A
		while (!monitor.isCancelled() && aProgfIter.hasNext()) {
			monitor.incrementProgress(1);
			Function func = aProgfIter.next();
			if (!func.isThunk() && func.getBody().getNumAddresses() >= minimumFunctionSize) {
				hashFunction(monitor, functionHashes, func, hasher, true);
			}
		}

		monitor.setMessage("Hashing functions in " + bProgram.getName());
		// Hash functions in Program B
		while (!monitor.isCancelled() && bProgfIter.hasNext()) {
			monitor.incrementProgress(1);
			Function func = bProgfIter.next();
			if (!func.isThunk() && func.getBody().getNumAddresses() >= minimumFunctionSize) {
				hashFunction(monitor, functionHashes, func, hasher, false);
			}
		}

		//Find the remaining hash matches ---> unique code match left and THERE is no symbol that matches
		//in the other program.
		final long progress = monitor.getProgress();
		monitor.setMaximum(progress + functionHashes.size());
		monitor.setProgress(progress);
		monitor.setMessage("Finding function matches");
		for (Match match : functionHashes.values()) {
			monitor.incrementProgress(1);
			if (monitor.isCancelled()) {
				break;
			}
			ArrayList<Address> aProgAddrs = match.aAddresses;
			ArrayList<Address> bProgAddrs = match.bAddresses;
			if ((includeOneToOne && aProgAddrs.size() == 1 && bProgAddrs.size() == 1) ||
				(includeNonOneToOne && !(aProgAddrs.size() == 1 && bProgAddrs.size() == 1))) {
				for (Address aAddr : aProgAddrs) {
					for (Address bAddr : bProgAddrs) {
						MatchedFunctions functionMatch =
							new MatchedFunctions(aProgram, bProgram, aAddr, bAddr,
								aProgAddrs.size(), bProgAddrs.size(), "Code Only Match");
						functionMatches.add(functionMatch);
					}
				}
			}
		}

		return functionMatches;
	}
This class does the work of matching subroutines. Every subroutine
in the current program is hashed and the start address is put into a 
table. There are often identical subroutines which may have the same hash
value. Then the subroutines in the other program are hashed as well. All unique
match pairs are returned as matches. The next step would be to use call graph
information or address order to get additional matches.
Ghidra/Features/Base/src/main/java/ghidra/app/plugin/match/MatchFunctions.java