@Override
	public void markup(Program program, boolean isBinary, TaskMonitor monitor, MessageLog log,
			NTHeader ntHeader)
			throws DuplicateNameException, CodeUnitInsertionException, IOException {

		int methodRowIndex = 0;
		for (CliAbstractTableRow method : rows) {
			methodRowIndex++;

			CliMethodDefRow methodRow = (CliMethodDefRow) method;

			// This indicates the method is abstract, runtime, or PInvokeImpl
			if (methodRow.RVA == 0) {
				continue;
			}

			Address addr = PeUtils.getMarkupAddress(program, isBinary, ntHeader, methodRow.RVA);
			Address startAddr = addr;
			Address endAddr = addr;

			if (methodRow.isPInvokeImpl() && methodRow.isNative()) {
				endAddr = startAddr.add(CLITABLEMETHODDEF_PINVOKE_JUMP_LENGTH - 1);
			}
			else {
				// Create MethodDef at this RVA
				BinaryReader reader =
					new BinaryReader(new MemoryByteProvider(program.getMemory(), addr),
						!program.getMemory().isBigEndian());
				CliMethodDef methodDef = new CliMethodDef(addr, reader);

				DataType methodDefDataType = methodDef.toDataType();
				PeUtils.createData(program, addr, methodDefDataType, log);

				// Get the function's address space, default to zero-length just in case
				startAddr = addr.add(methodDefDataType.getLength());
				endAddr = startAddr;
				if (methodDef.getMethodSize() > 0) {
					endAddr = startAddr.add(methodDef.getMethodSize() - 1);
				}

				// Do extra data sections in MethodDef
				if (methodDef.hasMoreSections()) {
					int extraSectionOffset =
						methodDefDataType.getLength() + methodDef.getMethodSize();

					// Round up to the next offset divisible by 4
					extraSectionOffset = ((extraSectionOffset + 3) / 4) * 4;

					reader.setPointerIndex(extraSectionOffset);
					CliMethodExtraSections extraSections = new CliMethodExtraSections(reader);
					Address extraSectionAddr = addr.add(extraSectionOffset);
					PeUtils.createData(program, extraSectionAddr, extraSections.toDataType(), log);
				}
			}

			AddressSetView funcAddrSet = new AddressSet(startAddr, endAddr);

			// Let Ghidra assign a default function name and then try to decode the
			// real one if it exists
			String funcName = null;
			if (methodRow.nameIndex > 0) {
				funcName = SymbolUtilities.replaceInvalidChars(
					metadataStream.getStringsStream().getString(methodRow.nameIndex), true);
			}

			// Get the function signature blob
			CliBlob blob = metadataStream.getBlobStream().getBlob(methodRow.sigIndex);
			Address sigAddr = CliAbstractStream.getStreamMarkupAddress(program, isBinary, monitor,
				log, ntHeader, metadataStream.getBlobStream(), methodRow.sigIndex);

			// Get the return type from the function signature
			CliSigMethodDef methodSig = new CliSigMethodDef(blob);
			metadataStream.getBlobStream().updateBlob(methodSig, sigAddr, program);
			DataType returnType = methodSig.getReturnType().getExecutionDataType();

			int maxSequence = 0;
			int stackOffset = 0;
			CliParam paramTypes[] = methodSig.getParamTypes();
			int paramCount = paramTypes.length;
			CliTableParam paramTable = (CliTableParam) metadataStream.getTable(CliTypeTable.Param);

			// Store the parameters in a Hashtable because by the time processing
			// finishes the number of actual parameters might change
			HashMap<Integer, ParameterImpl> parameterList = new HashMap<Integer, ParameterImpl>();

			// Some Static function first parameters being pointers to a ValueType
			// have the same number of parameters specified, but one or more are implied
			// pointers to the object they're associated with. It's not in the Parameter
			// Table and looking for the extra parameter in the table will walk you
			// into the parameter list of another function.
			ParameterImpl staticParameter = null;
			if (methodRow.isStatic() && paramCount > 0) {
				CliParam staticParam = paramTypes[0];
				String paramName = "";

				// Walk the path from the ELEMENT_TYPE_PTR to the ELEMENT_TYPE_VALUETYPE
				if (staticParam.getType() instanceof CliTypePtr) {
					CliTypePtr ptrToValueType = (CliTypePtr) staticParam.getType();
					if (ptrToValueType.getType() instanceof CliTypeValueType) {
						CliTypeValueType valueType = (CliTypeValueType) ptrToValueType.getType();

						// Get the table and row specifying the type name
						CliTypeTable tableType = valueType.getTable();
						int rowIndex = valueType.getRowIndex();

						int paramNameStringIndex = 0;
						CliAbstractTable table = metadataStream.getTable(tableType);
						CliAbstractTableRow row = table.getRow(rowIndex);
						if (tableType.id() == tableType.TypeDef.id()) {
							CliTypeDefRow typeDefRow = (CliTypeDefRow) row;
							paramNameStringIndex = typeDefRow.typeNameIndex;
						}
						else if (tableType.id() == tableType.TypeRef.id()) {
							CliTypeRefRow typeRefRow = (CliTypeRefRow) row;
							paramNameStringIndex = typeRefRow.typeNameIndex;
						}

						if (paramNameStringIndex > 0) {
							paramName =
								metadataStream.getStringsStream().getString(paramNameStringIndex);
							paramName = SymbolUtilities.replaceInvalidChars(paramName, true);

							DataType dataType = staticParam.getExecutionDataType();

							try {
								staticParameter =
									new ParameterImpl(paramName, dataType, stackOffset, program);
							}
							catch (InvalidInputException e) {
								Msg.warn(this, "Error processing parameter \"" + paramName +
									"\" in function \"" + funcName + "\": " + e.getMessage());
							}

							stackOffset += dataType.getLength();

							paramCount--;
						}
					}
				}
			}

			// Pull apart the function's Param table entries
			for (int i = 0; i < paramCount; i++) {
				CliParamRow paramRow = (CliParamRow) paramTable.getRow(methodRow.paramIndex + i);

				if (paramRow.sequence > maxSequence) {
					maxSequence = paramRow.sequence;
				}

				String paramName = SymbolUtilities.replaceInvalidChars(
					metadataStream.getStringsStream().getString(paramRow.nameIndex), true);

				DataType dataType = paramTypes[i].getExecutionDataType();

				if (paramRow.sequence == 0) {
					// Parameters with a 0 sequence number are the return type,
					// reduce the size of the array and put any previously discovered
					// parameters into it
					returnType = dataType;
				}
				else {
					// Parameters are placed in the proper order based on the sequence
					// field (1-based) to compensate for some static methods having an implied
					// first parameter that won't be represented in the Parameter Table
					// and some return types that are represented as parameters.
					try {
						parameterList.put((int) paramRow.sequence,
							new ParameterImpl(paramName, dataType, stackOffset, program));
					}
					catch (InvalidInputException e) {
						Msg.warn(this, "Error processing parameter \"" + paramName +
							"\" in function \"" + funcName + "\": " + e.getMessage());
					}

					stackOffset += dataType.getLength();
				}
			}

			ParameterImpl[] parameters = new ParameterImpl[maxSequence];
			parameterList.forEach((key, value) ->
			// Sequences are 1-based
			parameters[key - 1] = value);

			// For static functions, fill in the pointer to ValueType
			// parameters that are implied before the actual parameters
			if (methodRow.isStatic()) {
				if (staticParameter != null) {
					for (int i = 0; i < parameters.length; i++) {
						if (parameters[i] == null) {
							ParameterImpl param = null;
							try {
								param = new ParameterImpl(staticParameter.getName() + i,
									staticParameter.getDataType(), staticParameter.getStackOffset(),
									staticParameter.getProgram());
							}
							catch (InvalidInputException e1) {
								Msg.warn(this,
									"Couldn't clone " + staticParameter.getName() +
										" implied static function parameter in function : " +
										funcName + "in position " + i);
							}
							parameters[i] = param;
						}
					}
				}
			}

			FunctionManager funcMgr = program.getFunctionManager();

			try {
				// Create the function, if already created, update the existing function
				Function func = funcMgr.getFunctionAt(startAddr);
				if (func == null) {
					func = funcMgr.createFunction(funcName, startAddr, funcAddrSet,
						SourceType.ANALYSIS);
				}
				func.setReturnType(returnType, SourceType.ANALYSIS);
				func.updateFunction(null, null, FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true,
					SourceType.ANALYSIS, parameters);

				markToPreventIncorrectProcessorDisassembly(program, methodRow, startAddr, endAddr);
			}
			catch (CodeUnitInsertionException e) {
				// Ignore, something there already
			}
			catch (NullPointerException e) {
				Msg.warn(this, "Error processing function \"" + funcName + "\" (" + methodRowIndex +
					"): Bad parameters provided");
			}
			catch (InvalidInputException e) {
				Msg.warn(this, "Error processing function \" (\" + methodRowIndex + \")" +
					funcName + "\": Invalid function");
			}
			catch (OverlappingFunctionException e) {
				String err = "Error processing function \" (\" + methodRowIndex + \")" + funcName +
					"\": Overlapping function (" + startAddr + ", " + endAddr + ": ";

				Function existingFuncA = funcMgr.getFunctionContaining(startAddr);
				Function existingFuncB = funcMgr.getFunctionContaining(endAddr);

				if (existingFuncA != null && existingFuncB == null) {
					err = err + existingFuncA.getName();
				}
				else if (existingFuncA == null && existingFuncB != null) {
					err = err + existingFuncB.getName();
				}
				else if (existingFuncA != null && existingFuncA == existingFuncB) {
					err = err + existingFuncA.getName();
				}

				err = err + ")";

				Msg.warn(this, err);
			}
			catch (DuplicateNameException e) {
				String paramNames = "";
				for (int i = 0; i < parameters.length - 1; i++) {
					paramNames += parameters[i].getName() + ", ";
				}
				paramNames += parameters[parameters.length - 1].getName();
				Msg.warn(this, "Error processing function \"" + funcName + "\" (" + methodRowIndex +
					"): Duplicate parameter name (" + paramNames + ")");
			}
		}
	}
Describes the MethodDef table. Each row represents a method in a specific class. Each row is stored one after the other grouped by class.
References to the MethodDef table are coded to indicate where the methods for a class start and end.
Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pe/cli/tables/CliTableMethodDef.java