Skip to content

Instantly share code, notes, and snippets.

@d00telemental
Created June 17, 2025 23:48
Show Gist options
  • Save d00telemental/c4263d7b0637fdb69e7bc52203054143 to your computer and use it in GitHub Desktop.
Save d00telemental/c4263d7b0637fdb69e7bc52203054143 to your computer and use it in GitHub Desktop.
Script which uses a .map file to symbolize a .xex file loaded into Ghidra by WV's extension.
//Loads an associated .map file into an analyzed .xex assembly.
//@author
//@category
//@keybinding
//@menupath
//@toolbar
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class LoadLinkerMapXEX extends GhidraScript {
@Override
protected void run() throws Exception {
File mapFile = askFile("Provide .map file", "Load");
if (!mapFile.canRead() || !mapFile.getName().endsWith(".map")) {
throw new Exception("expected a .map file");
}
FileInputStream mapStream = new FileInputStream(mapFile);
List<MapSymbol> mapSymbols;
try {
mapSymbols = parseMapFile(mapStream, getMonitor());
} catch (IOException e) {
Msg.error(this, String.format(".map i/o error: %s", e.toString()));
throw e;
} catch (CancelledException e) {
Msg.warn(this, ".map loading cancelled");
return;
}
SymbolTable symbolTable = currentProgram.getSymbolTable();
AddressSpace addressSpace = currentProgram.getAddressFactory().getDefaultAddressSpace();
// This is needed to make Ghidra's bundled Microsoft demangler accept the loaded binary.
// As of 11.3.2, it searches for "Portable Executable (PE)" inside current executable format.
if (currentProgram.getExecutableFormat().contains("Portable Executable (PE)")) {
currentProgram.setExecutableFormat(currentProgram.getExecutableFormat() + " / Portable Executable (PE)");
}
int nAddedCount = 0;
getMonitor().initialize(mapSymbols.size(), "Creating .map symbols");
for (MapSymbol mapSymbolInfo : mapSymbols) {
getMonitor().increment();
try {
Address address = addressSpace.getAddress(mapSymbolInfo.addr);
Symbol symbol = symbolTable.getPrimarySymbol(address);
if (symbol == null) {
symbolTable.createLabel(address, mapSymbolInfo.name, SourceType.IMPORTED);
} else {
symbol.setName(mapSymbolInfo.name, SourceType.IMPORTED);
}
nAddedCount++;
} catch (InvalidInputException e) {
Msg.warn(this, String.format("failed to create symbol '%s': '%s", mapSymbolInfo.name, e.getMessage()));
}
}
Msg.info(this, String.format("added %d symbol(s)", nAddedCount));
}
// Code below is taken with minimal changes from Ghidra's own .map loader:
// https://github.com/NationalSecurityAgency/ghidra/blob/cd5e163c0a5d780da785d973e7025dd74f43cee7/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MapLoader.java
// ----------------------------------------
private record MapSymbol(String name, long addr) {}
private List<MapSymbol> parseMapFile(InputStream stream, TaskMonitor localMonitor)
throws IOException, CancelledException
{
List<MapSymbol> symbols = new ArrayList<>();
localMonitor.setMessage("Parsing MAP file...");
localMonitor.setIndeterminate(true);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
localMonitor.checkCancelled();
lineNumber++;
line = line.trim();
if (line.startsWith(";")) {
continue;
}
if (line.contains("Publics by Value")) {
lineNumber = parseMapFileSection(reader, symbols, lineNumber, localMonitor);
} else if (line.startsWith("Static symbols")) {
lineNumber = parseMapFileSection(reader, symbols, lineNumber, localMonitor);
}
}
}
return symbols;
}
private int parseMapFileSection(BufferedReader reader, List<MapSymbol> list, int lineNumber,
TaskMonitor localMonitor)
throws IOException, CancelledException
{
String line;
boolean added = false;
while ((line = reader.readLine()) != null) {
localMonitor.checkCancelled();
lineNumber++;
line = line.trim();
if (line.startsWith(";")) {
continue;
}
if (!line.isEmpty()) {
try {
list.add(parseMapSymbol(line, lineNumber));
added = true;
} catch (ParseException e) {
Msg.warn(this, String.format("invalid .map line: %s", line));
}
}
else if (added) {
break;
}
}
return lineNumber;
}
private MapSymbol parseMapSymbol(String line, int lineNumber)
throws ParseException
{
String[] parts = line.split("\\s+", 4);
if (parts.length < 3) {
throw new ParseException(
"Line %d has less than 3 fields (%d)"
.formatted(lineNumber, parts.length),
lineNumber);
}
try {
return new MapSymbol(parts[1], Long.parseLong(parts[2], 16));
} catch (NumberFormatException e) {
throw new ParseException(
"Line %d address '%s' could not be converted to a number"
.formatted(lineNumber, parts[2]),
lineNumber);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment