GP-5792 parse DWARF .debug_macro sections

This commit is contained in:
James 2025-07-14 14:18:46 +00:00 committed by dev747368
parent 70e28cf706
commit c3c09f2f17
19 changed files with 947 additions and 15 deletions

View file

@ -0,0 +1,67 @@
/* ###
* 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.
*/
// Parses the .debug_macro section of a file with DWARF debug info (version 5+) and
// prints the result to the ghidra console.
// @category DWARF
import java.io.IOException;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.bin.format.dwarf.*;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroHeader;
import ghidra.app.util.bin.format.dwarf.macro.entry.*;
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider;
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory;
public class DWARFMacroScript extends GhidraScript {
@Override
protected void run() throws Exception {
DWARFSectionProvider dsp =
DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor);
if (dsp == null) {
printerr("Unable to find DWARF information");
return;
}
try (DWARFProgram dprog =
new DWARFProgram(currentProgram, new DWARFImportOptions(), monitor, dsp)) {
dprog.init(monitor);
for (DWARFCompilationUnit cu : dprog.getCompilationUnits()) {
dumpMacros(cu.getMacros(), 0);
}
}
}
void dumpMacros(DWARFMacroHeader macroHeader, int indent) throws IOException {
for (DWARFMacroInfoEntry macroEntry : macroHeader.getEntries()) {
print(macroEntry.toString().indent(indent));
switch (macroEntry) {
case DWARFMacroImport macroImport:
dumpMacros(macroImport.getImportedMacroHeader(), indent + 2);
break;
case DWARFMacroStartFile macroStartFile:
indent += 2;
break;
case DWARFMacroEndFile macroEndFile:
indent -= 2;
break;
default:
}
}
}
}

View file

