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 2018160883..f87749735d 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 @@ -322,7 +322,7 @@ public class DWARFVariable { return false; } - setRamStorage(res + prog.getProgramBaseAddressFixup()); + setRamStorage(res); return true; } catch (DWARFExpressionException | UnsupportedOperationException @@ -406,7 +406,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);