From e3b75fe9f70852a07e9b68765a09a648f4a1279b Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:45:20 -0400 Subject: [PATCH] GP-4193 DWARFLine support, remove DWARF Line analyzer, misc fixes Refactor the old DWARF line code used by the line analyzer. Fix v4 location list reading when there was an empty range entry. Defer applying the ELF program address fixup value until a DWARF address(held in a long) is converted into a Ghidra address. Fix v5 location list reading - add missing DW_LLE_default_location impl. --- .../ghidra_scripts/DWARFLineInfoScript.java | 76 ++++ .../analysis/DwarfLineNumberAnalyzer.java | 176 -------- .../util/bin/format/dwarf/DIEAggregate.java | 29 +- .../bin/format/dwarf/DWARFAbbreviation.java | 4 +- .../format/dwarf/DWARFCompilationUnit.java | 60 +-- .../util/bin/format/dwarf/DWARFFunction.java | 14 +- .../format/dwarf/DWARFFunctionImporter.java | 47 +- .../bin/format/dwarf/DWARFImportOptions.java | 20 + .../util/bin/format/dwarf/DWARFImporter.java | 32 ++ .../app/util/bin/format/dwarf/DWARFLine.java | 424 ------------------ .../bin/format/dwarf/DWARFLocationList.java | 56 ++- .../util/bin/format/dwarf/DWARFProgram.java | 52 ++- .../util/bin/format/dwarf/DWARFRangeList.java | 17 +- .../bin/format/dwarf/DWARFUnitHeader.java | 34 +- .../util/bin/format/dwarf/DWARFVariable.java | 4 +- .../bin/format/dwarf/DwarfSectionNames.java | 98 ---- .../format/dwarf/attribs/DWARFAttribute.java | 4 +- .../util/bin/format/dwarf/line/DWARFFile.java | 157 +++++++ .../util/bin/format/dwarf/line/DWARFLine.java | 354 +++++++++++++++ .../{ => line}/DWARFLineContentType.java | 2 +- .../format/dwarf/line/DWARFLineException.java | 38 ++ .../line/DWARFLineNumberExtendedOpcodes.java | 33 ++ .../line/DWARFLineNumberStandardOpcodes.java | 37 ++ .../dwarf/line/DWARFLineProgramExecutor.java | 280 ++++++++++++ .../line/DWARFLineProgramInstruction.java | 34 ++ ...achine.java => DWARFLineProgramState.java} | 65 +-- .../util/bin/format/dwarf/line/FileEntry.java | 56 --- .../line/StatementProgramInstructions.java | 166 ------- .../dwarf/line/StatementProgramPrologue.java | 177 -------- .../dwarf/MockDWARFCompilationUnit.java | 4 +- .../bin/format/dwarf/MockDWARFProgram.java | 4 +- 31 files changed, 1234 insertions(+), 1320 deletions(-) create mode 100644 Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DwarfLineNumberAnalyzer.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLine.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DwarfSectionNames.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFFile.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java rename Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/{ => line}/DWARFLineContentType.java (98%) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineException.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberExtendedOpcodes.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberStandardOpcodes.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramExecutor.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramInstruction.java rename Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/{StateMachine.java => DWARFLineProgramState.java} (59%) delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.java new file mode 100644 index 0000000000..aeee1140d7 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.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. + */ +// Adds DWARF source file line number info to the current binary +//@category DWARF +import java.io.IOException; +import java.util.List; + +import ghidra.app.script.GhidraScript; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf.*; +import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr; +import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider; +import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.CodeUnit; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; + +public class DWARFLineInfoScript extends GhidraScript { + @Override + protected void run() throws Exception { + DWARFSectionProvider dsp = + DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor); + if (dsp == null) { + printerr("Unable to find DWARF information"); + return; + } + + DWARFImportOptions importOptions = new DWARFImportOptions(); + try (DWARFProgram dprog = new DWARFProgram(currentProgram, importOptions, monitor, dsp)) { + dprog.init(monitor); + addSourceLineInfo(dprog); + } + } + + private void addSourceLineInfo(DWARFProgram dprog) throws CancelledException, IOException { + BinaryReader reader = dprog.getDebugLineBR(); + if (reader == null) { + return; + } + int count = 0; + monitor.initialize(reader.length(), "DWARF Source Line Info"); + List compUnits = dprog.getCompilationUnits(); + for (DWARFCompilationUnit cu : compUnits) { + try { + monitor.checkCancelled(); + monitor.setProgress(cu.getLine().getStartOffset()); + List allSFA = cu.getLine().getAllSourceFileAddrInfo(cu, reader); + for (SourceFileAddr sfa : allSFA) { + Address addr = dprog.getCodeAddress(sfa.address()); + DWARFUtil.appendComment(currentProgram, addr, CodeUnit.EOL_COMMENT, "", + "%s:%d".formatted(sfa.fileName(), sfa.lineNum()), ";"); + count++; + } + } + catch (IOException e) { + Msg.error(this, + "Failed to read DWARF line info for cu %d".formatted(cu.getUnitNumber()), e); + } + } + println("Marked up " + count + " locations with source info"); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DwarfLineNumberAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DwarfLineNumberAnalyzer.java deleted file mode 100644 index 9a124586f9..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DwarfLineNumberAnalyzer.java +++ /dev/null @@ -1,176 +0,0 @@ -/* ### - * 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.plugin.core.analysis; - -import java.util.List; - -import java.io.File; -import java.io.IOException; - -import ghidra.app.services.*; -import ghidra.app.util.bin.*; -import ghidra.app.util.bin.format.dwarf.DwarfSectionNames; -import ghidra.app.util.bin.format.dwarf.line.*; -import ghidra.app.util.bin.format.macho.*; -import ghidra.app.util.importer.MessageLog; -import ghidra.app.util.opinion.ElfLoader; -import ghidra.app.util.opinion.MachoLoader; -import ghidra.program.model.address.*; -import ghidra.program.model.listing.CodeUnit; -import ghidra.program.model.listing.Program; -import ghidra.program.model.mem.MemoryBlock; -import ghidra.util.Msg; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -public class DwarfLineNumberAnalyzer extends AbstractAnalyzer { - private static final String NAME = "DWARF Line Number"; - private static final String DESCRIPTION = "Extracts DWARF debug line number information."; - - public DwarfLineNumberAnalyzer() { - super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); - setPriority(AnalysisPriority.FORMAT_ANALYSIS.after().after().after()); - setPrototype(); - setSupportsOneTimeAnalysis(); - } - - @Override - public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) - throws CancelledException { - AddressSpace space = program.getAddressFactory().getDefaultAddressSpace(); - - DwarfSectionNames sectionNames = new DwarfSectionNames(program); - try { - ByteProvider provider = getByteProvider(program, sectionNames); - if (provider == null) { - return true; - } - - BinaryReader reader = new BinaryReader(provider, !program.getLanguage().isBigEndian()); - - while (!monitor.isCancelled() && reader.hasNext()) { - long startIndex = reader.getPointerIndex(); - - StatementProgramPrologue prologue = new StatementProgramPrologue(reader); - - StateMachine machine = new StateMachine(); - machine.reset(prologue.isDefaultIsStatement()); - - StatementProgramInstructions instructions = - new StatementProgramInstructions(reader, machine, prologue); - - while (!monitor.isCancelled()) { - instructions.execute(); - //machine.print(); - - FileEntry entry = prologue.getFileNameByIndex(machine.file); - String directory = prologue.getDirectoryByIndex(entry.getDirectoryIndex()); - - Address address = space.getAddress(machine.address); - CodeUnit cu = program.getListing().getCodeUnitContaining(address); - if (cu != null) { - cu.setProperty("Source Path", - directory + File.separator + entry.getFileName()); - cu.setProperty("Source File", entry.getFileName()); - cu.setProperty("Source Line", machine.line); - } - - if (reader.getPointerIndex() - startIndex >= prologue.getTotalLength() + - StatementProgramPrologue.TOTAL_LENGTH_FIELD_LEN) { - break; - } - } - } - } - catch (Exception e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - return false; - } - return true; - } - - private ByteProvider getByteProvider(Program program, DwarfSectionNames sectionNames) - throws IOException { - File exePath = new File(program.getExecutablePath()); - if (MachoLoader.MACH_O_NAME.equals(program.getExecutableFormat())) { - File parent = exePath.getParentFile(); - File dSymFile = - new File(parent, exePath.getName() + ".dSYM/Contents/Resources/DWARF/" + - exePath.getName()); - if (!dSymFile.exists()) { - return null; - } - RandomAccessByteProvider provider = new RandomAccessByteProvider(dSymFile); - try { - MachHeader header = new MachHeader(provider); - header.parse(); - List
allSections = header.getAllSections(); - for (Section section : allSections) { - if (section.getSectionName().equals(sectionNames.SECTION_NAME_LINE())) { - return new InputStreamByteProvider(section.getDataStream(header), - section.getSize()); - } - } - return null; - } - catch (MachException e) { - } - finally { - provider.close(); - } - return null;//no line number section existed! - } - else if (ElfLoader.ELF_NAME.equals(program.getExecutableFormat())) { - // We now load the .debug section as an overlay block, no need for the - // original file - MemoryBlock block = program.getMemory().getBlock(sectionNames.SECTION_NAME_LINE()); - if (block != null) { - return MemoryByteProvider.createMemoryBlockByteProvider(program.getMemory(), block); - } - // TODO: this will not handle the case where the .debug section is - // in a separate file. Can the file in a separate location? - return null; // no line number section existed! - } - throw new IllegalArgumentException("Unrecognized program format: " + - program.getExecutableFormat()); - } - - @Override - public boolean canAnalyze(Program program) { - - return isElfOrMacho(program); - } - - private boolean hasDebugInfo(Program program) { - DwarfSectionNames sectionNames = new DwarfSectionNames(program); - - MemoryBlock block = null; - block = program.getMemory().getBlock(sectionNames.SECTION_NAME_LINE()); - - return block != null; - } - - private boolean isElfOrMacho(Program program) { - String format = program.getExecutableFormat(); - if (ElfLoader.ELF_NAME.equals(format)) { - return hasDebugInfo(program); - } - if (MachoLoader.MACH_O_NAME.equals(format)) { - return true; - } - return false; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DIEAggregate.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DIEAggregate.java index 94470288aa..731e341d85 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DIEAggregate.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DIEAggregate.java @@ -25,6 +25,7 @@ import org.apache.commons.lang3.ArrayUtils; import ghidra.app.util.bin.format.dwarf.attribs.*; import ghidra.app.util.bin.format.dwarf.expression.*; +import ghidra.app.util.bin.format.dwarf.line.DWARFLine; import ghidra.util.Msg; /** @@ -470,9 +471,15 @@ public class DIEAggregate { if (attr == null) { return null; } - int fileNum = (int) attr.getUnsignedValue(); - DWARFCompilationUnit cu = attrInfo.die.getCompilationUnit(); - return cu.isValidFileIndex(fileNum) ? cu.getFileByIndex(fileNum) : null; + try { + int fileNum = attr.getUnsignedIntExact(); + DWARFCompilationUnit cu = attrInfo.die.getCompilationUnit(); + DWARFLine line = cu.getLine(); + return line.getFilePath(fileNum, false); + } + catch (IOException e) { + return null; + } } /** @@ -710,7 +717,8 @@ public class DIEAggregate { /** * Return the range specified by the low_pc...high_pc attribute values. * - * @return {@link DWARFRange} containing low_pc - high_pc, or null if the low_pc is not present + * @return {@link DWARFRange} containing low_pc - high_pc, or empty range if the low_pc is + * not present */ public DWARFRange getPCRange() { DWARFNumericAttribute lowPc = getAttribute(DW_AT_low_pc, DWARFNumericAttribute.class); @@ -720,29 +728,26 @@ public class DIEAggregate { long rawLowPc = lowPc.getUnsignedValue(); long lowPcOffset = getProgram().getAddress(lowPc.getAttributeForm(), rawLowPc, getCompilationUnit()); - long highPcOffset = lowPcOffset + 1; + long highPcOffset = lowPcOffset; DWARFNumericAttribute highPc = getAttribute(DW_AT_high_pc, DWARFNumericAttribute.class); if (highPc != null) { if (highPc.getAttributeForm() == DWARFForm.DW_FORM_addr) { - long baseAddrFixup = getProgram().getProgramBaseAddressFixup(); - highPcOffset = highPc.getUnsignedValue() + baseAddrFixup; + highPcOffset = highPc.getUnsignedValue(); } else { highPcOffset = highPc.getUnsignedValue(); - if (highPcOffset != 0) { - highPcOffset = lowPcOffset + highPcOffset; - } + highPcOffset = lowPcOffset + highPcOffset; } } return new DWARFRange(lowPcOffset, highPcOffset); } catch (IOException e) { - // fall thru, return null + // fall thru, return empty } } - return null; + return DWARFRange.EMPTY; } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFAbbreviation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFAbbreviation.java index 6be1b256c8..56494adf50 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFAbbreviation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFAbbreviation.java @@ -15,8 +15,6 @@ */ package ghidra.app.util.bin.format.dwarf; -import static ghidra.app.util.bin.format.dwarf.DWARFTag.*; - import java.io.IOException; import java.util.*; @@ -152,7 +150,7 @@ public class DWARFAbbreviation { } public String getTagName() { - return tag != DW_TAG_UNKNOWN ? tag.name() : "DW_TAG_??? %d".formatted(tagId); + return tag.name(tagId); } /** 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 e2a7fcaee2..91d53bc0c0 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 @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Map; import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf.line.DWARFLine; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -170,7 +171,6 @@ public class DWARFCompilationUnit extends DWARFUnitHeader { * @param dwarfProgram {@link DWARFProgram} * @param startOffset offset in provider where it starts * @param endOffset offset in provider where it ends - * @param length how many bytes following the header the DIEs of this unit take * @param intSize 4 (DWARF_32) or 8 (DWARF_64) * @param dwarfVersion 2-5 * @param pointerSize default size of pointers @@ -179,9 +179,9 @@ public class DWARFCompilationUnit extends DWARFUnitHeader { * @param codeToAbbreviationMap map of abbreviation numbers to {@link DWARFAbbreviation} instances */ public DWARFCompilationUnit(DWARFProgram dwarfProgram, long startOffset, long endOffset, - long length, int intSize, short dwarfVersion, byte pointerSize, int unitNumber, + int intSize, short dwarfVersion, byte pointerSize, int unitNumber, long firstDIEOffset, Map codeToAbbreviationMap) { - super(dwarfProgram, startOffset, endOffset, length, intSize, dwarfVersion, unitNumber); + super(dwarfProgram, startOffset, endOffset, intSize, dwarfVersion, unitNumber); this.pointerSize = pointerSize; this.firstDIEOffset = firstDIEOffset; this.codeToAbbreviationMap = @@ -230,6 +230,10 @@ public class DWARFCompilationUnit extends DWARFUnitHeader { return firstDIEOffset; } + public DWARFLine getLine() { + return line; + } + /** * Get the filename that produced the compile unit * @@ -239,50 +243,6 @@ public class DWARFCompilationUnit extends DWARFUnitHeader { return diea.getString(DW_AT_name, null); } - /** - * Get a file name with the full path included based on a file index. - * @param index index of the file - * @return file name with full path or null if line information does not exist - * @throws IllegalArgumentException if a negative or invalid file index is given - */ - public String getFullFileByIndex(int index) { - if (index < 0) { - throw new IllegalArgumentException("Negative file index was given."); - } - if (this.line == null) { - return null; - } - - return this.line.getFullFile(index, null); - } - - /** - * Get a file name based on a file index. - * @param index index of the file - * @return file name or null if line information does not exist - * @throws IllegalArgumentException if a negative or invalid file index is given - */ - public String getFileByIndex(int index) { - if (index < 0) { - throw new IllegalArgumentException("Negative file index was given."); - } - if (this.line == null) { - return null; - } - - return this.line.getFile(index, null); - } - - /** - * Checks validity of a file index number. - * - * @param index file number, 1..N - * @return boolean true if index is a valid file number, false otherwise - */ - public boolean isValidFileIndex(int index) { - return line.isValidFileIndex(index); - } - /** * Get the producer of the compile unit * @return the producer of the compile unit @@ -330,6 +290,12 @@ public class DWARFCompilationUnit extends DWARFUnitHeader { return diea.getUnsignedLong(DW_AT_str_offsets_base, 0); } + /** + * Returns the range covered by this CU, as defined by the lo_pc and high_pc attribute values, + * defaulting to (0,0] if missing. + * + * @return {@link DWARFRange} that this CU covers, never null + */ public DWARFRange getPCRange() { return diea.getPCRange(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunction.java index 646fd0ce89..5471fe4791 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunction.java @@ -78,7 +78,7 @@ public class DWARFFunction { return null; } DWARFRangeList bodyRanges = getFuncBodyRanges(diea); - if (bodyRanges == null || bodyRanges.isEmpty()) { + if (bodyRanges.isEmpty()) { return null; } @@ -330,17 +330,17 @@ public class DWARFFunction { // TODO: dw_at_entry_pc is also sometimes available, typically in things like inlined_subroutines DWARFProgram dprog = diea.getProgram(); DWARFRangeList bodyRangeList = getFuncBodyRanges(diea); - if (bodyRangeList != null && !bodyRangeList.isEmpty()) { - DWARFRange bodyRange = - flattenDisjoint ? bodyRangeList.getFlattenedRange() : bodyRangeList.getFirst(); - return dprog.getAddressRange(bodyRange, true); + if (bodyRangeList.isEmpty()) { + return null; } - return null; + DWARFRange bodyRange = + flattenDisjoint ? bodyRangeList.getFlattenedRange() : bodyRangeList.getFirst(); + return dprog.getAddressRange(bodyRange, true); } public static DWARFRangeList getFuncBodyRanges(DIEAggregate diea) throws IOException { DWARFRange body = diea.getPCRange(); - if (body != null && !body.isEmpty()) { + if (!body.isEmpty()) { return new DWARFRangeList(body); } if (diea.hasAttribute(DW_AT_ranges)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java index 88dcc044d7..beafdc1845 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFFunctionImporter.java @@ -422,16 +422,16 @@ public class DWARFFunctionImporter { return; } - DWARFRangeList blockRanges = - Objects.requireNonNullElse(DWARFFunction.getFuncBodyRanges(diea), DWARFRangeList.EMTPY); - Address blockStart = - !blockRanges.isEmpty() ? prog.getCodeAddress(blockRanges.getFirst().getFrom()) : null; - - if (blockStart != null && importOptions.isOutputLexicalBlockComments()) { - boolean disjoint = blockRanges.getListCount() > 1; - DWARFName dni = prog.getName(diea); - appendComment(blockStart, CodeUnit.PRE_COMMENT, - "Begin: %s%s".formatted(dni.getName(), disjoint ? " - Disjoint" : ""), "\n"); + Address blockStart = null; + DWARFRangeList blockRanges = DWARFFunction.getFuncBodyRanges(diea); + if (!blockRanges.isEmpty()) { + blockStart = prog.getCodeAddress(blockRanges.getFirst().getFrom()); + if (importOptions.isOutputLexicalBlockComments()) { + boolean disjoint = blockRanges.getListCount() > 1; + DWARFName dni = prog.getName(diea); + appendComment(blockStart, CodeUnit.PRE_COMMENT, + "Begin: %s%s".formatted(dni.getName(), disjoint ? " - Disjoint" : ""), "\n"); + } } processFuncChildren(diea, dfunc, @@ -554,25 +554,22 @@ public class DWARFFunctionImporter { } String name = prog.getEntryName(diea); - DWARFRange labelPc; - if (name != null && (labelPc = diea.getPCRange()) != null) { + DWARFRange labelPc = diea.getPCRange(); + if (name != null && !labelPc.isEmpty() && labelPc.getFrom() != 0) { Address address = prog.getCodeAddress(labelPc.getFrom()); - if (address.getOffset() != 0) { - try { - SymbolTable symbolTable = currentProgram.getSymbolTable(); - symbolTable.createLabel(address, name, currentProgram.getGlobalNamespace(), - SourceType.IMPORTED); + try { + SymbolTable symbolTable = currentProgram.getSymbolTable(); + symbolTable.createLabel(address, name, currentProgram.getGlobalNamespace(), + SourceType.IMPORTED); - String locationInfo = DWARFSourceInfo.getDescriptionStr(diea); - if (locationInfo != null) { - appendComment(address, CodeUnit.EOL_COMMENT, locationInfo, "; "); - } - } - catch (InvalidInputException e) { - Msg.error(this, "Problem creating label at " + address + " with name " + name, - e); + String locationInfo = DWARFSourceInfo.getDescriptionStr(diea); + if (locationInfo != null) { + appendComment(address, CodeUnit.EOL_COMMENT, locationInfo, "; "); } } + catch (InvalidInputException e) { + Msg.error(this, "Problem creating label at " + address + " with name " + name, e); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java index 68918bcbd2..6d32032fe9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java @@ -38,6 +38,11 @@ public class DWARFImportOptions { "Include source code location info (filename:linenumber) in comments attached to the " + "Ghidra datatype or function or variable created."; + private static final String OPTION_SOURCE_LINEINFO = "Output Source Line Info"; + private static final String OPTION_SOURCE_LINEINFO_DESC = + "Place end-of-line comments containg the source code filename and line number at " + + "each location provided in the DWARF data"; + private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info"; private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC = "Include DWARF DIE offset info in comments attached to the Ghidra datatype or function " + @@ -98,6 +103,7 @@ public class DWARFImportOptions { private boolean specialCaseSizedBaseTypes = true; private boolean importLocalVariables = true; private boolean useBookmarks = true; + private boolean outputSourceLineInfo = false; /** * Create new instance @@ -360,6 +366,14 @@ public class DWARFImportOptions { return useBookmarks; } + public boolean isOutputSourceLineInfo() { + return outputSourceLineInfo; + } + + public void setOutputSourceLineInfo(boolean outputSourceLineInfo) { + this.outputSourceLineInfo = outputSourceLineInfo; + } + /** * See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)} * @@ -392,6 +406,9 @@ public class DWARFImportOptions { options.registerOption(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables(), null, OPTION_IMPORT_LOCAL_VARS_DESC); + + options.registerOption(OPTION_SOURCE_LINEINFO, isOutputSourceLineInfo(), null, + OPTION_SOURCE_LINEINFO_DESC); } /** @@ -414,5 +431,8 @@ public class DWARFImportOptions { setTryPackDataTypes(options.getBoolean(OPTION_TRY_PACK_STRUCTS, isTryPackStructs())); setImportLocalVariables( options.getBoolean(OPTION_IMPORT_LOCAL_VARS, isImportLocalVariables())); + setOutputSourceLineInfo( + options.getBoolean(OPTION_SOURCE_LINEINFO, isOutputSourceLineInfo())); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java index 236246f274..cf92957d08 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java @@ -20,7 +20,11 @@ import java.util.Collections; import java.util.List; import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; +import ghidra.program.model.listing.CodeUnit; import ghidra.util.Msg; import ghidra.util.Swing; import ghidra.util.exception.CancelledException; @@ -170,6 +174,30 @@ public class DWARFImporter { return new CategoryPath(newRoot, cpParts.subList(origRootParts.size(), cpParts.size())); } + private void addSourceLineInfo(BinaryReader reader) throws CancelledException, IOException { + if ( reader == null ) { + return; + } + monitor.initialize(reader.length(), "DWARF Source Line Info"); + List compUnits = prog.getCompilationUnits(); + for (DWARFCompilationUnit cu : compUnits) { + try { + monitor.checkCancelled(); + monitor.setProgress(cu.getLine().getStartOffset()); + List allSFA = cu.getLine().getAllSourceFileAddrInfo(cu, reader); + for (SourceFileAddr sfa : allSFA) { + Address addr = prog.getCodeAddress(sfa.address()); + DWARFUtil.appendComment(prog.getGhidraProgram(), addr, CodeUnit.EOL_COMMENT, "", + "%s:%d".formatted(sfa.fileName(), sfa.lineNum()), ";"); + } + } + catch (IOException e) { + Msg.error(this, + "Failed to read DWARF line info for cu %d".formatted(cu.getUnitNumber()), e); + } + } + } + /** * Imports DWARF information according to the {@link DWARFImportOptions} set. *

@@ -205,6 +233,10 @@ public class DWARFImporter { moveTypesIntoSourceFolders(); } + if (importOptions.isOutputSourceLineInfo()) { + addSourceLineInfo(prog.getDebugLineBR()); + } + importSummary.totalElapsedMS = System.currentTimeMillis() - start_ts; return importSummary; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLine.java deleted file mode 100644 index 7bcaf9c077..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLine.java +++ /dev/null @@ -1,424 +0,0 @@ -/* ### - * 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; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.io.FilenameUtils; - -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.format.dwarf.DWARFLineContentType.Def; -import ghidra.app.util.bin.format.dwarf.attribs.*; -import ghidra.program.model.data.LEB128; - -/** - * Represents source file line number mapping info. - */ -public class DWARFLine { - private long unit_length; - private int version; - private long header_length; - private int minimum_instruction_length; - private int maximum_operations_per_instruction; - private int default_is_stmt; - private int line_base; - private int line_range; - private int opcode_base; - private int[] standard_opcode_length; - private List include_directories = new ArrayList<>(); - private List file_names = new ArrayList<>(); - private int address_size; - private int segment_selector_size; - - public static DWARFLine empty() { - return new DWARFLine(); - } - - /** - * Read a v4 DWARFLine. - * - * @param dprog {@link DWARFProgram} - * @param reader {@link BinaryReader} stream - * @param lengthInfo {@link DWARFLengthValue} - * @param version DWARFLine version (from header) - * @return a new DWARFLine instance if DW_AT_stmt_list and stream are present, otherwise null - * @throws IOException if error reading data - * @throws DWARFException if bad DWARF values - */ - public static DWARFLine readV4(DWARFProgram dprog, BinaryReader reader, - DWARFLengthValue lengthInfo, int version) throws IOException, DWARFException { - - // length : dwarf_length - // version : 2 bytes - // header_len : dwarf_intsize - // min_instr_len : 1 byte - // .... - DWARFLine result = new DWARFLine(); - result.unit_length = lengthInfo.length(); - - result.version = version; - result.header_length = reader.readNextUnsignedValue(lengthInfo.intSize()); - result.minimum_instruction_length = reader.readNextUnsignedByte(); - - if (result.version >= 4) { - // Maximum operations per instruction only exists in DWARF version 4 or higher - result.maximum_operations_per_instruction = reader.readNextUnsignedByte(); - } - else { - result.maximum_operations_per_instruction = 1; - } - result.default_is_stmt = reader.readNextUnsignedByte(); - result.line_base = reader.readNextByte(); - result.line_range = reader.readNextUnsignedByte(); - result.opcode_base = reader.readNextUnsignedByte(); - result.standard_opcode_length = new int[result.opcode_base]; - result.standard_opcode_length[0] = 1; /* Should never be used */ - for (int i = 1; i < result.opcode_base; i++) { - result.standard_opcode_length[i] = reader.readNextUnsignedByte(); - } - - // Read all include directories - String include = reader.readNextAsciiString(); - while (include.length() != 0) { - result.include_directories.add(new DWARFFile(include)); - include = reader.readNextAsciiString(); - } - - // Read all files, ending when null (hit empty filename) - DWARFFile file; - while ((file = DWARFFile.readV4(reader)) != null) { - result.file_names.add(file); - } - - return result; - } - - /** - * Read a v5 DWARFLine. - * - * @param dprog {@link DWARFProgram} - * @param reader {@link BinaryReader} stream - * @param lengthInfo {@link DWARFLengthValue} - * @param version DWARFLine version (from header) - * @param cu {@link DWARFCompilationUnit} - * @return a new DWARFLine instance if DW_AT_stmt_list and stream are present, otherwise null - * @throws IOException if error reading data - * @throws DWARFException if bad DWARF values - */ - public static DWARFLine readV5(DWARFProgram dprog, BinaryReader reader, - DWARFLengthValue lengthInfo, int version, DWARFCompilationUnit cu) - throws IOException, DWARFException { - - // length : dwarf_length - // version : 2 bytes - // address_size : 1 byte - // segment_selector_size : 1 byte - // header_len : dwarf_intsize - // min_instr_len : 1 byte - // ... - DWARFLine result = new DWARFLine(); - result.unit_length = lengthInfo.length(); - result.version = version; - result.address_size = reader.readNextUnsignedByte(); - result.segment_selector_size = reader.readNextUnsignedByte(); - result.header_length = reader.readNextUnsignedValue(lengthInfo.intSize()); - result.minimum_instruction_length = reader.readNextUnsignedByte(); - result.maximum_operations_per_instruction = reader.readNextUnsignedByte(); - result.default_is_stmt = reader.readNextUnsignedByte(); - result.line_base = reader.readNextByte(); - result.line_range = reader.readNextUnsignedByte(); - result.opcode_base = reader.readNextUnsignedByte(); - result.standard_opcode_length = new int[result.opcode_base]; - result.standard_opcode_length[0] = 1; /* Should never be used */ - for (int i = 1; i < result.opcode_base; i++) { - result.standard_opcode_length[i] = reader.readNextUnsignedByte(); - } - int directory_entry_format_count = reader.readNextUnsignedByte(); - List dirFormatDefs = new ArrayList<>(); - for (int i = 0; i < directory_entry_format_count; i++) { - Def lcntDef = DWARFLineContentType.Def.read(reader); - dirFormatDefs.add(lcntDef); - } - - int directories_count = reader.readNextUnsignedVarIntExact(LEB128::unsigned); - for (int i = 0; i < directories_count; i++) { - DWARFFile dir = DWARFFile.readV5(reader, dirFormatDefs, cu); - result.include_directories.add(dir); - } - - int filename_entry_format_count = reader.readNextUnsignedByte(); - List fileFormatDefs = new ArrayList<>(); - for (int i = 0; i < filename_entry_format_count; i++) { - Def lcntDef = DWARFLineContentType.Def.read(reader); - fileFormatDefs.add(lcntDef); - } - - int file_names_count = reader.readNextUnsignedVarIntExact(LEB128::unsigned); - for (int i = 0; i < file_names_count; i++) { - DWARFFile dir = DWARFFile.readV5(reader, fileFormatDefs, cu); - result.file_names.add(dir); - } - - return result; - } - - record DirectoryEntryFormat(int contentTypeCode, int formCode) { - static DirectoryEntryFormat read(BinaryReader reader) throws IOException, IOException { - int contentTypeCode = reader.readNextUnsignedVarIntExact(LEB128::unsigned); - int formCode = reader.readNextUnsignedVarIntExact(LEB128::unsigned); - - return new DirectoryEntryFormat(contentTypeCode, formCode); - } - } - - private DWARFLine() { - // empty, use #read() - } - - /** - * Get a file name with the full path included. - * @param index index of the file - * @param compileDirectory current compile unit directory - * @return file name with full path - */ - public String getFullFile(int index, String compileDirectory) { - if (index == 0) { - //TODO: Handle index = 0 - throw new UnsupportedOperationException( - "Currently does not support retrieving the primary source file."); - } - else if (index > 0) { - // Retrieve the file by index (index starts at 1) - DWARFFile file = this.file_names.get(index - 1); - - File fileObj = new File(file.getName()); - - // Check to see if the file is an absolute path and return if so - if (fileObj.isAbsolute()) { - return file.getName(); - } - - // Otherwise we need to retrieve the directory - int diridx = (int) file.getDirectoryIndex(); - if (diridx == 0) { - // Use the compile directory if a directory index of 0 is given - if (compileDirectory != null) { - return compileDirectory + file.getName(); - } - throw new IllegalArgumentException( - "No compile directory was given when one was expected."); - } - else if (diridx > 0) { - // Retrieve and append the directory - DWARFFile directory = this.include_directories.get(diridx - 1); - return directory.getName() + file.getName(); - } - throw new IndexOutOfBoundsException( - "Negative directory index was found: " + Integer.toString(diridx)); - } - throw new IllegalArgumentException( - "Negative file index was given: " + Integer.toString(index)); - } - - /** - * Get a file name given a file index. - * @param index index of the file - * @param compileDirectory current compile unit directory - * @return file name - */ - public String getFile(int index, String compileDirectory) { - if (version < 5) { - if (index == 0) { - //TODO: Handle index = 0 - throw new UnsupportedOperationException( - "Currently does not support retrieving the primary source file."); - } - else if (index > 0) { - // Retrieve the file by index (index starts at 1) - DWARFFile file = this.file_names.get(index - 1); - return FilenameUtils.getName(file.getName()); - } - throw new IllegalArgumentException( - "Negative file index was given: " + Integer.toString(index)); - } - else if (version >= 5) { - if (index < 0 || file_names.size() <= index) { - throw new IllegalArgumentException("Bad file index: " + index); - } - DWARFFile file = this.file_names.get(index); - return FilenameUtils.getName(file.getName()); - } - return null; - } - - /** - * Returns true if file exists. - * - * @param index file number, excluding 0 - * @return boolean true if file exists - */ - public boolean isValidFileIndex(int index) { - index--; - return 0 <= index && index < file_names.size(); - } - - @Override - public String toString() { - StringBuffer buffer = new StringBuffer(); - buffer.append("Line Entry"); - buffer.append(" Include Directories: ["); - for (DWARFFile dir : this.include_directories) { - buffer.append(dir); - buffer.append(", "); - } - buffer.append("] File Names: ["); - for (DWARFFile file : this.file_names) { - buffer.append(file.toString()); - buffer.append(", "); - } - buffer.append("]"); - return buffer.toString(); - } - - /** - * DWARFFile is used to store file information for each entry in the line section header. - */ - public static class DWARFFile { - /** - * Reads a DWARFFile entry. - * - * @param reader BinaryReader - * @return new DWARFFile, or null if end-of-list was found - * @throws IOException if error reading - */ - public static DWARFFile readV4(BinaryReader reader) throws IOException { - String name = reader.readNextAsciiString(); - if (name.length() == 0) { - // empty name == end-of-list of files - return null; - } - - long directory_index = reader.readNext(LEB128::unsigned); - long modification_time = reader.readNext(LEB128::unsigned); - long length = reader.readNext(LEB128::unsigned); - - return new DWARFFile(name, directory_index, modification_time, length, null); - } - - /** - * Reads a DWARFFile entry. - * - * @param reader BinaryReader - * @param defs similar to a DIE's attributespec, a list of DWARFForms that define how values - * will be deserialized from the stream - * @param cu {@link DWARFCompilationUnit} - * @return new DWARFFile - * @throws IOException if error reading - */ - public static DWARFFile readV5(BinaryReader reader, List defs, - DWARFCompilationUnit cu) throws IOException { - - String name = null; - long directoryIndex = -1; - long modTime = 0; - long length = 0; - byte[] md5 = null; - for (DWARFLineContentType.Def def : defs) { - DWARFFormContext context = new DWARFFormContext(reader, cu, def); - DWARFAttributeValue val = def.getAttributeForm().readValue(context); - - switch (def.getAttributeId()) { - case DW_LNCT_path: - name = val instanceof DWARFStringAttribute strval - ? strval.getValue(cu) - : null; - break; - case DW_LNCT_directory_index: - directoryIndex = - val instanceof DWARFNumericAttribute numval ? numval.getValue() : -1; - break; - case DW_LNCT_timestamp: - modTime = - val instanceof DWARFNumericAttribute numval ? numval.getValue() : 0; - break; - case DW_LNCT_size: - length = val instanceof DWARFNumericAttribute numval - ? numval.getUnsignedValue() - : 0; - break; - case DW_LNCT_MD5: - md5 = val instanceof DWARFBlobAttribute blobval ? blobval.getBytes() : null; - break; - default: - // skip any DW_LNCT_??? values that we don't care about - break; - } - } - if (name == null) { - throw new IOException("No name value for DWARFLine file"); - } - return new DWARFFile(name, directoryIndex, modTime, length, md5); - } - - private String name; - private long directory_index; - private long modification_time; - private long length; - private byte[] md5; - - public DWARFFile(String name) { - this(name, -1, 0, 0, null); - } - - /** - * Create a new DWARF file entry with the given parameters. - * @param name name of the file - * @param directory_index index of the directory for this file - * @param modification_time modification time of the file - * @param length length of the file - */ - public DWARFFile(String name, long directory_index, long modification_time, long length, - byte[] md5) { - this.name = name; - this.directory_index = directory_index; - this.modification_time = modification_time; - this.length = length; - this.md5 = md5; - } - - public String getName() { - return this.name; - } - - public long getDirectoryIndex() { - return this.directory_index; - } - - public long getModificationTime() { - return this.modification_time; - } - - @Override - public String toString() { - return "Filename: %s, Length: 0x%x, Time: 0x%x, DirIndex: %d".formatted(name, length, - modification_time, directory_index); - } - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLocationList.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLocationList.java index c8055d9239..3025f42ba4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLocationList.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLocationList.java @@ -58,43 +58,35 @@ public class DWARFLocationList { List results = new ArrayList<>(); byte pointerSize = cu.getPointerSize(); + long baseAddress = cu.getPCRange().getFrom(); + long maxAddrVal = pointerSize == 4 ? NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG : -1; - DWARFRange cuRange = cu.getPCRange(); - long baseAddrOffset = (cuRange != null) ? cuRange.getFrom() : 0; - long baseFixup = cu.getProgram().getProgramBaseAddressFixup(); - long eolVal = pointerSize == 4 ? NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG : -1; - - // Loop through the debug_loc entry while (reader.hasNext()) { + // Read the beginning and ending addresses long beginning = reader.readNextUnsignedValue(pointerSize); - long ending = reader.readNextUnsignedValue(pointerSize); + long ending = reader.readNextUnsignedValue(pointerSize); // dwarf end addrs are exclusive + // End of the list if (beginning == 0 && ending == 0) { - // List end break; } - else if (beginning == ending) { - // don't add empty range - continue; - } // Check to see if this is a base address entry - if (beginning == eolVal) { - baseAddrOffset = ending + baseFixup; + if (beginning == maxAddrVal) { + baseAddress = ending; + continue; } - else { - beginning += baseAddrOffset; - ending += baseAddrOffset; - // byte array size is 2 bytes - int size = reader.readNextUnsignedShort(); + int size = reader.readNextUnsignedShort(); + byte[] expr = reader.readNextByteArray(size); - // Read the exprloc bytes - byte[] expr = reader.readNextByteArray(size); - - // TODO: verify end addr calc with DWARFstd.pdf, inclusive vs exclusive - results.add(new DWARFLocation(new DWARFRange(beginning, ending), expr)); + if (beginning == ending) { + // skip adding empty ranges because Ghidra can't use them + continue; } + + DWARFRange range = new DWARFRange(baseAddress + beginning, baseAddress + ending); + results.add(new DWARFLocation(range, expr)); } return new DWARFLocationList(results); } @@ -109,8 +101,7 @@ public class DWARFLocationList { */ public static DWARFLocationList readV5(BinaryReader reader, DWARFCompilationUnit cu) throws IOException { - long baseAddrFixup = cu.getProgram().getProgramBaseAddressFixup(); - long baseAddr = baseAddrFixup; + long baseAddr = cu.getPCRange().getFrom(); DWARFProgram dprog = cu.getProgram(); List list = new ArrayList<>(); @@ -149,24 +140,27 @@ public class DWARFLocationList { list.add(new DWARFLocation(baseAddr + startOfs, baseAddr + endOfs, expr)); break; } + case DW_LLE_default_location: { + byte[] expr = reader.readNext(DWARFLocationList::uleb128SizedByteArray); + list.add(new DWARFLocation(DWARFRange.EMPTY, expr)); + break; + } case DW_LLE_base_address: { - baseAddr = reader.readNextUnsignedValue(cu.getPointerSize()) + baseAddrFixup; + baseAddr = reader.readNextUnsignedValue(cu.getPointerSize()); break; } case DW_LLE_start_end: { long startAddr = reader.readNextUnsignedValue(cu.getPointerSize()); long endAddr = reader.readNextUnsignedValue(cu.getPointerSize()); byte[] expr = reader.readNext(DWARFLocationList::uleb128SizedByteArray); - list.add(new DWARFLocation(startAddr + baseAddrFixup, endAddr + baseAddrFixup, - expr)); + list.add(new DWARFLocation(startAddr, endAddr, expr)); break; } case DW_LLE_start_length: { long startAddr = reader.readNextUnsignedValue(cu.getPointerSize()); int len = reader.readNextUnsignedVarIntExact(LEB128::unsigned); byte[] expr = reader.readNext(DWARFLocationList::uleb128SizedByteArray); - list.add(new DWARFLocation(startAddr + baseAddrFixup, - startAddr + baseAddrFixup + len, expr)); + list.add(new DWARFLocation(startAddr, startAddr + len, expr)); break; } default: 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 4a93dfb5d1..4f7abc7b84 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 @@ -30,6 +30,7 @@ import ghidra.app.util.bin.format.dwarf.attribs.*; 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.sectionprovider.*; import ghidra.app.util.bin.format.golang.rtti.GoSymbolName; import ghidra.app.util.opinion.*; @@ -533,6 +534,10 @@ public class DWARFProgram implements Closeable { return dwarfDTM; } + public List getCompilationUnits() { + return compUnits; + } + public boolean isBigEndian() { return program.getLanguage().isBigEndian(); } @@ -541,6 +546,10 @@ public class DWARFProgram implements Closeable { return !program.getLanguage().isBigEndian(); } + public BinaryReader getDebugLineBR() { + return debugLineBR; + } + private BinaryReader getBinaryReaderFor(String sectionName, TaskMonitor monitor) throws IOException { ByteProvider bp = sectionProvider.getSectionAsByteProvider(sectionName, monitor); @@ -1137,7 +1146,7 @@ public class DWARFProgram implements Closeable { } /** - * Returns an address value, corrected for any Ghidra load offset shenanigans. + * Returns an address value. * * @param form the format of the numeric value * @param value raw offset or indirect address index (depending on the DWARFForm) @@ -1149,14 +1158,14 @@ public class DWARFProgram implements Closeable { switch (form) { case DW_FORM_addr: case DW_FORM_udata: - return value + programBaseAddressFixup; + return value; case DW_FORM_addrx: case DW_FORM_addrx1: case DW_FORM_addrx2: case DW_FORM_addrx3: case DW_FORM_addrx4: { long addr = addressListTable.getOffset((int) value, cu); - return addr + programBaseAddressFixup; + return addr; } default: throw new IOException("Unsupported form %s".formatted(form)); @@ -1221,31 +1230,18 @@ public class DWARFProgram implements Closeable { * * @param diea {@link DIEAggregate} * @param attribute attribute id that points to the line info - * @return {@link DWARFLine}, or null if attribute + * @return {@link DWARFLine}, never null, see {@link DWARFLine#empty()} * @throws IOException if error reading line data */ public DWARFLine getLine(DIEAggregate diea, DWARFAttribute attribute) throws IOException { DWARFNumericAttribute attrib = diea.getAttribute(attribute, DWARFNumericAttribute.class); if (attrib == null || debugLineBR == null) { - return null; + return DWARFLine.empty(); } long stmtListOffset = attrib.getUnsignedValue(); - debugLineBR.setPointerIndex(stmtListOffset); - - // probe for the DWARFLine version number - // length : dwarf_length - // version : 2 bytes - DWARFLengthValue lengthInfo = DWARFLengthValue.read(debugLineBR, getDefaultIntSize()); - if (lengthInfo == null) { - throw new DWARFException("Invalid DWARFLine length at 0x%x".formatted(stmtListOffset)); - } - - int version = debugLineBR.readNextUnsignedShort(); - - return version < 5 - ? DWARFLine.readV4(this, debugLineBR, lengthInfo, version) - : DWARFLine.readV5(this, debugLineBR, lengthInfo, version, - diea.getCompilationUnit()); + DWARFLine result = DWARFLine.read(debugLineBR.clone(stmtListOffset), getDefaultIntSize(), + diea.getCompilationUnit()); + return result; } /** @@ -1337,21 +1333,23 @@ public class DWARFProgram implements Closeable { public AddressRange getAddressRange(DWARFRange range, boolean isCode) { AddressSpace defAS = program.getAddressFactory().getDefaultAddressSpace(); - Address start = defAS.getAddress(range.getFrom(), true /* TODO check this */); - Address end = defAS.getAddress(range.getTo() - 1, true /* TODO check this */); + Address start = + defAS.getAddress(range.getFrom() + programBaseAddressFixup, true /* TODO check this */); + Address end = defAS.getAddress(range.getTo() - 1 + programBaseAddressFixup, + true /* TODO check this */); return new AddressRangeImpl(start, end); } - public Address getCodeAddress(Number offset) { + public Address getCodeAddress(long offset) { return program.getAddressFactory() .getDefaultAddressSpace() - .getAddress(offset.longValue(), true); + .getAddress(offset + programBaseAddressFixup, true); } - public Address getDataAddress(Number offset) { + public Address getDataAddress(long offset) { return program.getAddressFactory() .getDefaultAddressSpace() - .getAddress(offset.longValue(), true); + .getAddress(offset + programBaseAddressFixup, true); } public boolean stackGrowsNegative() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRangeList.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRangeList.java index 850d3f00f8..2348b99d5d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRangeList.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFRangeList.java @@ -45,8 +45,8 @@ public class DWARFRangeList { byte pointerSize = cu.getPointerSize(); List ranges = new ArrayList<>(); - DWARFRange cuRange = cu.getPCRange(); - long baseAddress = cuRange != null ? cuRange.getFrom() : 0; + long baseAddress = cu.getPCRange().getFrom(); + long maxAddrVal = pointerSize == 4 ? NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG : -1; while (reader.hasNext()) { // Read the beginning and ending addresses @@ -59,8 +59,7 @@ public class DWARFRangeList { } // Check to see if this is a base address entry - if (beginning == -1 || - (pointerSize == 4 && beginning == NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG)) { + if (beginning == maxAddrVal) { baseAddress = ending; continue; } @@ -85,8 +84,7 @@ public class DWARFRangeList { List list = new ArrayList<>(); DWARFProgram dprog = cu.getProgram(); - long baseAddrFixup = dprog.getProgramBaseAddressFixup(); - long baseAddr = baseAddrFixup; + long baseAddr = cu.getPCRange().getFrom(); while (reader.hasNext()) { int rleId = reader.readNextUnsignedByte(); @@ -121,20 +119,19 @@ public class DWARFRangeList { break; } case DW_RLE_base_address: { - baseAddr = reader.readNextUnsignedValue(cu.getPointerSize()) + baseAddrFixup; + baseAddr = reader.readNextUnsignedValue(cu.getPointerSize()); break; } case DW_RLE_start_end: { long startAddr = reader.readNextUnsignedValue(cu.getPointerSize()); long endAddr = reader.readNextUnsignedValue(cu.getPointerSize()); - list.add(new DWARFRange(startAddr + baseAddrFixup, endAddr + baseAddrFixup)); + list.add(new DWARFRange(startAddr, endAddr)); break; } case DW_RLE_start_length: { long startAddr = reader.readNextUnsignedValue(cu.getPointerSize()); int len = reader.readNextUnsignedVarIntExact(LEB128::unsigned); - list.add( - new DWARFRange(startAddr + baseAddrFixup, startAddr + baseAddrFixup + len)); + list.add(new DWARFRange(startAddr, startAddr + len)); break; } default: diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFUnitHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFUnitHeader.java index f3e97ccc11..649d7a97d7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFUnitHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFUnitHeader.java @@ -29,7 +29,7 @@ public class DWARFUnitHeader { * Reads the initial fields found in a unit header. * * @param dprog {@link DWARFProgram} - * @param reader {@link BinaryReader} .debug_info stream + * @param reader {@link BinaryReader} stream * @param abbrReader {@link BinaryReader} .debug_abbr stream * @param unitNumber ordinal of this item * @param monitor {@link TaskMonitor} @@ -58,7 +58,7 @@ public class DWARFUnitHeader { } DWARFUnitHeader partial = new DWARFUnitHeader(dprog, startOffset, endOffset, - lengthInfo.length(), lengthInfo.intSize(), version, unitNumber); + lengthInfo.intSize(), version, unitNumber); if (2 <= version && version <= 4) { return DWARFCompilationUnit.readV4(partial, reader, abbrReader, monitor); @@ -84,28 +84,23 @@ public class DWARFUnitHeader { protected final DWARFProgram dprog; /** - * Offset in the debug_info section of this compUnit's header + * Offset in the section of this header */ protected final long startOffset; /** - * Offset in the debug_info section of the end of this compUnit. (right after - * the last DIE record) + * Offset in the section of the end of this header. (exclusive) */ protected final long endOffset; - /** - * Length in bytes of this compUnit header and DIE records. - */ - protected final long length; - /** * size of integers, 4=int32 or 8=int64 */ protected final int intSize; /** - * DWARF ver number, as read from the compunit structure, currently not used but being kept. + * Version number, as read from the header. Note: Some header types use version numbers that do + * not match the general dwarfVersion. */ protected final short dwarfVersion; @@ -118,18 +113,16 @@ public class DWARFUnitHeader { this.dprog = other.dprog; this.startOffset = other.startOffset; this.endOffset = other.endOffset; - this.length = other.length; this.intSize = other.intSize; this.dwarfVersion = other.dwarfVersion; this.unitNumber = other.unitNumber; } - protected DWARFUnitHeader(DWARFProgram dprog, long startOffset, long endOffset, long length, - int intSize, short version, int unitNumber) { + protected DWARFUnitHeader(DWARFProgram dprog, long startOffset, long endOffset, int intSize, + short version, int unitNumber) { this.dprog = dprog; this.startOffset = startOffset; this.endOffset = endOffset; - this.length = length; this.intSize = intSize; this.dwarfVersion = version; this.unitNumber = unitNumber; @@ -143,17 +136,6 @@ public class DWARFUnitHeader { return dwarfVersion; } - /** - * An unsigned long (4 bytes in 32-bit or 8 bytes in 64-bit format) representing - * the length of the .debug_info contribution for this unit, not including the length - * field itself. - * - * @return the length in bytes of this unit - */ - public long getLength() { - return this.length; - } - /** * Returns the byte offset to the start of this unit. * @return the byte offset to the start of this unit diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFVariable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFVariable.java index b1c98bb8be..a3857d2d2a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFVariable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFVariable.java @@ -316,7 +316,7 @@ public class DWARFVariable { return false; } - setRamStorage(res + prog.getProgramBaseAddressFixup()); + setRamStorage(res); return true; } catch (DWARFExpressionException | UnsupportedOperationException @@ -400,7 +400,7 @@ public class DWARFVariable { } else if (exprEvaluator.getRawLastRegister() == -1 && res != 0) { // static global variable location - setRamStorage(res + prog.getProgramBaseAddressFixup()); + setRamStorage(res); } else { Msg.error(this, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DwarfSectionNames.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DwarfSectionNames.java deleted file mode 100644 index c06e300916..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DwarfSectionNames.java +++ /dev/null @@ -1,98 +0,0 @@ -/* ### - * 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; - -import ghidra.app.plugin.core.analysis.DwarfLineNumberAnalyzer; -import ghidra.app.util.opinion.ElfLoader; -import ghidra.app.util.opinion.MachoLoader; -import ghidra.program.model.listing.Program; - -/** - * Section name logic for the obsolete {@link DwarfLineNumberAnalyzer} - */ -@Deprecated(forRemoval = true) -public final class DwarfSectionNames { - private final static String MACHO_PREFIX = "__"; - private final static String ELF_PREFIX = "."; - - private String prefix = ""; - - /** - * Creates a new Dwarf Section Names for the specific program. - * @param program the program containing dwarf debug information. - * @throws IllegalArgumentException if the program's format is not handled. - */ - public DwarfSectionNames(Program program) { - if (MachoLoader.MACH_O_NAME.equals(program.getExecutableFormat())) { - prefix = MACHO_PREFIX; - } - else if (ElfLoader.ELF_NAME.equals(program.getExecutableFormat())) { - prefix = ELF_PREFIX; - } - else { - throw new IllegalArgumentException("Unrecognized program format: "+program.getExecutableFormat()); - } - } - - /** - * Holds tag, attribute names, and attribute forms encodings - */ - public String SECTION_NAME_ABBREV() { return prefix+"debug_abbrev"; } - /** - * A mapping between memory address and compilation - */ - public String SECTION_NAME_ARANGES() { return prefix+"debug_aranges"; } - /** - * Holds information about call frame activations - */ - public String SECTION_NAME_FRAME() { return prefix+"debug_frame"; } - /** - * Debugging information entries for DWARF v2 - */ - public String SECTION_NAME_INFO() { return prefix+"debug_info"; } - /** - * Line Number Program - */ - public String SECTION_NAME_LINE() { return prefix+"debug_line"; } - /** - * Location lists are used in place of location expressions whenever the object whose location is - * being described can change location during its lifetime. Location lists are contained in a separate - * object file section called .debug_loc. A location list is indicated by a location attribute - * whose value is represented as a constant offset from the beginning of the .debug_loc section - * to the first byte of the list for the object in question. - */ - public String SECTION_NAME_LOC() { return prefix+"debug_loc"; } - /** - * A lookup table for global objects and functions - */ - public String SECTION_NAME_MACINFO() { return prefix+"debug_macinfo"; } - /** - * A lookup table for global objects and functions - */ - public String SECTION_NAME_PUBNAMES() { return prefix+"debug_pubnames"; } - /** - * A lookup table for global types - */ - public String SECTION_NAME_PUBTYPES() { return prefix+"debug_pubtypes"; } - /** - * Address ranges referenced by DIEs - */ - public String SECTION_NAME_RANGES() { return prefix+"debug_ranges"; } - /** - * String table used by .debug_info - */ - public String SECTION_NAME_STR() { return prefix+"debug_str"; } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java index c1c581e51c..68d033c53b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/attribs/DWARFAttribute.java @@ -39,7 +39,7 @@ public enum DWARFAttribute { DW_AT_bit_offset(0xc), // dwarf-3 DW_AT_bit_size(0xd, constant, exprloc, reference), //DW_AT_element_list(0xf), - DW_AT_stmt_list(0x10, lineptr), + DW_AT_stmt_list(0x10, lineptr, constant), DW_AT_low_pc(0x11, address), DW_AT_high_pc(0x12, address, constant), DW_AT_language(0x13, constant), @@ -252,7 +252,7 @@ public enum DWARFAttribute { @Override protected String getRawAttributeIdDescription() { - return "DW_AT_???? %d (0x%x)".formatted(attributeId, attributeId); + return "DW_AT_???? %d (0x%x)".formatted(rawAttributeId, rawAttributeId); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFFile.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFFile.java new file mode 100644 index 0000000000..35fce39037 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFFile.java @@ -0,0 +1,157 @@ +/* ### + * 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.line; + +import java.io.IOException; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf.DWARFCompilationUnit; +import ghidra.app.util.bin.format.dwarf.attribs.*; +import ghidra.program.model.data.LEB128; + +/** + * DWARFFile is used to store file or directory entries in the DWARFLine. + */ +public class DWARFFile { + /** + * Reads a DWARFFile entry. + * + * @param reader BinaryReader + * @return new DWARFFile, or null if end-of-list was found + * @throws IOException if error reading + */ + public static DWARFFile readV4(BinaryReader reader) throws IOException { + String name = reader.readNextAsciiString(); + if (name.length() == 0) { + // empty name == end-of-list of files + return null; + } + + int directory_index = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + long modification_time = reader.readNext(LEB128::unsigned); + long length = reader.readNext(LEB128::unsigned); + + return new DWARFFile(name, directory_index, modification_time, length, null); + } + + /** + * Reads a DWARFFile entry. + * + * @param reader BinaryReader + * @param defs similar to a DIE's attributespec, a list of DWARFForms that define how values + * will be deserialized from the stream + * @param cu {@link DWARFCompilationUnit} + * @return new DWARFFile + * @throws IOException if error reading + */ + public static DWARFFile readV5(BinaryReader reader, List defs, + DWARFCompilationUnit cu) throws IOException { + + String name = null; + int directoryIndex = -1; + long modTime = 0; + long length = 0; + byte[] md5 = null; + for (DWARFLineContentType.Def def : defs) { + DWARFFormContext context = new DWARFFormContext(reader, cu, def); + DWARFAttributeValue val = def.getAttributeForm().readValue(context); + + switch (def.getAttributeId()) { + case DW_LNCT_path: + name = + val instanceof DWARFStringAttribute strval ? strval.getValue(cu) : null; + break; + case DW_LNCT_directory_index: + directoryIndex = val instanceof DWARFNumericAttribute numval + ? numval.getUnsignedIntExact() + : -1; + break; + case DW_LNCT_timestamp: + modTime = + val instanceof DWARFNumericAttribute numval ? numval.getValue() : 0; + break; + case DW_LNCT_size: + length = val instanceof DWARFNumericAttribute numval + ? numval.getUnsignedValue() + : 0; + break; + case DW_LNCT_MD5: + md5 = val instanceof DWARFBlobAttribute blobval ? blobval.getBytes() : null; + break; + default: + // skip any DW_LNCT_??? values that we don't care about + break; + } + } + if (name == null) { + throw new IOException("No name value for DWARFLine file"); + } + return new DWARFFile(name, directoryIndex, modTime, length, md5); + } + + private final String name; + private final int directory_index; + private final long modification_time; + private final long length; + private final byte[] md5; + + public DWARFFile(String name) { + this(name, -1, 0, 0, null); + } + + /** + * Create a new DWARF file entry with the given parameters. + * @param name name of the file + * @param directory_index index of the directory for this file + * @param modification_time modification time of the file + * @param length length of the file + */ + public DWARFFile(String name, int directory_index, long modification_time, long length, + byte[] md5) { + this.name = name; + this.directory_index = directory_index; + this.modification_time = modification_time; + this.length = length; + this.md5 = md5; + } + + public String getName() { + return this.name; + } + + public DWARFFile withName(String newName) { + return new DWARFFile(newName, directory_index, modification_time, length, md5); + } + + public int getDirectoryIndex() { + return this.directory_index; + } + + public long getModificationTime() { + return this.modification_time; + } + + public byte[] getMD5() { + return md5; + } + + @Override + public String toString() { + return "Filename: %s, Length: 0x%x, Time: 0x%x, DirIndex: %d".formatted(name, length, + modification_time, directory_index); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java new file mode 100644 index 0000000000..a0b8eb45fb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java @@ -0,0 +1,354 @@ +/* ### + * 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.line; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.dwarf.*; +import ghidra.app.util.bin.format.dwarf.line.DWARFLineContentType.Def; +import ghidra.formats.gfilesystem.FSUtilities; +import ghidra.program.model.data.LEB128; + +/** + * A structure read from .debug_line, contains indexed source filenames as well as a mapping between + * addresses and source filename and linenumbers. + *

+ * TODO: refactor this and other similar classes to derive from DWARFUnitHeader and simplify + */ +public class DWARFLine { + /** + * Returns a dummy DWARFLine instance that contains no information. + * + * @return {@link DWARFLine} instance with no info + */ + public static DWARFLine empty() { + return new DWARFLine(); + } + + public static DWARFLine read(BinaryReader reader, int defaultIntSize, DWARFCompilationUnit cu) + throws IOException { + // probe for the DWARFLine version number + // length : dwarf_length + // version : 2 bytes + DWARFLine result = new DWARFLine(); + result.dprog = cu.getProgram(); + result.startOffset = reader.getPointerIndex(); + DWARFLengthValue lengthInfo = DWARFLengthValue.read(reader, defaultIntSize); + if (lengthInfo == null) { + throw new DWARFException( + "Invalid DWARFLine length at 0x%x".formatted(result.startOffset)); + } + + result.length = lengthInfo.length(); + result.intSize = lengthInfo.intSize(); + result.endOffset = reader.getPointerIndex() + lengthInfo.length(); + + result.dwarfVersion = reader.readNextUnsignedShort(); + if (result.dwarfVersion < 5) { + DWARFLine.readV4(result, reader, cu); + } + else { + DWARFLine.readV5(result, reader, cu); + } + return result; + } + + private static void readV4(DWARFLine result, BinaryReader reader, DWARFCompilationUnit cu) + throws IOException, DWARFException { + + // length : dwarf_length (already) + // version : 2 bytes (already) + // header_len : dwarf_intsize + // min_instr_len : 1 byte + // .... + long header_length = reader.readNextUnsignedValue(result.intSize); + result.opcodes_start = reader.getPointerIndex() + header_length; + + result.minimum_instruction_length = reader.readNextUnsignedByte(); + + if (result.dwarfVersion >= 4) { + // Maximum operations per instruction only exists in DWARF version 4 or higher + result.maximum_operations_per_instruction = reader.readNextUnsignedByte(); + } + else { + result.maximum_operations_per_instruction = 1; + } + result.default_is_stmt = reader.readNextUnsignedByte() != 0; + result.line_base = reader.readNextByte(); + result.line_range = reader.readNextUnsignedByte(); + result.opcode_base = reader.readNextUnsignedByte(); + result.standard_opcode_length = new int[result.opcode_base]; + result.standard_opcode_length[0] = 1; /* Should never be used */ + for (int i = 1; i < result.opcode_base; i++) { + result.standard_opcode_length[i] = reader.readNextUnsignedByte(); + } + + // Add the cu's compDir as element 0 of the dir table + String defaultCompDir = cu.getCompileDirectory(); + if (defaultCompDir == null || defaultCompDir.isBlank()) { + defaultCompDir = ""; + } + result.directories.add(new DWARFFile(defaultCompDir)); + + // Read all include directories, which are only a list of names in v4 + String dirName = reader.readNextAsciiString(); + while (dirName.length() != 0) { + DWARFFile dir = new DWARFFile(dirName); + dir = fixupDir(dir, defaultCompDir); + + result.directories.add(dir); + dirName = reader.readNextAsciiString(); + } + + // Read all files, ending when null (hit empty filename) + DWARFFile file; + while ((file = DWARFFile.readV4(reader)) != null) { + result.files.add(file); + } + } + + private static void readV5(DWARFLine result, BinaryReader reader, DWARFCompilationUnit cu) + throws IOException, DWARFException { + + // length : dwarf_length (already) + // version : 2 bytes (already) + // address_size : 1 byte + // segment_selector_size : 1 byte + // header_len : dwarf_intsize + // min_instr_len : 1 byte + // ... + result.address_size = reader.readNextUnsignedByte(); + result.segment_selector_size = reader.readNextUnsignedByte(); + + long header_length = reader.readNextUnsignedValue(result.intSize); + result.opcodes_start = reader.getPointerIndex() + header_length; + + result.minimum_instruction_length = reader.readNextUnsignedByte(); + result.maximum_operations_per_instruction = reader.readNextUnsignedByte(); + result.default_is_stmt = reader.readNextUnsignedByte() != 0; + result.line_base = reader.readNextByte(); + result.line_range = reader.readNextUnsignedByte(); + result.opcode_base = reader.readNextUnsignedByte(); + result.standard_opcode_length = new int[result.opcode_base]; + result.standard_opcode_length[0] = 1; /* Should never be used */ + for (int i = 1; i < result.opcode_base; i++) { + result.standard_opcode_length[i] = reader.readNextUnsignedByte(); + } + int directory_entry_format_count = reader.readNextUnsignedByte(); + List dirFormatDefs = new ArrayList<>(); + for (int i = 0; i < directory_entry_format_count; i++) { + Def lcntDef = DWARFLineContentType.Def.read(reader); + dirFormatDefs.add(lcntDef); + } + + String defaultCompDir = cu.getCompileDirectory(); + if (defaultCompDir == null || defaultCompDir.isBlank()) { + defaultCompDir = ""; + } + + // read the directories, which are defined the same way files are + int directories_count = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + for (int i = 0; i < directories_count; i++) { + DWARFFile dir = DWARFFile.readV5(reader, dirFormatDefs, cu); + dir = fixupDir(dir, defaultCompDir); + result.directories.add(dir); + } + + int filename_entry_format_count = reader.readNextUnsignedByte(); + List fileFormatDefs = new ArrayList<>(); + for (int i = 0; i < filename_entry_format_count; i++) { + Def lcntDef = DWARFLineContentType.Def.read(reader); + fileFormatDefs.add(lcntDef); + } + + int file_names_count = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + for (int i = 0; i < file_names_count; i++) { + DWARFFile dir = DWARFFile.readV5(reader, fileFormatDefs, cu); + result.files.add(dir); + } + } + + private static DWARFFile fixupDir(DWARFFile dir, String defaultCompDir) { + // fix relative dir names using the compiledir string from the CU + if (!defaultCompDir.isEmpty()) { + if (dir.getName().equals(".")) { + return dir.withName(defaultCompDir); + } + else if (!isAbsolutePath(dir.getName())) { + return dir.withName(FSUtilities.appendPath(defaultCompDir, dir.getName())); + } + } + return dir; + } + + private static boolean isAbsolutePath(String s) { + return s.startsWith("/") || s.startsWith("\\") || + (s.length() > 3 && s.charAt(1) == ':' && (s.charAt(2) == '/' || s.charAt(2) == '\\')); + } + + record DirectoryEntryFormat(int contentTypeCode, int formCode) { + static DirectoryEntryFormat read(BinaryReader reader) throws IOException, IOException { + int contentTypeCode = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + int formCode = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + + return new DirectoryEntryFormat(contentTypeCode, formCode); + } + } + + private DWARFProgram dprog; + + private long startOffset; + + /** + * Offset in the section of the end of this header. (exclusive) + */ + private long endOffset; + + /** + * Length in bytes of this header. + */ + private long length; + + /** + * size of integers, 4=int32 or 8=int64 + */ + private int intSize; + + /** + * Version number, as read from the header. + */ + private int dwarfVersion; + + private int minimum_instruction_length; + private int maximum_operations_per_instruction; + private boolean default_is_stmt; + private int line_base; + private int line_range; + private int opcode_base; + private int[] standard_opcode_length; + private List directories = new ArrayList<>(); + private List files = new ArrayList<>(); + private int address_size; + private int segment_selector_size; + + private long opcodes_start = -1; // offset where line number program opcodes start + + private DWARFLine() { + // empty, use #read() + } + + public long getStartOffset() { + return startOffset; + } + + public long getEndOffset() { + return endOffset; + } + + public DWARFLineProgramExecutor getLineProgramexecutor(DWARFCompilationUnit cu, + BinaryReader reader) { + DWARFLineProgramExecutor lpe = new DWARFLineProgramExecutor(reader.clone(opcodes_start), + endOffset, cu.getPointerSize(), opcode_base, line_base, line_range, + minimum_instruction_length, default_is_stmt); + + return lpe; + } + + public record SourceFileAddr(long address, String fileName, int lineNum) {} + + public List getAllSourceFileAddrInfo(DWARFCompilationUnit cu, + BinaryReader reader) throws IOException { + try (DWARFLineProgramExecutor lpe = getLineProgramexecutor(cu, reader)) { + List results = new ArrayList<>(); + for (DWARFLineProgramState row : lpe.allRows()) { + results.add(new SourceFileAddr(row.address, getFilePath(row.file, true), row.line)); + } + + return results; + } + } + + public DWARFFile getDir(int index) throws IOException { + if (0 <= index && index < directories.size()) { + return directories.get(index); + } + throw new IOException( + "Invalid dir index %d for line table at 0x%x: ".formatted(index, startOffset)); + } + + /** + * Get a file name given a file index. + * + * @param index index of the file + * @return file {@link DWARFFile} + * @throws IOException if invalid index + */ + public DWARFFile getFile(int index) throws IOException { + if (dwarfVersion < 5) { + if (0 < index && index <= files.size()) { + // Retrieve the file by index (index starts at 1) + return files.get(index - 1); + } + } + else if (dwarfVersion >= 5) { + if (0 <= index && index < files.size()) { + return files.get(index); + } + } + throw new IOException( + "Invalid file index %d for line table at 0x%x: ".formatted(index, startOffset)); + } + + public String getFilePath(int index, boolean includePath) { + try { + DWARFFile f = getFile(index); + if (!includePath) { + return f.getName(); + } + + String dir = f.getDirectoryIndex() >= 0 + ? getDir(f.getDirectoryIndex()).getName() + : ""; + + return FSUtilities.appendPath(dir, f.getName()); + } + catch (IOException e) { + return null; + } + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("Line Entry"); + buffer.append(" Include Directories: ["); + for (DWARFFile dir : this.directories) { + buffer.append(dir); + buffer.append(", "); + } + buffer.append("] File Names: ["); + for (DWARFFile file : this.files) { + buffer.append(file.toString()); + buffer.append(", "); + } + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLineContentType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineContentType.java similarity index 98% rename from Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLineContentType.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineContentType.java index eb933c61d3..323bad5bf0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFLineContentType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineContentType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.util.bin.format.dwarf; +package ghidra.app.util.bin.format.dwarf.line; import java.io.IOException; import java.util.HashMap; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineException.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineException.java new file mode 100644 index 0000000000..d73804fd7c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineException.java @@ -0,0 +1,38 @@ +/* ### + * 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.line; + +import java.io.IOException; + +public class DWARFLineException extends IOException { + + public DWARFLineException() { + // empty + } + + public DWARFLineException(String message) { + super(message); + } + + public DWARFLineException(Throwable cause) { + super(cause); + } + + public DWARFLineException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberExtendedOpcodes.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberExtendedOpcodes.java new file mode 100644 index 0000000000..a34b30ad9d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberExtendedOpcodes.java @@ -0,0 +1,33 @@ +/* ### + * 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.line; + +import ghidra.app.util.bin.format.dwarf.DWARFUtil; + +public class DWARFLineNumberExtendedOpcodes { + public final static int DW_LNE_end_sequence = 1; + public final static int DW_LNE_set_address = 2; + public final static int DW_LNE_define_file = 3; // v2-v4, v5=reserved + public final static int DW_LNE_set_discriminator = 4; + + public final static int DW_LNE_lo_user = 0x80; + public final static int DW_LNE_hi_user = 0xff; + + public static String toString(int value) { + return DWARFUtil.toString(DWARFLineNumberExtendedOpcodes.class, value); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberStandardOpcodes.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberStandardOpcodes.java new file mode 100644 index 0000000000..20a879f5d5 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineNumberStandardOpcodes.java @@ -0,0 +1,37 @@ +/* ### + * 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.line; + +import ghidra.app.util.bin.format.dwarf.DWARFUtil; + +public class DWARFLineNumberStandardOpcodes { + public final static int DW_LNS_copy = 1; + public final static int DW_LNS_advance_pc = 2; + public final static int DW_LNS_advance_line = 3; + public final static int DW_LNS_set_file = 4; + public final static int DW_LNS_set_column = 5; + public final static int DW_LNS_negate_statement = 6; + public final static int DW_LNS_set_basic_block = 7; + public final static int DW_LNS_const_add_pc = 8; + public final static int DW_LNS_fixed_advanced_pc = 9; + public final static int DW_LNS_set_prologue_end = 10; + public final static int DW_LNS_set_epilog_begin = 11; + public final static int DW_LNS_set_isa = 12; + + public static String toString(int value) { + return DWARFUtil.toString(DWARFLineNumberStandardOpcodes.class, value); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramExecutor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramExecutor.java new file mode 100644 index 0000000000..adbf472ce6 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramExecutor.java @@ -0,0 +1,280 @@ +/* ### + * 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.line; + +import static ghidra.app.util.bin.format.dwarf.line.DWARFLineNumberExtendedOpcodes.*; +import static ghidra.app.util.bin.format.dwarf.line.DWARFLineNumberStandardOpcodes.*; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.LEB128; + +/** + * Handles executing, step-by-step, the address-to-sourcefile mapping instructions found at the + * end of a DWARFLine structure. + */ +public final class DWARFLineProgramExecutor implements Closeable { + private DWARFLineProgramState state; + private BinaryReader reader; + private final int pointerSize; + private final long streamEnd; + private final int opcodeBase; + private final int lineRange; + private final int lineBase; + private final int minInstrLen; + private final boolean defaultIsStatement; + + public DWARFLineProgramExecutor(BinaryReader reader, long streamEnd, int pointerSize, + int opcodeBase, int lineBase, int lineRange, int minInstrLen, + boolean defaultIsStatement) { + this.reader = reader; + this.streamEnd = streamEnd; + this.pointerSize = pointerSize; + this.opcodeBase = opcodeBase; + this.lineBase = lineBase; + this.lineRange = lineRange; + this.minInstrLen = minInstrLen; + this.defaultIsStatement = defaultIsStatement; + } + + @Override + public void close() { + reader = null; + } + + public boolean hasNext() { + return reader.getPointerIndex() < streamEnd; + } + + public DWARFLineProgramState currentState() { + return new DWARFLineProgramState(state); + } + + public DWARFLineProgramState nextRow() throws IOException { + while (hasNext()) { + DWARFLineProgramInstruction instr = step(); + if (instr.row() != null) { + return instr.row(); + } + } + return null; + } + + public List allRows() throws IOException { + List results = new ArrayList<>(); + + DWARFLineProgramState row; + while ((row = nextRow()) != null) { + results.add(row); + } + return results; + } + + /** + * Read the next instruction and executes it + * + * @return + * @throws IOException if an i/o error occurs + */ + public DWARFLineProgramInstruction step() throws IOException { + DWARFLineProgramInstruction instr = stepInstr(); + return instr; + } + + private DWARFLineProgramInstruction stepInstr() throws IOException { + if (state == null) { + state = new DWARFLineProgramState(defaultIsStatement); + } + + long instrOffset = reader.getPointerIndex(); + + int opcode = reader.readNextUnsignedByte(); + + if (opcode == 0) { + return executeExtended(instrOffset); + } + else if (opcode >= opcodeBase) { + return executeSpecial(instrOffset, opcode); + } + else { + return executeStandard(instrOffset, opcode); + } + } + + private DWARFLineProgramInstruction executeSpecial(long instrOffset, int specialOpcodeValue) { + int adjustedOpcode = (specialOpcodeValue & 0xff) - opcodeBase; + int addressIncrement = adjustedOpcode / lineRange; + int lineIncrement = lineBase + (adjustedOpcode % lineRange); + + addressIncrement &= 0xff; + lineIncrement &= 0xff; + + state.line += (byte) lineIncrement; + state.address += (addressIncrement * minInstrLen); + + DWARFLineProgramState row = currentState(); + + state.isBasicBlock = false; + state.prologueEnd = false; + state.epilogueBegin = false; + state.discriminator = 0; + + return new DWARFLineProgramInstruction(instrOffset, "DW_LN_special_" + specialOpcodeValue, + List.of(addressIncrement, lineIncrement), row); + } + + private DWARFLineProgramInstruction executeExtended(long instrOffset) throws IOException { + int length = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + + long oldIndex = reader.getPointerIndex(); + int extendedOpcode = reader.readNextByte(); + + String instr = DWARFLineNumberExtendedOpcodes.toString(extendedOpcode); + List operands = List.of(); + DWARFLineProgramState row = null; + + switch (extendedOpcode) { + case DW_LNE_end_sequence: + // end_seq is a special marker, and by definition specifies a row that is one byte + // after the last instruction of the sequence. + state.isEndSequence = true; + row = currentState(); + row.address--; // tweak backwards 1 byte + state = new DWARFLineProgramState(defaultIsStatement); + break; + case DW_LNE_set_address: + state.address = reader.readNextUnsignedValue(pointerSize); + operands = List.of(state.address); + break; + case DW_LNE_define_file: { + // this instruction is deprecated in v5+, and not fully supported in this + // impl + String sourceFilename = reader.readNextUtf8String(); + int dirIndex = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + long lastMod = reader.readNext(LEB128::unsigned); + long fileLen = reader.readNext(LEB128::unsigned); + break; + } + case DW_LNE_set_discriminator: + state.discriminator = reader.readNext(LEB128::unsigned); + operands = List.of(state.discriminator); + break; + default: + throw new DWARFLineException("Unknown extended instruction: " + instr); + } + + if (oldIndex + length != reader.getPointerIndex()) { + throw new DWARFLineException("Bad extended opcode decoding, length mismatch @0x%x: %s" + .formatted(oldIndex, instr)); + } + + return new DWARFLineProgramInstruction(instrOffset, instr, operands, row); + } + + private DWARFLineProgramInstruction executeStandard(long instrOffset, int opcode) + throws IOException { + + String instr = DWARFLineNumberStandardOpcodes.toString(opcode); + List operands = List.of(); + DWARFLineProgramState row = null; + + switch (opcode) { + case DW_LNS_copy: { + row = currentState(); + + state.discriminator = 0; + state.isBasicBlock = false; + state.prologueEnd = false; + state.epilogueBegin = false; + break; + } + case DW_LNS_advance_pc: { + long value = reader.readNext(LEB128::unsigned); + operands = List.of(value); + + state.address += (value * minInstrLen); + break; + } + case DW_LNS_advance_line: { + int value = reader.readNextVarInt(LEB128::signed); + operands = List.of(value); + + state.line += value; + break; + } + case DW_LNS_set_file: { + int value = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + operands = List.of(value); + + state.file = value; + break; + } + case DW_LNS_set_column: { + int value = reader.readNextUnsignedVarIntExact(LEB128::unsigned); + operands = List.of(value); + + state.column = value; + break; + } + case DW_LNS_negate_statement: { + state.isStatement = !state.isStatement; + break; + } + case DW_LNS_set_basic_block: { + state.isBasicBlock = true; + break; + } + case DW_LNS_const_add_pc: { + int adjustedOpcode = 255 - opcodeBase; + int addressIncrement = adjustedOpcode / lineRange; + state.address += (addressIncrement & 0xff); + break; + } + case DW_LNS_fixed_advanced_pc: { + int value = reader.readNextUnsignedShort(); + operands = List.of(value); + + state.address += value; + break; + } + + case DW_LNS_set_prologue_end: + state.prologueEnd = true; + break; + + case DW_LNS_set_epilog_begin: + state.epilogueBegin = true; + break; + + case DW_LNS_set_isa: { + long value = reader.readNext(LEB128::unsigned); + operands = List.of(value); + + state.isa = value; + break; + } + + default: + throw new DWARFLineException("Unsupported standard opcode: " + instr); + } + + return new DWARFLineProgramInstruction(instrOffset, instr, operands, row); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramInstruction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramInstruction.java new file mode 100644 index 0000000000..f4e1c8525b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramInstruction.java @@ -0,0 +1,34 @@ +/* ### + * 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.line; + +import java.util.List; + +public record DWARFLineProgramInstruction(long offset, String instr, List operands, + DWARFLineProgramState row) { + + public String getDesc() { + if (row != null) { + String flags = (row.isBasicBlock ? " basic block " : "") + + (row.isEndSequence ? " end-of-seq " : "") + (row.isStatement ? " statement " : "") + + (row.prologueEnd ? " prologue-end " : ""); + return "[%04x] %s %s - 0x%x, file: %d, line: %d, %s".formatted(offset, instr, operands, + row.address, row.file, row.line, flags); + } + return "[%04x] %s %s".formatted(offset, instr, operands); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StateMachine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramState.java similarity index 59% rename from Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StateMachine.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramState.java index 9617860c55..687fc01162 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StateMachine.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLineProgramState.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +15,24 @@ */ package ghidra.app.util.bin.format.dwarf.line; -import ghidra.util.Msg; - -public class StateMachine { +public class DWARFLineProgramState { /** * The program-counter value corresponding to a machine instruction * generated by the compiler. */ public long address; + /** * An unsigned integer indicating the identity of the source file * corresponding to a machine instruction. */ - public int file; + public int file = 1; /** * An unsigned integer indicating a source line number. Lines are * numbered beginning at 1. The compiler may emit the value 0 in cases * where an instruction cannot be attributed to any source line. */ - public int line; + public int line = 1; /** * An unsigned integer indicating a column number within a source line. * Columns are numbered beginning at 1. The value 0 is reserved to @@ -57,27 +55,42 @@ public class StateMachine { */ public boolean isEndSequence; - public void reset(boolean defaultIsStatement) { - address = 0; - file = 1; - line = 1; - column = 0; - isStatement = defaultIsStatement; - isBasicBlock = false; - isEndSequence = false; + public boolean prologueEnd; + + public boolean epilogueBegin; + + public long isa; + + public long discriminator; + + public DWARFLineProgramState(boolean defaultIsStatement) { + this.isStatement = defaultIsStatement; } - void print() { - Msg.info(this, "ADDR=" + Long.toHexString(address)); - Msg.info(this, " "); - Msg.info(this, "FILE=" + Long.toHexString(file)); - Msg.info(this, " "); - Msg.info(this, "LINE=" + Long.toHexString(line)); - Msg.info(this, " "); - Msg.info(this, "LINE=" + line); - Msg.info(this, " "); - Msg.info(this, "COL=" + Long.toHexString(column)); - Msg.info(this, " "); - Msg.info(this, ""); + public DWARFLineProgramState(DWARFLineProgramState other) { + this.address = other.address; + this.file = other.file; + this.line = other.line; + this.column = other.column; + this.isStatement = other.isStatement; + this.isBasicBlock = other.isBasicBlock; + this.isEndSequence = other.isEndSequence; + this.prologueEnd = other.prologueEnd; + this.epilogueBegin = other.epilogueBegin; + this.isa = other.isa; + this.discriminator = other.discriminator; } + + public boolean isSameFileLine(DWARFLineProgramState other) { + return file == other.file && line == other.line; + } + + @Override + public String toString() { + return String.format( + "DWARFLineProgramState [address=%s, file=%s, line=%s, column=%s, isStatement=%s, isBasicBlock=%s, isEndSequence=%s, prologueEnd=%s, epilogueBegin=%s, isa=%s, discriminator=%s]", + address, file, line, column, isStatement, isBasicBlock, isEndSequence, prologueEnd, + epilogueBegin, isa, discriminator); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java deleted file mode 100644 index fe6cfad535..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/FileEntry.java +++ /dev/null @@ -1,56 +0,0 @@ -/* ### - * 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.line; - -import java.io.IOException; - -import ghidra.app.util.bin.BinaryReader; -import ghidra.program.model.data.LEB128; - -public class FileEntry { - private String fileName; - private long directoryIndex; - private long lastModifiedTime; - private long fileLengthInBytes; - - FileEntry(BinaryReader reader) throws IOException { - fileName = reader.readNextAsciiString(); - if (fileName.length() == 0) { - return; - } - directoryIndex = reader.readNext(LEB128::unsigned); - lastModifiedTime = reader.readNext(LEB128::unsigned); - fileLengthInBytes = reader.readNext(LEB128::unsigned); - } - - public String getFileName() { - return fileName; - } - public long getDirectoryIndex() { - return directoryIndex; - } - public long getLastModifiedTime() { - return lastModifiedTime; - } - public long getFileLengthInBytes() { - return fileLengthInBytes; - } - - @Override - public String toString() { - return fileName; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java deleted file mode 100644 index c7ed14ab60..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramInstructions.java +++ /dev/null @@ -1,166 +0,0 @@ -/* ### - * 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.line; - -import java.io.IOException; - -import ghidra.app.util.bin.BinaryReader; -import ghidra.program.model.data.LEB128; - -public final class StatementProgramInstructions { - - //Standard Opcodes - - public final static int DW_LNS_copy = 1; - public final static int DW_LNS_advance_pc = 2; - public final static int DW_LNS_advance_line = 3; - public final static int DW_LNS_set_file = 4; - public final static int DW_LNS_set_column = 5; - public final static int DW_LNS_negate_statement = 6; - public final static int DW_LNS_set_basic_block = 7; - public final static int DW_LNS_const_add_pc = 8; - public final static int DW_LNS_fixed_advanced_pc = 9; - - public final static int DW_LNS_set_prologue_end = 10; - public final static int DW_LNS_set_epilog_begin = 11; - public final static int DW_LNS_set_isa = 12; - - //Extended Opcodes - - public final static int DW_LNE_end_sequence = 1; - public final static int DW_LNE_set_address = 2; - public final static int DW_LNE_define_file = 3; - - private BinaryReader reader; - private StateMachine machine; - private StatementProgramPrologue prologue; - - public StatementProgramInstructions(BinaryReader reader, StateMachine machine, - StatementProgramPrologue prologue) { - this.reader = reader; - this.machine = machine; - this.prologue = prologue; - } - - public void dispose() { - reader = null; - machine = null; - prologue = null; - } - - /** - * Read the next instruction and executes it - * on the given state machine. - * @throws IOException if an i/o error occurs - */ - public void execute() throws IOException { - int opcode = reader.readNextByte() & 0xff; - if (opcode == 0) { - executeExtended(opcode); - } - else if (opcode >= prologue.getOpcodeBase()) { - executeSpecial(opcode); - } - else { - executeStandard(opcode); - } - } - - private void executeSpecial(int specialOpcodeValue) { - int adjustedOpcode = (specialOpcodeValue & 0xff) - prologue.getOpcodeBase(); - int addressIncrement = adjustedOpcode / prologue.getLineRange(); - int lineIncrement = prologue.getLineBase() + (adjustedOpcode % prologue.getLineRange()); - - addressIncrement &= 0xff; - lineIncrement &= 0xff; - - machine.line += (byte) lineIncrement; - machine.address += (addressIncrement * prologue.getMinimumInstructionLength()); - machine.isBasicBlock = false; - } - - private void executeExtended(int opcode) throws IOException { - long length = reader.readNext(LEB128::unsigned); - - long oldIndex = reader.getPointerIndex(); - int extendedOpcode = reader.readNextByte(); - - switch (extendedOpcode) { - case DW_LNE_end_sequence: - machine.isEndSequence = true; - machine.reset(prologue.isDefaultIsStatement()); - break; - case DW_LNE_set_address: - machine.address = reader.readNextInt(); - break; - case DW_LNE_define_file://TODO - //break; - throw new UnsupportedOperationException(); - } - - if (oldIndex + length != reader.getPointerIndex()) { - throw new IllegalStateException("Index values do not match!"); - } - } - - private void executeStandard(int opcode) throws IOException { - switch (opcode) { - case DW_LNS_copy: { - machine.isBasicBlock = false; - break; - } - case DW_LNS_advance_pc: { - long value = reader.readNext(LEB128::unsigned); - machine.address += (value * prologue.getMinimumInstructionLength()); - break; - } - case DW_LNS_advance_line: { - long value = reader.readNext(LEB128::unsigned); - machine.line += value; - break; - } - case DW_LNS_set_file: { - long value = reader.readNext(LEB128::unsigned); - machine.file = (int) value; - break; - } - case DW_LNS_set_column: { - long value = reader.readNext(LEB128::unsigned); - machine.column = (int) value; - break; - } - case DW_LNS_negate_statement: { - machine.isStatement = !machine.isStatement; - break; - } - case DW_LNS_set_basic_block: { - machine.isBasicBlock = true; - break; - } - case DW_LNS_const_add_pc: { - int adjustedOpcode = 255 - prologue.getOpcodeBase(); - int addressIncrement = adjustedOpcode / prologue.getLineRange(); - machine.address += (addressIncrement & 0xff); - break; - } - case DW_LNS_fixed_advanced_pc: { - short value = reader.readNextShort(); - machine.address += value; - break; - } - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java deleted file mode 100644 index fa353c695c..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/StatementProgramPrologue.java +++ /dev/null @@ -1,177 +0,0 @@ -/* ### - * 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.line; - -import ghidra.app.util.bin.BinaryReader; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class StatementProgramPrologue { - public final static int TOTAL_LENGTH_FIELD_LEN = 4; - public final static int PRE_PROLOGUE_LEN = 4 + 2 + 4; - - private int totalLength; - private short version; - private int prologueLength; - private byte minimumInstructionLength; - private boolean defaultIsStatement; - private byte lineBase; - private byte lineRange; - private byte opcodeBase; - private byte [] standardOpcodeLengths; - - private List includeDirectories = new ArrayList(); - private List fileNames = new ArrayList(); - - public StatementProgramPrologue(BinaryReader reader) throws IOException { - totalLength = reader.readNextInt(); - version = reader.readNextShort(); - - if (version != 2) { - throw new IllegalStateException("Only DWARF v2 is supported."); - } - - prologueLength = reader.readNextInt(); - minimumInstructionLength = reader.readNextByte(); - defaultIsStatement = reader.readNextByte() != 0; - lineBase = reader.readNextByte(); - lineRange = reader.readNextByte(); - opcodeBase = reader.readNextByte(); - standardOpcodeLengths = reader.readNextByteArray(opcodeBase - 1); - - while (true) { - String dir = reader.readNextAsciiString(); - if (dir.length() == 0) { - break; - } - includeDirectories.add(dir); - } - - while (true) { - FileEntry entry = new FileEntry(reader); - if (entry.getFileName().length() == 0) { - break; - } - fileNames.add(entry); - } - } - - /** - * Returns the size in bytes of the statement information for this - * compilation unit (not including the total_length field itself). - * @return size in bytes of the statement information - */ - public int getTotalLength() { - return totalLength; - } - /** - * Returns the version identifier for the statement information format. - * @return the version identifier for the statement information format - */ - public int getVersion() { - return version & 0xffff; - } - /** - * Returns the number of bytes following the prologue_length field to the - * beginning of the first byte of the statement program itself. - * @return the number of bytes following the prologue_length - */ - public int getPrologueLength() { - return prologueLength; - } - /** - * Returns the size in bytes of the smallest target machine instruction. - * Statement program opcodes that alter the address register first - * multiply their operands by this value. - * @return the size in bytes of the smallest target machine instruction - */ - public int getMinimumInstructionLength() { - return minimumInstructionLength & 0xff; - } - /** - * Returns the initial value of the is_stmt register. - * @return the initial value of the is_stmt register - */ - public boolean isDefaultIsStatement() { - return defaultIsStatement; - } - /** - * Returns the line base value. - * This parameter affects the meaning of the special opcodes. See below. - * @return the line base value - */ - public int getLineBase() { - return lineBase & 0xff; - } - /** - * Returns the line range value. - * This parameter affects the meaning of the special opcodes. See below. - * @return the line range value - */ - public int getLineRange() { - return lineRange & 0xff; - } - /** - * Returns the number assigned to the first special opcode. - * @return the number assigned to the first special opcode - */ - public int getOpcodeBase() { - return opcodeBase & 0xff; - } - /** - * return the array for each of the standard opcodes - * @return the array for each of the standard opcodes - */ - public byte [] getStandardOpcodeLengths() { - return standardOpcodeLengths; - } - /** - * @return each path that was searched for included source files - */ - public List getIncludeDirectories() { - return includeDirectories; - } - /** - * @return an entry for each source file that contributed to the statement - */ - public List getFileNames() { - return fileNames; - } - /** - * Returns the file entry at the given index. - * @param fileIndex the file index - * @return the file entry at the given index - */ - public FileEntry getFileNameByIndex(int fileIndex) { - return fileNames.get(fileIndex - 1); - } - /** - * The directory index represents an entry in the - * include directories section. If the directoryIndex - * is LEB128(0), then the file was found in the current - * directory. - * @param directoryIndex the directory index - * @return the directory or current directory - */ - public String getDirectoryByIndex(long directoryIndex) { - if (directoryIndex == 0) { - return "."; - } - return includeDirectories.get((int)directoryIndex - 1); - } -} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFCompilationUnit.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFCompilationUnit.java index 18bf93b58f..25ee22da17 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFCompilationUnit.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFCompilationUnit.java @@ -24,8 +24,8 @@ public class MockDWARFCompilationUnit extends DWARFCompilationUnit { private int dieCount; public MockDWARFCompilationUnit(MockDWARFProgram dwarfProgram, long startOffset, long endOffset, - long length, int intSize, short version, byte pointerSize, int compUnitNumber) { - super(dwarfProgram, startOffset, endOffset, length, intSize, version, pointerSize, + int intSize, short version, byte pointerSize, int compUnitNumber) { + super(dwarfProgram, startOffset, endOffset, intSize, version, pointerSize, compUnitNumber, startOffset, null); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFProgram.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFProgram.java index 112e8b187e..cecd738253 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFProgram.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/MockDWARFProgram.java @@ -68,8 +68,8 @@ public class MockDWARFProgram extends DWARFProgram { compUnitDieIndex.put(dieOffsets.length - 1, currentCompUnit); } long start = compUnits.size() * 0x1000; - currentCompUnit = new MockDWARFCompilationUnit(this, start, start + 0x1000, 0, - dwarfIntSize, (short) 4, (byte) 8, 0); + currentCompUnit = new MockDWARFCompilationUnit(this, start, start + 0x1000, dwarfIntSize, + (short) 4, (byte) 8, 0); compUnits.add(currentCompUnit); compUnitDieIndex.put(dieOffsets.length - 1, currentCompUnit);