@ -23,6 +23,7 @@ import java.util.Map;
import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine; import ghidra.app.util.bin.format.dwarf.line.DWARFLine;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroHeader;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -233,6 +234,13 @@ public class DWARFCompilationUnit extends DWARFUnitHeader {
return line; return line;
} }
public DWARFMacroHeader getMacros() {
long macrosOffset = diea.getUnsignedLong(DW_AT_macros, -1);
return macrosOffset != -1
? diea.getProgram().getMacroHeader(macrosOffset, this)
: DWARFMacroHeader.EMTPY;
}
/** /**
* Get the filename that produced the compile unit * Get the filename that produced the compile unit
* *

View file

@ -34,6 +34,8 @@ import ghidra.app.util.bin.format.dwarf.expression.DWARFExpressionException;
import ghidra.app.util.bin.format.dwarf.external.ExternalDebugInfo; import ghidra.app.util.bin.format.dwarf.external.ExternalDebugInfo;
import ghidra.app.util.bin.format.dwarf.funcfixup.DWARFFunctionFixup; import ghidra.app.util.bin.format.dwarf.funcfixup.DWARFFunctionFixup;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine; import ghidra.app.util.bin.format.dwarf.line.DWARFLine;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroHeader;
import ghidra.app.util.bin.format.dwarf.macro.entry.DWARFMacroInfoEntry;
import ghidra.app.util.bin.format.dwarf.sectionprovider.*; import ghidra.app.util.bin.format.dwarf.sectionprovider.*;
import ghidra.app.util.bin.format.golang.rtti.GoSymbolName; import ghidra.app.util.bin.format.golang.rtti.GoSymbolName;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
@ -158,6 +160,7 @@ public class DWARFProgram implements Closeable {
private BinaryReader debugAbbrBR; private BinaryReader debugAbbrBR;
private BinaryReader debugAddr; // v5+ private BinaryReader debugAddr; // v5+
private BinaryReader debugStrOffsets; // v5+ private BinaryReader debugStrOffsets; // v5+
private BinaryReader debugMacros; // v5+
// dieOffsets, siblingIndexes, parentIndexes contain for each DIE the information needed // dieOffsets, siblingIndexes, parentIndexes contain for each DIE the information needed
// to read each DIE and to navigate to parent / child / sibling elements. // to read each DIE and to navigate to parent / child / sibling elements.
@ -195,6 +198,8 @@ public class DWARFProgram implements Closeable {
// In other words, a map of inbound links to a DIEA. // In other words, a map of inbound links to a DIEA.
private ListValuedMap<Long, Long> typeReferers = new ArrayListValuedHashMap<>(); private ListValuedMap<Long, Long> typeReferers = new ArrayListValuedHashMap<>();
private Map<Long, DWARFLine> cachedDWARFLines = new HashMap<>();
/** /**
* Main constructor for DWARFProgram. * Main constructor for DWARFProgram.
* <p> * <p>
@ -250,6 +255,8 @@ public class DWARFProgram implements Closeable {
this.debugAddr = getBinaryReaderFor(DWARFSectionNames.DEBUG_ADDR, monitor); this.debugAddr = getBinaryReaderFor(DWARFSectionNames.DEBUG_ADDR, monitor);
this.debugStrOffsets = getBinaryReaderFor(DWARFSectionNames.DEBUG_STROFFSETS, monitor); this.debugStrOffsets = getBinaryReaderFor(DWARFSectionNames.DEBUG_STROFFSETS, monitor);
this.debugMacros = getBinaryReaderFor(DWARFSectionNames.DEBUG_MACRO, monitor);
this.rangeListTable = this.rangeListTable =
new DWARFIndirectTable(this.debugRngLists, DWARFCompilationUnit::getRangeListsBase); new DWARFIndirectTable(this.debugRngLists, DWARFCompilationUnit::getRangeListsBase);
this.addressListTable = this.addressListTable =
@ -374,7 +381,7 @@ public class DWARFProgram implements Closeable {
typeReferers.put(typeRef.getOffset(), diea.getOffset()); typeReferers.put(typeRef.getOffset(), diea.getOffset());
} }
} }
monitor.initialize(0, "");
} }
protected void indexDIEAggregates(LongArrayList aggrTargets, TaskMonitor monitor) protected void indexDIEAggregates(LongArrayList aggrTargets, TaskMonitor monitor)
@ -1281,11 +1288,41 @@ public class DWARFProgram implements Closeable {
return DWARFLine.empty(); return DWARFLine.empty();
} }
long stmtListOffset = attrib.getUnsignedValue(); long stmtListOffset = attrib.getUnsignedValue();
DWARFLine result = DWARFLine.read(debugLineBR.clone(stmtListOffset), getDefaultIntSize(), return getLine(stmtListOffset, diea.getCompilationUnit(), true);
diea.getCompilationUnit()); }
public DWARFLine getLine(long offset, DWARFCompilationUnit cu, boolean readIfMissing)
throws IOException {
DWARFLine result = cachedDWARFLines.get(offset);
if (result == null && readIfMissing) {
result = DWARFLine.read(debugLineBR.clone(offset), getDefaultIntSize(), cu);
cachedDWARFLines.put(offset, result);
}
return result; return result;
} }
public DWARFMacroHeader getMacroHeader(long offset, DWARFCompilationUnit cu) {
if (debugMacros != null) {
try {
return DWARFMacroHeader.readV5(debugMacros.clone(offset), cu);
}
catch (IOException e) {
// ignore, fall thru return emtpy
}
}
return DWARFMacroHeader.EMTPY;
}
public List<DWARFMacroInfoEntry> getMacroEntries(DWARFMacroHeader macroHeader)
throws IOException {
if (debugMacros == null) {
return List.of();
}
return DWARFMacroHeader.readMacroEntries(
debugMacros.clone(macroHeader.getEntriesStartOffset()), macroHeader);
}
/** /**
* Returns iterable that traverses all {@link DIEAggregate}s in the program. * Returns iterable that traverses all {@link DIEAggregate}s in the program.
* *

View file

@ -82,7 +82,7 @@ public class StringTable {
*/ */
public String getStringAtOffset(long offset) throws IOException { public String getStringAtOffset(long offset) throws IOException {
if (!isValid(offset)) { if (!isValid(offset)) {
throw new IOException("Invalid offset requested " + offset); throw new IOException("Invalid offset requested %d [0x%x]".formatted(offset, offset));
} }
String s = cache.get(offset); String s = cache.get(offset);

View file

@ -36,6 +36,10 @@ public abstract class DWARFAttributeValue {
return def.getAttributeName(); return def.getAttributeName();
} }
public String getValueString(DWARFCompilationUnit cu) {
return toString();
}
public String toString(DWARFCompilationUnit compilationUnit) { public String toString(DWARFCompilationUnit compilationUnit) {
return toString(); return toString();
} }

View file

@ -15,6 +15,7 @@
*/ */
package ghidra.app.util.bin.format.dwarf.attribs; package ghidra.app.util.bin.format.dwarf.attribs;
import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit;
import ghidra.util.NumericUtilities; import ghidra.util.NumericUtilities;
/** /**
@ -36,6 +37,11 @@ public class DWARFBlobAttribute extends DWARFAttributeValue {
return bytes.length; return bytes.length;
} }
@Override
public String getValueString(DWARFCompilationUnit cu) {
return NumericUtilities.convertBytesToString(bytes, " ");
}
@Override @Override
public String toString() { public String toString() {
return "%s : %s = [%d]%s".formatted(getAttributeName(), getAttributeForm(), bytes.length, return "%s : %s = [%d]%s".formatted(getAttributeName(), getAttributeForm(), bytes.length,

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.app.util.bin.format.dwarf.attribs; package ghidra.app.util.bin.format.dwarf.attribs;
import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit;
/** /**
* DWARF boolean attribute. * DWARF boolean attribute.
*/ */
@ -30,6 +32,11 @@ public class DWARFBooleanAttribute extends DWARFAttributeValue {
return value; return value;
} }
@Override
public String getValueString(DWARFCompilationUnit cu) {
return "%b".formatted(value);
}
@Override @Override
public String toString() { public String toString() {
return "%s : %s = %s".formatted(getAttributeName(), getAttributeForm(), getValue()); return "%s : %s = %s".formatted(getAttributeName(), getAttributeForm(), getValue());

View file

@ -95,6 +95,12 @@ public class DWARFNumericAttribute extends DWARFAttributeValue {
return value.getValue(); return value.getValue();
} }
@Override
public String getValueString(DWARFCompilationUnit cu) {
long v = getValue();
return "%d [0x%x]".formatted(v, v);
}
public long getUnsignedValue() { public long getUnsignedValue() {
return value.getUnsignedValue(); return value.getUnsignedValue();
} }

View file

@ -32,6 +32,11 @@ public class DWARFStringAttribute extends DWARFAttributeValue {
return value; return value;
} }
@Override
public String getValueString(DWARFCompilationUnit cu) {
return getValue(cu);
}
@Override @Override
public String toString() { public String toString() {
return "%s : %s = \"%s\"".formatted(getAttributeName(), getAttributeForm(), value); return "%s : %s = \"%s\"".formatted(getAttributeName(), getAttributeForm(), value);

View file

@ -0,0 +1,164 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFForm;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine;
import ghidra.app.util.bin.format.dwarf.macro.entry.DWARFMacroInfoEntry;
import ghidra.program.model.data.LEB128;
/**
* Represents a DWARF Macro Header
*/
public class DWARFMacroHeader {
private static final int OFFSET_SIZE_FLAG_MASK = 0x1;
private static final int DEBUG_LINE_OFFSET_FLAG_MASK = 0x2;
private static final int OPCODE_OPERANDS_TABLE_FLAG_MASK = 0x4;
/**
* Reads a {@code DWARFMacroHeader} from a stream.
*
* @param reader source of bytes
* @param cu {@link DWARFCompilationUnit} that pointed to this macro header
* @return macro header, never null
* @throws IOException if reading fails
*/
public static DWARFMacroHeader readV5(BinaryReader reader, DWARFCompilationUnit cu)
throws IOException {
long startOffset = reader.getPointerIndex();
int version = reader.readNextUnsignedShort();
if (version != 5) {
throw new IllegalArgumentException("Unsupported DWARF Macro version: " + version);
}
int flags = reader.readNextUnsignedByte();
int intSize = (flags & OFFSET_SIZE_FLAG_MASK) == OFFSET_SIZE_FLAG_MASK ? 8 : 4;
DWARFLine line = null;
long debug_line_offset = -1;
if ((flags & DEBUG_LINE_OFFSET_FLAG_MASK) != 0) {
debug_line_offset = reader.readNextUnsignedValue(intSize);
line = cu.getProgram().getLine(debug_line_offset, cu, false);
}
Map<Integer, List<DWARFForm>> opcodeMap = DWARFMacroOpcode.defaultOpcodeOperandMap;
if ((flags & OPCODE_OPERANDS_TABLE_FLAG_MASK) != 0) {
opcodeMap = new HashMap<>(opcodeMap);
readMacroOpcodeTable(reader, opcodeMap);
}
return new DWARFMacroHeader(startOffset, version, flags, debug_line_offset, intSize,
reader.getPointerIndex(), cu, line, opcodeMap);
}
private static void readMacroOpcodeTable(BinaryReader reader,
Map<Integer, List<DWARFForm>> opcodeMap) throws IOException {
// TODO: needs testing with actual data emitted from toolchain
int numOpcodes = reader.readNextUnsignedByte();
for (int i = 0; i < numOpcodes; i++) {
int opcode = reader.readNextUnsignedByte();
int numOperands = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
DWARFForm[] operandForms = new DWARFForm[numOperands];
for (int formIndex = 0; formIndex < numOperands; formIndex++) {
int formInt = reader.readNextUnsignedByte();
operandForms[formIndex] = DWARFForm.of(formInt);
}
opcodeMap.put(opcode, List.of(operandForms));
}
}
public static List<DWARFMacroInfoEntry> readMacroEntries(BinaryReader reader,
DWARFMacroHeader macroHeader) throws IOException {
List<DWARFMacroInfoEntry> results = new ArrayList<>();
DWARFMacroInfoEntry entry;
while ((entry = DWARFMacroInfoEntry.read(reader, macroHeader)) != null) {
results.add(entry);
}
return results;
}
private long startOffset;
private int version;
private int flags;
private long debug_line_offset;
private int intSize;
private long entriesStartOffset;
private Map<Integer, List<DWARFForm>> opcodeMap;
private DWARFCompilationUnit cu;
private DWARFLine line;
public DWARFMacroHeader(long startOffset, int version, int flags, long debug_line_offset,
int intSize, long entriesStartOffset, DWARFCompilationUnit cu, DWARFLine line,
Map<Integer, List<DWARFForm>> opcodeMap) {
this.startOffset = startOffset;
this.version = version;
this.flags = flags;
this.debug_line_offset = debug_line_offset;
this.intSize = intSize;
this.entriesStartOffset = entriesStartOffset;
this.cu = cu;
this.line = line;
this.opcodeMap = opcodeMap;
}
public DWARFLine getLine() {
return line;
}
public long getDebug_line_offset() {
return debug_line_offset;
}
public int getIntSize() {
return intSize;
}
public long getEntriesStartOffset() {
return entriesStartOffset;
}
public List<DWARFMacroInfoEntry> getEntries() throws IOException {
return cu.getProgram().getMacroEntries(this);
}
public DWARFCompilationUnit getCompilationUnit() {
return cu;
}
public Map<Integer, List<DWARFForm>> getOpcodeMap() {
return opcodeMap;
}
@Override
public String toString() {
return "DWARFMacroHeader: startOffset=0x%x, debug_line_offset=0x%x, intSize=%d"
.formatted(startOffset, debug_line_offset, intSize);
}
//---------------------------------------------------------------------------------------------
public static final DWARFMacroHeader EMTPY =
new DWARFMacroHeader(0, 0, 0, 0, 0, 0, null, DWARFLine.empty(), null) {
@Override
public List<DWARFMacroInfoEntry> getEntries() throws IOException {
return List.of();
}
};
}

View file

@ -0,0 +1,105 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro;
import static ghidra.app.util.bin.format.dwarf.attribs.DWARFForm.*;
import java.util.*;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFAttributeDef;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFForm;
/**
* DWARF macro entry opcodes and their expected operand types.
* <p>
* DWARF5
*/
public enum DWARFMacroOpcode {
/** This is not an official opcode in the DWARF standard, but represents the
* entry with opcode 0 that terminates a macro unit.
*/
MACRO_UNIT_TERMINATOR(0, "unknown"),
DW_MACRO_define(0x1, "#define", DW_FORM_udata, DW_FORM_string),
DW_MACRO_undef(0x2, "#undef", DW_FORM_udata, DW_FORM_string),
DW_MACRO_start_file(0x3, "startfile", DW_FORM_udata, DW_FORM_udata),
DW_MACRO_end_file(0x4, "endfile"),
DW_MACRO_define_strp(0x5, "#define", DW_FORM_udata, DW_FORM_strp),
DW_MACRO_undef_strp(0x6, "#undef", DW_FORM_udata, DW_FORM_strp),
DW_MACRO_import(0x7, "#include", DW_FORM_sec_offset),
DW_MACRO_define_sup(0x8, "#define", DW_FORM_udata, DW_FORM_strp_sup),
DW_MACRO_undef_sup(0x9, "#undef", DW_FORM_udata, DW_FORM_strp_sup),
DW_MACRO_import_sup(0xa, "#include", DW_FORM_sec_offset),
DW_MACRO_define_strx(0xb, "#define", DW_FORM_udata, DW_FORM_strx),
DW_MACRO_undef_strx(0xc, "#undef", DW_FORM_udata, DW_FORM_strx);
//DW_MACRO_lo_user(0xe0),
//DW_MACRO_hi_user(0xff);
private final int rawOpcode;
private final String description;
private final DWARFForm[] operandForms;
// enum is small enough that linear search is probably fast enough
private static DWARFMacroOpcode[] lookupValues = values();
DWARFMacroOpcode(int rawOpcode, String description, DWARFForm... operandForms) {
this.rawOpcode = rawOpcode;
this.description = description;
this.operandForms = operandForms;
}
public int getRawOpcode() {
return rawOpcode;
}
public String getDescription() {
return description;
}
public DWARFForm[] getOperandForms() {
return operandForms;
}
public static DWARFMacroOpcode of(int opcodeVal) {
for (DWARFMacroOpcode opcode : lookupValues) {
if (opcode.rawOpcode == opcodeVal) {
return opcode;
}
}
return null;
}
public static final Map<Integer, List<DWARFForm>> defaultOpcodeOperandMap =
getDefaultOpcodeOperandMap();
private static Map<Integer, List<DWARFForm>> getDefaultOpcodeOperandMap() {
Map<Integer, List<DWARFForm>> results = new HashMap<>();
for (DWARFMacroOpcode opcode : DWARFMacroOpcode.values()) {
results.put(opcode.getRawOpcode(), List.of(opcode.getOperandForms()));
}
return Collections.unmodifiableMap(results);
}
public static class Def extends DWARFAttributeDef<DWARFMacroOpcode> {
public Def(DWARFMacroOpcode opcode, int rawOpcode, DWARFForm form) {
super(opcode, rawOpcode, form, -1 /* NA */);
}
}
}

View file

@ -0,0 +1,118 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro.entry;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFStringAttribute;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroHeader;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroOpcode;
import ghidra.util.exception.AssertException;
/**
* Represents a "#define ...." macro element.
*/
public class DWARFMacroDefine extends DWARFMacroInfoEntry {
//@formatter:off
private static final Pattern PARSEMACROREGEX = Pattern.compile(
"([^( ]+)" + // "NAME" group=1
"|([^( ]+) ([^ ()]+)" + // "NAME VALUE" group=2,3
"|([^( ]+)\\(([^)]+)\\) (.*)"); // "NAME(arg, arg) BODY" group=4,5,6
//@formatter:on
public record MacroInfo(String macro, String symbolName, List<String> parameters,
boolean isFunctionLike, String definition) {
@Override
public String toString() {
if (macro.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder("Macro Symbol: ");
sb.append(symbolName);
sb.append(" ");
if (isFunctionLike) {
sb.append("(function-like) ");
sb.append("parameters[%d]: ".formatted(parameters.size()));
for (int i = 0; i < parameters.size(); i++) {
sb.append(parameters.get(i));
if (i != parameters.size() - 1) {
sb.append(",");
}
}
sb.append(" ");
}
else {
sb.append("(object-like) ");
}
sb.append("definition: ");
sb.append(definition.isEmpty() ? "-none-" : "\"%s\"".formatted(definition));
return sb.toString();
}
}
public static MacroInfo parseMacro(String macroString) {
Matcher m = PARSEMACROREGEX.matcher(macroString);
if (!m.matches() || m.group(1) != null) {
return new MacroInfo(macroString, macroString, List.of(), false, "");
}
if (m.group(2) != null) {
return new MacroInfo(macroString, m.group(2), List.of(), false, m.group(3));
}
if (m.group(4) != null) {
return new MacroInfo(macroString, m.group(4), Arrays.asList(m.group(5).split(",")),
true, m.group(6));
}
throw new AssertException();
}
public DWARFMacroDefine(int lineNumber, String defineString, DWARFMacroHeader parent) {
super(DWARFMacroOpcode.DW_MACRO_define, parent);
operandValues[0] = new DWARFNumericAttribute(lineNumber, operandDef(0));
operandValues[1] = new DWARFStringAttribute(defineString, operandDef(1));
}
public DWARFMacroDefine(DWARFMacroInfoEntry other){
super(other);
}
public int getLineNumber() throws IOException {
return getOperand(0, DWARFNumericAttribute.class).getUnsignedIntExact();
}
public String getMacro() throws IOException {
return getOperand(1, DWARFStringAttribute.class).getValue(macroHeader.getCompilationUnit());
}
public MacroInfo getMacroInfo() throws IOException {
return parseMacro(getMacro());
}
@Override
public String toString() {
try {
return "%s: line: %d, %s".formatted(getName(), getLineNumber(), getMacroInfo());
}
catch (IOException e) {
return super.toString();
}
}
}

View file

@ -0,0 +1,31 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro.entry;
/**
* Represents the end of an included source file.
*/
public class DWARFMacroEndFile extends DWARFMacroInfoEntry {
public DWARFMacroEndFile(DWARFMacroInfoEntry other) {
super(other);
}
@Override
public String toString() {
return super.toString();
}
}

View file

@ -0,0 +1,43 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro.entry;
import java.io.IOException;
import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroHeader;
/**
* Represents the inclusion of macro entries from another macro header.
*/
public class DWARFMacroImport extends DWARFMacroInfoEntry {
public DWARFMacroImport(DWARFMacroInfoEntry other) {
super(other);
}
public long getOffset() throws IOException {
return getOperand(0, DWARFNumericAttribute.class).getUnsignedValue();
}
public DWARFMacroHeader getImportedMacroHeader() throws IOException {
long offset = getOffset();
DWARFCompilationUnit cu = macroHeader.getCompilationUnit();
return cu.getProgram().getMacroHeader(offset, cu);
}
}

View file

@ -0,0 +1,159 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro.entry;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.attribs.*;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroHeader;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroOpcode;
import ghidra.app.util.bin.format.dwarf.macro.DWARFMacroOpcode.Def;
/**
* Represents a generic macro info entry, and can contain any macro entry element.
* <p>
* Specific macro entry classes are derived from this and provide getters to ease fetching
* values that are expected for that class. These classes are expected to implement a copy-ctor
* that accepts a DWARFMacroInfoEntry containing the raw data to be wrapped, and must be registered
* in {@link #toSpecializedForm(DWARFMacroInfoEntry)} method's switch() statement.
*/
public class DWARFMacroInfoEntry {
/**
* Reads a DWARF macro info entry from the stream.
*
* @param reader {@link BinaryReader} stream
* @param macroHeader the parent {@link DWARFMacroHeader}
* @return a {@link DWARFMacroInfoEntry}, or subclass if element is a known opcode, or
* {@code null} if the element was the end-of-list marker
* @throws IOException if error reading or unknown opcode
*/
public static DWARFMacroInfoEntry read(BinaryReader reader, DWARFMacroHeader macroHeader)
throws IOException {
Map<Integer, List<DWARFForm>> opcodeMap = macroHeader.getOpcodeMap();
long startOffset = reader.getPointerIndex();
int rawOpcode = reader.readNextUnsignedByte();
DWARFMacroOpcode opcode = DWARFMacroOpcode.of(rawOpcode);
List<DWARFForm> operandForms = opcodeMap.get(rawOpcode);
if (operandForms == null) {
throw new IOException("Unknown DW_MACRO opcode %x at position %d [0x%x]"
.formatted(rawOpcode, startOffset, startOffset));
}
DWARFAttributeValue[] operandValues = new DWARFAttributeValue[operandForms.size()];
for (int i = 0; i < operandForms.size(); i++) {
DWARFForm form = operandForms.get(i);
Def opcodeDef = new DWARFMacroOpcode.Def(opcode, rawOpcode, form);
DWARFFormContext readContext = new DWARFFormContext(reader,
macroHeader.getCompilationUnit(), opcodeDef, macroHeader.getIntSize());
operandValues[i] = form.readValue(readContext);
}
DWARFMacroInfoEntry genericEntry =
new DWARFMacroInfoEntry(opcode, rawOpcode, operandValues, macroHeader);
return toSpecializedForm(genericEntry);
}
public static DWARFMacroInfoEntry toSpecializedForm(DWARFMacroInfoEntry genericEntry) {
return switch (genericEntry.getOpcode()) {
case MACRO_UNIT_TERMINATOR -> null;
case DW_MACRO_define, DW_MACRO_define_strp, DW_MACRO_define_sup, DW_MACRO_define_strx -> new DWARFMacroDefine(
genericEntry);
case DW_MACRO_undef, DW_MACRO_undef_strp, DW_MACRO_undef_sup, DW_MACRO_undef_strx -> new DWARFMacroUndef(
genericEntry);
case DW_MACRO_start_file -> new DWARFMacroStartFile(genericEntry);
case DW_MACRO_end_file -> new DWARFMacroEndFile(genericEntry);
case DW_MACRO_import, DW_MACRO_import_sup -> new DWARFMacroImport(genericEntry);
default -> genericEntry;
};
}
protected DWARFMacroOpcode opcode;
protected int rawOpcode;
protected DWARFAttributeValue[] operandValues;
protected DWARFMacroHeader macroHeader;
protected DWARFMacroInfoEntry(DWARFMacroOpcode opcode, DWARFMacroHeader macroHeader) {
this.opcode = opcode;
this.rawOpcode = opcode.getRawOpcode();
this.macroHeader = macroHeader;
this.operandValues = new DWARFAttributeValue[opcode.getOperandForms().length];
}
public DWARFMacroInfoEntry(DWARFMacroOpcode opcode, int rawOpcode,
DWARFAttributeValue[] operandValues, DWARFMacroHeader macroHeader) {
this.opcode = opcode;
this.rawOpcode = rawOpcode;
this.operandValues = operandValues;
this.macroHeader = macroHeader;
}
protected DWARFMacroInfoEntry(DWARFMacroInfoEntry other) {
this.opcode = other.opcode;
this.rawOpcode = other.rawOpcode;
this.operandValues = other.operandValues;
this.macroHeader = other.macroHeader;
}
public DWARFMacroOpcode getOpcode() {
return opcode;
}
public String getName() {
return opcode != null
? opcode.getDescription()
: "DW_MACRO_unknown[%x]".formatted(rawOpcode);
}
public <T extends DWARFAttributeValue> T getOperand(int index, Class<T> valueClass)
throws IOException {
DWARFAttributeValue val = operandValues[index];
if (valueClass.isInstance(val)) {
return valueClass.cast(val);
}
throw new IOException("Incompatible operand type %s for %s"
.formatted(valueClass.getSimpleName(), toString()));
}
protected DWARFAttributeDef<DWARFMacroOpcode> operandDef(int operandIndex) {
// TODO: we are re-using the opcode's enum as the identity of each operand value, which
// isn't technically correct.
return new DWARFMacroOpcode.Def(opcode, rawOpcode, opcode.getOperandForms()[operandIndex]);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getName());
if (operandValues.length > 0) {
sb.append(": ");
for (int i = 0; i < operandValues.length; i++) {
if (i != 0) {
sb.append(", ");
}
sb.append(operandValues[i].getValueString(macroHeader.getCompilationUnit()));
}
}
return sb.toString();
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro.entry;
import java.io.IOException;
import ghidra.app.util.bin.format.dwarf.DWARFImporter;
import ghidra.app.util.bin.format.dwarf.attribs.DWARFNumericAttribute;
import ghidra.app.util.bin.format.dwarf.line.DWARFFile;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine;
import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.util.SourceFileUtils;
/**
* Represents the start of a source file.
*/
public class DWARFMacroStartFile extends DWARFMacroInfoEntry {
public DWARFMacroStartFile(DWARFMacroInfoEntry other) {
super(other);
}
public int getLineNumber() throws IOException {
return getOperand(0, DWARFNumericAttribute.class).getUnsignedIntExact();
}
public int getFileNumber() throws IOException {
return getOperand(1, DWARFNumericAttribute.class).getUnsignedIntExact();
}
public SourceFile getSourceFile() throws IOException {
int fileIndex = getFileNumber();
DWARFLine dLine = macroHeader.getLine();
DWARFFile dFile = dLine.getFile(fileIndex);
String normalizedPath = SourceFileUtils.normalizeDwarfPath(dFile.getPathName(dLine),
DWARFImporter.DEFAULT_COMPILATION_DIR);
byte[] md5 = dFile.getMD5();
SourceFileIdType type = md5 == null ? SourceFileIdType.NONE : SourceFileIdType.MD5;
return new SourceFile(normalizedPath, type, md5);
}
@Override
public String toString() {
try {
return "%s: line: %d, filenum: %d %s".formatted(getName(), getLineNumber(),
getFileNumber(), getSourceFile());
}
catch (IOException e) {
// ignore
}
return super.toString();
}
}

View file

@ -0,0 +1,27 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro.entry;
/**
* Represents a "#undef" macro element.
*/
public class DWARFMacroUndef extends DWARFMacroDefine {
public DWARFMacroUndef(DWARFMacroInfoEntry other) {
super(other);
}
}

View file

@ -32,6 +32,7 @@ public final class DWARFSectionNames {
public static final String DEBUG_PUBNAMES = "debug_pubnames"; public static final String DEBUG_PUBNAMES = "debug_pubnames";
public static final String DEBUG_PUBTYPES = "debug_pubtypes"; public static final String DEBUG_PUBTYPES = "debug_pubtypes";
public static final String DEBUG_MACINFO = "debug_macinfo"; public static final String DEBUG_MACINFO = "debug_macinfo";
public static final String DEBUG_MACRO = "debug_macro"; // v5+
public static final String DEBUG_ADDR = "debug_addr"; public static final String DEBUG_ADDR = "debug_addr";
public static final String[] MINIMAL_DWARF_SECTIONS = { DEBUG_INFO, DEBUG_ABBREV }; public static final String[] MINIMAL_DWARF_SECTIONS = { DEBUG_INFO, DEBUG_ABBREV };

View file

@ -0,0 +1,76 @@
/* ###
* 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.
*/
package ghidra.app.util.bin.format.dwarf.macro;
import static org.junit.Assert.*;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.util.bin.format.dwarf.DWARFTestBase;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine;
import ghidra.app.util.bin.format.dwarf.macro.entry.DWARFMacroDefine;
import ghidra.app.util.bin.format.dwarf.macro.entry.DWARFMacroDefine.MacroInfo;
public class DWARFMacroParsingTests extends DWARFTestBase {
DWARFMacroHeader header;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
ensureCompUnit();
header = new DWARFMacroHeader(0, 5, 0, 0, 4, 0, cu, DWARFLine.empty(),
DWARFMacroOpcode.defaultOpcodeOperandMap);
}
@Test
public void testDefinedOnly() throws IOException {
DWARFMacroDefine entry = new DWARFMacroDefine(1, "TEST", header);
MacroInfo macroInfo = entry.getMacroInfo();
assertEquals(1, entry.getLineNumber());
assertFalse(macroInfo.isFunctionLike());
assertTrue(macroInfo.parameters().isEmpty());
assertEquals(macroInfo.definition(), StringUtils.EMPTY);
}
@Test
public void testObjectLikeMacro() throws IOException {
DWARFMacroDefine entry = new DWARFMacroDefine(2, "TEST 0x4", header);
MacroInfo macroInfo = entry.getMacroInfo();
assertEquals(2, entry.getLineNumber());
assertFalse(macroInfo.isFunctionLike());
assertTrue(macroInfo.parameters().isEmpty());
assertEquals("0x4", macroInfo.definition());
}
@Test
public void testFunctionLikeMacro() throws IOException {
DWARFMacroDefine entry = new DWARFMacroDefine(3, "SUM(A,B) (A+B)", header);
MacroInfo macroInfo = entry.getMacroInfo();
assertEquals(3, entry.getLineNumber());
assertTrue(macroInfo.isFunctionLike());
assertEquals(2, macroInfo.parameters().size());
assertEquals("A", macroInfo.parameters().get(0));
assertEquals("B", macroInfo.parameters().get(1));
assertEquals("(A+B)", macroInfo.definition());
}
}