diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARFMacroScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARFMacroScript.java new file mode 100644 index 0000000000..eaba9c16d1 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/DWARFMacroScript.java @@ -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: + } + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFCompilationUnit.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFCompilationUnit.java index ce94a0baa9..e45cdd14cb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFCompilationUnit.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFCompilationUnit.java @@ -23,6 +23,7 @@ import java.util.Map; import ghidra.app.util.bin.BinaryReader; 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.task.TaskMonitor; @@ -233,6 +234,13 @@ public class DWARFCompilationUnit extends DWARFUnitHeader { 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 * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java index e917ecc9bc..dc980409b4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java @@ -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.funcfixup.DWARFFunctionFixup; 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.golang.rtti.GoSymbolName; import ghidra.app.util.opinion.*; @@ -158,6 +160,7 @@ public class DWARFProgram implements Closeable { private BinaryReader debugAbbrBR; private BinaryReader debugAddr; // v5+ private BinaryReader debugStrOffsets; // v5+ + private BinaryReader debugMacros; // v5+ // dieOffsets, siblingIndexes, parentIndexes contain for each DIE the information needed // 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. private ListValuedMap typeReferers = new ArrayListValuedHashMap<>(); + private Map cachedDWARFLines = new HashMap<>(); + /** * Main constructor for DWARFProgram. *

@@ -250,6 +255,8 @@ public class DWARFProgram implements Closeable { this.debugAddr = getBinaryReaderFor(DWARFSectionNames.DEBUG_ADDR, monitor); this.debugStrOffsets = getBinaryReaderFor(DWARFSectionNames.DEBUG_STROFFSETS, monitor); + this.debugMacros = getBinaryReaderFor(DWARFSectionNames.DEBUG_MACRO, monitor); + this.rangeListTable = new DWARFIndirectTable(this.debugRngLists, DWARFCompilationUnit::getRangeListsBase); this.addressListTable = @@ -374,7 +381,7 @@ public class DWARFProgram implements Closeable { typeReferers.put(typeRef.getOffset(), diea.getOffset()); } } - + monitor.initialize(0, ""); } protected void indexDIEAggregates(LongArrayList aggrTargets, TaskMonitor monitor) @@ -1281,11 +1288,41 @@ public class DWARFProgram implements Closeable { return DWARFLine.empty(); } long stmtListOffset = attrib.getUnsignedValue(); - DWARFLine result = DWARFLine.read(debugLineBR.clone(stmtListOffset), getDefaultIntSize(), - diea.getCompilationUnit()); + return getLine(stmtListOffset, diea.getCompilationUnit(), true); + } + + 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; } + 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 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. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/StringTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/StringTable.java index 21b6104a27..80bd2427be 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/StringTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/StringTable.java @@ -82,7 +82,7 @@ public class StringTable { */ public String getStringAtOffset(long offset) throws IOException { 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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttributeValue.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttributeValue.java index fc491f5848..f6ee22e0cb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttributeValue.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttributeValue.java @@ -4,9 +4,9 @@ * 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. @@ -36,6 +36,10 @@ public abstract class DWARFAttributeValue { return def.getAttributeName(); } + public String getValueString(DWARFCompilationUnit cu) { + return toString(); + } + public String toString(DWARFCompilationUnit compilationUnit) { return toString(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBlobAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBlobAttribute.java index 7c1de70999..0e37cabb7e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBlobAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBlobAttribute.java @@ -15,6 +15,7 @@ */ package ghidra.app.util.bin.format.dwarf.attribs; +import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit; import ghidra.util.NumericUtilities; /** @@ -36,6 +37,11 @@ public class DWARFBlobAttribute extends DWARFAttributeValue { return bytes.length; } + @Override + public String getValueString(DWARFCompilationUnit cu) { + return NumericUtilities.convertBytesToString(bytes, " "); + } + @Override public String toString() { return "%s : %s = [%d]%s".formatted(getAttributeName(), getAttributeForm(), bytes.length, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBooleanAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBooleanAttribute.java index 15544e6634..dd4c3b03b3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBooleanAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFBooleanAttribute.java @@ -4,9 +4,9 @@ * 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. @@ -15,6 +15,8 @@ */ package ghidra.app.util.bin.format.dwarf.attribs; +import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit; + /** * DWARF boolean attribute. */ @@ -30,6 +32,11 @@ public class DWARFBooleanAttribute extends DWARFAttributeValue { return value; } + @Override + public String getValueString(DWARFCompilationUnit cu) { + return "%b".formatted(value); + } + @Override public String toString() { return "%s : %s = %s".formatted(getAttributeName(), getAttributeForm(), getValue()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFNumericAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFNumericAttribute.java index 9f86eb8872..5394c95994 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFNumericAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFNumericAttribute.java @@ -4,9 +4,9 @@ * 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. @@ -95,6 +95,12 @@ public class DWARFNumericAttribute extends DWARFAttributeValue { return value.getValue(); } + @Override + public String getValueString(DWARFCompilationUnit cu) { + long v = getValue(); + return "%d [0x%x]".formatted(v, v); + } + public long getUnsignedValue() { return value.getUnsignedValue(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFStringAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFStringAttribute.java index 14a742e592..c67a9f5d46 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFStringAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFStringAttribute.java @@ -4,9 +4,9 @@ * 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. @@ -32,6 +32,11 @@ public class DWARFStringAttribute extends DWARFAttributeValue { return value; } + @Override + public String getValueString(DWARFCompilationUnit cu) { + return getValue(cu); + } + @Override public String toString() { return "%s : %s = \"%s\"".formatted(getAttributeName(), getAttributeForm(), value); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroHeader.java new file mode 100644 index 0000000000..329faa1a7b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroHeader.java @@ -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> 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> 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 readMacroEntries(BinaryReader reader, + DWARFMacroHeader macroHeader) throws IOException { + List 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> 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> 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 getEntries() throws IOException { + return cu.getProgram().getMacroEntries(this); + } + + public DWARFCompilationUnit getCompilationUnit() { + return cu; + } + + public Map> 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 getEntries() throws IOException { + return List.of(); + } + }; + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroOpcode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroOpcode.java new file mode 100644 index 0000000000..048bc2c175 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroOpcode.java @@ -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. + *

+ * 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> defaultOpcodeOperandMap = + getDefaultOpcodeOperandMap(); + + private static Map> getDefaultOpcodeOperandMap() { + Map> 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 { + + public Def(DWARFMacroOpcode opcode, int rawOpcode, DWARFForm form) { + super(opcode, rawOpcode, form, -1 /* NA */); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroDefine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroDefine.java new file mode 100644 index 0000000000..37f7d1636c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroDefine.java @@ -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 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(); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroEndFile.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroEndFile.java new file mode 100644 index 0000000000..f89c06ce60 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroEndFile.java @@ -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(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroImport.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroImport.java new file mode 100644 index 0000000000..70f357787d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroImport.java @@ -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); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroInfoEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroInfoEntry.java new file mode 100644 index 0000000000..934d2650c6 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroInfoEntry.java @@ -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. + *

+ * 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> opcodeMap = macroHeader.getOpcodeMap(); + + long startOffset = reader.getPointerIndex(); + int rawOpcode = reader.readNextUnsignedByte(); + DWARFMacroOpcode opcode = DWARFMacroOpcode.of(rawOpcode); + List 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 getOperand(int index, Class 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 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(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroStartFile.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroStartFile.java new file mode 100644 index 0000000000..e2edd28037 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroStartFile.java @@ -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(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroUndef.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroUndef.java new file mode 100644 index 0000000000..022b45fcda --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/macro/entry/DWARFMacroUndef.java @@ -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); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/DWARFSectionNames.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/DWARFSectionNames.java index 2b3ff4afce..61253a2deb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/DWARFSectionNames.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/DWARFSectionNames.java @@ -4,9 +4,9 @@ * 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. @@ -32,8 +32,9 @@ public final class DWARFSectionNames { public static final String DEBUG_PUBNAMES = "debug_pubnames"; public static final String DEBUG_PUBTYPES = "debug_pubtypes"; 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[] MINIMAL_DWARF_SECTIONS = { DEBUG_INFO, DEBUG_ABBREV }; } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroParsingTests.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroParsingTests.java new file mode 100644 index 0000000000..acfc2d3757 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/macro/DWARFMacroParsingTests.java @@ -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()); + + } + +}