Created
June 17, 2025 23:48
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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