Merge remote-tracking branch 'origin/GP-4193_dev747368_dwarfline_source_info--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-03-28 12:36:22 -04:00
commit 6439d61594
31 changed files with 1234 additions and 1320 deletions

View file

@ -0,0 +1,76 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 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<DWARFCompilationUnit> compUnits = dprog.getCompilationUnits();
for (DWARFCompilationUnit cu : compUnits) {
try {
monitor.checkCancelled();
monitor.setProgress(cu.getLine().getStartOffset());
List<SourceFileAddr> 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");
}
}

View file

@ -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<Section> 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;
}
}

View file

@ -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;
}
/**

View file

@ -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);
}
/**

View file

@ -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<Integer, DWARFAbbreviation> 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();
}

View file

@ -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)) {

View file

@ -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);
}
}
}

View file

@ -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()));
}
}

View file

@ -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<DWARFCompilationUnit> compUnits = prog.getCompilationUnits();
for (DWARFCompilationUnit cu : compUnits) {
try {
monitor.checkCancelled();
monitor.setProgress(cu.getLine().getStartOffset());
List<SourceFileAddr> 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.
* <p>
@ -205,6 +233,10 @@ public class DWARFImporter {
moveTypesIntoSourceFolders();
}
if (importOptions.isOutputSourceLineInfo()) {
addSourceLineInfo(prog.getDebugLineBR());
}
importSummary.totalElapsedMS = System.currentTimeMillis() - start_ts;
return importSummary;

View file

@ -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<DWARFFile> include_directories = new ArrayList<>();
private List<DWARFFile> 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<DWARFLineContentType.Def> 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<DWARFLineContentType.Def> 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<DWARFLineContentType.Def> 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);
}
}
}

View file

@ -58,43 +58,35 @@ public class DWARFLocationList {
List<DWARFLocation> 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<DWARFLocation> 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:

View file

@ -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<DWARFCompilationUnit> 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() {

View file

@ -45,8 +45,8 @@ public class DWARFRangeList {
byte pointerSize = cu.getPointerSize();
List<DWARFRange> 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<DWARFRange> 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:

View file

@ -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

View file

@ -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,

View file

@ -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"; }
}

View file

@ -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

View file

@ -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<DWARFLineContentType.Def> 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);
}
}

View file

@ -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.
* <p>
* 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<DWARFLineContentType.Def> 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<DWARFLineContentType.Def> 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<DWARFFile> directories = new ArrayList<>();
private List<DWARFFile> 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<SourceFileAddr> getAllSourceFileAddrInfo(DWARFCompilationUnit cu,
BinaryReader reader) throws IOException {
try (DWARFLineProgramExecutor lpe = getLineProgramexecutor(cu, reader)) {
List<SourceFileAddr> 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();
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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<DWARFLineProgramState> allRows() throws IOException {
List<DWARFLineProgramState> 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<Number> 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<Number> 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);
}
}

View file

@ -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<Number> 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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}
}

View file

@ -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<String> includeDirectories = new ArrayList<String>();
private List<FileEntry> fileNames = new ArrayList<FileEntry>();
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<String> getIncludeDirectories() {
return includeDirectories;
}
/**
* @return an entry for each source file that contributed to the statement
*/
public List<FileEntry> 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);
}
}

View file

@ -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);
}

View file

@ -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);