diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractC13Lines.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractC13Lines.java new file mode 100644 index 0000000000..fef021b46e --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractC13Lines.java @@ -0,0 +1,291 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * C13Lines information. As best as we know, only one of C11Lines or C13Lines (We are actually + * creating a C13Debug class at a higher level, and making C13Lines be the specific C13Debug + * information for "type" 0xf2 (and maybe 0xf4) can be found after the symbol information in + * module debug streams. + */ +public class AbstractC13Lines extends C13Section { + + private long offCon; // uint32 + private int segCon; // uint16 + private int flags; // uint16 + private long lenCon; // uint32 + + private List fileRecords = new ArrayList<>(); + + protected AbstractC13Lines(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + super(ignore); + if (reader.numRemaining() < 12) { + throw new PdbException("Not enough data for header"); + } + offCon = reader.parseUnsignedIntVal(); + segCon = reader.parseUnsignedShortVal(); + flags = reader.parseUnsignedShortVal(); + lenCon = reader.parseUnsignedIntVal(); + + boolean hasColumn = ((flags & 0X0001) != 0); + + while (reader.hasMore()) { + monitor.checkCanceled(); + FileRecord fileRecord = FileRecord.parse(reader, hasColumn, monitor); + if (fileRecord == null) { + break; + } + fileRecords.add(fileRecord); + } + } + + long getOffCon() { + return offCon; + } + + int getSegCon() { + return segCon; + } + + int getFlags() { + return flags; + } + + long getLenCon() { + return lenCon; + } + + @Override + public String toString() { + return String.format( + "%s: offCon = %ld, segCon = %d, flags = 0x%04x, lenCon = %d; num records = %d", + getClass().getSimpleName(), offCon, segCon, flags, lenCon, fileRecords.size()); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13Lines----------------------------------------------------\n"); + dumpInternal(writer); + writer.write("End C13Lines------------------------------------------------\n"); + } + + protected void dumpInternal(Writer writer) throws IOException { + writer.write(String.format("offCon: 0x%08x segCon: %d flags: 0x%08x lenCon: 0x%08x\n", + offCon, segCon, flags, lenCon)); + + for (FileRecord record : fileRecords) { + record.dump(writer, offCon); + } + } + + static class FileRecord { + private long fileId; // uint32 + private long nLines; // uint32 + private long lenFileBlock; // uint32 + private List lineRecords = new ArrayList<>(); + + static FileRecord parse(PdbByteReader reader, boolean hasColumn, TaskMonitor monitor) + throws PdbException { + return new FileRecord(reader, hasColumn, monitor); + } + + private FileRecord(PdbByteReader reader, boolean hasColumn, TaskMonitor monitor) + throws PdbException { + if (reader.numRemaining() < 12) { + throw new PdbException("Not enough data for FileRecord header"); + } + fileId = reader.parseUnsignedIntVal(); + nLines = reader.parseUnsignedIntVal(); + lenFileBlock = reader.parseUnsignedIntVal(); + + long lenMinusHeader = lenFileBlock - 12; // 12 is size of header + Long x = nLines; + int nLinesI = x.intValue(); + int sizeLines = nLinesI * 8; + int sizeColumns = nLinesI * (hasColumn ? 4 : 0); + int sizeRequired = sizeLines + sizeColumns; + + // was test ">" but both are suspect... not all records might have the columns + if (lenMinusHeader != sizeRequired) { + throw new PdbException("Corrupt FileRecord"); + } + if (reader.numRemaining() < sizeRequired) { + throw new PdbException("Not enough data for FileRecord records"); + } + + PdbByteReader lineReader = reader.getSubPdbByteReader(sizeLines); + PdbByteReader columnReader = + (hasColumn ? reader.getSubPdbByteReader(sizeColumns) : null); + + for (int i = 0; i < nLines; i++) { + LineRecord lineRecord = LineRecord.parse(lineReader, columnReader); + lineRecords.add(lineRecord); + } + } + + long getFileId() { + return fileId; + } + + long getNLines() { + return nLines; + } + + long getLenFileBlock() { + return lenFileBlock; + } + + List getLineRecords() { + return lineRecords; + } + + void dump(Writer writer, long offCon) throws IOException { + writer.write(String.format("fileId: %06x, nLines: %d, lenFileBlock: %d\n", + getFileId(), getNLines(), getLenFileBlock())); + for (int i = 0; i < getNLines(); i++) { + List records = getLineRecords(); + records.get(i).dump(writer, offCon); + writer.write("\n"); + } + } + } + + static class LineRecord { + private long offset; // uint32 + private long bitVals; // uint32 + private ColumnRecord columnRecord = null; + + long getOffset() { + return offset; + } + + long getBitVals() { + return bitVals; + } + + long getLineNumStart() { + return bitVals & 0xffffffL; + } + + long getDeltaLineEnd() { + return (bitVals >> 24) & 0x7fL; + } + + ColumnRecord getColumnRecord() { + return columnRecord; + } + + /** + * Returns true if the line number is that of an statement + * @return true if for an statement + */ + boolean isStatement() { + return (bitVals & 0x80000000L) != 0L; + } + + /** + * Returns true if the line number is that of an expression + * @return true if for an expression + */ + boolean isExpression() { + return !isStatement(); + } + + static LineRecord parse(PdbByteReader lineReader, PdbByteReader columnReader) + throws PdbException { + return new LineRecord(lineReader, columnReader); + } + + private LineRecord(PdbByteReader lineReader, PdbByteReader columnReader) + throws PdbException { + offset = lineReader.parseUnsignedIntVal(); + bitVals = lineReader.parseUnsignedIntVal(); + if (columnReader != null) { // means hasColumn is true + columnRecord = ColumnRecord.parse(columnReader); + } + } + + private boolean isSpecialLine() { + long start = getLineNumStart(); + return (start == 0xfeefeeL || start == 0xf00f00L); + } + + void dump(Writer writer, long offCon) throws IOException { + String lineStart = (isSpecialLine() ? String.format("%06x", getLineNumStart()) + : String.format("%d", getLineNumStart())); + if (columnRecord != null) { + if (columnRecord.getOffsetColumnEnd() != 0L) { + writer.write(String.format("%5d:%5d-%5d-%5d 0x%08x %s", getLineNumStart(), + columnRecord.getOffsetColumnStart(), getLineNumStart() + getDeltaLineEnd(), + columnRecord.getOffsetColumnEnd(), getOffset() + offCon, + (isStatement() ? "Statement" : "Expression"))); + } + else { + writer.write(String.format("%s-%5d 0x%08x %s", lineStart, + columnRecord.getOffsetColumnStart(), getOffset() + offCon, + (isStatement() ? "Statement" : "Expression"))); + } + } + else { + writer.write(String.format("%s 0x%08x %s", lineStart, getOffset() + offCon, + (isStatement() ? "Statement" : "Expression"))); + } + } + } + + static class ColumnRecord { + private int offsetColumnStart; // unsigned short + private int offsetColumnEnd; // unsigned short + + int getOffsetColumnStart() { + return offsetColumnStart; + } + + int getOffsetColumnEnd() { + return offsetColumnEnd; + } + + static ColumnRecord parse(PdbByteReader reader) throws PdbException { + return new ColumnRecord(reader); + } + + private ColumnRecord(PdbByteReader reader) throws PdbException { + offsetColumnStart = reader.parseUnsignedShortVal(); + offsetColumnEnd = reader.parseUnsignedShortVal(); + } + + @Override + public String toString() { + return String.format("Start: 0x%04x, End: 0x%04x", getOffsetColumnStart(), + getOffsetColumnEnd()); + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractModuleInformation.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractModuleInformation.java index 01003f15b8..22a4061fee 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractModuleInformation.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractModuleInformation.java @@ -54,12 +54,16 @@ public abstract class AbstractModuleInformation { protected long nameIndexCompilerPdbPath; // unsigned 32-bit //============================================================================================== + protected AbstractPdb pdb; + private Map filenameByOffset = new HashMap<>(); //============================================================================================== // API //============================================================================================== - public AbstractModuleInformation() { + public AbstractModuleInformation(AbstractPdb pdb) { + Objects.requireNonNull(pdb, "pdb cannot be null"); + this.pdb = pdb; } /** @@ -96,12 +100,28 @@ public abstract class AbstractModuleInformation { /** * Returns the size of the local symbols debug information. - * @return Size of the local symbosl debug information. + * @return Size of the local symbols debug information. */ public int getSizeLocalSymbolsDebugInformation() { return sizeLocalSymbolsDebugInformation; } + /** + * Returns the size of the older-style line number information + * @return Size of the older-style line number information + */ + public int getSizeLineNumberDebugInformation() { + return sizeLineNumberDebugInformation; + } + + /** + * Returns the size of the C13-style line number information + * @return Size of the C13-style line number information + */ + public int getSizeC13StyleLineNumberInformation() { + return sizeC13StyleLineNumberInformation; + } + /** * Returns the name of the module. * @return Name of the module. diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java index 931d9666ee..bec4339a9e 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java @@ -547,7 +547,7 @@ public abstract class AbstractPdb implements AutoCloseable { PdbByteReader getReaderForStreamNumber(int streamNumber, int streamOffset, int numToRead, TaskMonitor monitor) throws IOException, CancelledException { MsfStream stream = msf.getStream(streamNumber); - numToRead = Math.min(numToRead, stream.getLength()); + numToRead = Math.min(numToRead, stream.getLength() - streamOffset); byte[] bytes = stream.read(streamOffset, numToRead, monitor); PdbByteReader reader = new PdbByteReader(bytes); return reader; @@ -697,8 +697,10 @@ public abstract class AbstractPdb implements AutoCloseable { * debugging only. * @param writer {@link Writer}. * @throws IOException On issue writing to the {@link Writer}. + * @throws CancelledException Upon user cancellation + * @throws PdbException Upon not enough data left to parse */ - public void dumpSubStreams(Writer writer) throws IOException { + public void dumpSubStreams(Writer writer) throws IOException, CancelledException, PdbException { writer.write("SubStreams--------------------------------------------------\n"); if (typeProgramInterface != null) { writer.write("TypeProgramInterface----------------------------------------\n"); diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractSectionContribution.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractSectionContribution.java index 46d4347c79..3cb20ef537 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractSectionContribution.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractSectionContribution.java @@ -60,6 +60,11 @@ public abstract class AbstractSectionContribution { return imod; } + @Override + public String toString() { + return dump(); + } + //============================================================================================== // Abstract Methods //============================================================================================== diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractUnimplementedC13Section.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractUnimplementedC13Section.java new file mode 100644 index 0000000000..48f3c2523f --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractUnimplementedC13Section.java @@ -0,0 +1,52 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; + +import ghidra.util.task.TaskMonitor; + +/** + * Abstract class for C13 Sections which do not yet have a good implementation. + * Class exists to output the fact (in a dump) that data of this type has been encountered. + */ +abstract class AbstractUnimplementedC13Section extends C13Section { + + private PdbByteReader myReader = null; + + protected AbstractUnimplementedC13Section(PdbByteReader reader, boolean ignore, + TaskMonitor monitor) { + super(ignore); + myReader = reader; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + @Override + void dump(Writer writer) throws IOException { + String n = getClass().getSimpleName(); + int len = n.length(); + writer.write(n + dashes.substring(len)); + writer.write("***NOT IMPLEMENTED*** Bytes follow...\n"); + writer.write(myReader.dump()); + writer.write("\n"); + writer.write("End " + n + dashes.substring(len + 4)); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C11Lines.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C11Lines.java index ea04f0d1e1..d503ef9cbf 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C11Lines.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C11Lines.java @@ -22,15 +22,13 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** - * C11Lines information. As best as we know, only one of C11Lines or C13Lines (not implemented - * yet) can be found after the symbol information in module debug streams. + * C11Lines information. As best as we know, only one of C11Lines or C13Lines can be found after + * the symbol information in module debug streams. *

* Note: we have not tested or put this to use yet. */ public class C11Lines { - private AbstractPdb pdb; - private int cFile; // unsigned short private int cSeg; // unsigned short // array of (Windows C) unsigned long values (which is 32-bit int); we are limiting to java int. @@ -50,11 +48,12 @@ public class C11Lines { private List>> offsets; // unsigned int private List>> lineNumbers; // unsigned short - public C11Lines(AbstractPdb pdb) { - this.pdb = pdb; + public static C11Lines parse(AbstractPdb pdb, PdbByteReader reader, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C11Lines(pdb, reader, monitor); } - public void parse(PdbByteReader reader, TaskMonitor monitor) + private C11Lines(AbstractPdb pdb, PdbByteReader reader, TaskMonitor monitor) throws PdbException, CancelledException { if (reader.numRemaining() < 4) { return; diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CoffSymbolRva.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CoffSymbolRva.java new file mode 100644 index 0000000000..f8041d8f34 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CoffSymbolRva.java @@ -0,0 +1,35 @@ +/* ### + * 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.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type COFF_SYMBOL_RVA. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13CoffSymbolRva extends AbstractUnimplementedC13Section { + static C13CoffSymbolRva parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new C13CoffSymbolRva(reader, ignore, monitor); + } + + protected C13CoffSymbolRva(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CrossScopeExports.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CrossScopeExports.java new file mode 100644 index 0000000000..454db17f5f --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CrossScopeExports.java @@ -0,0 +1,114 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * PDB C13 Cross-Scope Exports information. + */ +public class C13CrossScopeExports extends C13Section { + + private List crossScopeExports = new ArrayList<>(); + + /** + * Parse and return a {@link C13CrossScopeExports}. + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param ignore flag indicating whether the record should be ignored + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13CrossScopeExports parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C13CrossScopeExports(reader, ignore, monitor); + } + + protected C13CrossScopeExports(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws CancelledException, PdbException { + super(ignore); + while (reader.numRemaining() >= CrossScopeExport.getBaseRecordSize()) { + monitor.checkCanceled(); + CrossScopeExport crossExport = new CrossScopeExport(reader); + crossScopeExports.add(crossExport); + } + if (reader.hasMore()) { + Msg.debug(C13CrossScopeExports.class, + String.format("Num Extra C13CrossScopeExports bytes: %d", reader.numRemaining())); + } + } + + List getCrossScopeExports() { + return crossScopeExports; + } + + @Override + public String toString() { + return String.format("%s: num cross-scope exports = %d", getClass().getSimpleName(), + crossScopeExports.size()); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13CrossScopeExports----------------------------------------\n"); + for (CrossScopeExport crossScopeExport : crossScopeExports) { + writer.write(crossScopeExport.toString()); + writer.write('\n'); + } + writer.write("End C13CrossScopeExports------------------------------------\n"); + } + + static class CrossScopeExport { + private long localId; // unsigned 32-bit + private long globalId; // unsigned 32-bit + + private static int getBaseRecordSize() { + return 8; + } + + CrossScopeExport(PdbByteReader reader) throws PdbException { + localId = reader.parseUnsignedIntVal(); + globalId = reader.parseUnsignedIntVal(); + } + + long getLocalId() { + return localId; + } + + long getGlobalId() { + return globalId; + } + + @Override + public String toString() { + return String.format("0x%08x, 0x%08x", localId, globalId); + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CrossScopeImports.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CrossScopeImports.java new file mode 100644 index 0000000000..9b669e3c34 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13CrossScopeImports.java @@ -0,0 +1,128 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * PDB C13 Cross-Scope Imports information.... also known as Cross-Scope References. + */ +public class C13CrossScopeImports extends C13Section { + + private List crossScopeImports = new ArrayList<>(); + + /** + * Parse and return a {@link C13CrossScopeImports}. + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param ignore flag indicating whether the record should be ignored + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13CrossScopeImports parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C13CrossScopeImports(reader, ignore, monitor); + } + + protected C13CrossScopeImports(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws CancelledException, PdbException { + super(ignore); + while (reader.numRemaining() >= CrossScopeImport.getBaseRecordSize()) { + monitor.checkCanceled(); + CrossScopeImport crossImport = new CrossScopeImport(reader); + crossScopeImports.add(crossImport); + } + if (reader.hasMore()) { + Msg.debug(C13CrossScopeExports.class, + String.format("Num Extra C13CrossScopeExports bytes: %d", reader.numRemaining())); + } + } + + List getCrossScopeImports() { + return crossScopeImports; + } + + @Override + public String toString() { + return String.format("%s: num cross-scope imports = %d", getClass().getSimpleName(), + crossScopeImports.size()); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13CrossScopeImports----------------------------------------\n"); + for (CrossScopeImport crossScopeImport : crossScopeImports) { + writer.write(crossScopeImport.toString()); + writer.write('\n'); + } + writer.write("End C13CrossScopeImports------------------------------------\n"); + } + + static class CrossScopeImport { + private int offsetObjectFilePath; // the module file; signed 32-bit + private long numCrossReferences; // unsigned 32-bit + private List referenceIds; // Array of unsigned 32-bit values + + private static int getBaseRecordSize() { + return 8; + } + + CrossScopeImport(PdbByteReader reader) throws PdbException { + offsetObjectFilePath = reader.parseInt(); + numCrossReferences = reader.parseUnsignedIntVal(); + referenceIds = new ArrayList<>(); + for (long i = 0; i < numCrossReferences; i++) { + referenceIds.add(reader.parseUnsignedIntVal()); + } + } + + long getOffsetObjectFilePath() { + return offsetObjectFilePath; + } + + long getNumCrossReferences() { + return numCrossReferences; + } + + List getReferenceIds() { + return referenceIds; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(String.format("0x%08x, %5d", offsetObjectFilePath, numCrossReferences)); + for (Long id : referenceIds) { + builder.append(String.format(" 0x%08x", id)); + } + return builder.toString(); + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FileChecksums.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FileChecksums.java new file mode 100644 index 0000000000..1e60e30500 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FileChecksums.java @@ -0,0 +1,161 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; +import java.util.*; + +import ghidra.util.Msg; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * PDB C13 Module File Checksums. + */ +public class C13FileChecksums extends C13Section { + + private List fileChecksums = new ArrayList<>(); + + /** + * Parse and return a {@link C13FileChecksums}. + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param ignore flag indicating whether the record should be ignored + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13FileChecksums parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C13FileChecksums(reader, ignore, monitor); + } + + protected C13FileChecksums(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws CancelledException, PdbException { + super(ignore); + while (reader.numRemaining() >= FileChecksum.getBaseRecordSize()) { + monitor.checkCanceled(); + FileChecksum fileChecksum = new FileChecksum(reader); + fileChecksums.add(fileChecksum); + } + if (reader.hasMore()) { + Msg.debug(C13FileChecksums.class, + String.format("Num Extra C13FileChecksums bytes: %d", reader.numRemaining())); + } + } + + public List getFileChecksums() { + return fileChecksums; + } + + @Override + public String toString() { + return String.format( + "%s: num checksums = %d", getClass().getSimpleName(), fileChecksums.size()); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13FileChecksums--------------------------------------------\n"); + for (FileChecksum checksum : fileChecksums) { + writer.write(checksum.toString()); + writer.write('\n'); + } + writer.write("End C13FileChecksums----------------------------------------\n"); + } + + static class FileChecksum { + private long offsetFilename; // unsigned 32-bit + private int length; + private int checksumTypeValue; + private byte[] bytes; + + private static int getBaseRecordSize() { + return 6; + } + + FileChecksum(PdbByteReader reader) throws PdbException { + offsetFilename = reader.parseUnsignedIntVal(); + length = reader.parseUnsignedByteVal(); + checksumTypeValue = reader.parseUnsignedByteVal(); + bytes = reader.parseBytes(length); + reader.align4(); + } + + long getOffsetFilename() { + return offsetFilename; + } + + long getLength() { + return length; + } + + long getChecksumTypeValue() { + return checksumTypeValue; + } + + byte[] getChecsumBytes() { + return bytes; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(String.format("0x%08x, 0x%02x %s(%02x): ", offsetFilename, length, + ChecksumType.fromValue(checksumTypeValue), checksumTypeValue)); + builder.append(NumericUtilities.convertBytesToString(bytes)); + return builder.toString(); + } + } + + private static enum ChecksumType { + UnknownChecksumType(-0x01), + NoneChecksumType(0x00), + Md5ChecksumType(0x01), + Sha1ChecksumType(0x02), + Sha256ChecksumType(0x03); + + private static final Map BY_VALUE = new HashMap<>(); + static { + for (ChecksumType val : values()) { + BY_VALUE.put(val.value, val); + } + } + + private final int value; + + public static ChecksumType fromValue(int val) { + ChecksumType t = BY_VALUE.getOrDefault(val, UnknownChecksumType); + if (t == UnknownChecksumType && val != UnknownChecksumType.value) { + Msg.warn(null, + String.format("PDB: C13FileChecksum - Unknown checksum type %08x", val)); + } + return t; + } + + private ChecksumType(int value) { + this.value = value; + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FrameData.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FrameData.java new file mode 100644 index 0000000000..1068631135 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FrameData.java @@ -0,0 +1,35 @@ +/* ### + * 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.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type FRAMEDATA. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13FrameData extends AbstractUnimplementedC13Section { + static C13FrameData parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new C13FrameData(reader, ignore, monitor); + } + + protected C13FrameData(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FuncMdTokenMap.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FuncMdTokenMap.java new file mode 100644 index 0000000000..2d70ba7bb4 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13FuncMdTokenMap.java @@ -0,0 +1,35 @@ +/* ### + * 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.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type FUNC_MDTOKEN_MAP. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13FuncMdTokenMap extends AbstractUnimplementedC13Section { + static C13FuncMdTokenMap parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new C13FuncMdTokenMap(reader, ignore, monitor); + } + + protected C13FuncMdTokenMap(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13IlLines.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13IlLines.java new file mode 100644 index 0000000000..465c334d00 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13IlLines.java @@ -0,0 +1,61 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; + +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * C13IlLines information. This is C13 IL Lines, where "IL" meaning is uncertain... could mean + * Incremental Link. MSFT defers parsing to C13Lines, so it is the same format, which we have + * given to a common parent, {@link AbstractC13Lines}. + */ +public class C13IlLines extends AbstractC13Lines { + + /** + * Parse and return a {@link C13IlLines}. + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param ignore flag indicating whether the record should be ignored + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13IlLines parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C13IlLines(reader, ignore, monitor); + } + + protected C13IlLines(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + super(reader, ignore, monitor); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13IlLines--------------------------------------------------\n"); + dumpInternal(writer); + writer.write("End C13IlLines----------------------------------------------\n"); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13InlineeLines.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13InlineeLines.java new file mode 100644 index 0000000000..8c12802c26 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13InlineeLines.java @@ -0,0 +1,188 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * PDB C13InlineeLines information. + */ +public class C13InlineeLines extends C13Section { + + // These are actually DWORDs, but we are ignoring the unsigned nature and using int. + private static final int InlineeSourceLineSignature = 0x0; + private static final int ExtendedInlineeSourceLineSignature = 0x1; + + private int signature; //actually a DWORD (unsigned int) + private List inlineeLines = new ArrayList<>(); + + /** + * Parse and return a {@link C13InlineeLines}. + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param ignore flag indicating whether the record should be ignored + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13InlineeLines parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C13InlineeLines(reader, ignore, monitor); + } + + private static List parseInlineeLines(PdbByteReader reader, + TaskMonitor monitor) throws CancelledException, PdbException { + List lines = new ArrayList<>(); + while (reader.numRemaining() >= InlineeSourceLine.getBaseRecordSize()) { + monitor.checkCanceled(); + InlineeSourceLine line = new InlineeSourceLine(reader); + lines.add(line); + } + return lines; + } + + private static List parseExtendedInlineeLines(PdbByteReader reader, + TaskMonitor monitor) throws CancelledException, PdbException { + List lines = new ArrayList<>(); + while (reader.numRemaining() >= ExtendedInlineeSourceLine.getBaseRecordSize()) { + monitor.checkCanceled(); + ExtendedInlineeSourceLine line = new ExtendedInlineeSourceLine(reader, monitor); + lines.add(line); + } + return lines; + } + + protected C13InlineeLines(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + super(ignore); + signature = reader.parseInt(); //actually a DWORD (unsigned int) + switch (signature) { + case InlineeSourceLineSignature: + inlineeLines = parseInlineeLines(reader, monitor); + break; + case ExtendedInlineeSourceLineSignature: + inlineeLines = parseExtendedInlineeLines(reader, monitor); + break; + default: + inlineeLines = new ArrayList<>(); + break; + } + if (reader.hasMore()) { + Msg.debug(C13InlineeLines.class, + String.format("Extra inlinee bytes remain for signature: 0x%03x", signature)); + } + } + + List getInlineeLines() { + return inlineeLines; + } + + @Override + public String toString() { + return String.format( + "%s: num inlinee lines = %d", getClass().getSimpleName(), inlineeLines.size()); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13InlineeLines---------------------------------------------\n"); + writer.write(String.format("Signature: 0x%03x\n", signature)); + for (InlineeSourceLine line : inlineeLines) { + writer.write(line.toString()); + writer.write('\n'); + } + writer.write("End C13InlineeLines-----------------------------------------\n"); + } + + static class InlineeSourceLine { + protected long inlinee; // unsigned 32-bit + protected int fileId; + protected int sourceLineNum; + + private static int getBaseRecordSize() { + return 12; + } + + InlineeSourceLine(PdbByteReader reader) throws PdbException { + inlinee = reader.parseUnsignedIntVal(); + fileId = reader.parseInt(); + sourceLineNum = reader.parseInt(); + } + + long getInlinee() { + return inlinee; + } + + long getFileId() { + return fileId; + } + + long getSourceLineNum() { + return sourceLineNum; + } + + @Override + public String toString() { + return String.format("0x%09x, 0x%06x, %d", inlinee, fileId, sourceLineNum); + } + } + + static class ExtendedInlineeSourceLine extends InlineeSourceLine { + + private static int getBaseRecordSize() { + return 16; + } + + private List extraFileIds = new ArrayList<>(); // array of longs + + ExtendedInlineeSourceLine(PdbByteReader reader, TaskMonitor monitor) + throws PdbException, CancelledException { + super(reader); + long numExtraFiles = reader.parseUnsignedIntVal(); // unsigned int + for (long i = 0; i < numExtraFiles; i++) { + monitor.checkCanceled(); + extraFileIds.add(reader.parseInt()); + } + } + + int getNumExtraFileIds() { + return extraFileIds.size(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(String.format("0x%09x, 0x%06x, %d", inlinee, fileId, sourceLineNum)); + for (Integer id : extraFileIds) { + builder.append(String.format(" 0x%06x", id)); + } + return builder.toString(); + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Lines.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Lines.java new file mode 100644 index 0000000000..84661f5ee7 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Lines.java @@ -0,0 +1,63 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; + +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * C13Lines information. As best as we know, only one of C11Lines or C13Lines (We have actually + * created a C13Section class at a higher level, and making C13Lines be the specific lines + * information for "type" 0xf2 (and maybe 0xf4) can be found after the symbol information in + * module debug streams. + */ +public class C13Lines extends AbstractC13Lines { + + /** + * Parse and return a {@link C13Lines}. + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param ignore flag indicating whether the record should be ignored + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13Lines parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + return new C13Lines(reader, ignore, monitor); + } + + protected C13Lines(PdbByteReader reader, boolean ignore, TaskMonitor monitor) + throws PdbException, CancelledException { + super(reader, ignore, monitor); + } + + /** + * Dumps this class to a Writer + * @param writer {@link Writer} to which to dump the information + * @throws IOException Upon IOException writing to the {@link Writer} + */ + @Override + void dump(Writer writer) throws IOException { + writer.write("C13Lines----------------------------------------------------\n"); + dumpInternal(writer); + writer.write("End C13Lines------------------------------------------------\n"); + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13MergedAssemblyInput.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13MergedAssemblyInput.java new file mode 100644 index 0000000000..8e2aeca83d --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13MergedAssemblyInput.java @@ -0,0 +1,37 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.bin.format.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type MERGED_ASSEMBLY_INPUT. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13MergedAssemblyInput extends AbstractUnimplementedC13Section { + static C13MergedAssemblyInput parse(PdbByteReader reader, boolean ignore, + TaskMonitor monitor) { + return new C13MergedAssemblyInput(reader, ignore, monitor); + } + + protected C13MergedAssemblyInput(PdbByteReader reader, boolean ignore, + TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Section.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Section.java new file mode 100644 index 0000000000..cb5f176370 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Section.java @@ -0,0 +1,95 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; + +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Abstract class for C13 section types. + */ +abstract class C13Section { + protected static final String dashes = + "------------------------------------------------------------\n"; + + private boolean ignore; + + protected C13Section(boolean ignore) { + this.ignore = ignore; + } + + boolean ignore() { + return ignore; + } + + void dump(Writer writer) throws IOException { + String n = getClass().getSimpleName(); + int len = n.length(); + writer.write(n + dashes.substring(len)); + writer.write("End " + n + dashes.substring(len + 4)); + } + + /** + * Parse and return a {@link C13Section} of a specific type pointed to by a section record. + * @param reader reader to parse from + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return the parsed data + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + */ + static C13Section parse(PdbByteReader reader, TaskMonitor monitor) + throws CancelledException, PdbException { + int typeVal = reader.parseInt(); + boolean ignore = C13Type.ignore(typeVal); + C13Type type = C13Type.fromValue(typeVal); + int length = reader.parseInt(); + PdbByteReader recordReader = reader.getSubPdbByteReader(length); + + switch (type) { + case SYMBOLS: + return C13Symbols.parse(recordReader, ignore, monitor); + case LINES: + return C13Lines.parse(recordReader, ignore, monitor); + case STRING_TABLE: + return C13StringTable.parse(recordReader, ignore, monitor); + case FILE_CHECKSUMS: + return C13FileChecksums.parse(recordReader, ignore, monitor); + case FRAMEDATA: + return C13FrameData.parse(recordReader, ignore, monitor); + case INLINEE_LINES: + return C13InlineeLines.parse(recordReader, ignore, monitor); + case CROSS_SCOPE_IMPORTS: + return C13CrossScopeImports.parse(recordReader, ignore, monitor); + case CROSS_SCOPE_EXPORTS: + return C13CrossScopeExports.parse(recordReader, ignore, monitor); + case IL_LINES: + return C13IlLines.parse(recordReader, ignore, monitor); + case FUNC_MDTOKEN_MAP: + return C13FuncMdTokenMap.parse(recordReader, ignore, monitor); + case TYPE_MDTOKEN_MAP: + return C13TypeMdTokenMap.parse(recordReader, ignore, monitor); + case MERGED_ASSEMBLY_INPUT: + return C13MergedAssemblyInput.parse(recordReader, ignore, monitor); + case COFF_SYMBOL_RVA: // Relative Virtual Address + return C13CoffSymbolRva.parse(recordReader, ignore, monitor); + default: + return UnknownC13Section.parse(recordReader, ignore, monitor); + } + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13SectionIterator.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13SectionIterator.java new file mode 100644 index 0000000000..4de1ad564f --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13SectionIterator.java @@ -0,0 +1,122 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.util.NoSuchElementException; + +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Iterator for {@link C13Section} data being read from C13 section of module stream. + * @param the iterator type + */ +class C13SectionIterator implements ParsingIterator { + + private PdbByteReader reader; + private Class clazz; + private boolean processIgnore; + private TaskMonitor monitor; + + private C13Type requestedType; + + private C13Type detectedType; // section type detected + private T currentSection = null; + + /** + * An Iterator of C13 Section types + * @param reader PdbByteReader containing only C13 Section information and in newly + * constructed state + * @param clazz the class of the iterator type + * @param processIgnore processes records marked as ignore + * @param monitor {@link TaskMonitor} used for checking user cancellation + * @throws CancelledException upon user cancellation + */ + public C13SectionIterator(PdbByteReader reader, Class clazz, boolean processIgnore, + TaskMonitor monitor) throws CancelledException { + this.reader = reader; + this.clazz = clazz; + this.requestedType = C13Type.fromClassValue(clazz); + this.processIgnore = processIgnore; + this.monitor = monitor; + } + + @Override + public boolean hasNext() throws CancelledException { + if (currentSection == null) { + find(); + } + return (currentSection != null); + } + + @Override + public T next() throws CancelledException, NoSuchElementException { + if (hasNext()) { + T returnSection = currentSection; + currentSection = null; + return returnSection; + } + throw new NoSuchElementException("next() called with no more elements"); + } + + @Override + public T peek() throws CancelledException, NoSuchElementException { + if (hasNext()) { + return currentSection; + } + throw new NoSuchElementException("peek() called with no more elements"); + } + + private void find() throws CancelledException { + try { + currentSection = findAndParse(); + } + catch (PdbException e) { + Msg.error(this, "Problem seen in find()", e); + currentSection = null; + } + } + + /** + * Finds and parses the next C13 Section type requested + * @return the found and parsed element. Can be null if not found + * @throws CancelledException upon user cancellation + * @throws PdbException upon problem parsing data + */ + public T findAndParse() throws CancelledException, PdbException { + while (reader.hasMore()) { + monitor.checkCanceled(); + int index = reader.getIndex(); + int typeVal = reader.parseInt(); + boolean ignore = C13Type.ignore(typeVal); + detectedType = C13Type.fromValue(typeVal); + int len = reader.parseInt(); + if ((!ignore || processIgnore) && + (requestedType == C13Type.ALL || detectedType == requestedType)) { + reader.setIndex(index); + C13Section parsedSection = C13Section.parse(reader, monitor); + return (parsedSection.getClass().equals(clazz) || + C13Section.class.equals(clazz)) + ? clazz.cast(parsedSection) + : null; + } + reader.skip(len); + } + return null; + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13StringTable.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13StringTable.java new file mode 100644 index 0000000000..10e7b1a1fb --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13StringTable.java @@ -0,0 +1,35 @@ +/* ### + * 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.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type STRING_TABLE. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13StringTable extends AbstractUnimplementedC13Section { + static C13StringTable parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new C13StringTable(reader, ignore, monitor); + } + + protected C13StringTable(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Symbols.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Symbols.java new file mode 100644 index 0000000000..29a5fb23db --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Symbols.java @@ -0,0 +1,35 @@ +/* ### + * 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.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type SYMBOLS. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13Symbols extends AbstractUnimplementedC13Section { + static C13Symbols parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new C13Symbols(reader, ignore, monitor); + } + + protected C13Symbols(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Type.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Type.java new file mode 100644 index 0000000000..c812e9137b --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13Type.java @@ -0,0 +1,128 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.util.HashMap; +import java.util.Map; + +import ghidra.util.Msg; + +/** + * Enum storing the type value of C13 Section along with the class of the section implementation. + * The value is what will be parsed prior to the rest of record that will be parsed according to + * its type. The class information is available to ensure proper casting to the type specified. + *

+ * Note that we have created two additional enumerates: one for values that don't match in the + * {@link #fromValue(int)} method and can typically be used default switch cases. The other + * is used to "select" ALL standard C13 Section types when used appropriately. Of course, there + * is a chance that the enumerate values we have chosen for these could cause an unforeseen + * problem, but we tried to choose values that will not be problematic. + *

+ * Note that lookups by value mask off an "ignore" bit, and since we are an enum, we cannot store + * the fact of ignore or not unless we double the number of enumerates. + * However, we have incorporated a utility method testing the "ignore" value on the parsed value + * prior to doing the lookup of with the {@link #fromValue(int)} method. + */ +enum C13Type { + UNKNOWN(0x80000000, UnknownC13Section.class), // We created; fix/eliminate if causes problems + ALL(0x00000000, C13Section.class), // We created; fix if causes problems + SYMBOLS(0xf1, C13Symbols.class), + LINES(0xf2, C13Lines.class), + STRING_TABLE(0xf3, C13StringTable.class), + FILE_CHECKSUMS(0xf4, C13FileChecksums.class), + FRAMEDATA(0xf5, C13FrameData.class), + INLINEE_LINES(0xf6, C13InlineeLines.class), + CROSS_SCOPE_IMPORTS(0xf7, C13CrossScopeImports.class), + CROSS_SCOPE_EXPORTS(0xf8, C13CrossScopeExports.class), + IL_LINES(0xf9, C13IlLines.class), + FUNC_MDTOKEN_MAP(0xfa, C13FuncMdTokenMap.class), + TYPE_MDTOKEN_MAP(0xfb, C13TypeMdTokenMap.class), + MERGED_ASSEMBLY_INPUT(0xfc, C13MergedAssemblyInput.class), + COFF_SYMBOL_RVA(0xfd, C13CoffSymbolRva.class); + + private static final int IGNORE_BIT = 0x80000000; + private static final int IGNORE_BIT_MASK = ~IGNORE_BIT; + + private static final Map BY_VALUE = new HashMap<>(); + private static final Map, C13Type> BY_CLASS_VALUE = new HashMap<>(); + static { + for (C13Type val : values()) { + BY_VALUE.put(val.value, val); + BY_CLASS_VALUE.put(val.classValue, val); + } + } + + private final int value; + private final Class classValue; + + /** + * Returns the C13Type corresponding to the parse value for the C13 Section type. + * @param val the parse value + * @return the C13Type + */ + public static C13Type fromValue(int val) { + C13Type t = BY_VALUE.getOrDefault(maskIgnore(val), UNKNOWN); + if (t == UNKNOWN) { + Msg.debug(C13Type.class, String.format("C13Debug - Unknown section type %08x", val)); + } + return t; + } + + /** + * Returns the C13Type which has the (parse) value that is used to identify a section of the + * type specified by the {@code classVal} parameter + * @param classVal the implementation class we are need + * @return the C13Type for this type + */ + public static C13Type fromClassValue(Class classVal) { + C13Type t = BY_CLASS_VALUE.getOrDefault(classVal, UNKNOWN); + if (t == UNKNOWN) { + Msg.debug(C13Type.class, + String.format("C13Debug - Unknown classValue %s", classVal.getSimpleName())); + } + return t; + } + + public static boolean ignore(int val) { + return ((val & IGNORE_BIT) != 0); + } + + public static int maskIgnore(int val) { + return val & IGNORE_BIT_MASK; + } + + private C13Type(int value, Class classValue) { + this.value = value; + this.classValue = classValue; + } + + /** + * Returns the value of the enum + * @return the value + */ + public int getValue() { + return value; + } + + /** + * Returns the Class that is associated with the enum + * @return the Class + */ + public Class getSectionClass() { + return classValue; + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13TypeMdTokenMap.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13TypeMdTokenMap.java new file mode 100644 index 0000000000..f36cfd3963 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13TypeMdTokenMap.java @@ -0,0 +1,35 @@ +/* ### + * 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.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * Class for C13Type TYPE_MDTOKEN_MAP. + *

+ * This temporary class implementation currently extends {@link AbstractUnimplementedC13Section}, + * but this should be changed to {@link C13Section} when the format is understood and the + * implementation is made concrete. + */ +class C13TypeMdTokenMap extends AbstractUnimplementedC13Section { + static C13TypeMdTokenMap parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new C13TypeMdTokenMap(reader, ignore, monitor); + } + + protected C13TypeMdTokenMap(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/GlobalReferenceIterator.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/GlobalReferenceIterator.java new file mode 100644 index 0000000000..58efe8f281 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/GlobalReferenceIterator.java @@ -0,0 +1,114 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.util.NoSuchElementException; + +import ghidra.app.util.bin.format.pdb2.pdbreader.msf.MsfStream; +import ghidra.app.util.bin.format.pdb2.pdbreader.symbol.AbstractMsSymbol; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Iterator for Global Reference Offsets section of module stream. This iterator returns + * an {@link AbstractMsSymbol} iterator from the global symbols section that has been initialized + * with the offset specified in this modules global reference offset section. + */ +class GlobalReferenceIterator implements ParsingIterator { + + private AbstractPdb pdb; + private int symbolsStreamNumber; + + private TaskMonitor monitor; + + private GlobalReferenceOffsetIterator offsetIterator = null; + + private MsSymbolIterator currentGlobalSymbolIterator = null; + + /** + * An Iterator of Global Reference Symbol Iterators (iterator of iterators). + * @param pdb {@link AbstractPdb} that owns the Symbols to be parsed + * @param reader PdbByteReader containing only Global Reference Offsets information and in + * newly constructed state + * @param monitor {@link TaskMonitor} used for checking user cancellation + * @throws CancelledException upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + public GlobalReferenceIterator(AbstractPdb pdb, PdbByteReader reader, TaskMonitor monitor) + throws CancelledException, PdbException { + this.pdb = pdb; + this.monitor = monitor; + PdbDebugInfo debugInfo = pdb.getDebugInfo(); + if (debugInfo == null) { + throw new PdbException( + "Cannot create " + getClass() + " because PDB Debug Info is null"); + } + symbolsStreamNumber = debugInfo.getSymbolRecordsStreamNumber(); + if (symbolsStreamNumber == 0xffff) { + throw new PdbException( + "Cannot create " + getClass() + " because there is no symbol stream"); + } + offsetIterator = new GlobalReferenceOffsetIterator(reader); + } + + @Override + public boolean hasNext() throws CancelledException { + if (currentGlobalSymbolIterator == null) { + find(); + } + return (currentGlobalSymbolIterator != null); + } + + @Override + public MsSymbolIterator next() throws CancelledException, NoSuchElementException { + if (hasNext()) { + MsSymbolIterator returnGlobalSymbolIterator = currentGlobalSymbolIterator; + currentGlobalSymbolIterator = null; + return returnGlobalSymbolIterator; + } + throw new NoSuchElementException("next() called with no more elements"); + } + + @Override + public MsSymbolIterator peek() throws CancelledException, NoSuchElementException { + if (hasNext()) { + return currentGlobalSymbolIterator; + } + throw new NoSuchElementException("peek() called with no more elements"); + } + + private void find() throws CancelledException { + + if (!offsetIterator.hasNext()) { + currentGlobalSymbolIterator = null; + return; + } + try { + Long offset = offsetIterator.next(); + PdbByteReader reader = + pdb.getReaderForStreamNumber(symbolsStreamNumber, offset.intValue(), + MsfStream.MAX_STREAM_LENGTH, monitor); + currentGlobalSymbolIterator = new MsSymbolIterator(pdb, reader); + } + catch (IOException e) { + Msg.error(this, "Problem seen in find()", e); + currentGlobalSymbolIterator = null; + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/GlobalReferenceOffsetIterator.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/GlobalReferenceOffsetIterator.java new file mode 100644 index 0000000000..81c35b9895 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/GlobalReferenceOffsetIterator.java @@ -0,0 +1,94 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.util.NoSuchElementException; + +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; + +/** + * Iterator for Global Reference Offsets section of module stream. + */ +class GlobalReferenceOffsetIterator implements ParsingIterator { + + private PdbByteReader reader; + + private Long currentGlobalReferenceOffset = null; + + /** + * An Iterator of Global Reference Offsets + * @param reader PdbByteReader containing only Global Reference Offsets information and in + * newly constructed state + * @throws CancelledException upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + public GlobalReferenceOffsetIterator(PdbByteReader reader) + throws CancelledException, PdbException { + this.reader = reader; + processHeader(); + } + + @Override + public boolean hasNext() { + if (currentGlobalReferenceOffset == null) { + find(); + } + return (currentGlobalReferenceOffset != null); + } + + @Override + public Long next() throws NoSuchElementException { + if (hasNext()) { + Long returnGlobalReferenceOffset = currentGlobalReferenceOffset; + currentGlobalReferenceOffset = null; + return returnGlobalReferenceOffset; + } + throw new NoSuchElementException("next() called with no more elements"); + } + + @Override + public Long peek() throws NoSuchElementException { + if (hasNext()) { + return currentGlobalReferenceOffset; + } + throw new NoSuchElementException("peek() called with no more elements"); + } + + private void find() { + try { + currentGlobalReferenceOffset = reader.parseUnsignedIntVal(); + } + catch (PdbException e) { + Msg.error(this, "Problem seen in find()", e); + currentGlobalReferenceOffset = null; + } + } + + /** + * Reads and validates size field; leaves reader pointing at first record. + * @throws PdbException Upon not enough data left to parse + */ + private void processHeader() throws PdbException { + int sizeField = reader.parseInt(); + if (sizeField + 4 != reader.getLimit()) { + throw new PdbException( + String.format("Error in module global refs size field: %d != %d", sizeField, + reader.getLimit())); + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/Module.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/Module.java new file mode 100644 index 0000000000..5886f1e50b --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/Module.java @@ -0,0 +1,435 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.io.IOException; +import java.io.Writer; +import java.util.*; + +import ghidra.app.util.bin.format.pdb2.pdbreader.msf.MsfStream; +import ghidra.app.util.bin.format.pdb2.pdbreader.symbol.AbstractMsSymbol; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Note that this class is new, in-progress creation, being designed as a better interface for + * getting information for any particular module (stream) in a more random-access manner. + *

+ * This class represents Module Stream data of a PDB file. This is different from the + * {@link AbstractModuleInformation} and children classes that are parsed from the DBI stream, + * which describes (or is control information for) what is the stream from which this + * {@link Module} is parsed. Note that we use the {@link AbstractModuleInformation} as one of + * the construction parameter to this class. + *

+ * This class is only suitable for reading; not for writing or modifying a PDB. + *

+ * We have intended to implement according to the Microsoft PDB API (source); see the API for + * truth. + */ +public class Module { + + private AbstractPdb pdb; + private AbstractModuleInformation moduleInformation; + private TaskMonitor monitor; + + private int streamNumber; + private MsfStream stream = null; + + private int offsetSymbols; + private int offsetLines; + private int offsetC13Lines; + private int offsetGlobalRefs; + + private int sizeSymbols; + private int sizeLines; + private int sizeC13Lines; + private int sizeGlobalRefs; + + private boolean doDumpGlobalRefererenceInfo = false; + + //============================================================================================== + public Module(AbstractPdb pdb, AbstractModuleInformation moduleInformation, + TaskMonitor monitor) { + Objects.requireNonNull(pdb, "pdb cannot be null"); + Objects.requireNonNull(moduleInformation, "moduleInformation cannot be null"); + this.pdb = pdb; + this.moduleInformation = moduleInformation; + this.monitor = monitor; + precalculateStreamLocations(); + } + + public AbstractModuleInformation getModuleInformation() { + return moduleInformation; + } + + private void precalculateStreamLocations() { + streamNumber = moduleInformation.getStreamNumberDebugInformation(); + if (streamNumber == 0xffff) { + return; + } + stream = pdb.getMsf().getStream(streamNumber); + if (stream == null) { + return; + } + int length = stream.getLength(); + + sizeSymbols = moduleInformation.getSizeLocalSymbolsDebugInformation(); + sizeLines = moduleInformation.getSizeLineNumberDebugInformation(); + sizeC13Lines = moduleInformation.getSizeC13StyleLineNumberInformation(); + + offsetSymbols = 0; + offsetLines = sizeSymbols; + offsetC13Lines = offsetLines + sizeLines; + offsetGlobalRefs = offsetC13Lines + sizeC13Lines; + // Note that sizeGlobalRefs includes the size field found within the stream and the field + // should have a value that is 4 less than this size here. Note that if additional + // data is added to this stream by MSFT after these global at a future date, then this + // calculation will not be correct. + sizeGlobalRefs = length - offsetGlobalRefs; + } + + //============================================================================================== + /** + * Return the C11 Lines for this Module + * @return the C11 Lines + * @throws CancelledException upon user cancellation + * @throws PdbException upon issue reading this Module's stream + */ + public C11Lines getLineInformation() + throws CancelledException, PdbException { + if (sizeLines == 0) { + return null; + } + try { + PdbByteReader reader = + pdb.getReaderForStreamNumber(streamNumber, offsetLines, sizeLines, + monitor); + // This parser has not been tested with real data + C11Lines c11Lines = C11Lines.parse(pdb, reader, monitor); + return c11Lines; + } + catch (IOException e) { + return null; + } + } + + //============================================================================================== + /** + * Returns an MsSymbolIterator for the symbols of this module + * @return the iterator + * @throws CancelledException upon user cancellation + * @throws PdbException upon invalid cvSignature + */ + public MsSymbolIterator getSymbolIterator() throws CancelledException, PdbException { + PdbByteReader symbolsReader = getSymbolsReader(); + parseSignature(symbolsReader); + MsSymbolIterator iterator = new MsSymbolIterator(pdb, symbolsReader); + return iterator; + } + + private void parseSignature(PdbByteReader symbolsReader) throws PdbException { + if (symbolsReader == PdbByteReader.DUMMY) { + return; // DUMMY is empty. + } + // cvSignature: + // >64K = C6 + // 1 = C7 + // 2 = C11 (vc5.x) + // 3 = ??? (not specified, and not marked as reserved) + // 4 = C13 (vc7.x) + // 5-64K = RESERVED + // + // Both cvdump (1660 and 1668) and mod.cpp (575) seem to indicate that the first module + // might have the cvSignature of C7 or C11 (when C7/C11), but modules thereafter will not + // or may not have the value. C13 would always have the C13 signature. + + // NOTE: the following logic was originally intended for when processing multiple modules, + // back-to-back. It won't work here, as the getSig value is not retained. Thing is, we + // have no real data to test the questionable requirement (what we think was in MSFT + // design) at this time. + boolean getSig = true; + int cvSignature = 0; + if (getSig) { + cvSignature = symbolsReader.parseInt(); + } + switch (cvSignature) { + case 1: + case 2: + // We have no 1,2 examples to test this logic for cvSignature. Confirming + // or rejecting this logic is important for simplifying/refactoring this + // method or writing new methods to allow for extraction of information from + // individual modules. The current implementation has cross-module logic + // (setting state in the processing of the first and using this state in the + // processing of follow-on modules). + getSig = false; + break; + case 4: + break; + default: + if (cvSignature < 0x10000) { + throw new PdbException( + "PDB Error: Invalid module CV signature in stream " + streamNumber); + } + break; + } + + } + + //============================================================================================== + /** + * Returns a C13SectionIterator that iterators over all C13Sections of this module + * @return the iterator + * @throws CancelledException upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + public C13SectionIterator getC13SectionIterator() + throws CancelledException, PdbException { + C13SectionIterator iterator = getC13SectionFilteredIterator(C13Section.class); + return iterator; + } + + /** + * Returns a C13SectionIterator that iterators over all filtered C13Sections of this module + * @param clazz The class of the filter type + * @return the iterator + * @throws CancelledException upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + public C13SectionIterator getC13SectionFilteredIterator( + Class clazz) throws CancelledException, PdbException { + PdbByteReader c13SectionReader = getC13LinesReader(); + C13SectionIterator iterator = + new C13SectionIterator<>(c13SectionReader, clazz, true, monitor); + return iterator; + } + + //============================================================================================== + /** + * Returns a GlobalReferenceOffsetIterator, but note that there is no determined end for + * iteration other than running out of data... it is very unlikely that it should be iterated + * until it is out of data. Context should probably be used. For instance, if the global + * symbol that is first in this iterator is a GPROC32, then it should probably be iterated over + * nested blocks until the closing END is found for the GPROC32 + * @return the iterator + * @throws CancelledException upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + public GlobalReferenceOffsetIterator getGlobalReferenceOffsetIterator() + throws CancelledException, PdbException { + PdbByteReader globalRefsReader = getGlobalRefsReader(); + GlobalReferenceOffsetIterator iterator = + new GlobalReferenceOffsetIterator(globalRefsReader); + return iterator; + } + + //============================================================================================== + /** + * Returns a GlobalReferenceIterator. Iterations of the GlobalReferenceIterator returns + * new MsSymbolIterators, but note that there is no determined end for each MsSymbolIterator + * other than running out of data... it is very unlikely that it should be iterated until + * it is out of data. Context should probably be used. For instance, if the global symbol + * that is first in this iterator is a GPROC32, then it should probably be iterated over + * nested blocks until the closing END is found for the GPROC32 + * @return the iterator + * @throws CancelledException upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + public GlobalReferenceIterator getGlobalReferenceIterator() + throws CancelledException, PdbException { + PdbByteReader globalRefsReader = getGlobalRefsReader(); + GlobalReferenceIterator iterator = + new GlobalReferenceIterator(pdb, globalRefsReader, monitor); + return iterator; + } + + //============================================================================================== + private PdbByteReader getSymbolsReader() throws CancelledException { + return getReader(offsetSymbols, sizeSymbols, "Symbols"); + } + + // Not yet used, but intended for when we change C11 Lines to the iterator model. + @SuppressWarnings("unused") + private PdbByteReader getLinesReader() throws CancelledException { + return getReader(offsetLines, sizeLines, "Lines"); + } + + private PdbByteReader getC13LinesReader() throws CancelledException { + return getReader(offsetC13Lines, sizeC13Lines, + "C13Lines"); + } + + private PdbByteReader getGlobalRefsReader() throws CancelledException { + return getReader(offsetGlobalRefs, sizeGlobalRefs, "GlobalRefs"); + } + + private PdbByteReader getReader(int offset, int size, String sectionName) + throws CancelledException { + if (streamNumber == 0xffff) { + return PdbByteReader.DUMMY; + } + + try { + PdbByteReader reader = pdb.getReaderForStreamNumber(streamNumber, monitor); + reader.skip(offset); + try { + if (size == -1) { + size = reader.parseInt(); + } + if (size == 0) { + return PdbByteReader.DUMMY; + } + return reader.getSubPdbByteReader(size); + } + catch (PdbException e) { + PdbLog.message("Exception retrieving PdbByteReader for stream " + streamNumber + + " sectionName: " + e.getMessage()); + return PdbByteReader.DUMMY; + } + } + catch (IOException e) { + PdbLog.message("Exception sub-reader from reader for stream " + streamNumber + + " sectionName: " + e.getMessage()); + return PdbByteReader.DUMMY; + } + + } + + //============================================================================================== + // Note that we are slowly changing the model to an iterator model so that not everything + // is loaded into the class (note that as of this writing, the PdbByteReader still contains + // full byte array of data, consuming memory at the time of use). + /** + * Dumps this class to a Writer. + * @param writer {@link Writer} to which to dump the information + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation + * @throws IOException Upon IOException writing to the {@link Writer} + */ + void dump(Writer writer) + throws CancelledException, PdbException, IOException { + + writer.write("Module------------------------------------------------------\n"); + + dumpSymbols(writer); + dumpC11Lines(writer); + dumpC13Sections(writer); + + // These can add tons of output, so have a flag to control whether they are output. + if (doDumpGlobalRefererenceInfo) { + dumpGlobalReferenceOffsets(writer); + dumpGlobalReferences(writer); + } + + writer.write("End Module--------------------------------------------------\n"); + } + + private void dumpSymbols(Writer writer) + throws IOException, CancelledException, PdbException { + writer.write("Symbols-----------------------------------------------------\n"); + MsSymbolIterator symbolIterator = getSymbolIterator(); + while (symbolIterator.hasNext()) { + AbstractMsSymbol symbol = symbolIterator.next(); + writer.append(symbol.toString()); + } + writer.write("End Symbols-------------------------------------------------\n"); + } + + private void dumpC11Lines(Writer writer) + throws IOException, CancelledException, PdbException { + // Need to confirm C11 parsing and then convert it to an Iterator model; would be very + // helpful to find some real data + writer.write("C11Lines----------------------------------------------------\n"); + C11Lines c11lines = getLineInformation(); + if (c11lines != null) { + writer.write(c11lines.dump()); + } + writer.write("End C11Lines------------------------------------------------\n"); + } + + private void dumpC13Sections(Writer writer) + throws IOException, CancelledException, PdbException { + writer.write("C13Sections-------------------------------------------------\n"); + C13SectionIterator c13Iterator = + getC13SectionFilteredIterator(C13Section.class); + while (c13Iterator.hasNext()) { + C13Section c13Section = c13Iterator.next(); + c13Section.dump(writer); + } + writer.write("End C13Sections---------------------------------------------\n"); + + // These are here as examples of what we might output in the future... the C13 types + // in a type-by-type basis, including Dummy types. +// C13SectionIterator c13SymbolsIterator = +// getC13SectionFilteredIterator(DummyC13Symbols.class); +// while (c13SymbolsIterator.hasNext()) { +// DummyC13Symbols dummyC13Symbols = c13SymbolsIterator.next(); +// dummyC13Symbols.dump(writer); +// } +// +// C13SectionIterator c13LinesIterator = +// getC13SectionFilteredIterator(C13Lines.class); +// while (c13LinesIterator.hasNext()) { +// C13Lines myC13Lines = c13LinesIterator.next(); +// myC13Lines.dump(writer); +// } + } + + // Need to confirm the global ref offsets and symbols by "study." + private void dumpGlobalReferenceOffsets(Writer writer) + throws IOException, CancelledException, PdbException { + writer.write("GlobalReferenceSymbolOffsets--------------------------------\n"); + List tmp = new ArrayList<>(); + GlobalReferenceOffsetIterator globalRefsOffsetIterator = + getGlobalReferenceOffsetIterator(); + while (globalRefsOffsetIterator.hasNext()) { + Long val = globalRefsOffsetIterator.next(); + writer.append(String.format("0x%08x\n", val)); + tmp.add(val); + } + int cnt = 0; + GlobalReferenceOffsetIterator globalReferenceOffsetIterator = + getGlobalReferenceOffsetIterator(); + while (globalReferenceOffsetIterator.hasNext()) { + long val = globalReferenceOffsetIterator.next(); + long val2 = tmp.get(cnt++); + if (val != val2) { + int a = 1; + a = a + 1; + } + } + writer.write("End GlobalReferenceSymbolOffsets----------------------------\n"); + } + + // Need to confirm the global ref offsets and symbols by "study." + private void dumpGlobalReferences(Writer writer) + throws IOException, CancelledException, PdbException { + writer.write("GlobalReferenceSymbols--------------------------------------\n"); + GlobalReferenceIterator globalReferenceIterator = + getGlobalReferenceIterator(); + while (globalReferenceIterator.hasNext()) { + MsSymbolIterator symIter = globalReferenceIterator.next(); + if (symIter.hasNext()) { + AbstractMsSymbol sym = symIter.next(); + writer.append(String.format("%s\n", sym.toString())); + } + else { + writer.append("No sym in MsSymIterator returned by GlobalReferensIterator\n"); + } + } + writer.write("End GlobalReferenceSymbols----------------------------------\n"); + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation500.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation500.java index 4468cf8719..1db3e1bdfd 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation500.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation500.java @@ -15,24 +15,16 @@ */ package ghidra.app.util.bin.format.pdb2.pdbreader; -import java.util.Objects; - /** * This class is the version of {@link AbstractModuleInformation} for Microsoft v5.00 PDB. */ public class ModuleInformation500 extends AbstractModuleInformation { - //============================================================================================== - // Internals - //============================================================================================== - private AbstractPdb pdb; - //============================================================================================== // API //============================================================================================== public ModuleInformation500(AbstractPdb pdb) { - Objects.requireNonNull(pdb, "pdb cannot be null"); - this.pdb = pdb; + super(pdb); sectionContribution = new SectionContribution400(); } diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation600.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation600.java index 9533da110b..1214278b9d 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation600.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ModuleInformation600.java @@ -15,24 +15,16 @@ */ package ghidra.app.util.bin.format.pdb2.pdbreader; -import java.util.Objects; - /** * This class is the version of {@link AbstractModuleInformation} for Microsoft v6.00 PDB. */ public class ModuleInformation600 extends AbstractModuleInformation { - //============================================================================================== - // Internals - //============================================================================================== - private AbstractPdb pdb; - //============================================================================================== // API //============================================================================================== public ModuleInformation600(AbstractPdb pdb) { - Objects.requireNonNull(pdb, "pdb cannot be null"); - this.pdb = pdb; + super(pdb); sectionContribution = new SectionContribution600(); } diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/MsSymbolIterator.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/MsSymbolIterator.java new file mode 100644 index 0000000000..07c77c7c57 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/MsSymbolIterator.java @@ -0,0 +1,85 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.util.NoSuchElementException; + +import ghidra.app.util.bin.format.pdb2.pdbreader.symbol.AbstractMsSymbol; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; + +/** + * Iterator for {@link AbstractMsSymbol AbstractMsSymbols} being read from a stream. + */ +class MsSymbolIterator implements ParsingIterator { + + private AbstractPdb pdb; + private PdbByteReader reader; + + private AbstractMsSymbol currentSymbol = null; + + /** + * Constructor. + * @param pdb {@link AbstractPdb} that owns the Symbols to be parsed + * @param reader for the stream section containing the symbol information + * @throws CancelledException upon user cancellation + */ + public MsSymbolIterator(AbstractPdb pdb, PdbByteReader reader) throws CancelledException { + this.pdb = pdb; + this.reader = reader; + } + + @Override + public boolean hasNext() throws CancelledException { + if (currentSymbol == null) { + find(); + } + return (currentSymbol != null); + } + + @Override + public AbstractMsSymbol next() throws CancelledException, NoSuchElementException { + if (hasNext()) { + AbstractMsSymbol returnSymbol = currentSymbol; + currentSymbol = null; + return returnSymbol; + } + throw new NoSuchElementException("next() called with no more elements"); + } + + @Override + public AbstractMsSymbol peek() throws CancelledException, NoSuchElementException { + if (hasNext()) { + return currentSymbol; + } + throw new NoSuchElementException("peek() called with no more elements"); + } + + private void find() throws CancelledException { + if (!reader.hasMore()) { + currentSymbol = null; + return; + } + try { + currentSymbol = SymbolParser.parseLengthAndSymbol(pdb, reader); + } + catch (PdbException e) { + Msg.error(this, "Problem seen in find()", e); + currentSymbol = null; + } + } + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ParsingIterator.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ParsingIterator.java new file mode 100644 index 0000000000..e97460876e --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/ParsingIterator.java @@ -0,0 +1,59 @@ +/* ### + * 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.pdb2.pdbreader; + +import java.util.NoSuchElementException; + +import ghidra.util.exception.CancelledException; + +/** + * Parsing Iterator, which allows CancelledException. + *

+ * Has {@link #hasNext()} and {@link #next()}. + *

+ * Also has {@link #peek()}, which performs the same operation as {@link #next()} without advancing + * the iterator. + *

+ * Does not have {@code remove()} and {@code forEachRemaining()} that are in {@code Iterator}. + *

+ *@param the iterator type + */ +interface ParsingIterator { + + /** + * Returns {@code true} if more elements exist + * @return {@code true} if more elements exist + * @throws CancelledException upon user cancellation + */ + boolean hasNext() throws CancelledException; + + /** + * Returns the next element in the iteration. + * @return the next element in the iteration + * @throws CancelledException upon user cancellation + * @throws NoSuchElementException if the iteration has no more elements + */ + E next() throws CancelledException, NoSuchElementException; + + /** + * Returns the next element in the iteration without advancing the iterator. + * @return the next element in the iteration + * @throws CancelledException upon user cancellation + * @throws NoSuchElementException if the iteration has no more elements + */ + E peek() throws CancelledException, NoSuchElementException; + +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbByteReader.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbByteReader.java index b9ed252be3..cf9905fa7a 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbByteReader.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbByteReader.java @@ -39,10 +39,12 @@ import ghidra.util.LittleEndianDataConverter; * *

Other utility methods exist for setting/getting the {@code index} or for moving the * {@code index} along to align or pad-out according to how a C/C++ structure would be padded in - * memory. + * memory. */ public class PdbByteReader { + public static final PdbByteReader DUMMY = new PdbByteReader(new byte[] {}); + //============================================================================================== // Internals //============================================================================================== diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbDebugInfo.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbDebugInfo.java index bad6fe6ec2..5bc4d2437c 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbDebugInfo.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbDebugInfo.java @@ -73,6 +73,12 @@ public abstract class PdbDebugInfo { protected GlobalSymbolInformation globalSymbolInformation; protected PublicSymbolInformation publicSymbolInformation; + //============================================================================================== + // NEW STUFF FROM REFACTOR/REWORK (can be duplicative with other stuff)... might be turned off + // during development. + private boolean doNewStuff = false; + private List modules = new ArrayList<>(); + //============================================================================================== // API //============================================================================================== @@ -100,7 +106,7 @@ public abstract class PdbDebugInfo { /** * Deserializes the {@link PdbDebugInfo}-based instance. - * The pdb is updated with dbiAge and targetProcessor during deserialization + * The PDB is updated with dbiAge and targetProcessor during deserialization * of new DBI header. * @param headerOnly if true only the DBI header fields will be parsed * @param monitor {@link TaskMonitor} used for checking cancellation. @@ -122,6 +128,12 @@ public abstract class PdbDebugInfo { deserializeHeader(reader); deserializeInternalSubstreams(reader, monitor); deserializeAdditionalSubstreams(monitor); + // BELOW: NEW STUFF FROM REFACTOR/REWORK (can be duplicative with other stuff) + if (doNewStuff) { + parseModules(monitor); + compareSymbols(monitor); //temporary to ensure same results with previous work. + } + // ABOVE: NEW STUFF FROM REFACTOR/REWORK (can be duplicative with other stuff) } return versionNumber; } @@ -518,8 +530,10 @@ public abstract class PdbDebugInfo { * instance. * @param writer {@link Writer} to which to dump the information. * @throws IOException Upon IOException writing to the {@link Writer}. + * @throws CancelledException Upon user cancellation + * @throws PdbException Upon not enough data left to parse */ - protected void dump(Writer writer) throws IOException { + protected void dump(Writer writer) throws IOException, CancelledException, PdbException { writer.write("DebugInfoHeader---------------------------------------------\n"); dumpHeader(writer); writer.write("\nEnd DebugInfoHeader-----------------------------------------\n"); @@ -536,13 +550,22 @@ public abstract class PdbDebugInfo { * {@link PdbDebugInfo}-based instance. * @param writer {@link Writer} to which to dump the information. * @throws IOException Upon IOException writing to the {@link Writer}. + * @throws CancelledException Upon user cancellation + * @throws PdbException Upon not enough data left to parse */ - protected void dumpAdditionalSubstreams(Writer writer) throws IOException { + protected void dumpAdditionalSubstreams(Writer writer) + throws IOException, CancelledException, PdbException { symbolRecords.dump(writer); writer.write("\n"); globalSymbolInformation.dump(writer); writer.write("\n"); publicSymbolInformation.dump(writer); + if (doNewStuff) { + dumpSymbols(writer); + for (Module module : modules) { + module.dump(writer); + } + } } /** @@ -586,4 +609,139 @@ public abstract class PdbDebugInfo { } } + //============================================================================================== + // NEW STUFF FROM REFACTOR/REWORK (can be duplicative with other stuff)... might be turned off + // during development. + private void parseModules(TaskMonitor monitor) throws CancelledException { + for (AbstractModuleInformation moduleInformation : moduleInformationList) { + monitor.checkCanceled(); + Module module = new Module(pdb, moduleInformation, monitor); + modules.add(module); + } + } + + private int numModules() { + return modules.size(); + } + + /** + * Return the Module based upon the module number. + * @param moduleNum the module number + * @return the module + */ + public Module getModule(int moduleNum) { + return modules.get(moduleNum); + } + + // NOTE: Designs are not done regarding possibly iterators for iterating only globals or publics + /** + * Returns the symbol iterator for general (public and global symbols. + * @param monitor monitor for the job + * @return an iterator over all symbols of the module + * @throws CancelledException Upon user cancellation + * @throws IOException upon issue reading the stream + */ + public MsSymbolIterator getSymbolIterator(TaskMonitor monitor) + throws CancelledException, IOException { + if (streamNumberSymbolRecords == 0xffff) { + return null; + } + PdbByteReader reader = pdb.getReaderForStreamNumber(streamNumberSymbolRecords, monitor); + MsSymbolIterator iterator = new MsSymbolIterator(pdb, reader); + return iterator; + } + + /** + * Returns the symbol iterator symbols of the specified module. + * @param moduleNum the module number + * @return an iterator over all symbols of the module + * @throws CancelledException Upon user cancellation + * @throws PdbException Upon not enough data left to parse + */ + MsSymbolIterator getSymbolIterator(int moduleNum) throws CancelledException, PdbException { + Module module = modules.get(moduleNum); + return module.getSymbolIterator(); + } + + private void dumpSymbols(Writer writer) throws CancelledException, IOException { + // TODO: in GP-2367 (rename/refactor) ticket... put in appropriate monitor + MsSymbolIterator iterator = getSymbolIterator(TaskMonitor.DUMMY); + List symbols = new ArrayList<>(); + while (iterator.hasNext()) { + symbols.add(iterator.next()); + } + } + + // This method is temporary. It only exists for ensuring results as we transition processing + // mechanisms. + private void compareSymbols(TaskMonitor monitor) + throws CancelledException, PdbException, IOException { + PdbDebugInfo debugInfo = pdb.getDebugInfo(); + if (debugInfo == null) { + return; + } + + // Compare general symbols + MsSymbolIterator iterator = getSymbolIterator(monitor); + List symbols = new ArrayList<>(); + while (iterator.hasNext()) { + symbols.add(iterator.next()); + } + if (symbols.size() != symbolRecords.getSymbolsByOffset().size()) { + // Set break-point on next line. Multiple lines here to eliminate Eclipse warning. + int a = 1; + a = a + 1; + } + int cnt = 0; + for (Map.Entry entry : symbolRecords.getSymbolsByOffset() + .entrySet()) { + AbstractMsSymbol msym = entry.getValue(); + AbstractMsSymbol lsym = symbols.get(cnt); + String mstr = msym.toString(); + String lstr = lsym.toString(); + if (!mstr.equals(lstr)) { + // Set break-point on next line. Multiple lines here to eliminate Eclipse warning. + int b = 1; + b = b + 1; + } + cnt++; + } + + // Compare module symbols + for (int modnum = 0; modnum < numModules(); modnum++) { + Module module = modules.get(modnum); + MsSymbolIterator moduleSymbolsIterator = module.getSymbolIterator(); + cnt = 0; + Map map = symbolRecords.getModuleSymbolsByOffset(modnum); + List keys = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + Long key = entry.getKey(); + keys.add(key); + } + Collections.sort(keys); + for (Long key : keys) { + AbstractMsSymbol msym = map.get(key); + if (!moduleSymbolsIterator.hasNext()) { + // Set break-point on next line. Multiple lines here to eliminate Eclipse warning. + int c = 1; + c = c + 1; + break; + } + AbstractMsSymbol lsym = moduleSymbolsIterator.next(); + String mstr = msym.toString(); + String lstr = lsym.toString(); + if (!mstr.equals(lstr)) { + // Set break-point on next line. Multiple lines here to eliminate Eclipse warning. + int b = 1; + b = b + 1; + } + cnt++; + } + if (moduleSymbolsIterator.hasNext()) { + // Set break-point on next line. Multiple lines here to eliminate Eclipse warning. + int d = 1; + d = d + 1; + } + } + } } diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbReaderOptions.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbReaderOptions.java index e6b93a2a96..bb7c083b9b 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbReaderOptions.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbReaderOptions.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SegmentMapDescription.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SegmentMapDescription.java index 7109f3629d..32d111ded8 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SegmentMapDescription.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SegmentMapDescription.java @@ -121,6 +121,11 @@ public class SegmentMapDescription { segLength = substreamReader.parseUnsignedIntVal(); } + @Override + public String toString() { + return dump(); + } + /** * Dumps the {@link SegmentMapDescription}. This method is for debugging only. * @return {@link String} of pretty output. diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolParser.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolParser.java index c5e7fce084..64e848313b 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolParser.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolParser.java @@ -35,6 +35,23 @@ public class SymbolParser { private SymbolParser() { } + /** + * Deserializes the record length and {@link AbstractMsSymbol} from the {@link PdbByteReader} + * and returns the symbol. + * @param pdb {@link AbstractPdb} that owns the Symbols to be parsed. + * @param reader {@link PdbByteReader} from which to deserialize the symbol record. + * @return {@link AbstractMsSymbol} that was parsed. + * @throws PdbException upon error parsing a field. + * @throws CancelledException Upon user cancellation. + */ + public static AbstractMsSymbol parseLengthAndSymbol(AbstractPdb pdb, PdbByteReader reader) + throws PdbException, CancelledException { + int recordLength = reader.parseUnsignedShortVal(); + PdbByteReader recordReader = reader.getSubPdbByteReader(recordLength); + recordReader.markAlign(2); + return parse(pdb, recordReader); + } + /** * Deserializes an {@link AbstractMsSymbol} from the {@link PdbByteReader} and returns it. * @param pdb {@link AbstractPdb} that owns the Symbols to be parsed. diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolRecords.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolRecords.java index 5901a104cd..fcab933840 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolRecords.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/SymbolRecords.java @@ -68,61 +68,105 @@ public class SymbolRecords { * Deserializes the {@link SymbolRecords} from the stream noted in the DBI header. * @param monitor {@link TaskMonitor} used for checking cancellation. * @throws IOException On file seek or read, invalid parameters, bad file configuration, or - * inability to read required bytes. - * @throws PdbException Upon not enough data left to parse. - * @throws CancelledException Upon user cancellation. + * inability to read required bytes + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation */ void deserialize(TaskMonitor monitor) throws IOException, PdbException, CancelledException { - int streamNumber; - PdbByteReader reader; + processSymbols(monitor); + processModuleSymbols(monitor); + } + private void processSymbols(TaskMonitor monitor) + throws IOException, PdbException, CancelledException { PdbDebugInfo debugInfo = pdb.getDebugInfo(); if (debugInfo == null) { return; } - streamNumber = debugInfo.getSymbolRecordsStreamNumber(); + int streamNumber = debugInfo.getSymbolRecordsStreamNumber(); if (streamNumber <= 0) { return; } - - reader = pdb.getReaderForStreamNumber(streamNumber, monitor); + PdbByteReader reader = pdb.getReaderForStreamNumber(streamNumber, monitor); symbolsByOffset = deserializeSymbolRecords(pdb, reader, monitor); + } - for (AbstractModuleInformation module : debugInfo.moduleInformationList) { - streamNumber = module.getStreamNumberDebugInformation(); - if (streamNumber != 0xffff) { -// System.out.println("\n\nStreamNumber: " + streamNumber); - reader = pdb.getReaderForStreamNumber(streamNumber, monitor); - int x = reader.parseInt(); // TODO: do not know what this value is. - int sizeDebug = module.getSizeLocalSymbolsDebugInformation(); - sizeDebug -= x; //TODO: seems right, but need to evaluate this - PdbByteReader debugReader = reader.getSubPdbByteReader(sizeDebug); - Map oneModuleSymbolsByOffset = - deserializeSymbolRecords(pdb, debugReader, monitor); - moduleSymbolsByOffset.add(oneModuleSymbolsByOffset); - // TODO: figure out the rest of the bytes in the stream - // As of 20190618: feel that this is where we will find C11Lines or C13Lines - // information. -// PdbByteReader rest = reader.getSubPdbByteReader(reader.numRemaining()); -// System.out.println(rest.dump()); - } - else { - moduleSymbolsByOffset.add(new TreeMap<>()); - } + // Could split this method up into separate methods: one for module symbols and the other for + // Lines processing. Note: would be processing streams more than once; lines would need to + // skip over the symbols. + private void processModuleSymbols(TaskMonitor monitor) + throws IOException, PdbException, CancelledException { + // cvSignature: + // >64K = C6 + // 1 = C7 + // 2 = C11 (vc5.x) + // 3 = ??? (not specified, and not marked as reserved) + // 4 = C13 (vc7.x) + // 5-64K = RESERVED + // + // Both cvdump (1660 and 1668) and mod.cpp (575) seem to indicate that the first module + // might have the cvSignature of C7 or C11 (when C7/C11), but modules thereafter will not + // or may not have the value. C13 would always have the C13 signature. + boolean getSig = true; + int cvSignature = 0; + PdbDebugInfo debugInfo = pdb.getDebugInfo(); + if (debugInfo == null) { + return; } + for (AbstractModuleInformation module : debugInfo.moduleInformationList) { + monitor.checkCanceled(); + int streamNumber = module.getStreamNumberDebugInformation(); + if (streamNumber == 0xffff) { + moduleSymbolsByOffset.add(new TreeMap<>()); + continue; + } + + PdbByteReader reader = pdb.getReaderForStreamNumber(streamNumber, monitor); + + int sizeSymbolsSection = module.getSizeLocalSymbolsDebugInformation(); + PdbByteReader symbolsReader = reader.getSubPdbByteReader(sizeSymbolsSection); + // See comment above regarding getSig boolean + if (getSig) { + cvSignature = symbolsReader.parseInt(); + } + switch (cvSignature) { + case 1: + case 2: + // We have no 1,2 examples to test this logic for cvSignature. Confirming + // or rejecting this logic is important for simplifying/refactoring this + // method or writing new methods to allow for extraction of information from + // individual modules. The current implementation has cross-module logic + // (setting state in the processing of the first and using this state in the + // processing of follow-on modules). + getSig = false; + break; + case 4: + break; + default: + if (cvSignature < 0x10000) { + throw new PdbException( + "Invalid module CV signature in stream " + streamNumber); + } + break; + } + + Map oneModuleSymbolsByOffset = + deserializeSymbolRecords(pdb, symbolsReader, monitor); + moduleSymbolsByOffset.add(oneModuleSymbolsByOffset); + } } /** * Deserializes the {@link AbstractMsSymbol} symbols from the {@link PdbByteReader} and * returns a {@link Map}<{@link Long},{@link AbstractMsSymbol}> of buffer offsets to - * symbols. - * @param pdb {@link AbstractPdb} that owns the Symbols to be parsed. - * @param reader {@link PdbByteReader} containing the symbol records to deserialize. - * @param monitor {@link TaskMonitor} used for checking cancellation. - * @return map of buffer offsets to {@link AbstractMsSymbol symbols}. - * @throws PdbException Upon not enough data left to parse. - * @throws CancelledException Upon user cancellation. + * symbols + * @param pdb {@link AbstractPdb} that owns the Symbols to be parsed + * @param reader {@link PdbByteReader} containing the symbol records to deserialize + * @param monitor {@link TaskMonitor} used for checking cancellation + * @return map of buffer offsets to {@link AbstractMsSymbol symbols} + * @throws PdbException Upon not enough data left to parse + * @throws CancelledException Upon user cancellation */ public static Map deserializeSymbolRecords(AbstractPdb pdb, PdbByteReader reader, TaskMonitor monitor) throws PdbException, CancelledException { @@ -134,11 +178,7 @@ public class SymbolRecords { // Including length in byte array for alignment purposes. int offset = reader.getIndex(); - int recordLength = reader.parseUnsignedShortVal(); - - PdbByteReader recordReader = reader.getSubPdbByteReader(recordLength); - recordReader.markAlign(2); - AbstractMsSymbol symbol = SymbolParser.parse(pdb, recordReader); + AbstractMsSymbol symbol = SymbolParser.parseLengthAndSymbol(pdb, reader); mySymbolsByOffset.put((long) offset, symbol); } return mySymbolsByOffset; @@ -147,7 +187,7 @@ public class SymbolRecords { /** * Debug method for dumping information from this Symbol Records instance. * @param writer {@link Writer} to which to dump the information. - * @throws IOException Upon IOException writing to the {@link Writer}. + * @throws IOException Upon IOException writing to the {@link Writer} */ protected void dump(Writer writer) throws IOException { writer.write("SymbolRecords-----------------------------------------------\n"); @@ -166,7 +206,7 @@ public class SymbolRecords { * Debug method for dumping the symbols from a symbol map * @param mySymbolsByOffset the {@link Map}<{@link Long},{@link AbstractMsSymbol}> to dump. * @param writer {@link Writer} to which to dump the information. - * @throws IOException Upon IOException writing to the {@link Writer}. + * @throws IOException Upon IOException writing to the {@link Writer} */ protected void dumpSymbolMap(Map mySymbolsByOffset, Writer writer) throws IOException { diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/UnknownC13Section.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/UnknownC13Section.java new file mode 100644 index 0000000000..9d6aed2397 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/UnknownC13Section.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.bin.format.pdb2.pdbreader; + +import ghidra.util.task.TaskMonitor; + +/** + * A default/unknown C13Section class that we have created for completeness (default switch). + */ +class UnknownC13Section extends AbstractUnimplementedC13Section { + static UnknownC13Section parse(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + return new UnknownC13Section(reader, ignore, monitor); + } + + protected UnknownC13Section(PdbByteReader reader, boolean ignore, TaskMonitor monitor) { + super(reader, ignore, monitor); + } +} diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/type/ReferencedSymbolMsType.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/type/ReferencedSymbolMsType.java index 5e4e16b8a0..a0b8858d03 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/type/ReferencedSymbolMsType.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/type/ReferencedSymbolMsType.java @@ -43,9 +43,7 @@ public class ReferencedSymbolMsType extends AbstractMsType { public ReferencedSymbolMsType(AbstractPdb pdb, PdbByteReader reader) throws PdbException, CancelledException { super(pdb, reader); - int recordLength = reader.parseUnsignedShortVal(); - PdbByteReader recordReader = reader.getSubPdbByteReader(recordLength); - symbolRecord = SymbolParser.parse(pdb, recordReader); + symbolRecord = SymbolParser.parseLengthAndSymbol(pdb, reader); } @Override diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/ReferenceSymbolApplier.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/ReferenceSymbolApplier.java index 925fd708f2..996a59edc9 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/ReferenceSymbolApplier.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/pdb/pdbapplicator/ReferenceSymbolApplier.java @@ -75,8 +75,7 @@ public class ReferenceSymbolApplier extends MsSymbolApplier { } long getOffsetInReferencedSymbolGroup() { - // Adjusting offset to the offset we use for parsing the complete record. - return symbol.getOffsetActualSymbolInDollarDollarSymbols() - 4; + return symbol.getOffsetActualSymbolInDollarDollarSymbols(); } } diff --git a/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13SectionsTest.java b/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13SectionsTest.java new file mode 100644 index 0000000000..12d2ca52ca --- /dev/null +++ b/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/C13SectionsTest.java @@ -0,0 +1,1300 @@ +/* ### + * 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.pdb2.pdbreader; + +import static org.junit.Assert.*; + +import java.io.StringWriter; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.app.util.bin.format.pdb2.pdbreader.C13FileChecksums.FileChecksum; +import ghidra.util.task.TaskMonitor; + +/** + * Tests for PDB C13Section data. + */ +public class C13SectionsTest extends AbstractGenericTest { + + private static byte[] c13SectionsBytes; + + @Before + public void setUp() throws Exception { + c13SectionsBytes = createManyC13SectionsBytes(); + } + + //============================================================================================== + @Test + public void testC13Types() throws Exception { + int ignore = 0x80000000; + + assertFalse(C13Type.ignore(0xf1)); + assertEquals(C13Type.SYMBOLS, C13Type.fromValue(0xf1)); + assertTrue(C13Type.ignore(0xf1 | ignore)); + assertEquals(C13Type.SYMBOLS, C13Type.fromValue(0xf1 | ignore)); + assertEquals(C13Type.SYMBOLS, C13Type.fromClassValue(C13Symbols.class)); + + assertFalse(C13Type.ignore(0xf2)); + assertEquals(C13Type.LINES, C13Type.fromValue(0xf2)); + assertTrue(C13Type.ignore(0xf2 | ignore)); + assertEquals(C13Type.LINES, C13Type.fromValue(0xf2 | ignore)); + assertEquals(C13Type.LINES, C13Type.fromClassValue(C13Lines.class)); + + assertFalse(C13Type.ignore(0xf3)); + assertEquals(C13Type.STRING_TABLE, C13Type.fromValue(0xf3)); + assertTrue(C13Type.ignore(0xf3 | ignore)); + assertEquals(C13Type.STRING_TABLE, C13Type.fromValue(0xf3 | ignore)); + assertEquals(C13Type.STRING_TABLE, C13Type.fromClassValue(C13StringTable.class)); + + assertFalse(C13Type.ignore(0xf4)); + assertEquals(C13Type.FILE_CHECKSUMS, C13Type.fromValue(0xf4)); + assertTrue(C13Type.ignore(0xf4 | ignore)); + assertEquals(C13Type.FILE_CHECKSUMS, C13Type.fromValue(0xf4 | ignore)); + assertEquals(C13Type.FILE_CHECKSUMS, C13Type.fromClassValue(C13FileChecksums.class)); + + assertFalse(C13Type.ignore(0xf5)); + assertEquals(C13Type.FRAMEDATA, C13Type.fromValue(0xf5)); + assertTrue(C13Type.ignore(0xf5 | ignore)); + assertEquals(C13Type.FRAMEDATA, C13Type.fromValue(0xf5 | ignore)); + assertEquals(C13Type.FRAMEDATA, C13Type.fromClassValue(C13FrameData.class)); + + assertFalse(C13Type.ignore(0xf6)); + assertEquals(C13Type.INLINEE_LINES, C13Type.fromValue(0xf6)); + assertTrue(C13Type.ignore(0xf6 | ignore)); + assertEquals(C13Type.INLINEE_LINES, C13Type.fromValue(0xf6 | ignore)); + assertEquals(C13Type.INLINEE_LINES, C13Type.fromClassValue(C13InlineeLines.class)); + + assertFalse(C13Type.ignore(0xf7)); + assertEquals(C13Type.CROSS_SCOPE_IMPORTS, C13Type.fromValue(0xf7)); + assertTrue(C13Type.ignore(0xf7 | ignore)); + assertEquals(C13Type.CROSS_SCOPE_IMPORTS, C13Type.fromValue(0xf7 | ignore)); + assertEquals(C13Type.CROSS_SCOPE_IMPORTS, + C13Type.fromClassValue(C13CrossScopeImports.class)); + + assertFalse(C13Type.ignore(0xf8)); + assertEquals(C13Type.CROSS_SCOPE_EXPORTS, C13Type.fromValue(0xf8)); + assertTrue(C13Type.ignore(0xf8 | ignore)); + assertEquals(C13Type.CROSS_SCOPE_EXPORTS, C13Type.fromValue(0xf8 | ignore)); + assertEquals(C13Type.CROSS_SCOPE_EXPORTS, + C13Type.fromClassValue(C13CrossScopeExports.class)); + + assertFalse(C13Type.ignore(0xf9)); + assertEquals(C13Type.IL_LINES, C13Type.fromValue(0xf9)); + assertTrue(C13Type.ignore(0xf9 | ignore)); + assertEquals(C13Type.IL_LINES, C13Type.fromValue(0xf9 | ignore)); + assertEquals(C13Type.IL_LINES, C13Type.fromClassValue(C13IlLines.class)); + + assertFalse(C13Type.ignore(0xfa)); + assertEquals(C13Type.FUNC_MDTOKEN_MAP, C13Type.fromValue(0xfa)); + assertTrue(C13Type.ignore(0xfa | ignore)); + assertEquals(C13Type.FUNC_MDTOKEN_MAP, C13Type.fromValue(0xfa | ignore)); + assertEquals(C13Type.FUNC_MDTOKEN_MAP, C13Type.fromClassValue(C13FuncMdTokenMap.class)); + + assertFalse(C13Type.ignore(0xfb)); + assertEquals(C13Type.TYPE_MDTOKEN_MAP, C13Type.fromValue(0xfb)); + assertTrue(C13Type.ignore(0xfb | ignore)); + assertEquals(C13Type.TYPE_MDTOKEN_MAP, C13Type.fromValue(0xfb | ignore)); + assertEquals(C13Type.TYPE_MDTOKEN_MAP, C13Type.fromClassValue(C13TypeMdTokenMap.class)); + + assertFalse(C13Type.ignore(0xfc)); + assertEquals(C13Type.MERGED_ASSEMBLY_INPUT, C13Type.fromValue(0xfc)); + assertTrue(C13Type.ignore(0xfc | ignore)); + assertEquals(C13Type.MERGED_ASSEMBLY_INPUT, C13Type.fromValue(0xfc | ignore)); + assertEquals(C13Type.MERGED_ASSEMBLY_INPUT, + C13Type.fromClassValue(C13MergedAssemblyInput.class)); + + assertFalse(C13Type.ignore(0xfd)); + assertEquals(C13Type.COFF_SYMBOL_RVA, C13Type.fromValue(0xfd)); + assertTrue(C13Type.ignore(0xfd | ignore)); + assertEquals(C13Type.COFF_SYMBOL_RVA, C13Type.fromValue(0xfd | ignore)); + assertEquals(C13Type.COFF_SYMBOL_RVA, C13Type.fromClassValue(C13CoffSymbolRva.class)); + + //--------------------------- + + assertFalse(C13Type.ignore(0xff)); + assertEquals(C13Type.UNKNOWN, C13Type.fromValue(0xff)); + assertTrue(C13Type.ignore(0xff | ignore)); + assertEquals(C13Type.UNKNOWN, C13Type.fromValue(0xff | ignore)); + + assertEquals(C13Type.ALL, C13Type.fromClassValue(C13Section.class)); + + } + + //============================================================================================== + @Test + public void testC13NoneFileChecksum() throws Exception { + byte[] bytes = createC13NoneFileChecksumBytes(0x1000); + PdbByteReader reader = new PdbByteReader(bytes); + FileChecksum fileChecksum = new FileChecksum(reader); + String result = fileChecksum.toString(); + assertEquals("0x00001000, 0x00 NoneChecksumType(00): ", result); + } + + @Test + public void testC13Md5FileChecksum() throws Exception { + byte[] bytes = createC13Md5FileChecksumBytes(0x1010); + PdbByteReader reader = new PdbByteReader(bytes); + FileChecksum fileChecksum = new FileChecksum(reader); + String result = fileChecksum.toString(); + assertEquals("0x00001010, 0x10 Md5ChecksumType(01): " + "554433221100ffeeddccbbaa99887766", + result); + } + + @Test + public void testC13Sha1FileChecksum() throws Exception { + byte[] bytes = createC13Sha1FileChecksumBytes(0x1020); + PdbByteReader reader = new PdbByteReader(bytes); + FileChecksum fileChecksum = new FileChecksum(reader); + String result = fileChecksum.toString(); + assertEquals( + "0x00001020, 0x28 Sha1ChecksumType(02): " + + "101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637", + result); + } + + @Test + public void testC13Sha256FileChecksum() throws Exception { + byte[] bytes = createC13Sha256FileChecksumBytes(0x1030); + PdbByteReader reader = new PdbByteReader(bytes); + FileChecksum fileChecksum = new FileChecksum(reader); + String result = fileChecksum.toString(); + assertEquals("0x00001030, 0x40 Sha256ChecksumType(03): " + + "00225566002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566", + result); + } + + @Test + public void testC13UnknownFileChecksum() throws Exception { + byte[] bytes = + createC13FileChecksumBytes(0x1040, 0x04, new byte[] { 0x33, 0x44, 0x55, 0x66 }); + PdbByteReader reader = new PdbByteReader(bytes); + FileChecksum fileChecksum = new FileChecksum(reader); + String result = fileChecksum.toString(); + assertEquals("0x00001040, 0x04 UnknownChecksumType(04): 33445566", result); + } + + @Test + public void testC13NoneFileChecksumSizeMismatch() throws Exception { + try { + createC13FileChecksumBytes(0x1050, 0x00, new byte[] { 0x01 }); + fail("Expected an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // Expected + } + } + + @Test + public void testC13FileChecksums() throws Exception { + PdbByteReader reader = new PdbByteReader(createC13FileChecksumsSectionBytes(0)); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13FileChecksums); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13FileChecksums--------------------------------------------\n" + + "0x00002000, 0x00 NoneChecksumType(00): \n" + + "0x00002010, 0x00 NoneChecksumType(00): \n" + + "0x00002020, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002030, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002040, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002050, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002060, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "0x00002070, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "End C13FileChecksums----------------------------------------\n", writer.toString()); + } + + //============================================================================================== + private byte[] createC13FileChecksumsSectionBytes(long twiddle) { + PdbByteWriter recordsWriter = new PdbByteWriter(); + recordsWriter.putBytes(createC13NoneFileChecksumBytes(0x2000 + twiddle)); + recordsWriter.putBytes(createC13NoneFileChecksumBytes(0x2010 + twiddle)); + recordsWriter.putBytes(createC13Md5FileChecksumBytes(0x2020 + twiddle)); + recordsWriter.putBytes(createC13Md5FileChecksumBytes(0x2030 + twiddle)); + recordsWriter.putBytes(createC13Sha1FileChecksumBytes(0x2040 + twiddle)); + recordsWriter.putBytes(createC13Sha1FileChecksumBytes(0x2050 + twiddle)); + recordsWriter.putBytes(createC13Sha256FileChecksumBytes(0x2060 + twiddle)); + recordsWriter.putBytes(createC13Sha256FileChecksumBytes(0x2070 + twiddle)); + return createC13SectionBytes(C13Type.FILE_CHECKSUMS, recordsWriter.get()); + } + + /** + * Creates byte array of data for a C13 NoneFileChecksum + * @param offsetFilename unsigned integer value passed in a long + * @return final byte array of checksum data + */ + private byte[] createC13NoneFileChecksumBytes(long offsetFilename) { + return createC13FileChecksumBytes(offsetFilename, 0x00, new byte[] {}); + } + + /** + * Creates byte array of data for a C13 Md5FileChecksum + * @param offsetFilename unsigned integer value passed in a long + * @return final byte array of checksum data + */ + private byte[] createC13Md5FileChecksumBytes(long offsetFilename) { + return createC13FileChecksumBytes(offsetFilename, 0x01, new byte[] { 0x55, 0x44, 0x33, 0x22, + 0x11, 0x00, (byte) 0xff, (byte) 0xee, (byte) 0xdd, (byte) 0xcc, (byte) 0xbb, + (byte) 0xaa, (byte) 0x99, (byte) 0x88, 0x77, 0x66 }); + } + + /** + * Creates byte array of data for a C13 Sha1FileChecksum + * @param offsetFilename unsigned integer value passed in a long + * @return final byte array of checksum data + */ + private byte[] createC13Sha1FileChecksumBytes(long offsetFilename) { + return createC13FileChecksumBytes(offsetFilename, 0x02, + new byte[] { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37 }); + } + + /** + * Creates byte array of data for a C13 Sha256FileChecksum + * @param offsetFilename unsigned integer value passed in a long + * @return final byte array of checksum data + */ + private byte[] createC13Sha256FileChecksumBytes(long offsetFilename) { + return createC13FileChecksumBytes(offsetFilename, 0x03, + new byte[] { 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, + 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, + 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, + 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, + 0x55, 0x66, 0x00, 0x22, 0x55, 0x66, 0x00, 0x22, 0x55, 0x66 }); + } + + /** + * Creates byte array of data for a C13FileChecksum + * @param offsetFilename unsigned integer value passed in a long + * @param type unsigned byte value passed in an integer + * @param bytes bytes of the checksum + * @return final byte array of checksum data + */ + private byte[] createC13FileChecksumBytes(long offsetFilename, int type, byte[] bytes) { + int len = bytes.length; + int neededLen; + switch (type) { + case 0x00: // None + neededLen = 0; + break; + case 0x01: // MD5 + neededLen = 16; + break; + case 0x02: // SHA1 + neededLen = 40; + break; + case 0x03: // SHA256 + neededLen = 64; + break; + default: // Unknown type + neededLen = len; // We really don't know what length is needed, but this passes test + break; + } + if (len != neededLen || len > 0xff) { + throw new IllegalArgumentException("bad length"); + } + + PdbByteWriter writer = new PdbByteWriter(); + //Consider ByteBuffer.allocate().put().put().put().put().array() model in future? + writer.putUnsignedInt(offsetFilename); + writer.putUnsignedByte(neededLen); + writer.putUnsignedByte(type); + writer.putBytes(bytes); + writer.putAlign(0); + return writer.get(); + + } + + //============================================================================================== + @Test + public void testC13Lines() throws Exception { + byte[] C13LinesSectionBytes = createC13LinesSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13LinesSectionBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13Lines); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13Lines----------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000000 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 36\n" + + "16 0x00004100 Statement\n" + + "17 0x00004101 Statement\n" + + "18 0x00004102 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 28\n" + + "32 0x00004200 Expression\n" + + "33 0x00004201 Expression\n" + + "End C13Lines------------------------------------------------\n", writer.toString()); + } + + @Test + public void testC13LinesWithColumns() throws Exception { + byte[] C13LinesSectionBytes = createC13LinesWithColumnsSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13LinesSectionBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13Lines); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13Lines----------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000001 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 48\n" + + " 16: 0- 16- 1 0x00004100 Statement\n" + + " 17: 2- 17- 3 0x00004101 Statement\n" + + " 18: 4- 18- 5 0x00004102 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 36\n" + + " 32: 0- 32- 1 0x00004200 Expression\n" + + " 33: 2- 34- 3 0x00004201 Expression\n" + + "End C13Lines------------------------------------------------\n", writer.toString()); + } + + @Test + public void testC13IlLines() throws Exception { + byte[] C13LinesSectionBytes = createC13IlLinesSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13LinesSectionBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13IlLines); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13IlLines--------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000000 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 36\n" + + "16 0x00004100 Statement\n" + + "17 0x00004101 Statement\n" + + "18 0x00004102 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 28\n" + + "32 0x00004200 Expression\n" + + "33 0x00004201 Expression\n" + + "End C13IlLines----------------------------------------------\n", writer.toString()); + } + + @Test + public void testC13IlLinesWithColumns() throws Exception { + byte[] C13LinesSectionBytes = createC13IlLinesWithColumnsSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13LinesSectionBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13IlLines); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13IlLines--------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000001 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 48\n" + + " 16: 0- 16- 1 0x00004100 Statement\n" + + " 17: 2- 17- 3 0x00004101 Statement\n" + + " 18: 4- 18- 5 0x00004102 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 36\n" + + " 32: 0- 32- 1 0x00004200 Expression\n" + + " 33: 2- 34- 3 0x00004201 Expression\n" + + "End C13IlLines----------------------------------------------\n", writer.toString()); + } + + //============================================================================================== + // These are not the best mechanisms for putting together test data. Possible future where + // all PDB objects and read (parse) and write (serialize) methods to read/write byte streams. + // Then we could compose nested objects as objects instead of byte arrays. + private byte[] createC13LinesSectionBytes(long twiddle) { + PdbByteWriter c13LinesWriter = new PdbByteWriter(); + createC13AbstractLinesSectionBytes(c13LinesWriter, twiddle); + return createC13SectionBytes(C13Type.LINES, c13LinesWriter.get()); + } + + private byte[] createC13LinesWithColumnsSectionBytes(long twiddle) { + PdbByteWriter c13LinesWriter = new PdbByteWriter(); + createC13AbstractLinesWithColumnsSectionBytes(c13LinesWriter, twiddle); + return createC13SectionBytes(C13Type.LINES, c13LinesWriter.get()); + } + + private byte[] createC13IlLinesSectionBytes(long twiddle) { + PdbByteWriter c13LinesWriter = new PdbByteWriter(); + createC13AbstractLinesSectionBytes(c13LinesWriter, twiddle); + return createC13SectionBytes(C13Type.IL_LINES, c13LinesWriter.get()); + } + + private byte[] createC13IlLinesWithColumnsSectionBytes(long twiddle) { + PdbByteWriter c13LinesWriter = new PdbByteWriter(); + createC13AbstractLinesWithColumnsSectionBytes(c13LinesWriter, twiddle); + return createC13SectionBytes(C13Type.IL_LINES, c13LinesWriter.get()); + } + + private void createC13AbstractLinesSectionBytes(PdbByteWriter c13LinesWriter, long twiddle) { + PdbByteWriter linesWriter1 = new PdbByteWriter(); + linesWriter1.putBytes( + createC13LinesLineRecord(0x100L + twiddle, createBitVals(0x10, 0x00, true))); + linesWriter1.putBytes( + createC13LinesLineRecord(0x101L + twiddle, createBitVals(0x11, 0x00, true))); + linesWriter1.putBytes( + createC13LinesLineRecord(0x102L + twiddle, createBitVals(0x12, 0x00, true))); + long nLines1 = 3L; + byte[] lineRecordsBytes1 = linesWriter1.get(); + + long fileId1 = 0x1000L; + byte[] fileRecordBytes1 = createC13LinesFileRecord(fileId1, nLines1, lineRecordsBytes1); + + //--- + + PdbByteWriter linesWriter2 = new PdbByteWriter(); + linesWriter2.putBytes(createC13LinesLineRecord(0x200L, createBitVals(0x20, 0x00, false))); + linesWriter2.putBytes(createC13LinesLineRecord(0x201L, createBitVals(0x21, 0x01, false))); + long nLines2 = 2L; + byte[] lineRecordsBytes2 = linesWriter2.get(); + + long fileId2 = 0x2000L; + byte[] fileRecordBytes2 = createC13LinesFileRecord(fileId2, nLines2, lineRecordsBytes2); + + //--- + + long offCon = 0x4000L; // unsigned int + int segCon = 0x01; // unsigned short + int flags = 0x0000; // unsigned short + long lenCon = 0x10; // unsigned int + + // Writer to c13LinesWriter passed in as argument + c13LinesWriter.putUnsignedInt(offCon); + c13LinesWriter.putUnsignedShort(segCon); + c13LinesWriter.putUnsignedShort(flags); + c13LinesWriter.putUnsignedInt(lenCon); + c13LinesWriter.putBytes(fileRecordBytes1); + c13LinesWriter.putBytes(fileRecordBytes2); + } + + private void createC13AbstractLinesWithColumnsSectionBytes(PdbByteWriter c13LinesWriter, + long twiddle) { + PdbByteWriter linesWriter1 = new PdbByteWriter(); + linesWriter1.putBytes( + createC13LinesLineRecord(0x100L + twiddle, createBitVals(0x10, 0x00, true))); + linesWriter1.putBytes( + createC13LinesLineRecord(0x101L + twiddle, createBitVals(0x11, 0x00, true))); + linesWriter1.putBytes( + createC13LinesLineRecord(0x102L + twiddle, createBitVals(0x12, 0x00, true))); + linesWriter1.putBytes(createC13LinesColumnRecord(0x0, 0x01)); + linesWriter1.putBytes(createC13LinesColumnRecord(0x2, 0x03)); + linesWriter1.putBytes(createC13LinesColumnRecord(0x4, 0x05)); + long nLines1 = 3L; + byte[] lineRecordsBytes1 = linesWriter1.get(); + + long fileId1 = 0x1000L; + byte[] fileRecordBytes1 = createC13LinesFileRecord(fileId1, nLines1, lineRecordsBytes1); + + //--- + + PdbByteWriter linesWriter2 = new PdbByteWriter(); + linesWriter2.putBytes(createC13LinesLineRecord(0x200L, createBitVals(0x20, 0x00, false))); + linesWriter2.putBytes(createC13LinesLineRecord(0x201L, createBitVals(0x21, 0x01, false))); + linesWriter2.putBytes(createC13LinesColumnRecord(0x0, 0x01)); + linesWriter2.putBytes(createC13LinesColumnRecord(0x2, 0x03)); + long nLines2 = 2L; + byte[] lineRecordsBytes2 = linesWriter2.get(); + + long fileId2 = 0x2000L; + byte[] fileRecordBytes2 = createC13LinesFileRecord(fileId2, nLines2, lineRecordsBytes2); + + //--- + + long offCon = 0x4000L; // unsigned int + int segCon = 0x01; // unsigned short + int flags = 0x0001; // unsigned short .... 0x0001 bit means has columns + long lenCon = 0x10; // unsigned int + + // Writer to c13LinesWriter passed in as argument + c13LinesWriter.putUnsignedInt(offCon); + c13LinesWriter.putUnsignedShort(segCon); + c13LinesWriter.putUnsignedShort(flags); + c13LinesWriter.putUnsignedInt(lenCon); + c13LinesWriter.putBytes(fileRecordBytes1); + c13LinesWriter.putBytes(fileRecordBytes2); + } + + private byte[] createC13LinesFileRecord(long fileId, long nLines, byte[] lineRecordBytes) { + long lenFileBlock = lineRecordBytes.length + 12; + PdbByteWriter writer = new PdbByteWriter(); + writer.putUnsignedInt(fileId); + writer.putUnsignedInt(nLines); + writer.putUnsignedInt(lenFileBlock); + writer.putBytes(lineRecordBytes); + return writer.get(); + } + + private byte[] createC13LinesLineRecord(long offset, long bitVals) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putUnsignedInt(offset); + writer.putUnsignedInt(bitVals); + return writer.get(); + } + + private byte[] createC13LinesColumnRecord(int offsetColumnStart, + int offsetColumnEnd) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putUnsignedShort(offsetColumnStart); + writer.putUnsignedShort(offsetColumnEnd); + return writer.get(); + } + + private long createBitVals(long lineNumStart, long deltaLineEnd, boolean isStatement) { + long bitVals = 0; + if (lineNumStart > 0xffffffL) { + fail("lineNumStart too big"); + } + bitVals = lineNumStart; + if (deltaLineEnd > 0x7fL) { + fail("deltaLineEnd too big"); + } + bitVals |= (deltaLineEnd << 24); + bitVals |= (isStatement ? 0x80000000L : 0x0); + return bitVals; + } + + //============================================================================================== + @Test + public void testC13CrossScopeExports() throws Exception { + byte[] C13CrossScopeExportsBytes = createC13CrossExportSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13CrossScopeExportsBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13CrossScopeExports); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13CrossScopeExports----------------------------------------\n" + + "0x00000100, 0x00001000\n" + + "0x00000101, 0x00001001\n" + + "End C13CrossScopeExports------------------------------------\n", writer.toString()); + } + + //============================================================================================== + private byte[] createC13CrossExportSectionBytes(long twiddle) { + PdbByteWriter recordsWriter = new PdbByteWriter(); + recordsWriter.putBytes(createC13CrossExportRecord(0x100L + twiddle, 0x1000L)); + recordsWriter.putBytes(createC13CrossExportRecord(0x101L + twiddle, 0x1001L)); + return createC13SectionBytes(C13Type.CROSS_SCOPE_EXPORTS, recordsWriter.get()); + } + + private byte[] createC13CrossExportRecord(long localId, long globalId) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putUnsignedInt(localId); + writer.putUnsignedInt(globalId); + return writer.get(); + } + + //============================================================================================== + @Test + public void testC13CrossScopeImports() throws Exception { + byte[] C13CrossScopeImportsBytes = createC13CrossImportSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13CrossScopeImportsBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13CrossScopeImports); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13CrossScopeImports----------------------------------------\n" + + "0x00000100, 1 0x00001000\n" + + "0x00000101, 2 0x00002000 0x00002001\n" + + "End C13CrossScopeImports------------------------------------\n", writer.toString()); + } + + //============================================================================================== + private byte[] createC13CrossImportSectionBytes(int twiddle) { + PdbByteWriter recordsWriter = new PdbByteWriter(); + recordsWriter.putBytes(createC13CrossImportRecord(0x100 + twiddle, new long[] { 0x1000L })); + recordsWriter.putBytes( + createC13CrossImportRecord(0x101 + twiddle, new long[] { 0x2000L, 0x2001L })); + return createC13SectionBytes(C13Type.CROSS_SCOPE_IMPORTS, recordsWriter.get()); + } + + private byte[] createC13CrossImportRecord(int offsetObjectFilePath, long[] referenceIds) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putInt(offsetObjectFilePath); + writer.putUnsignedInt(referenceIds.length); + for (long id : referenceIds) { + writer.putUnsignedInt(id); + } + return writer.get(); + } + + //============================================================================================== + @Test + public void testC13InlineeLines() throws Exception { + byte[] C13InlineeBytes = createC13InlineeLinesSectionBytes(0); + PdbByteReader reader = new PdbByteReader(C13InlineeBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13InlineeLines); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13InlineeLines---------------------------------------------\n" + + "Signature: 0x000\n" + + "0x000001000, 0x000001, 256\n" + + "0x000002000, 0x000002, 512\n" + + "End C13InlineeLines-----------------------------------------\n", writer.toString()); + } + + @Test + public void testC13ExtendedInlineeLines() throws Exception { + byte[] C13InlineeBytes = createC13ExtednedInlineeLinesSectionBytes(); + PdbByteReader reader = new PdbByteReader(C13InlineeBytes); + C13Section section = C13Section.parse(reader, TaskMonitor.DUMMY); + assertTrue(section instanceof C13InlineeLines); + StringWriter writer = new StringWriter(); + section.dump(writer); + assertEquals("C13InlineeLines---------------------------------------------\n" + + "Signature: 0x001\n" + + "0x000001000, 0x000001, 256\n" + + "0x000002000, 0x000002, 512 0x000003 0x000004\n" + + "End C13InlineeLines-----------------------------------------\n", writer.toString()); + } + + //============================================================================================== + private byte[] createC13InlineeLinesSectionBytes(long twiddle) { + PdbByteWriter recordsWriter = new PdbByteWriter(); + recordsWriter.putInt(0x00); // InlineeLines signature + recordsWriter.putBytes(createC13InlineeLinesRecord(0x1000L + twiddle, 0x1, 0x100)); + recordsWriter.putBytes(createC13InlineeLinesRecord(0x2000L + twiddle, 0x2, 0x200)); + byte[] C13InlineeBytes = + createC13SectionBytes(C13Type.INLINEE_LINES, recordsWriter.get()); + return C13InlineeBytes; + } + + private byte[] createC13ExtednedInlineeLinesSectionBytes() { + PdbByteWriter recordsWriter = new PdbByteWriter(); + recordsWriter.putInt(0x01); // ExtendedInlineeLines signature + recordsWriter + .putBytes(createC13ExtendedInlineeLinesRecord(0x1000L, 0x1, 0x100, new int[] {})); + recordsWriter.putBytes( + createC13ExtendedInlineeLinesRecord(0x2000L, 0x2, 0x200, new int[] { 0x3, 0x4 })); + byte[] C13InlineeBytes = + createC13SectionBytes(C13Type.INLINEE_LINES, recordsWriter.get()); + return C13InlineeBytes; + } + + private byte[] createC13InlineeLinesRecord(long inlinee, int fileId, int sourceLineNum) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putUnsignedInt(inlinee); + writer.putInt(fileId); + writer.putInt(sourceLineNum); + return writer.get(); + } + + private byte[] createC13ExtendedInlineeLinesRecord(long inlinee, int fileId, int sourceLineNum, + int[] extraIds) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putUnsignedInt(inlinee); + writer.putInt(fileId); + writer.putInt(sourceLineNum); + writer.putUnsignedInt(extraIds.length); + for (int id : extraIds) { + writer.putInt(id); + } + return writer.get(); + } + + //============================================================================================== + @Test + public void testC13StringTableSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13StringTable.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13StringTable----------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 00\n" + + "End C13StringTable------------------------------------------\n" + + "C13StringTable----------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 11 11\n" + + "End C13StringTable------------------------------------------\n" + + "C13StringTable----------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 22\n" + + "End C13StringTable------------------------------------------\n" + + "C13StringTable----------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 33 33\n" + + "End C13StringTable------------------------------------------\n", writer.toString()); + } + + @Test + public void testC13FileChecksumsSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13FileChecksums.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13FileChecksums--------------------------------------------\n" + + "0x00002000, 0x00 NoneChecksumType(00): \n" + + "0x00002010, 0x00 NoneChecksumType(00): \n" + + "0x00002020, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002030, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002040, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002050, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002060, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "0x00002070, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "End C13FileChecksums----------------------------------------\n" + + "C13FileChecksums--------------------------------------------\n" + + "0x00002001, 0x00 NoneChecksumType(00): \n" + + "0x00002011, 0x00 NoneChecksumType(00): \n" + + "0x00002021, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002031, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002041, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002051, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002061, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "0x00002071, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "End C13FileChecksums----------------------------------------\n" + + "C13FileChecksums--------------------------------------------\n" + + "0x00002002, 0x00 NoneChecksumType(00): \n" + + "0x00002012, 0x00 NoneChecksumType(00): \n" + + "0x00002022, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002032, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002042, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002052, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002062, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "0x00002072, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "End C13FileChecksums----------------------------------------\n" + + "C13FileChecksums--------------------------------------------\n" + + "0x00002003, 0x00 NoneChecksumType(00): \n" + + "0x00002013, 0x00 NoneChecksumType(00): \n" + + "0x00002023, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002033, 0x10 Md5ChecksumType(01): 554433221100ffeeddccbbaa99887766\n" + + "0x00002043, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002053, 0x28 Sha1ChecksumType(02): 101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f3031323334353637\n" + + "0x00002063, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "0x00002073, 0x40 Sha256ChecksumType(03): 00225566002255660022556600225566" + + "002255660022556600225566002255660022556600225566002255660022556600225566" + + "002255660022556600225566\n" + + "End C13FileChecksums----------------------------------------\n", writer.toString()); + } + + @Test + public void testC13FrameDataSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13FrameData.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13FrameData------------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 00\n" + + "End C13FrameData--------------------------------------------\n" + + "C13FrameData------------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 11 11\n" + + "End C13FrameData--------------------------------------------\n" + + "C13FrameData------------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 22\n" + + "End C13FrameData--------------------------------------------\n" + + "C13FrameData------------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 33 33\n" + + "End C13FrameData--------------------------------------------\n", writer.toString()); + } + + @Test + public void testC13InlineeLinesSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13InlineeLines.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13InlineeLines---------------------------------------------\n" + + "Signature: 0x000\n" + + "0x000001000, 0x000001, 256\n" + + "0x000002000, 0x000002, 512\n" + + "End C13InlineeLines-----------------------------------------\n" + + "C13InlineeLines---------------------------------------------\n" + + "Signature: 0x000\n" + + "0x000001001, 0x000001, 256\n" + + "0x000002001, 0x000002, 512\n" + + "End C13InlineeLines-----------------------------------------\n" + + "C13InlineeLines---------------------------------------------\n" + + "Signature: 0x000\n" + + "0x000001002, 0x000001, 256\n" + + "0x000002002, 0x000002, 512\n" + + "End C13InlineeLines-----------------------------------------\n" + + "C13InlineeLines---------------------------------------------\n" + + "Signature: 0x000\n" + + "0x000001003, 0x000001, 256\n" + + "0x000002003, 0x000002, 512\n" + + "End C13InlineeLines-----------------------------------------\n", writer.toString()); + } + + @Test + public void testC13CrossScopeImportsSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13CrossScopeImports.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13CrossScopeImports----------------------------------------\n" + + "0x00000100, 1 0x00001000\n" + + "0x00000101, 2 0x00002000 0x00002001\n" + + "End C13CrossScopeImports------------------------------------\n" + + "C13CrossScopeImports----------------------------------------\n" + + "0x00000101, 1 0x00001000\n" + + "0x00000102, 2 0x00002000 0x00002001\n" + + "End C13CrossScopeImports------------------------------------\n" + + "C13CrossScopeImports----------------------------------------\n" + + "0x00000102, 1 0x00001000\n" + + "0x00000103, 2 0x00002000 0x00002001\n" + + "End C13CrossScopeImports------------------------------------\n" + + "C13CrossScopeImports----------------------------------------\n" + + "0x00000103, 1 0x00001000\n" + + "0x00000104, 2 0x00002000 0x00002001\n" + + "End C13CrossScopeImports------------------------------------\n", writer.toString()); + } + + @Test + public void testC13CrossScopeExportsSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13CrossScopeExports.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13CrossScopeExports----------------------------------------\n" + + "0x00000100, 0x00001000\n" + + "0x00000101, 0x00001001\n" + + "End C13CrossScopeExports------------------------------------\n" + + "C13CrossScopeExports----------------------------------------\n" + + "0x00000101, 0x00001000\n" + + "0x00000102, 0x00001001\n" + + "End C13CrossScopeExports------------------------------------\n" + + "C13CrossScopeExports----------------------------------------\n" + + "0x00000102, 0x00001000\n" + + "0x00000103, 0x00001001\n" + + "End C13CrossScopeExports------------------------------------\n" + + "C13CrossScopeExports----------------------------------------\n" + + "0x00000103, 0x00001000\n" + + "0x00000104, 0x00001001\n" + + "End C13CrossScopeExports------------------------------------\n", writer.toString()); + } + + @Test + public void testC13IlLinesSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13IlLines.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13IlLines--------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000000 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 36\n" + + "16 0x00004100 Statement\n" + + "17 0x00004101 Statement\n" + + "18 0x00004102 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 28\n" + + "32 0x00004200 Expression\n" + + "33 0x00004201 Expression\n" + + "End C13IlLines----------------------------------------------\n" + + "C13IlLines--------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000000 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 36\n" + + "16 0x00004101 Statement\n" + + "17 0x00004102 Statement\n" + + "18 0x00004103 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 28\n" + + "32 0x00004200 Expression\n" + + "33 0x00004201 Expression\n" + + "End C13IlLines----------------------------------------------\n" + + "C13IlLines--------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000000 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 36\n" + + "16 0x00004102 Statement\n" + + "17 0x00004103 Statement\n" + + "18 0x00004104 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 28\n" + + "32 0x00004200 Expression\n" + + "33 0x00004201 Expression\n" + + "End C13IlLines----------------------------------------------\n" + + "C13IlLines--------------------------------------------------\n" + + "offCon: 0x00004000 segCon: 1 flags: 0x00000000 lenCon: 0x00000010\n" + + "fileId: 001000, nLines: 3, lenFileBlock: 36\n" + + "16 0x00004103 Statement\n" + + "17 0x00004104 Statement\n" + + "18 0x00004105 Statement\n" + + "fileId: 002000, nLines: 2, lenFileBlock: 28\n" + + "32 0x00004200 Expression\n" + + "33 0x00004201 Expression\n" + + "End C13IlLines----------------------------------------------\n", writer.toString()); + } + + @Test + public void testC13FuncMdTokenMapSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13FuncMdTokenMap.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13FuncMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 00\n" + + "End C13FuncMdTokenMap---------------------------------------\n" + + "C13FuncMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 11 11\n" + + "End C13FuncMdTokenMap---------------------------------------\n" + + "C13FuncMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 22\n" + + "End C13FuncMdTokenMap---------------------------------------\n" + + "C13FuncMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 33 33\n" + + "End C13FuncMdTokenMap---------------------------------------\n", writer.toString()); + } + + @Test + public void testC13TypeMdTokenMapSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13TypeMdTokenMap.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13TypeMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 00\n" + + "End C13TypeMdTokenMap---------------------------------------\n" + + "C13TypeMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 11 11\n" + + "End C13TypeMdTokenMap---------------------------------------\n" + + "C13TypeMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 22\n" + + "End C13TypeMdTokenMap---------------------------------------\n" + + "C13TypeMdTokenMap-------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 33 33\n" + + "End C13TypeMdTokenMap---------------------------------------\n", writer.toString()); + } + + @Test + public void testC13MergedAssemblyInputSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13MergedAssemblyInput.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13MergedAssemblyInput--------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 00\n" + + "End C13MergedAssemblyInput----------------------------------\n" + + "C13MergedAssemblyInput--------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 11 11\n" + + "End C13MergedAssemblyInput----------------------------------\n" + + "C13MergedAssemblyInput--------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 22\n" + + "End C13MergedAssemblyInput----------------------------------\n" + + "C13MergedAssemblyInput--------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 33 33\n" + + "End C13MergedAssemblyInput----------------------------------\n", writer.toString()); + } + + @Test + public void testC13CoffSymbolRvaSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + StringWriter writer = new StringWriter(); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13CoffSymbolRva.class, true, + TaskMonitor.DUMMY); + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + c13Section.dump(writer); + } + assertEquals("C13CoffSymbolRva--------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 00\n" + + "End C13CoffSymbolRva----------------------------------------\n" + + "C13CoffSymbolRva--------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 11 11\n" + + "End C13CoffSymbolRva----------------------------------------\n" + + "C13CoffSymbolRva--------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 1\n" + + "index: 0\n" + + "first: 0\n" + + "last: 1\n" + + "000000 22\n" + + "End C13CoffSymbolRva----------------------------------------\n" + + "C13CoffSymbolRva--------------------------------------------\n" + + "***NOT IMPLEMENTED*** Bytes follow...\n" + + "limit: 2\n" + + "index: 0\n" + + "first: 0\n" + + "last: 2\n" + + "000000 33 33\n" + + "End C13CoffSymbolRva----------------------------------------\n", writer.toString()); + } + + @Test + // We care comparing enum types instead of comparing long dump strings because the string + // would be quite large. + public void testC13AllSectionIterator() throws Exception { + PdbByteReader reader = new PdbByteReader(c13SectionsBytes); + C13SectionIterator iterator = + new C13SectionIterator<>(reader, C13Section.class, true, TaskMonitor.DUMMY); + int expectedTypeVal = C13Type.SYMBOLS.getValue(); + int cnt = 0; + while (iterator.hasNext()) { + C13Section c13Section = iterator.next(); + C13Type found = C13Type.fromClassValue(c13Section.getClass()); + if (found.getValue() != expectedTypeVal) { + fail("Section type not expected"); + } + cnt++; + if (cnt % 2 == 0) { // see createManyC13SectionBytes... doing pairs of two of same + if (expectedTypeVal == C13Type.COFF_SYMBOL_RVA.getValue()) { + expectedTypeVal = C13Type.SYMBOLS.getValue(); // another round + } + else { + expectedTypeVal++; + } + } + } + } + + //============================================================================================== + private byte[] createManyC13SectionsBytes() { + PdbByteWriter writer = new PdbByteWriter(); + writer.putBytes(createC13SectionBytes(C13Type.SYMBOLS, new byte[] { 0x00 })); + writer.putBytes(createC13SectionBytes(C13Type.SYMBOLS, new byte[] { 0x11, 0x11 })); + writer.putBytes(createC13LinesSectionBytes(0)); + writer.putBytes(createC13LinesSectionBytes(1)); + writer.putBytes(createC13SectionBytes(C13Type.STRING_TABLE, new byte[] { 0x00 })); + writer.putBytes(createC13SectionBytes(C13Type.STRING_TABLE, new byte[] { 0x11, 0x11 })); + writer.putBytes(createC13FileChecksumsSectionBytes(0)); + writer.putBytes(createC13FileChecksumsSectionBytes(1)); + writer.putBytes(createC13SectionBytes(C13Type.FRAMEDATA, new byte[] { 0x00 })); + writer.putBytes(createC13SectionBytes(C13Type.FRAMEDATA, new byte[] { 0x11, 0x11 })); + writer.putBytes(createC13InlineeLinesSectionBytes(0)); + writer.putBytes(createC13InlineeLinesSectionBytes(1)); + writer.putBytes(createC13CrossImportSectionBytes(0)); + writer.putBytes(createC13CrossImportSectionBytes(1)); + writer.putBytes(createC13CrossExportSectionBytes(0)); + writer.putBytes(createC13CrossExportSectionBytes(1)); + writer.putBytes(createC13IlLinesSectionBytes(0)); + writer.putBytes(createC13IlLinesSectionBytes(1)); + writer.putBytes(createC13SectionBytes(C13Type.FUNC_MDTOKEN_MAP, new byte[] { 0x00 })); + writer.putBytes(createC13SectionBytes(C13Type.FUNC_MDTOKEN_MAP, new byte[] { 0x11, 0x11 })); + writer.putBytes(createC13SectionBytes(C13Type.TYPE_MDTOKEN_MAP, new byte[] { 0x00 })); + writer.putBytes(createC13SectionBytes(C13Type.TYPE_MDTOKEN_MAP, new byte[] { 0x11, 0x11 })); + writer.putBytes(createC13SectionBytes(C13Type.MERGED_ASSEMBLY_INPUT, new byte[] { 0x00 })); + writer.putBytes( + createC13SectionBytes(C13Type.MERGED_ASSEMBLY_INPUT, new byte[] { 0x11, 0x11 })); + writer.putBytes(createC13SectionBytes(C13Type.COFF_SYMBOL_RVA, new byte[] { 0x00 })); + writer.putBytes(createC13SectionBytes(C13Type.COFF_SYMBOL_RVA, new byte[] { 0x11, 0x11 })); + // another round + writer.putBytes(createC13SectionBytes(C13Type.SYMBOLS, new byte[] { 0x22 })); + writer.putBytes(createC13SectionBytes(C13Type.SYMBOLS, new byte[] { 0x33, 0x33 })); + writer.putBytes(createC13LinesSectionBytes(2)); + writer.putBytes(createC13LinesSectionBytes(3)); + writer.putBytes(createC13SectionBytes(C13Type.STRING_TABLE, new byte[] { 0x22 })); + writer.putBytes(createC13SectionBytes(C13Type.STRING_TABLE, new byte[] { 0x33, 0x33 })); + writer.putBytes(createC13FileChecksumsSectionBytes(2)); + writer.putBytes(createC13FileChecksumsSectionBytes(3)); + writer.putBytes(createC13SectionBytes(C13Type.FRAMEDATA, new byte[] { 0x22 })); + writer.putBytes(createC13SectionBytes(C13Type.FRAMEDATA, new byte[] { 0x33, 0x33 })); + writer.putBytes(createC13InlineeLinesSectionBytes(2)); + writer.putBytes(createC13InlineeLinesSectionBytes(3)); + writer.putBytes(createC13CrossImportSectionBytes(2)); + writer.putBytes(createC13CrossImportSectionBytes(3)); + writer.putBytes(createC13CrossExportSectionBytes(2)); + writer.putBytes(createC13CrossExportSectionBytes(3)); + writer.putBytes(createC13IlLinesSectionBytes(2)); + writer.putBytes(createC13IlLinesSectionBytes(3)); + writer.putBytes(createC13SectionBytes(C13Type.FUNC_MDTOKEN_MAP, new byte[] { 0x22 })); + writer.putBytes(createC13SectionBytes(C13Type.FUNC_MDTOKEN_MAP, new byte[] { 0x33, 0x33 })); + writer.putBytes(createC13SectionBytes(C13Type.TYPE_MDTOKEN_MAP, new byte[] { 0x22 })); + writer.putBytes(createC13SectionBytes(C13Type.TYPE_MDTOKEN_MAP, new byte[] { 0x33, 0x33 })); + writer.putBytes(createC13SectionBytes(C13Type.MERGED_ASSEMBLY_INPUT, new byte[] { 0x22 })); + writer.putBytes( + createC13SectionBytes(C13Type.MERGED_ASSEMBLY_INPUT, new byte[] { 0x33, 0x33 })); + writer.putBytes(createC13SectionBytes(C13Type.COFF_SYMBOL_RVA, new byte[] { 0x22 })); + writer.putBytes(createC13SectionBytes(C13Type.COFF_SYMBOL_RVA, new byte[] { 0x33, 0x33 })); + return writer.get(); + } + + //============================================================================================== + /** + * Creates C13Section from record bytes; thus it creates a header with type and length, which + * is then followed by the record bytes + * @return byte array of full C13Section + */ + private byte[] createC13SectionBytes(C13Type type, byte[] recordBytes) { + PdbByteWriter writer = new PdbByteWriter(); + writer.putInt(type.getValue()); + writer.putInt(recordBytes.length); + writer.putBytes(recordBytes); + return writer.get(); + } + +} diff --git a/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbTestUtils.java b/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbTestUtils.java index 4edee9b899..dcd62f5658 100644 --- a/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbTestUtils.java +++ b/Ghidra/Features/PDB/src/test/java/ghidra/app/util/bin/format/pdb2/pdbreader/PdbTestUtils.java @@ -260,16 +260,16 @@ public class PdbTestUtils { /** * Packs an unsigned int (java long) with values from the parameters here. * @param language the language - * @param compiledForEditAndContinue true if compiled fro edit-and-continue + * @param compiledForEditAndContinue true if compiled for edit-and-continue * @param notCompiledWithDebugInfo true if not compiled with debug info * @param compiledWithLinkTimeCodeGeneration true if compiled with link-time code generation - * @param compiledWithBzalignNoDataAlign tru if compiled with BS align no data align - * @param managedCodeDataPresent tru if managed code data is present + * @param compiledWithBzalignNoDataAlign true if compiled with BS align no data align + * @param managedCodeDataPresent true if managed code data is present * @param compiledWithGsBufferSecurityChecks true if compiled with GS Buffer security checks * @param compiledWithHotPatch true if compiled with ability to hot-patch * @param convertedWithCvtcil true if converted from (.NET IL) Common Intermediate Language Module * @param microsoftIntermediateLanguageNetModule true if MSFT intermediate language net module - * @param compiledWithSdl true if compiledwith SDL + * @param compiledWithSdl true if compiled with SDL * @param compiledWithLtcgPgoOrPgu true if compiled with light PGO or PGU * @param dotExpModule true if dot exp module * @return the flags packed into single integral form/value