diff agent/src/share/classes/sun/jvm/hotspot/debugger/win32/coff/COFFFileParser.java @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c18cbe5936b8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/share/classes/sun/jvm/hotspot/debugger/win32/coff/COFFFileParser.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,3933 @@
+/*
+ * Copyright 2000-2004 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+package sun.jvm.hotspot.debugger.win32.coff;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.*;
+
+import sun.jvm.hotspot.utilities.memo.*;
+import sun.jvm.hotspot.utilities.Assert;
+import sun.jvm.hotspot.debugger.DataSource;
+import sun.jvm.hotspot.debugger.MappedByteBufferDataSource;
+
+/** Top-level factory which parses COFF files, including object files,
+    Portable Executables and DLLs. Returns {@link
+    sun.jvm.hotspot.debugger.win32.coff.COFFFile} objects. This class is a
+    singleton. */
+
+public class COFFFileParser {
+  private static COFFFileParser soleInstance;
+
+  // Constants from the file format documentation
+  private static final int COFF_HEADER_SIZE = 20;
+  private static final int SECTION_HEADER_SIZE = 40;
+  private static final int SYMBOL_SIZE = 18;
+  private static final int RELOCATION_SIZE = 10;
+  private static final int LINE_NUMBER_SIZE = 6;
+
+  private static final String US_ASCII = "US-ASCII";
+
+  private COFFFileParser() {}
+
+  /** This class is a singleton; returns the sole instance. */
+  public static COFFFileParser getParser() {
+    if (soleInstance == null) {
+      soleInstance = new COFFFileParser();
+    }
+    return soleInstance;
+  }
+
+  public COFFFile parse(String filename) throws COFFException {
+    try {
+      File file = new File(filename);
+      FileInputStream stream = new FileInputStream(file);
+      MappedByteBuffer buf = stream.getChannel().map(FileChannel.MapMode.READ_ONLY,
+                                                     0,
+                                                     file.length());
+
+      // This is pretty confusing. The file format is little-endian
+      // and so is the CPU. In order for the multi-byte accessors to
+      // work properly we must NOT change the endianness of the
+      // MappedByteBuffer. Need to think about this some more and file
+      // a bug if there is one. (FIXME)
+      //   buf.order(ByteOrder.nativeOrder());
+      return parse(new MappedByteBufferDataSource(buf));
+    } catch (FileNotFoundException e) {
+      throw new COFFException(e);
+    } catch (IOException e) {
+      throw new COFFException(e);
+    }
+  }
+
+  public COFFFile parse(DataSource source) throws COFFException {
+    return new COFFFileImpl(source);
+  }
+
+  class COFFFileImpl implements COFFFile {
+    private DataSource file;
+    private long       filePos;
+    private boolean isImage;
+    private long    imageHeaderOffset;
+    private MemoizedObject header = new MemoizedObject() {
+        public Object computeValue() {
+          return new COFFHeaderImpl();
+        }
+      };
+
+    COFFFileImpl(DataSource file) throws COFFException {
+      this.file = file;
+      initialize();
+    }
+
+    public boolean isImage() {
+      return isImage;
+    }
+
+    public COFFHeader getHeader() {
+      return (COFFHeaderImpl) header.getValue();
+    }
+
+    class COFFHeaderImpl implements COFFHeader {
+      private short machine;
+      private short numberOfSections;
+      private int   timeDateStamp;
+      private int   pointerToSymbolTable;
+      private int   numberOfSymbols;
+      private short sizeOfOptionalHeader;
+      private short characteristics;
+      private MemoizedObject[] sectionHeaders;
+      private MemoizedObject[] symbols;
+
+      private MemoizedObject stringTable = new MemoizedObject() {
+          public Object computeValue() {
+            int ptr = getPointerToSymbolTable();
+            if (ptr == 0) {
+              return new StringTable(0);
+            } else {
+              return new StringTable(ptr + SYMBOL_SIZE * getNumberOfSymbols());
+            }
+          }
+        };
+
+      COFFHeaderImpl() {
+        seek(imageHeaderOffset);
+        machine = readShort();
+        numberOfSections = readShort();
+        timeDateStamp = readInt();
+        pointerToSymbolTable = readInt();
+        numberOfSymbols = readInt();
+        sizeOfOptionalHeader = readShort();
+        characteristics = readShort();
+
+        // Set up section headers
+        sectionHeaders = new MemoizedObject[numberOfSections];
+        for (int i = 0; i < numberOfSections; i++) {
+          final int secHdrOffset = (int)
+            (imageHeaderOffset + COFF_HEADER_SIZE + sizeOfOptionalHeader + i * SECTION_HEADER_SIZE);
+          sectionHeaders[i] = new MemoizedObject() {
+              public Object computeValue() {
+                return new SectionHeaderImpl(secHdrOffset);
+              }
+            };
+        }
+
+        // Set up symbols
+        symbols = new MemoizedObject[numberOfSymbols];
+        for (int i = 0; i < numberOfSymbols; i++) {
+          final int symbolOffset = pointerToSymbolTable + i * SYMBOL_SIZE;
+          symbols[i] = new MemoizedObject() {
+              public Object computeValue() {
+                return new COFFSymbolImpl(symbolOffset);
+              }
+            };
+        }
+      }
+
+      public short          getMachineType()          { return machine; }
+      public short          getNumberOfSections()     { return numberOfSections; }
+      public int            getTimeDateStamp()        { return timeDateStamp; }
+      public int            getPointerToSymbolTable() { return pointerToSymbolTable; }
+      public int            getNumberOfSymbols()      { return numberOfSymbols; }
+      public short          getSizeOfOptionalHeader() { return sizeOfOptionalHeader; }
+      public OptionalHeader getOptionalHeader() throws COFFException {
+        if (getSizeOfOptionalHeader() == 0) {
+          return null;
+        }
+        return new OptionalHeaderImpl((int) (imageHeaderOffset + COFF_HEADER_SIZE));
+      }
+      public short          getCharacteristics()      { return characteristics; }
+      public boolean hasCharacteristic(short characteristic) {
+        return ((characteristics & characteristic) != 0);
+      }
+      public SectionHeader getSectionHeader(int index) {
+        // NOTE zero-basing of index
+        return (SectionHeader) sectionHeaders[index - 1].getValue();
+      }
+      public COFFSymbol    getCOFFSymbol(int index)    {
+        return (COFFSymbol) symbols[index].getValue();
+      }
+      public int getNumberOfStrings() {
+        return getStringTable().getNum();
+      }
+      public String getString(int i) {
+        return getStringTable().get(i);
+      }
+
+      StringTable          getStringTable() { return (StringTable) stringTable.getValue(); }
+
+      // NOTE: can destroy current seek() position!
+      int rvaToFileOffset(int rva) {
+        if (rva == 0) return 0;
+        // Search for section containing RVA
+        for (int i = 1; i <= getNumberOfSections(); i++) {
+          SectionHeader sec = getSectionHeader(i);
+          int va = sec.getVirtualAddress();
+          int sz = sec.getSize();
+          if ((va <= rva) && (rva < (va + sz))) {
+            return sec.getPointerToRawData() + (rva - va);
+          }
+        }
+        throw new COFFException("Unable to find RVA 0x" +
+                                Integer.toHexString(rva) +
+                                " in any section");
+      }
+
+      class OptionalHeaderImpl implements OptionalHeader {
+        private short magic;
+        private MemoizedObject standardFields;
+        private MemoizedObject windowsSpecificFields;
+        private MemoizedObject dataDirectories;
+
+        private static final int STANDARD_FIELDS_OFFSET = 2;
+        private static final int PE32_WINDOWS_SPECIFIC_FIELDS_OFFSET = 28;
+        private static final int PE32_DATA_DIRECTORIES_OFFSET = 96;
+        private static final int PE32_PLUS_WINDOWS_SPECIFIC_FIELDS_OFFSET = 24;
+        private static final int PE32_PLUS_DATA_DIRECTORIES_OFFSET = 112;
+
+        OptionalHeaderImpl(final int offset) {
+          seek(offset);
+          magic = readShort();
+
+          final boolean isPE32Plus = (magic == MAGIC_PE32_PLUS);
+          final int standardFieldsOffset = offset + STANDARD_FIELDS_OFFSET;
+          final int windowsSpecificFieldsOffset = offset +
+            (isPE32Plus
+             ? PE32_PLUS_WINDOWS_SPECIFIC_FIELDS_OFFSET
+             : PE32_WINDOWS_SPECIFIC_FIELDS_OFFSET);
+          final int dataDirectoriesOffset = offset +
+            (isPE32Plus
+             ? PE32_PLUS_DATA_DIRECTORIES_OFFSET
+             : PE32_DATA_DIRECTORIES_OFFSET);
+
+          standardFields = new MemoizedObject() {
+              public Object computeValue() {
+                return new OptionalHeaderStandardFieldsImpl(standardFieldsOffset,
+                                                            isPE32Plus);
+              }
+            };
+          windowsSpecificFields = new MemoizedObject() {
+              public Object computeValue() {
+                return new OptionalHeaderWindowsSpecificFieldsImpl(windowsSpecificFieldsOffset,
+                                                                   isPE32Plus);
+              }
+            };
+          dataDirectories = new MemoizedObject() {
+              public Object computeValue() {
+                return new OptionalHeaderDataDirectoriesImpl(dataDirectoriesOffset,
+                                                             getWindowsSpecificFields().getNumberOfRvaAndSizes());
+              }
+            };
+        }
+
+        public short getMagicNumber() {
+          return magic;
+        }
+
+        public OptionalHeaderStandardFields getStandardFields() {
+          return (OptionalHeaderStandardFields) standardFields.getValue();
+        }
+
+        public OptionalHeaderWindowsSpecificFields getWindowsSpecificFields() {
+          return (OptionalHeaderWindowsSpecificFields) windowsSpecificFields.getValue();
+        }
+        public OptionalHeaderDataDirectories getDataDirectories() {
+          return (OptionalHeaderDataDirectories) dataDirectories.getValue();
+        }
+      }
+
+      class OptionalHeaderStandardFieldsImpl implements OptionalHeaderStandardFields {
+        private boolean isPE32Plus;
+        private byte majorLinkerVersion;
+        private byte minorLinkerVersion;
+        private int sizeOfCode;
+        private int sizeOfInitializedData;
+        private int sizeOfUninitializedData;
+        private int addressOfEntryPoint;
+        private int baseOfCode;
+        private int baseOfData;
+
+        OptionalHeaderStandardFieldsImpl(int offset,
+                                         boolean isPE32Plus) {
+          this.isPE32Plus = isPE32Plus;
+          seek(offset);
+          majorLinkerVersion = readByte();
+          minorLinkerVersion = readByte();
+          sizeOfCode = readInt();
+          sizeOfInitializedData = readInt();
+          sizeOfUninitializedData = readInt();
+          addressOfEntryPoint = readInt();
+          baseOfCode = readInt();
+          if (isPE32Plus) {
+            baseOfData = readInt();
+          }
+        }
+
+        public byte getMajorLinkerVersion() { return majorLinkerVersion; }
+        public byte getMinorLinkerVersion() { return minorLinkerVersion; }
+        public int getSizeOfCode()              { return sizeOfCode; }
+        public int getSizeOfInitializedData()   { return sizeOfInitializedData; }
+        public int getSizeOfUninitializedData() { return sizeOfUninitializedData; }
+        public int getAddressOfEntryPoint()     { return addressOfEntryPoint; }
+        public int getBaseOfCode()              { return baseOfCode; }
+        public int getBaseOfData() throws COFFException {
+          if (isPE32Plus) {
+            throw new COFFException("Not present in PE32+ files");
+          }
+          return baseOfData;
+        }
+      }
+
+      class OptionalHeaderWindowsSpecificFieldsImpl implements OptionalHeaderWindowsSpecificFields {
+        private long imageBase;
+        private int sectionAlignment;
+        private int fileAlignment;
+        private short majorOperatingSystemVersion;
+        private short minorOperatingSystemVersion;
+        private short majorImageVersion;
+        private short minorImageVersion;
+        private short majorSubsystemVersion;
+        private short minorSubsystemVersion;
+        private int sizeOfImage;
+        private int sizeOfHeaders;
+        private int checkSum;
+        private short subsystem;
+        private short dllCharacteristics;
+        private long sizeOfStackReserve;
+        private long sizeOfStackCommit;
+        private long sizeOfHeapReserve;
+        private long sizeOfHeapCommit;
+        private int loaderFlags;
+        private int numberOfRvaAndSizes;
+
+        OptionalHeaderWindowsSpecificFieldsImpl(int offset, boolean isPE32Plus) {
+          seek(offset);
+
+          if (!isPE32Plus) {
+            imageBase = maskInt(readInt());
+          } else {
+            imageBase = readLong();
+          }
+          sectionAlignment = readInt();
+          fileAlignment = readInt();
+          majorOperatingSystemVersion = readShort();
+          minorOperatingSystemVersion = readShort();
+          majorImageVersion = readShort();
+          minorImageVersion = readShort();
+          majorSubsystemVersion = readShort();
+          minorSubsystemVersion = readShort();
+          readInt(); // Reserved
+          sizeOfImage = readInt();
+          sizeOfHeaders = readInt();
+          checkSum = readInt();
+          subsystem = readShort();
+          dllCharacteristics = readShort();
+          if (!isPE32Plus) {
+            sizeOfStackReserve = maskInt(readInt());
+            sizeOfStackCommit  = maskInt(readInt());
+            sizeOfHeapReserve  = maskInt(readInt());
+            sizeOfHeapCommit   = maskInt(readInt());
+          } else {
+            sizeOfStackReserve = readLong();
+            sizeOfStackCommit  = readLong();
+            sizeOfHeapReserve  = readLong();
+            sizeOfHeapCommit   = readLong();
+          }
+          loaderFlags = readInt();
+          numberOfRvaAndSizes = readInt();
+        }
+
+        public long getImageBase()              { return imageBase; }
+        public int getSectionAlignment()        { return sectionAlignment; }
+        public int getFileAlignment()           { return fileAlignment; }
+        public short getMajorOperatingSystemVersion() { return majorOperatingSystemVersion; }
+        public short getMinorOperatingSystemVersion() { return minorOperatingSystemVersion; }
+        public short getMajorImageVersion()     { return majorImageVersion; }
+        public short getMinorImageVersion()     { return minorImageVersion; }
+        public short getMajorSubsystemVersion() { return majorSubsystemVersion; }
+        public short getMinorSubsystemVersion() { return minorSubsystemVersion; }
+        public int getSizeOfImage()             { return sizeOfImage; }
+        public int getSizeOfHeaders()           { return sizeOfHeaders; }
+        public int getCheckSum()                { return checkSum; }
+        public short getSubsystem()             { return subsystem; }
+        public short getDLLCharacteristics()    { return dllCharacteristics; }
+        public long getSizeOfStackReserve()     { return sizeOfStackReserve; }
+        public long getSizeOfStackCommit()      { return sizeOfStackCommit; }
+        public long getSizeOfHeapReserve()      { return sizeOfHeapReserve; }
+        public long getSizeOfHeapCommit()       { return sizeOfHeapCommit; }
+        public int getLoaderFlags()             { return loaderFlags; }
+        public int getNumberOfRvaAndSizes()     { return numberOfRvaAndSizes; }
+
+        private long maskInt(long arg) {
+          return (arg & 0x00000000FFFFFFFFL);
+        }
+      }
+
+      class OptionalHeaderDataDirectoriesImpl implements OptionalHeaderDataDirectories {
+        private int numberOfRvaAndSizes;
+        private MemoizedObject[] dataDirectories;
+        private MemoizedObject   exportDirectoryTable;
+        private MemoizedObject   debugDirectory;
+
+        private static final int DATA_DIRECTORY_SIZE = 8;
+
+        OptionalHeaderDataDirectoriesImpl(int offset,
+                                          int numberOfRvaAndSizes) {
+          this.numberOfRvaAndSizes = numberOfRvaAndSizes;
+          dataDirectories = new MemoizedObject[numberOfRvaAndSizes];
+          for (int i = 0; i < numberOfRvaAndSizes; i++) {
+            final int dirOffset = offset + (i * DATA_DIRECTORY_SIZE);
+            dataDirectories[i] = new MemoizedObject() {
+                public Object computeValue() {
+                  return new DataDirectoryImpl(dirOffset);
+                }
+              };
+          }
+
+          exportDirectoryTable = new MemoizedObject() {
+              public Object computeValue() {
+                DataDirectory dir = getExportTable();
+                if (dir.getRVA() == 0 || dir.getSize() == 0) {
+                  return null;
+                }
+                return new ExportDirectoryTableImpl(rvaToFileOffset(dir.getRVA()), dir.getSize());
+              }
+            };
+
+          debugDirectory = new MemoizedObject() {
+              public Object computeValue() {
+                DataDirectory dir = getDebug();
+                if (dir.getRVA() == 0 || dir.getSize() == 0) {
+                  return null;
+                }
+                return new DebugDirectoryImpl(rvaToFileOffset(dir.getRVA()), dir.getSize());
+              }
+            };
+        }
+
+        public DataDirectory getExportTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(0)].getValue();
+        }
+        public DataDirectory getImportTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(1)].getValue();
+        }
+        public DataDirectory getResourceTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(2)].getValue();
+        }
+        public DataDirectory getExceptionTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(3)].getValue();
+        }
+        public DataDirectory getCertificateTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(4)].getValue();
+        }
+        public DataDirectory getBaseRelocationTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(5)].getValue();
+        }
+        public DataDirectory getDebug() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(6)].getValue();
+        }
+        public DataDirectory getArchitecture() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(7)].getValue();
+        }
+        public DataDirectory getGlobalPtr() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(8)].getValue();
+        }
+        public DataDirectory getTLSTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(9)].getValue();
+        }
+        public DataDirectory getLoadConfigTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(10)].getValue();
+        }
+        public DataDirectory getBoundImportTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(11)].getValue();
+        }
+        public DataDirectory getImportAddressTable() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(12)].getValue();
+        }
+        public DataDirectory getDelayImportDescriptor() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(13)].getValue();
+        }
+        public DataDirectory getCOMPlusRuntimeHeader() throws COFFException {
+          return (DataDirectory) dataDirectories[checkIndex(14)].getValue();
+        }
+
+        public ExportDirectoryTable getExportDirectoryTable() throws COFFException {
+          return (ExportDirectoryTable) exportDirectoryTable.getValue();
+        }
+
+        public DebugDirectory getDebugDirectory() throws COFFException {
+          return (DebugDirectory) debugDirectory.getValue();
+        }
+
+        private int checkIndex(int index) throws COFFException {
+          if ((index < 0) || (index >= dataDirectories.length)) {
+            throw new COFFException("Directory " + index + " unavailable (only " +
+                                    numberOfRvaAndSizes + " tables present)");
+          }
+          return index;
+        }
+      }
+
+      class DataDirectoryImpl implements DataDirectory {
+        int rva;
+        int size;
+
+        DataDirectoryImpl(int offset) {
+          seek(offset);
+          rva  = readInt();
+          size = readInt();
+        }
+
+        public int getRVA()  { return rva; }
+        public int getSize() { return size; }
+      }
+
+      class ExportDirectoryTableImpl implements ExportDirectoryTable {
+        private int offset;
+        private int size;
+
+        private int exportFlags;
+        private int timeDateStamp;
+        private short majorVersion;
+        private short minorVersion;
+        private int nameRVA;
+        private int ordinalBase;
+        private int addressTableEntries;
+        private int numberOfNamePointers;
+        private int exportAddressTableRVA;
+        private int namePointerTableRVA;
+        private int ordinalTableRVA;
+
+        private MemoizedObject dllName;
+
+        private MemoizedObject exportNameTable;
+        private MemoizedObject exportNamePointerTable;
+        private MemoizedObject exportOrdinalTable;
+        private MemoizedObject exportAddressTable;
+
+        ExportDirectoryTableImpl(int offset, int size) {
+          this.offset = offset;
+          this.size   = size;
+          seek(offset);
+          exportFlags = readInt();
+          timeDateStamp = readInt();
+          majorVersion = readShort();
+          minorVersion = readShort();
+          nameRVA = readInt();
+          ordinalBase = readInt();
+          addressTableEntries = readInt();
+          numberOfNamePointers = readInt();
+          exportAddressTableRVA = readInt();
+          namePointerTableRVA = readInt();
+          ordinalTableRVA = readInt();
+
+          dllName = new MemoizedObject() {
+              public Object computeValue() {
+                seek(rvaToFileOffset(getNameRVA()));
+                return readCString();
+              }
+            };
+
+          exportNamePointerTable = new MemoizedObject() {
+              public Object computeValue() {
+                int[] pointers = new int[getNumberOfNamePointers()];
+                seek(rvaToFileOffset(getNamePointerTableRVA()));
+                // Must make two passes to avoid rvaToFileOffset
+                // destroying seek() position
+                for (int i = 0; i < pointers.length; i++) {
+                  pointers[i] = readInt();
+                }
+                for (int i = 0; i < pointers.length; i++) {
+                  pointers[i] = rvaToFileOffset(pointers[i]);
+                }
+                return pointers;
+              }
+            };
+
+          exportNameTable = new MemoizedObject() {
+              public Object computeValue() {
+                return new ExportNameTable(getExportNamePointerTable());
+              }
+            };
+
+          exportOrdinalTable = new MemoizedObject() {
+              public Object computeValue() {
+                short[] ordinals = new short[getNumberOfNamePointers()];
+                seek(rvaToFileOffset(getOrdinalTableRVA()));
+                for (int i = 0; i < ordinals.length; i++) {
+                  ordinals[i] = readShort();
+                }
+                return ordinals;
+              }
+            };
+
+          exportAddressTable = new MemoizedObject() {
+              public Object computeValue() {
+                int[] addresses = new int[getNumberOfAddressTableEntries()];
+                seek(rvaToFileOffset(getExportAddressTableRVA()));
+                // Must make two passes to avoid rvaToFileOffset
+                // destroying seek() position
+                for (int i = 0; i < addresses.length; i++) {
+                  addresses[i] = readInt();
+                }
+                for (int i = 0; i < addresses.length; i++) {
+                  addresses[i] = rvaToFileOffset(addresses[i]);
+                }
+                return addresses;
+              }
+            };
+        }
+
+        public int   getExportFlags()   { return exportFlags; }
+        public int   getTimeDateStamp() { return timeDateStamp; }
+        public short getMajorVersion()  { return majorVersion; }
+        public short getMinorVersion()  { return minorVersion; }
+        public int   getNameRVA()       { return nameRVA; }
+
+        public String getDLLName() {
+          return (String) dllName.getValue();
+        }
+
+        public int getOrdinalBase()                 { return ordinalBase; }
+        public int getNumberOfAddressTableEntries() { return addressTableEntries; }
+        public int getNumberOfNamePointers()        { return numberOfNamePointers; }
+        public int getExportAddressTableRVA()       { return exportAddressTableRVA; }
+        public int getNamePointerTableRVA()         { return namePointerTableRVA; }
+        public int getOrdinalTableRVA()             { return ordinalTableRVA; }
+
+        public String getExportName(int i) {
+          return getExportNameTable().get(i);
+        }
+
+        public short  getExportOrdinal(int i) {
+          return getExportOrdinalTable()[i];
+        }
+
+        public boolean isExportAddressForwarder(short ordinal) {
+          int addr = getExportAddress(ordinal);
+          return ((offset <= addr) && (addr < (offset + size)));
+        }
+
+        public String getExportAddressForwarder(short ordinal) {
+          seek(getExportAddress(ordinal));
+          return readCString();
+        }
+
+        public int    getExportAddress(short ordinal) {
+
+          ///////////////////////
+          // FIXME: MAJOR HACK //
+          ///////////////////////
+
+          // According to the documentation, the first line here is
+          // correct. However, it doesn't seem to work. The second
+          // one, however, does.
+
+          // OK, it's probably due to using negative indices in the
+          // export address table in "real life"...need to rethink
+          // this when I'm more awake
+
+          //          return getExportAddressTable()[ordinal - ordinalBase];
+          return getExportAddressTable()[ordinal];
+        }
+
+        private ExportNameTable getExportNameTable() {
+          return (ExportNameTable) exportNameTable.getValue();
+        }
+
+        private int[] getExportNamePointerTable() {
+          return (int[]) exportNamePointerTable.getValue();
+        }
+
+        private short[] getExportOrdinalTable() {
+          return (short[]) exportOrdinalTable.getValue();
+        }
+
+        private int[] getExportAddressTable() {
+          return (int[]) exportAddressTable.getValue();
+        }
+      }
+
+      class ExportNameTable {
+        private MemoizedObject[] names;
+
+        ExportNameTable(final int[] exportNamePointerTable) {
+          names = new MemoizedObject[exportNamePointerTable.length];
+          for (int i = 0; i < exportNamePointerTable.length; i++) {
+            final int idx = i;
+            names[idx] = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(exportNamePointerTable[idx]);
+                  return readCString();
+                }
+              };
+            };
+        }
+
+        String get(int i) {
+          return (String) names[i].getValue();
+        }
+      }
+
+      class DebugDirectoryImpl implements DebugDirectory {
+        private int offset;
+        private int size;
+        private int numEntries;
+
+        private static final int DEBUG_DIRECTORY_ENTRY_SIZE = 28;
+
+        DebugDirectoryImpl(int offset, int size) {
+          this.offset = offset;
+          this.size = size;
+
+          if ((size % DEBUG_DIRECTORY_ENTRY_SIZE) != 0) {
+            throw new COFFException("Corrupt DebugDirectory at offset 0x" +
+                                    Integer.toHexString(offset));
+          }
+
+          numEntries = size / DEBUG_DIRECTORY_ENTRY_SIZE;
+        }
+
+        public int getNumEntries() { return numEntries; }
+        public DebugDirectoryEntry getEntry(int i) {
+          if ((i < 0) || (i >= getNumEntries())) throw new IndexOutOfBoundsException();
+          return new DebugDirectoryEntryImpl(offset + i * DEBUG_DIRECTORY_ENTRY_SIZE);
+        }
+      }
+
+      class DebugDirectoryEntryImpl implements DebugDirectoryEntry, DebugTypes {
+        private int characteristics;
+        private int timeDateStamp;
+        private short majorVersion;
+        private short minorVersion;
+        private int type;
+        private int sizeOfData;
+        private int addressOfRawData;
+        private int pointerToRawData;
+
+        DebugDirectoryEntryImpl(int offset) {
+          seek(offset);
+          characteristics = readInt();
+          timeDateStamp = readInt();
+          majorVersion = readShort();
+          minorVersion = readShort();
+          type = readInt();
+          sizeOfData = readInt();
+          addressOfRawData = readInt();
+          pointerToRawData = readInt();
+        }
+
+        public int   getCharacteristics()  { return characteristics; }
+        public int   getTimeDateStamp()    { return timeDateStamp; }
+        public short getMajorVersion()     { return majorVersion; }
+        public short getMinorVersion()     { return minorVersion; }
+        public int   getType()             { return type; }
+        public int   getSizeOfData()       { return sizeOfData; }
+        public int   getAddressOfRawData() { return addressOfRawData; }
+        public int   getPointerToRawData() { return pointerToRawData; }
+
+        public DebugVC50 getDebugVC50() {
+          // See whether we can recognize VC++ 5.0 debug information.
+          try {
+            if (getType() != IMAGE_DEBUG_TYPE_CODEVIEW) return null;
+
+            int offset = getPointerToRawData();
+            seek(offset);
+            if (readByte() == 'N' &&
+                readByte() == 'B' &&
+                readByte() == '1' &&
+                readByte() == '1') {
+              return new DebugVC50Impl(offset);
+            }
+          } catch (COFFException e) {
+            e.printStackTrace();
+          }
+          return null;
+        }
+
+        public byte  getRawDataByte(int i) {
+          if (i < 0 || i >= getSizeOfData()) {
+            throw new IndexOutOfBoundsException();
+          }
+          seek(getPointerToRawData() + i);
+          return readByte();
+        }
+      }
+
+      class DebugVC50Impl implements DebugVC50, DebugVC50TypeLeafIndices {
+        private int lfaBase;
+
+        private int subsectionDirectoryOffset;
+        private MemoizedObject subsectionDirectory;
+
+        DebugVC50Impl(int offset) {
+          lfaBase = offset;
+          seek(offset);
+          readInt();  // Discard NB11
+          subsectionDirectoryOffset = globalOffset(readInt());
+
+          // Ensure information is complete
+          verify();
+
+          subsectionDirectory = new MemoizedObject() {
+              public Object computeValue() {
+                return new DebugVC50SubsectionDirectoryImpl(getSubsectionDirectoryOffset());
+              }
+            };
+        }
+
+        public int getSubsectionDirectoryOffset() {
+          return subsectionDirectoryOffset;
+        }
+
+        public DebugVC50SubsectionDirectory getSubsectionDirectory() {
+          return (DebugVC50SubsectionDirectory) subsectionDirectory.getValue();
+        }
+
+        private int globalOffset(int offset) {
+          return offset + lfaBase;
+        }
+
+        private void verify() {
+          // Seek to subsection directory manually and look for
+          // signature following it. This finishes validating that we
+          // have VC++ 5.0 debug info. Throw COFFException if not
+          // found; will cause caller to return null.
+          seek(subsectionDirectoryOffset);
+          int headerLength = readShort();
+          int entryLength  = readShort();
+          int numEntries   = readInt();
+          int endOffset    = subsectionDirectoryOffset + headerLength + numEntries * entryLength;
+          seek(endOffset);
+
+          if (readByte() == 'N' &&
+              readByte() == 'B' &&
+              readByte() == '1' &&
+              readByte() == '1') {
+            return;
+          }
+
+          throw new COFFException("Did not find NB11 signature at end of debug info");
+        }
+
+        class DebugVC50SubsectionDirectoryImpl
+          implements DebugVC50SubsectionDirectory,
+                     DebugVC50SubsectionTypes {
+          private int   offset;
+          private short dirHeaderLength;
+          private short dirEntryLength;
+          private int   numEntries;
+
+          DebugVC50SubsectionDirectoryImpl(int offset) {
+            this.offset = offset;
+            // Read header
+            seek(offset);
+            dirHeaderLength = readShort();
+            dirEntryLength  = readShort();
+            numEntries      = readInt();
+          }
+
+          public short getHeaderLength() { return dirHeaderLength; }
+          public short getEntryLength()  { return dirEntryLength;  }
+          public int   getNumEntries()   { return numEntries;      }
+
+          public DebugVC50Subsection getSubsection(int i) {
+            // Fetch the subsection type and instantiate the correct
+            // type of subsection based on it
+            seek(offset + dirHeaderLength + (i * dirEntryLength));
+            short ssType = readShort();
+            short iMod   = readShort(); // Unneeded?
+            int   lfo    = globalOffset(readInt());
+            int   cb     = readInt();
+            switch (ssType) {
+            case SST_MODULE:
+              return new DebugVC50SSModuleImpl(ssType, iMod, cb, lfo);
+            case SST_TYPES:
+              return new DebugVC50SSTypesImpl(ssType, iMod, cb, lfo);
+            case SST_PUBLIC:
+              return new DebugVC50SSPublicImpl(ssType, iMod, cb, lfo);
+            case SST_PUBLIC_SYM:
+              return new DebugVC50SSPublicSymImpl(ssType, iMod, cb, lfo);
+            case SST_SYMBOLS:
+              return new DebugVC50SSSymbolsImpl(ssType, iMod, cb, lfo);
+            case SST_ALIGN_SYM:
+              return new DebugVC50SSAlignSymImpl(ssType, iMod, cb, lfo);
+            case SST_SRC_LN_SEG:
+              return new DebugVC50SSSrcLnSegImpl(ssType, iMod, cb, lfo);
+            case SST_SRC_MODULE:
+              return new DebugVC50SSSrcModuleImpl(ssType, iMod, cb, lfo);
+            case SST_LIBRARIES:
+              return new DebugVC50SSLibrariesImpl(ssType, iMod, cb, lfo);
+            case SST_GLOBAL_SYM:
+              return new DebugVC50SSGlobalSymImpl(ssType, iMod, cb, lfo);
+            case SST_GLOBAL_PUB:
+              return new DebugVC50SSGlobalPubImpl(ssType, iMod, cb, lfo);
+            case SST_GLOBAL_TYPES:
+              return new DebugVC50SSGlobalTypesImpl(ssType, iMod, cb, lfo);
+            case SST_MPC:
+              return new DebugVC50SSMPCImpl(ssType, iMod, cb, lfo);
+            case SST_SEG_MAP:
+              return new DebugVC50SSSegMapImpl(ssType, iMod, cb, lfo);
+            case SST_SEG_NAME:
+              return new DebugVC50SSSegNameImpl(ssType, iMod, cb, lfo);
+            case SST_PRE_COMP:
+              return new DebugVC50SSPreCompImpl(ssType, iMod, cb, lfo);
+            case SST_UNUSED:
+              return null;
+            case SST_OFFSET_MAP_16:
+              return new DebugVC50SSOffsetMap16Impl(ssType, iMod, cb, lfo);
+            case SST_OFFSET_MAP_32:
+              return new DebugVC50SSOffsetMap32Impl(ssType, iMod, cb, lfo);
+            case SST_FILE_INDEX:
+              return new DebugVC50SSFileIndexImpl(ssType, iMod, cb, lfo);
+            case SST_STATIC_SYM:
+              return new DebugVC50SSStaticSymImpl(ssType, iMod, cb, lfo);
+            default:
+              throw new COFFException("Unknown section type " + ssType);
+            }
+          }
+        }
+
+        ////////////////////////////////////
+        //                                //
+        // Implementations of subsections //
+        //                                //
+        ////////////////////////////////////
+
+        class DebugVC50SubsectionImpl implements DebugVC50Subsection {
+          private short ssType;
+          private short iMod;
+          private int   ssSize;
+
+          DebugVC50SubsectionImpl(short ssType, short iMod, int ssSize, int offset) {
+            this.ssType = ssType;
+            this.iMod   = iMod;
+            this.ssSize = ssSize;
+          }
+
+          public short getSubsectionType()        { return ssType; }
+          public short getSubsectionModuleIndex() { return iMod; }
+          public int   getSubsectionSize()        { return ssSize; }
+        }
+
+        class DebugVC50SSModuleImpl extends DebugVC50SubsectionImpl implements DebugVC50SSModule {
+          private int offset;
+          private short ovlNumber;
+          private short iLib;
+          private short cSeg;
+          private short style;
+          private MemoizedObject segInfo;
+          private MemoizedObject name;
+
+          private static final int HEADER_SIZE   = 8;
+          private static final int SEG_INFO_SIZE = 12;
+
+          DebugVC50SSModuleImpl(short ssType, short iMod, int ssSize, final int offset) {
+            super(ssType, iMod, ssSize, offset);
+            this.offset = offset;
+            seek(offset);
+            ovlNumber = readShort();
+            iLib      = readShort();
+            cSeg      = readShort();
+            style     = readShort();
+            segInfo   = new MemoizedObject() {
+                public Object computeValue() {
+                  int base = offset + HEADER_SIZE;
+                  DebugVC50SegInfo[] res = new DebugVC50SegInfo[cSeg];
+                  for (int i = 0; i < cSeg; i++) {
+                    res[i] = new DebugVC50SegInfoImpl(base);
+                    base += SEG_INFO_SIZE;
+                  }
+                  return res;
+                }
+              };
+            name      = new MemoizedObject() {
+                public Object computeValue() {
+                  return readLengthPrefixedStringAt(offset + (HEADER_SIZE + cSeg * SEG_INFO_SIZE));
+                }
+              };
+          }
+
+          public short getOverlayNumber()   { return ovlNumber; }
+          public short getLibrariesIndex()  { return iLib; }
+          public short getNumCodeSegments() { return cSeg; }
+          public short getDebuggingStyle()  { return style; }
+          public DebugVC50SegInfo getSegInfo(int i) { return ((DebugVC50SegInfo[]) segInfo.getValue())[i]; }
+          public String getName()           { return (String) name.getValue(); }
+        }
+
+        class DebugVC50SegInfoImpl implements DebugVC50SegInfo {
+          private short seg;
+          private int   offset;
+          private int   cbSeg;
+
+          DebugVC50SegInfoImpl(int offset) {
+            seek(offset);
+            seg = readShort();
+            readShort(); // pad
+            offset = readInt();
+            cbSeg = readInt();
+          }
+
+          public short getSegment() { return seg; }
+          public int getOffset() { return offset; }
+          public int getSegmentCodeSize() { return cbSeg; }
+        }
+
+        class DebugVC50SSTypesImpl extends DebugVC50SubsectionImpl implements DebugVC50SSTypes {
+          DebugVC50SSTypesImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSPublicImpl extends DebugVC50SubsectionImpl implements DebugVC50SSPublic {
+          DebugVC50SSPublicImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSPublicSymImpl extends DebugVC50SubsectionImpl implements DebugVC50SSPublicSym {
+          DebugVC50SSPublicSymImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSSymbolsImpl extends DebugVC50SubsectionImpl implements DebugVC50SSSymbols {
+          DebugVC50SSSymbolsImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSAlignSymImpl extends DebugVC50SubsectionImpl implements DebugVC50SSAlignSym {
+          private int offset;
+
+          DebugVC50SSAlignSymImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+            this.offset = offset;
+          }
+
+          public DebugVC50SymbolIterator getSymbolIterator() {
+            return new DebugVC50SymbolIteratorImpl(offset, getSubsectionSize());
+          }
+        }
+
+        class DebugVC50SSSrcLnSegImpl extends DebugVC50SubsectionImpl implements DebugVC50SSSrcLnSeg {
+          DebugVC50SSSrcLnSegImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSSrcModuleImpl extends DebugVC50SubsectionImpl implements DebugVC50SSSrcModule {
+          private int offset;
+          private short cFile;
+          private short cSeg;
+          private MemoizedObject baseSrcFiles;
+          private MemoizedObject segOffsets;
+          private MemoizedObject segs;
+
+          DebugVC50SSSrcModuleImpl(short ssType, short iMod, int ssSize, final int offset) {
+            super(ssType, iMod, ssSize, offset);
+
+            this.offset = offset;
+            seek(offset);
+            cFile = readShort();
+            cSeg  = readShort();
+
+            baseSrcFiles = new MemoizedObject() {
+                public Object computeValue() {
+                  int[] offsets = new int[getNumSourceFiles()];
+                  seek(offset + 4);
+                  for (int i = 0; i < getNumSourceFiles(); i++) {
+                    offsets[i] = offset + readInt();
+                  }
+                  DebugVC50SrcModFileDescImpl[] res = new DebugVC50SrcModFileDescImpl[offsets.length];
+                  for (int i = 0; i < res.length; i++) {
+                    res[i] = new DebugVC50SrcModFileDescImpl(offsets[i], offset);
+                  }
+                  return res;
+                }
+              };
+
+            segOffsets = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4 * (getNumSourceFiles() + 1));
+                  int[] res = new int[2 * getNumCodeSegments()];
+                  for (int i = 0; i < 2 * getNumCodeSegments(); i++) {
+                    res[i] = readInt();
+                  }
+                  return res;
+                }
+              };
+
+            segs = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4 * (getNumSourceFiles() + 1) + 8 * getNumCodeSegments());
+                  short[] res = new short[getNumCodeSegments()];
+                  for (int i = 0; i < getNumCodeSegments(); i++) {
+                    res[i] = readShort();
+                  }
+                  return res;
+                }
+              };
+          }
+
+          public int getNumSourceFiles()  { return cFile & 0xFFFF; }
+          public int getNumCodeSegments() { return cSeg & 0xFFFF;  }
+          public DebugVC50SrcModFileDesc getSourceFileDesc(int i) {
+            return ((DebugVC50SrcModFileDescImpl[]) baseSrcFiles.getValue())[i];
+          }
+
+          public int getSegmentStartOffset(int i) {
+            return ((int[]) segOffsets.getValue())[2*i];
+          }
+
+          public int getSegmentEndOffset(int i) {
+            return ((int[]) segOffsets.getValue())[2*i+1];
+          }
+
+          public int getSegment(int i) {
+            return ((short[]) segs.getValue())[i] & 0xFFFF;
+          }
+        }
+
+        class DebugVC50SrcModFileDescImpl implements DebugVC50SrcModFileDesc {
+          private short cSeg;
+          private MemoizedObject baseSrcLn;
+          private MemoizedObject segOffsets;
+          private MemoizedObject name;
+
+          DebugVC50SrcModFileDescImpl(final int offset, final int baseOffset) {
+            seek(offset);
+            cSeg = readShort();
+
+            baseSrcLn = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4);
+                  int[] offsets = new int[getNumCodeSegments()];
+                  for (int i = 0; i < getNumCodeSegments(); i++) {
+                    offsets[i] = baseOffset + readInt();
+                  }
+                  DebugVC50SrcModLineNumberMapImpl[] res =
+                    new DebugVC50SrcModLineNumberMapImpl[getNumCodeSegments()];
+                  for (int i = 0; i < getNumCodeSegments(); i++) {
+                    res[i] = new DebugVC50SrcModLineNumberMapImpl(offsets[i]);
+                  }
+                  return res;
+                }
+              };
+
+            segOffsets = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4 * (getNumCodeSegments() + 1));
+                  int[] res = new int[2 * getNumCodeSegments()];
+                  for (int i = 0; i < 2 * getNumCodeSegments(); i++) {
+                    res[i] = readInt();
+                  }
+                  return res;
+                }
+              };
+
+            name = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4 + 12 * getNumCodeSegments());
+                  // NOTE: spec says name length is two bytes, but it's really one
+                  int cbName = readByte() & 0xFF;
+                  byte[] res = new byte[cbName];
+                  readBytes(res);
+                  try {
+                    return new String(res, US_ASCII);
+                  } catch (UnsupportedEncodingException e) {
+                    throw new COFFException(e);
+                  }
+                }
+              };
+          }
+
+          public int getNumCodeSegments() { return cSeg & 0xFFFF; }
+
+          public DebugVC50SrcModLineNumberMap getLineNumberMap(int i) {
+            return ((DebugVC50SrcModLineNumberMapImpl[]) baseSrcLn.getValue())[i];
+          }
+
+          public int getSegmentStartOffset(int i) {
+            return ((int[]) segOffsets.getValue())[2*i];
+          }
+
+          public int getSegmentEndOffset(int i) {
+            return ((int[]) segOffsets.getValue())[2*i+1];
+          }
+
+          public String getSourceFileName() {
+            return (String) name.getValue();
+          }
+        }
+
+        class DebugVC50SrcModLineNumberMapImpl implements DebugVC50SrcModLineNumberMap {
+          private short seg;
+          private short cPair;
+          private MemoizedObject offsets;
+          private MemoizedObject lineNumbers;
+
+          DebugVC50SrcModLineNumberMapImpl(final int offset) {
+            seek(offset);
+            seg = readShort();
+            cPair = readShort();
+            offsets = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4);
+                  int[] res = new int[getNumSourceLinePairs()];
+                  for (int i = 0; i < getNumSourceLinePairs(); i++) {
+                    res[i] = readInt();
+                  }
+                  return res;
+                }
+              };
+
+            lineNumbers = new MemoizedObject() {
+                public Object computeValue() {
+                  seek(offset + 4 * (getNumSourceLinePairs() + 1));
+                  short[] res = new short[getNumSourceLinePairs()];
+                  for (int i = 0; i < getNumSourceLinePairs(); i++) {
+                    res[i] = readShort();
+                  }
+                  return res;
+                }
+              };
+          }
+
+          public int getSegment() { return seg; }
+          public int getNumSourceLinePairs() { return cPair; }
+          public int getCodeOffset(int i) {
+            return ((int[]) offsets.getValue())[i];
+          }
+          public int getLineNumber(int i) {
+            return ((short[]) lineNumbers.getValue())[i] & 0xFFFF;
+          }
+        }
+
+        class DebugVC50SSLibrariesImpl extends DebugVC50SubsectionImpl implements DebugVC50SSLibraries {
+          DebugVC50SSLibrariesImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+
+          // FIXME: NOT FINISHED
+        }
+
+        class DebugVC50SSSymbolBaseImpl extends DebugVC50SubsectionImpl implements DebugVC50SSSymbolBase {
+          private int   offset;
+          private short symHash;
+          private short addrHash;
+          private int   cbSymbol;
+          private int   cbSymHash;
+          private int   cbAddrHash;
+
+          private static final int HEADER_SIZE = 16;
+
+          DebugVC50SSSymbolBaseImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+            this.offset = offset;
+            seek(offset);
+            symHash    = readShort();
+            addrHash   = readShort();
+            cbSymbol   = readInt();
+            cbSymHash  = readInt();
+            cbAddrHash = readInt();
+          }
+
+          public short getSymHashIndex()  { return symHash; }
+          public short getAddrHashIndex() { return addrHash; }
+          public int getSymTabSize()      { return cbSymbol; }
+          public int getSymHashSize()     { return cbSymHash; }
+          public int getAddrHashSize()    { return cbAddrHash; }
+
+          public DebugVC50SymbolIterator getSymbolIterator() {
+            return new DebugVC50SymbolIteratorImpl(offset + HEADER_SIZE, cbSymbol);
+          }
+        }
+
+        class DebugVC50SSGlobalSymImpl extends DebugVC50SSSymbolBaseImpl implements DebugVC50SSGlobalSym {
+          DebugVC50SSGlobalSymImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+        class DebugVC50SSGlobalPubImpl extends DebugVC50SSSymbolBaseImpl implements DebugVC50SSGlobalPub {
+          DebugVC50SSGlobalPubImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSGlobalTypesImpl extends DebugVC50SubsectionImpl implements DebugVC50SSGlobalTypes {
+          private int offset;
+          private int cType;
+
+          DebugVC50SSGlobalTypesImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+            this.offset = offset;
+            seek(offset);
+            readInt(); // Discard "flags"
+            cType = readInt();
+          }
+
+          public int getNumTypes() { return cType; }
+          // FIXME: should memoize these
+          public int getTypeOffset(int i) {
+            seek(offset + 4 * (i + 2));
+            return readInt() + offsetOfFirstType();
+          }
+
+          public DebugVC50TypeIterator getTypeIterator() {
+            return new DebugVC50TypeIteratorImpl(this,
+                                                 offsetOfFirstType(),
+                                                 cType);
+          }
+
+          private int offsetOfFirstType() {
+            return offset + 4 * (getNumTypes() + 2);
+          }
+        }
+
+        class DebugVC50SSMPCImpl extends DebugVC50SubsectionImpl implements DebugVC50SSMPC {
+          DebugVC50SSMPCImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSSegMapImpl extends DebugVC50SubsectionImpl implements DebugVC50SSSegMap {
+          private short cSeg;
+          private short cSegLog;
+          private MemoizedObject segDescs;
+
+          DebugVC50SSSegMapImpl(short ssType, short iMod, int ssSize, final int offset) {
+            super(ssType, iMod, ssSize, offset);
+            seek(offset);
+            cSeg = readShort();
+            cSegLog = readShort();
+            segDescs = new MemoizedObject() {
+                public Object computeValue() {
+                  DebugVC50SegDesc[] descs = new DebugVC50SegDesc[cSeg];
+                  for (int i = 0; i < cSeg; i++) {
+                    descs[i] = new DebugVC50SegDescImpl(offset + 4 + (20 * i));
+                  }
+                  return descs;
+                }
+              };
+          }
+
+          public short getNumSegDesc() { return cSeg; }
+          public short getNumLogicalSegDesc() { return cSegLog; }
+          public DebugVC50SegDesc getSegDesc(int i) { return ((DebugVC50SegDesc[]) segDescs.getValue())[i]; }
+        }
+
+        class DebugVC50SegDescImpl implements DebugVC50SegDesc {
+          private short flags;
+          private short ovl;
+          private short group;
+          private short frame;
+          private short iSegName;
+          private short iClassName;
+          private int   offset;
+          private int   cbSeg;
+
+          DebugVC50SegDescImpl(int offset) {
+            seek(offset);
+            flags = readShort();
+            ovl = readShort();
+            group = readShort();
+            frame = readShort();
+            iSegName = readShort();
+            iClassName = readShort();
+            offset = readInt();
+            cbSeg = readInt();
+          }
+
+          public short getFlags() { return flags; }
+          public short getOverlayNum() { return ovl; }
+          public short getGroup() { return group; }
+          public short getFrame() { return frame; }
+          public short getName() { return iSegName; }
+          public short getClassName() { return iClassName; }
+          public int   getOffset() { return offset; }
+          public int   getSize() { return cbSeg; }
+        }
+
+
+        class DebugVC50SSSegNameImpl extends DebugVC50SubsectionImpl implements DebugVC50SSSegName {
+          private int offset;
+          private int size;
+          private MemoizedObject names;
+
+          DebugVC50SSSegNameImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+            this.offset = offset;
+            this.size   = ssSize;
+            seek(offset);
+            names = new MemoizedObject() {
+                public Object computeValue() {
+                  int i = 0;
+                  List data = new ArrayList();
+                  while (i < size) {
+                    String s = readCString();
+                    data.add(s);
+                    i += s.length();
+                  }
+                  String[] res = new String[data.size()];
+                  res = (String[]) data.toArray(res);
+                  return res;
+                }
+              };
+          }
+
+          public String getSegName(int i) {
+            return ((String[]) names.getValue())[i];
+          }
+        }
+
+        class DebugVC50SSPreCompImpl extends DebugVC50SubsectionImpl implements DebugVC50SSPreComp {
+          DebugVC50SSPreCompImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSOffsetMap16Impl extends DebugVC50SubsectionImpl implements DebugVC50SSOffsetMap16 {
+          DebugVC50SSOffsetMap16Impl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSOffsetMap32Impl extends DebugVC50SubsectionImpl implements DebugVC50SSOffsetMap32 {
+          DebugVC50SSOffsetMap32Impl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        class DebugVC50SSFileIndexImpl extends DebugVC50SubsectionImpl implements DebugVC50SSFileIndex {
+          private int offset;
+          private short cMod; // Number of modules in the executable
+          private short cRef; // Total number of file name references
+          private MemoizedObject modStart;
+          private MemoizedObject cRefCnt;
+          // FIXME: probably useless; needs fixup to be converted into
+          // indices rather than offsets
+          private MemoizedObject nameRef;
+          private MemoizedObject names;
+
+          DebugVC50SSFileIndexImpl(short ssType, short iMod, int ssSize, final int offset) {
+            super(ssType, iMod, ssSize, offset);
+            this.offset = offset;
+            seek(offset);
+            cMod = readShort();
+            cRef = readShort();
+            modStart = new MemoizedObject() {
+                public Object computeValue() {
+                  short[] vals = new short[cMod];
+                  seek(4 + offset);
+                  for (int i = 0; i < cMod; i++) {
+                    vals[i] = readShort();
+                  }
+                  return vals;
+                }
+              };
+            cRefCnt = new MemoizedObject() {
+                public Object computeValue() {
+                  short[] vals = new short[cMod];
+                  seek(4 + offset + (2 * cMod));
+                  for (int i = 0; i < cMod; i++) {
+                    vals[i] = readShort();
+                  }
+                  return vals;
+                }
+              };
+            nameRef = new MemoizedObject() {
+                public Object computeValue() {
+                  int[] vals = new int[cRef];
+                  seek(4 + offset + (4 * cMod));
+                  for (int i = 0; i < cMod; i++) {
+                    vals[i] = readInt();
+                  }
+                  return vals;
+                }
+              };
+            names = new MemoizedObject() {
+                public Object computeValue() {
+                  String[] vals = new String[cRef];
+                  for (int i = 0; i < cRef; i++) {
+                    vals[i] = readCString();
+                  }
+                  return vals;
+                }
+              };
+          }
+
+          public short getNumModules()    { return cMod; }
+          public short getNumReferences() { return cRef; }
+          public short[] getModStart()    { return (short[]) modStart.getValue(); }
+          public short[] getRefCount()    { return (short[]) cRefCnt.getValue(); }
+          public int[] getNameRef()       { return (int[]) nameRef.getValue(); }
+          public String[] getNames()      { return (String[]) names.getValue(); }
+        }
+
+        class DebugVC50SSStaticSymImpl extends DebugVC50SSSymbolBaseImpl implements DebugVC50SSStaticSym {
+          DebugVC50SSStaticSymImpl(short ssType, short iMod, int ssSize, int offset) {
+            super(ssType, iMod, ssSize, offset);
+          }
+        }
+
+        //////////////////////////////////////////////////
+        //                                              //
+        // Implementations of symbol and type iterators //
+        //                                              //
+        //////////////////////////////////////////////////
+
+        class DebugVC50SymbolIteratorImpl implements DebugVC50SymbolIterator {
+          private int base;
+          private int size;
+          private int pos;
+          private int curSymSize;
+          private int curSymType;
+
+          private static final int HEADER_SIZE = 4;
+
+          DebugVC50SymbolIteratorImpl(int base, int size) {
+            this(base, size, base);
+          }
+
+          private DebugVC50SymbolIteratorImpl(int base, int size, int pos) {
+            this.base = base;
+            this.size = size;
+            this.pos = pos;
+            seek(pos);
+            curSymSize = readShort() & 0xFFFF;
+            curSymType = readShort() & 0xFFFF;
+          }
+
+          public boolean done() {
+            return (pos == (base + size));
+          }
+
+          public void next() throws NoSuchElementException {
+            if (done()) throw new NoSuchElementException("No more symbols");
+            pos += curSymSize + 2;
+            seek(pos);
+            curSymSize = readShort() & 0xFFFF;
+            curSymType = readShort() & 0xFFFF;
+          }
+
+          public short getLength() {
+            return (short) curSymSize;
+          }
+
+          public int getType() {
+            return curSymType;
+          }
+
+          public int getOffset() {
+            return pos + HEADER_SIZE;
+          }
+
+          /////////////////////////
+          // S_COMPILE accessors //
+          /////////////////////////
+
+          public byte getCompilerTargetProcessor() {
+            symSeek(0);
+            return readByte();
+          }
+
+          public int getCompilerFlags() {
+            symSeek(1);
+            int res = 0;
+            for (int i = 0; i < 3; i++) {
+              int b = readByte() & 0xFF;
+              res = (res << 8) | b;
+            }
+            return res;
+          }
+
+          public String getComplierVersion() {
+            return readLengthPrefixedStringAt(4);
+          }
+
+          //////////////////////////
+          // S_REGISTER accessors //
+          //////////////////////////
+
+          public int getRegisterSymbolType() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public short getRegisterEnum() {
+            symSeek(4);
+            return readShort();
+          }
+
+          public String getRegisterSymbolName() {
+            return readLengthPrefixedStringAt(6);
+          }
+
+          //////////////////////////
+          // S_CONSTANT accessors //
+          //////////////////////////
+
+          public int getConstantType() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public int getConstantValueAsInt() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(4);
+          }
+
+          public long getConstantValueAsLong() throws DebugVC50WrongNumericTypeException {
+            return readLongNumericLeafAt(4);
+          }
+
+          public float getConstantValueAsFloat() throws DebugVC50WrongNumericTypeException {
+            return readFloatNumericLeafAt(4);
+          }
+
+          public double getConstantValueAsDouble() throws DebugVC50WrongNumericTypeException {
+            return readDoubleNumericLeafAt(4);
+          }
+
+          public String getConstantName() {
+            return readLengthPrefixedStringAt(4 + numericLeafLengthAt(4));
+          }
+
+          /////////////////////
+          // S_UDT accessors //
+          /////////////////////
+
+          public int getUDTType() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public String getUDTName() {
+            return readLengthPrefixedStringAt(4);
+          }
+
+          /////////////////////////
+          // S_SSEARCH accessors //
+          /////////////////////////
+
+          public int getSearchSymbolOffset() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public short getSearchSegment() {
+            symSeek(4);
+            return readShort();
+          }
+
+          /////////////////////
+          // S_END accessors //
+          /////////////////////
+
+          // (No accessors)
+
+          //////////////////////
+          // S_SKIP accessors //
+          //////////////////////
+
+          // (No accessors)
+
+          ///////////////////////////
+          // S_CVRESERVE accessors //
+          ///////////////////////////
+
+          // (No accessors)
+
+          /////////////////////////
+          // S_OBJNAME accessors //
+          /////////////////////////
+
+          public int getObjectCodeViewSignature() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public String getObjectName() {
+            return readLengthPrefixedStringAt(4);
+          }
+
+          ////////////////////////
+          // S_ENDARG accessors //
+          ////////////////////////
+
+          // (No accessors)
+
+          //////////////////////////
+          // S_COBOLUDT accessors //
+          //////////////////////////
+
+          // (Elided as they are irrelevant)
+
+          /////////////////////////
+          // S_MANYREG accessors //
+          /////////////////////////
+
+          public int getManyRegType() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public byte getManyRegCount() {
+            symSeek(4);
+            return readByte();
+          }
+
+          public byte getManyRegRegister(int i) {
+            symSeek(5 + i);
+            return readByte();
+          }
+
+          public String getManyRegName() {
+            return readLengthPrefixedStringAt(5 + getManyRegCount());
+          }
+
+          ////////////////////////
+          // S_RETURN accessors //
+          ////////////////////////
+
+          public short getReturnFlags() {
+            symSeek(0);
+            return readShort();
+          }
+
+          public byte getReturnStyle() {
+            symSeek(2);
+            return readByte();
+          }
+
+          public byte getReturnRegisterCount() {
+            symSeek(3);
+            return readByte();
+          }
+
+          public byte getReturnRegister(int i) {
+            symSeek(4 + i);
+            return readByte();
+          }
+
+          ///////////////////////////
+          // S_ENTRYTHIS accessors //
+          ///////////////////////////
+
+          public void advanceToEntryThisSymbol() {
+            seek(pos + 4);
+            int tmpSymSize = readShort();
+            int tmpSymType = readShort();
+            if (Assert.ASSERTS_ENABLED) {
+              // Make sure that ends of inner and outer symbols line
+              // up, otherwise need more work
+              Assert.that(pos + curSymSize + 2 == pos + 4 + tmpSymSize,
+                          "advanceToEntryThisSymbol needs more work");
+            }
+            pos += 4;
+            curSymSize = tmpSymSize;
+            curSymType = tmpSymType;
+          }
+
+          ///////////////////////////////////////////////////////////////////////
+          //                                                                   //
+          //                                                                   //
+          // Symbols for (Intel) 16:32 Segmented and 32-bit Flat Architectures //
+          //                                                                   //
+          //                                                                   //
+          ///////////////////////////////////////////////////////////////////////
+
+          /////////////////////////
+          // S_BPREL32 accessors //
+          /////////////////////////
+
+          public int getBPRelOffset() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public int getBPRelType() {
+            symSeek(4);
+            return readInt();
+          }
+
+          public String getBPRelName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          ///////////////////////////////////////
+          // S_LDATA32 and S_GDATA32 accessors //
+          ///////////////////////////////////////
+
+          public int getLGDataType() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public int getLGDataOffset() {
+            symSeek(4);
+            return readInt();
+          }
+
+          public short getLGDataSegment() {
+            symSeek(8);
+            return readShort();
+          }
+
+          public String getLGDataName() {
+            return readLengthPrefixedStringAt(10);
+          }
+
+          ///////////////////////
+          // S_PUB32 accessors //
+          ///////////////////////
+
+          // FIXME: has the same format as the above; consider updating
+          // documentation. No separate accessors provided.
+
+          ///////////////////////////////////////
+          // S_LPROC32 and S_GPROC32 accessors //
+          ///////////////////////////////////////
+
+          public DebugVC50SymbolIterator getLGProcParent() {
+            int offs = getLGProcParentOffset();
+            if (offs == 0) return null;
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getLGProcParentOffset() {
+            symSeek(0);
+            int offs = readInt();
+            if (offs == 0) return 0;
+            return base + offs;
+          }
+
+          public DebugVC50SymbolIterator getLGProcEnd() {
+            int offs = getLGProcEndOffset();
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getLGProcEndOffset() {
+            symSeek(4);
+            int offs = readInt();
+            if (Assert.ASSERTS_ENABLED) {
+              Assert.that(offs != 0, "should not have null end offset for procedure symbols");
+            }
+            return base + offs;
+          }
+
+          public DebugVC50SymbolIterator getLGProcNext() {
+            int offs = getLGProcNextOffset();
+            if (offs == 0) return null;
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getLGProcNextOffset() {
+            symSeek(8);
+            int offs = readInt();
+            if (offs == 0) return 0;
+            return base + offs;
+          }
+
+          public int getLGProcLength() {
+            symSeek(12);
+            return readInt();
+          }
+
+          public int getLGProcDebugStart() {
+            symSeek(16);
+            return readInt();
+          }
+
+          public int getLGProcDebugEnd() {
+            symSeek(20);
+            return readInt();
+          }
+
+          public int getLGProcType() {
+            symSeek(24);
+            return readInt();
+          }
+
+          public int getLGProcOffset() {
+            symSeek(28);
+            return readInt();
+          }
+
+          public short getLGProcSegment() {
+            symSeek(32);
+            return readShort();
+          }
+
+          public byte getLGProcFlags() {
+            symSeek(34);
+            return readByte();
+          }
+
+          public String getLGProcName() {
+            return readLengthPrefixedStringAt(35);
+          }
+
+          /////////////////////////
+          // S_THUNK32 accessors //
+          /////////////////////////
+
+          public DebugVC50SymbolIterator getThunkParent() {
+            int offs = getThunkParentOffset();
+            if (offs == 0) return null;
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getThunkParentOffset() {
+            symSeek(0);
+            int offs = readInt();
+            if (offs == 0) return 0;
+            return base + offs;
+          }
+
+          public DebugVC50SymbolIterator getThunkEnd() {
+            symSeek(4);
+            int offs = readInt();
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getThunkEndOffset() {
+            symSeek(4);
+            int offs = readInt();
+            if (Assert.ASSERTS_ENABLED) {
+              Assert.that(offs != 0, "should not have null end offset for thunk symbols");
+            }
+            return base + offs;
+          }
+
+          public DebugVC50SymbolIterator getThunkNext() {
+            int offs = getThunkNextOffset();
+            if (offs == 0) return null;
+            return new DebugVC50SymbolIteratorImpl(base, size, base + offs);
+          }
+
+          public int getThunkNextOffset() {
+            symSeek(8);
+            int offs = readInt();
+            if (offs == 0) return 0;
+            return base + offs;
+          }
+
+          public int getThunkOffset() {
+            symSeek(12);
+            return readInt();
+          }
+
+          public short getThunkSegment() {
+            symSeek(16);
+            return readShort();
+          }
+
+          public short getThunkLength() {
+            symSeek(18);
+            return readShort();
+          }
+
+          public byte getThunkType() {
+            symSeek(20);
+            return readByte();
+          }
+
+          public String getThunkName() {
+            return readLengthPrefixedStringAt(21);
+          }
+
+          public short getThunkAdjustorThisDelta() {
+            symSeek(21 + lengthPrefixedStringLengthAt(21));
+            return readShort();
+          }
+
+          public String getThunkAdjustorTargetName() {
+            return readLengthPrefixedStringAt(23 + lengthPrefixedStringLengthAt(21));
+          }
+
+          public short getThunkVCallDisplacement() {
+            symSeek(21 + lengthPrefixedStringLengthAt(21));
+            return readShort();
+          }
+
+          public int getThunkPCodeOffset() {
+            symSeek(21 + lengthPrefixedStringLengthAt(21));
+            return readInt();
+          }
+
+          public short getThunkPCodeSegment() {
+            symSeek(25 + lengthPrefixedStringLengthAt(21));
+            return readShort();
+          }
+
+          /////////////////////////
+          // S_BLOCK32 accessors //
+          /////////////////////////
+
+          public DebugVC50SymbolIterator getBlockParent() {
+            int offs = getBlockParentOffset();
+            if (offs == 0) return null;
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getBlockParentOffset() {
+            symSeek(0);
+            int offs = readInt();
+            if (offs == 0) return 0;
+            return base + offs;
+          }
+
+          public DebugVC50SymbolIterator getBlockEnd() {
+            symSeek(4);
+            int offs = readInt();
+            return new DebugVC50SymbolIteratorImpl(base, size, offs);
+          }
+
+          public int getBlockEndOffset() {
+            symSeek(4);
+            int offs = readInt();
+            if (Assert.ASSERTS_ENABLED) {
+              Assert.that(offs != 0, "should not have null end offset for block symbols");
+            }
+            return base + offs;
+          }
+
+          public int getBlockLength() {
+            symSeek(8);
+            return readInt();
+          }
+
+          public int getBlockOffset() {
+            symSeek(12);
+            return readInt();
+          }
+
+          public short getBlockSegment() {
+            symSeek(16);
+            return readShort();
+          }
+
+          public String getBlockName() {
+            return readLengthPrefixedStringAt(18);
+          }
+
+          ////////////////////////
+          // S_WITH32 accessors //
+          ////////////////////////
+
+          // FIXME: this is a Pascal construct; ignored for now
+
+          /////////////////////////
+          // S_LABEL32 accessors //
+          /////////////////////////
+
+          public int getLabelOffset() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public short getLabelSegment() {
+            symSeek(4);
+            return readShort();
+          }
+
+          public byte getLabelFlags() {
+            symSeek(6);
+            return readByte();
+          }
+
+          public String getLabelName() {
+            return readLengthPrefixedStringAt(7);
+          }
+
+          ////////////////////////////
+          // S_CEXMODEL32 accessors //
+          ////////////////////////////
+
+          public int getChangeOffset() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public short getChangeSegment() {
+            symSeek(4);
+            return readShort();
+          }
+
+          public short getChangeModel() {
+            symSeek(6);
+            return readShort();
+          }
+
+          ////////////////////////////
+          // S_VFTTABLE32 accessors //
+          ////////////////////////////
+
+          public int getVTableRoot() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public int getVTablePath() {
+            symSeek(4);
+            return readInt();
+          }
+
+          public int getVTableOffset() {
+            symSeek(8);
+            return readInt();
+          }
+
+          public short getVTableSegment() {
+            symSeek(12);
+            return readShort();
+          }
+
+          //////////////////////////
+          // S_REGREL32 accessors //
+          //////////////////////////
+
+          public int getRegRelOffset() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public int getRegRelType() {
+            symSeek(4);
+            return readInt();
+          }
+
+          public short getRegRelRegister() {
+            symSeek(8);
+            return readShort();
+          }
+
+          public String getRegRelName() {
+            return readLengthPrefixedStringAt(10);
+          }
+
+          ///////////////////////////////////////////
+          // S_LTHREAD32 and S_GTHREAD32 accessors //
+          ///////////////////////////////////////////
+
+          public int getLThreadType() {
+            symSeek(0);
+            return readInt();
+          }
+
+          public int getLThreadOffset() {
+            symSeek(4);
+            return readInt();
+          }
+
+          public short getLThreadSegment() {
+            symSeek(8);
+            return readShort();
+          }
+
+          public String getLThreadName() {
+            return readLengthPrefixedStringAt(10);
+          }
+
+          //----------------------------------------------------------------------
+          // Internals only below this point
+          //
+
+          private void symSeek(int offsetInSym) {
+            seek(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private int numericLeafLengthAt(int offsetInSym) {
+            return DebugVC50Impl.this.numericLeafLengthAt(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private int readIntNumericLeafAt(int offsetInSym) {
+            return DebugVC50Impl.this.readIntNumericLeafAt(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private long readLongNumericLeafAt(int offsetInSym) {
+            return DebugVC50Impl.this.readLongNumericLeafAt(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private float readFloatNumericLeafAt(int offsetInSym) {
+            return DebugVC50Impl.this.readFloatNumericLeafAt(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private double readDoubleNumericLeafAt(int offsetInSym) {
+            return DebugVC50Impl.this.readDoubleNumericLeafAt(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private int lengthPrefixedStringLengthAt(int offsetInSym) {
+            return DebugVC50Impl.this.lengthPrefixedStringLengthAt(pos + HEADER_SIZE + offsetInSym);
+          }
+
+          private String readLengthPrefixedStringAt(int offsetInSym) {
+            return DebugVC50Impl.this.readLengthPrefixedStringAt(pos + HEADER_SIZE + offsetInSym);
+          }
+        }
+
+        class DebugVC50TypeIteratorImpl implements DebugVC50TypeIterator,
+                        DebugVC50TypeLeafIndices, DebugVC50MemberAttributes, DebugVC50TypeEnums {
+          private DebugVC50SSGlobalTypes parent;
+          private int   base;
+          private int   numTypes;
+          private int   typeIndex;
+          private int   typeRecordOffset;
+          private int   typeStringOffset;
+          private int   typeRecordSize;
+          private int   typeStringLeaf;
+
+          DebugVC50TypeIteratorImpl(DebugVC50SSGlobalTypes parent, int base, int numTypes) {
+            this(parent, base, numTypes, 0, base);
+          }
+
+          private DebugVC50TypeIteratorImpl(DebugVC50SSGlobalTypes parent, int base, int numTypes, int curType, int offset) {
+            this.parent = parent;
+            this.base = base;
+            this.numTypes = numTypes;
+            this.typeIndex = curType;
+            if (!done()) {
+              typeRecordOffset = offset;
+              loadTypeRecord();
+            }
+          }
+
+          public boolean done() {
+            return (typeIndex == numTypes);
+          }
+
+          public void next() throws NoSuchElementException {
+            if (done()) throw new NoSuchElementException();
+            ++typeIndex;
+            if (!done()) {
+              typeRecordOffset = parent.getTypeOffset(typeIndex);
+              loadTypeRecord();
+            }
+          }
+
+          public short getLength() {
+            return (short) typeRecordSize;
+          }
+
+          public int getTypeIndex() {
+            return biasTypeIndex(typeIndex);
+          }
+
+          public int getNumTypes() {
+            return numTypes;
+          }
+
+          public boolean typeStringDone() {
+            return (typeStringOffset - typeRecordOffset - 2) >= typeRecordSize;
+          }
+
+          public void typeStringNext() throws NoSuchElementException {
+            if (typeStringDone()) throw new NoSuchElementException();
+            typeStringOffset += typeStringLength();
+            loadTypeString();
+          }
+
+          public int typeStringLeaf() {
+            return typeStringLeaf;
+          }
+
+          public int typeStringOffset() {
+            return typeStringOffset;
+          }
+
+          ///////////////////////////
+          // LF_MODIFIER accessors //
+          ///////////////////////////
+
+          public int getModifierIndex() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public short getModifierAttribute() {
+            typeSeek(6);
+            return readShort();
+          }
+
+          //////////////////////////
+          // LF_POINTER accessors //
+          //////////////////////////
+
+          public int getPointerType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getPointerAttributes() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public int getPointerBasedOnTypeIndex() {
+            typeSeek(10);
+            return readInt();
+          }
+
+          public String getPointerBasedOnTypeName() {
+            return readLengthPrefixedStringAt(14);
+          }
+
+          public int getPointerToMemberClass() {
+            typeSeek(10);
+            return readInt();
+          }
+
+          public short getPointerToMemberFormat() {
+            typeSeek(14);
+            return readShort();
+          }
+
+          ////////////////////////
+          // LF_ARRAY accessors //
+          ////////////////////////
+
+          public int getArrayElementType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getArrayIndexType() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public int getArrayLength() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(10);
+          }
+
+          public String getArrayName() {
+            return readLengthPrefixedStringAt(10 + numericLeafLengthAt(10));
+          }
+
+          /////////////////////////////////////////
+          // LF_CLASS and LF_STRUCTURE accessors //
+          /////////////////////////////////////////
+
+          public short getClassCount() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public short getClassProperty() {
+            typeSeek(4);
+            return readShort();
+          }
+
+          public int getClassFieldList() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public DebugVC50TypeIterator getClassFieldListIterator() {
+            int index = unbiasTypeIndex(getClassFieldList());
+            int offset = parent.getTypeOffset(index);
+            return new DebugVC50TypeIteratorImpl(parent, base, numTypes, index, offset);
+          }
+
+          public int getClassDerivationList() {
+            typeSeek(10);
+            return readInt();
+          }
+
+          public int getClassVShape() {
+            typeSeek(14);
+            return readInt();
+          }
+
+          public int getClassSize() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(18);
+          }
+
+          public String getClassName() {
+            return readLengthPrefixedStringAt(18 + numericLeafLengthAt(18));
+          }
+
+          ////////////////////////
+          // LF_UNION accessors //
+          ////////////////////////
+
+          public short getUnionCount() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public short getUnionProperty() {
+            typeSeek(4);
+            return readShort();
+          }
+
+          public int getUnionFieldList() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public DebugVC50TypeIterator getUnionFieldListIterator() {
+            int index = unbiasTypeIndex(getUnionFieldList());
+            int offset = parent.getTypeOffset(index);
+            return new DebugVC50TypeIteratorImpl(parent, base, numTypes, index, offset);
+          }
+
+          public int getUnionSize() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(10);
+          }
+
+          public String getUnionName() {
+            return readLengthPrefixedStringAt(10 + numericLeafLengthAt(10));
+          }
+
+          ///////////////////////
+          // LF_ENUM accessors //
+          ///////////////////////
+
+          public short getEnumCount() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public short getEnumProperty() {
+            typeSeek(4);
+            return readShort();
+          }
+
+          public int getEnumType() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public int getEnumFieldList() {
+            typeSeek(10);
+            return readInt();
+          }
+
+          public DebugVC50TypeIterator getEnumFieldListIterator() {
+            int index = unbiasTypeIndex(getEnumFieldList());
+            int offset = parent.getTypeOffset(index);
+            return new DebugVC50TypeIteratorImpl(parent, base, numTypes, index, offset);
+          }
+
+          public String getEnumName() {
+            return readLengthPrefixedStringAt(14);
+          }
+
+          ////////////////////////////
+          // LF_PROCEDURE accessors //
+          ////////////////////////////
+
+          public int getProcedureReturnType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public byte getProcedureCallingConvention() {
+            typeSeek(6);
+            return readByte();
+          }
+
+          public short getProcedureNumberOfParameters() {
+            typeSeek(8);
+            return readShort();
+          }
+
+          public int getProcedureArgumentList() {
+            typeSeek(10);
+            return readInt();
+          }
+
+          public DebugVC50TypeIterator getProcedureArgumentListIterator() {
+            int index = unbiasTypeIndex(getProcedureArgumentList());
+            int offset = parent.getTypeOffset(index);
+            return new DebugVC50TypeIteratorImpl(parent, base, numTypes, index, offset);
+          }
+
+          ////////////////////////////
+          // LF_MFUNCTION accessors //
+          ////////////////////////////
+
+          public int getMFunctionReturnType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getMFunctionContainingClass() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public int getMFunctionThis() {
+            typeSeek(10);
+            return readInt();
+          }
+
+          public byte getMFunctionCallingConvention() {
+            typeSeek(14);
+            return readByte();
+          }
+
+          public short getMFunctionNumberOfParameters() {
+            typeSeek(16);
+            return readShort();
+          }
+
+          public int getMFunctionArgumentList() {
+            typeSeek(18);
+            return readInt();
+          }
+
+          public DebugVC50TypeIterator getMFunctionArgumentListIterator() {
+            int index = unbiasTypeIndex(getMFunctionArgumentList());
+            int offset = parent.getTypeOffset(index);
+            return new DebugVC50TypeIteratorImpl(parent, base, numTypes, index, offset);
+          }
+
+          public int getMFunctionThisAdjust() {
+            typeSeek(22);
+            return readInt();
+          }
+
+          //////////////////////////
+          // LF_VTSHAPE accessors //
+          //////////////////////////
+
+          public short getVTShapeCount() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getVTShapeDescriptor(int i) {
+            typeSeek(4 + (i / 2));
+            int val = readByte() & 0xFF;
+            if ((i % 2) != 0) {
+              val = val >> 4;
+            }
+            return val;
+          }
+
+          /////////////////////////
+          // LF_BARRAY accessors //
+          /////////////////////////
+
+          public int getBasicArrayType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          ////////////////////////
+          // LF_LABEL accessors //
+          ////////////////////////
+
+          public short getLabelAddressMode() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          ///////////////////////////
+          // LF_DIMARRAY accessors //
+          ///////////////////////////
+
+          public int getDimArrayType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getDimArrayDimInfo() {
+            typeSeek(6);
+            return readInt();
+          }
+
+          public String getDimArrayName() {
+            return readLengthPrefixedStringAt(10);
+          }
+
+          //////////////////////////
+          // LF_VFTPATH accessors //
+          //////////////////////////
+
+          public int getVFTPathCount() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getVFTPathBase(int i) {
+            typeSeek(6 + (4 * i));
+            return readInt();
+          }
+
+          ///////////////////////
+          // LF_SKIP accessors //
+          ///////////////////////
+
+          public int getSkipIndex() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          //////////////////////////
+          // LF_ARGLIST accessors //
+          //////////////////////////
+
+          public int getArgListCount() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getArgListType(int i) {
+            typeSeek(6 + (4 * i));
+            return readInt();
+          }
+
+          /////////////////////////
+          // LF_DEFARG accessors //
+          /////////////////////////
+
+          public int getDefaultArgType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public String getDefaultArgExpression() {
+            return readLengthPrefixedStringAt(6);
+          }
+
+          //////////////////////////
+          // LF_DERIVED accessors //
+          //////////////////////////
+
+          public int getDerivedCount() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public int getDerivedType(int i) {
+            typeSeek(6);
+            return readInt();
+          }
+
+          ///////////////////////////
+          // LF_BITFIELD accessors //
+          ///////////////////////////
+
+          public int getBitfieldFieldType() {
+            typeSeek(2);
+            return readInt();
+          }
+
+          public byte getBitfieldLength() {
+            typeSeek(6);
+            return readByte();
+          }
+
+          public byte getBitfieldPosition() {
+            typeSeek(7);
+            return readByte();
+          }
+
+          ////////////////////////
+          // LF_MLIST accessors //
+          ////////////////////////
+
+          public short getMListAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getMListLength() {
+            return (getLength() - 6 - (isMListIntroducingVirtual() ? 4 : 0)) / 4;
+          }
+
+          public int getMListType(int i) {
+            typeSeek(6 + 4 * i);
+            return readInt();
+          }
+
+          public boolean isMListIntroducingVirtual() {
+            return isIntroducingVirtual(getMListAttribute());
+          }
+
+          public int getMListVtabOffset() {
+            typeSeek(6 + 4 * getMListLength());
+            return readInt();
+          }
+
+          /////////////////////////
+          // LF_REFSYM accessors //
+          /////////////////////////
+
+          public DebugVC50SymbolIterator getRefSym() {
+            typeSeek(2);
+            int len = readShort() & 0xFFFF;
+            return new DebugVC50SymbolIteratorImpl(typeStringOffset + 2, len);
+          }
+
+          /////////////////////////
+          // LF_BCLASS accessors //
+          /////////////////////////
+
+          public short getBClassAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getBClassType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public int getBClassOffset() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(8);
+          }
+
+          //////////////////////////
+          // LF_VBCLASS accessors //
+          //////////////////////////
+
+          public short getVBClassAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getVBClassBaseClassType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public int getVBClassVirtualBaseClassType() {
+            typeSeek(8);
+            return readInt();
+          }
+
+          public int getVBClassVBPOff() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(12);
+          }
+
+          public int getVBClassVBOff() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(12 + numericLeafLengthAt(12));
+          }
+
+          ///////////////////////////
+          // LF_IVBCLASS accessors //
+          ///////////////////////////
+
+          public short getIVBClassAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getIVBClassBType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public int getIVBClassVBPType() {
+            typeSeek(8);
+            return readInt();
+          }
+
+          public int getIVBClassVBPOff() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(12);
+          }
+
+          public int getIVBClassVBOff() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(12 + numericLeafLengthAt(12));
+          }
+
+          ////////////////////////////
+          // LF_ENUMERATE accessors //
+          ////////////////////////////
+
+          public short getEnumerateAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public long getEnumerateValue() {
+            return readIntNumericLeafAt(4);
+          }
+
+          public String getEnumerateName() {
+            return readLengthPrefixedStringAt(4 + numericLeafLengthAt(4));
+          }
+
+          ////////////////////////////
+          // LF_FRIENDFCN accessors //
+          ////////////////////////////
+
+          public int getFriendFcnType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public String getFriendFcnName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          ////////////////////////
+          // LF_INDEX accessors //
+          ////////////////////////
+
+          public int getIndexValue() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public DebugVC50TypeIterator getIndexIterator() {
+            int index = unbiasTypeIndex(getIndexValue());
+            int offset = parent.getTypeOffset(index);
+            return new DebugVC50TypeIteratorImpl(parent, base, numTypes, index, offset);
+          }
+
+          /////////////////////////
+          // LF_MEMBER accessors //
+          /////////////////////////
+
+          public short getMemberAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getMemberType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public int getMemberOffset() throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(8);
+          }
+
+          public String getMemberName() {
+            return readLengthPrefixedStringAt(8 + numericLeafLengthAt(8));
+          }
+
+          ///////////////////////////
+          // LF_STMEMBER accessors //
+          ///////////////////////////
+
+          public short getStaticAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getStaticType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public String getStaticName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          /////////////////////////
+          // LF_METHOD accessors //
+          /////////////////////////
+
+          public short getMethodCount() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getMethodList() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public String getMethodName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          /////////////////////////////
+          // LF_NESTEDTYPE accessors //
+          /////////////////////////////
+
+          public int getNestedType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public String getNestedName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          ///////////////////////////
+          // LF_VFUNCTAB accessors //
+          ///////////////////////////
+
+          public int getVFuncTabType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          ////////////////////////////
+          // LF_FRIENDCLS accessors //
+          ////////////////////////////
+
+          public int getFriendClsType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          ////////////////////////////
+          // LF_ONEMETHOD accessors //
+          ////////////////////////////
+
+          public short getOneMethodAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getOneMethodType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public boolean isOneMethodIntroducingVirtual() {
+            return isIntroducingVirtual(getOneMethodAttribute());
+          }
+
+          public int getOneMethodVBaseOff() {
+            typeSeek(8);
+            return readInt();
+          }
+
+          public String getOneMethodName() {
+            int baseLen = 8 + (isOneMethodIntroducingVirtual() ? 4 : 0);
+            return readLengthPrefixedStringAt(baseLen);
+          }
+
+          ///////////////////////////
+          // LF_VFUNCOFF accessors //
+          ///////////////////////////
+
+          public int getVFuncOffType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public int getVFuncOffOffset() {
+            typeSeek(8);
+            return readInt();
+          }
+
+          ///////////////////////////////
+          // LF_NESTEDTYPEEX accessors //
+          ///////////////////////////////
+
+          public short getNestedExAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getNestedExType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public String getNestedExName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          ///////////////////////////////
+          // LF_MEMBERMODIFY accessors //
+          ///////////////////////////////
+
+          public short getMemberModifyAttribute() {
+            typeSeek(2);
+            return readShort();
+          }
+
+          public int getMemberModifyType() {
+            typeSeek(4);
+            return readInt();
+          }
+
+          public String getMemberModifyName() {
+            return readLengthPrefixedStringAt(8);
+          }
+
+          ////////////////////////////
+          // Numeric Leaf accessors //
+          ////////////////////////////
+
+          public short getNumericTypeAt(int byteOffset) {
+            typeSeek(byteOffset);
+            return readShort();
+          }
+
+          public int getNumericLengthAt(int byteOffset)
+            throws DebugVC50WrongNumericTypeException {
+            return numericLeafLengthAt(byteOffset);
+          }
+
+          public int getNumericIntAt(int byteOffset)
+            throws DebugVC50WrongNumericTypeException {
+            return readIntNumericLeafAt(byteOffset);
+          }
+
+          public long getNumericLongAt(int byteOffset)
+            throws DebugVC50WrongNumericTypeException {
+            // FIXME
+            throw new RuntimeException("Unimplemented");
+          }
+
+          public float getNumericFloatAt(int byteOffset)
+            throws DebugVC50WrongNumericTypeException {
+            // FIXME
+            throw new RuntimeException("Unimplemented");
+          }
+
+          public double getNumericDoubleAt(int byteOffset)
+            throws DebugVC50WrongNumericTypeException {
+            // FIXME
+            throw new RuntimeException("Unimplemented");
+          }
+
+          public byte[] getNumericDataAt(int byteOffset)
+            throws DebugVC50WrongNumericTypeException {
+            // FIXME
+            throw new RuntimeException("Unimplemented");
+          }
+
+          //----------------------------------------------------------------------
+          // Internals only below this point
+          //
+
+          private void loadTypeRecord() {
+            seek(typeRecordOffset);
+            typeRecordSize = readShort() & 0xFFFF;
+            typeStringOffset = typeRecordOffset + 2;
+            loadTypeString();
+          }
+
+          private void loadTypeString() {
+            seek(typeStringOffset);
+            int lo = readByte() & 0xFF;
+            // See if it is one of the single-byte leaves
+            if (lo >= LF_PAD0) {
+              typeStringLeaf = lo;
+            } else {
+              int hi = readByte() & 0xFF;
+              typeStringLeaf = (hi << 8) | lo;
+            }
+          }
+
+          private void typeSeek(int offset) {
+            seek(typeStringOffset + offset);
+          }
+
+          private int typeStringLength() {
+            // LF_PAD
+            if (typeStringLeaf >= 0xF0 && typeStringLeaf <= 0xFF) {
+              return (typeStringLeaf - 0xF0);
+            }
+
+            switch (typeStringLeaf) {
+
+              // Leaf indices for type records that can be referenced
+              // from symbols:
+            case LF_MODIFIER: return 8;
+            case LF_POINTER: {
+              int extraLen = 0;
+              int attr = (getPointerAttributes() & POINTER_PTRTYPE_MASK) >> POINTER_PTRTYPE_SHIFT;
+              int mode = (getPointerAttributes() & POINTER_PTRMODE_MASK) >> POINTER_PTRMODE_SHIFT;
+              if (attr == POINTER_PTRTYPE_BASED_ON_TYPE) {
+                extraLen = 4 + numericLeafLengthAt(typeStringOffset + 14);
+              } else if (mode == POINTER_PTRMODE_PTR_TO_DATA_MEMBER ||
+                         mode == POINTER_PTRMODE_PTR_TO_METHOD) {
+                extraLen = 6;
+              }
+              return 10 + extraLen;
+            }
+            case LF_ARRAY: {
+              int temp = 10 + numericLeafLengthAt(10);
+              return temp + lengthPrefixedStringLengthAt(temp);
+            }
+            case LF_CLASS:
+            case LF_STRUCTURE: {
+              int temp = 18 + numericLeafLengthAt(18);
+              return temp + lengthPrefixedStringLengthAt(temp);
+            }
+            case LF_UNION: {
+              int temp = 10 + numericLeafLengthAt(10);
+              return temp + lengthPrefixedStringLengthAt(temp);
+            }
+            case LF_ENUM: {
+              return 14 + lengthPrefixedStringLengthAt(14);
+            }
+            case LF_PROCEDURE: return 14;
+            case LF_MFUNCTION: return 26;
+            case LF_VTSHAPE:   return 4 + ((getVTShapeCount() + 1) / 2);
+            case LF_COBOL0:
+            case LF_COBOL1:    throw new COFFException("COBOL symbols unimplemented");
+            case LF_BARRAY:    return 6;
+            case LF_LABEL:     return 4;
+            case LF_NULL:      return 2;
+            case LF_NOTTRAN:   return 2;
+            case LF_DIMARRAY:  return 10 + lengthPrefixedStringLengthAt(10);
+            case LF_VFTPATH:   return 6 + 4 * getVFTPathCount();
+            case LF_PRECOMP:   return 14 + lengthPrefixedStringLengthAt(14);
+            case LF_ENDPRECOMP: return 6;
+            case LF_OEM:       throw new COFFException("OEM symbols unimplemented");
+            case LF_TYPESERVER: return 10 + lengthPrefixedStringLengthAt(10);
+
+            case LF_SKIP:      return 6 + numericLeafLengthAt(6);
+            case LF_ARGLIST:   return 6 + 4 * getArgListCount();
+            case LF_DEFARG:    return 6 + lengthPrefixedStringLengthAt(6);
+              // case LF_FIELDLIST: throw new COFFException("Should not see LF_FIELDLIST leaf");
+            case LF_FIELDLIST: return 2;
+            case LF_DERIVED:   return 6 + 4 * getDerivedCount();
+            case LF_BITFIELD:  return 8;
+            case LF_METHODLIST: {
+              return 6 + 4 * getMListLength() + (isMListIntroducingVirtual() ? 4 : 0);
+            }
+            case LF_DIMCONU:
+            case LF_DIMCONLU:
+            case LF_DIMVARU:
+            case LF_DIMVARLU:  throw new COFFException("LF_DIMCONU, LF_DIMCONLU, LF_DIMVARU, and LF_DIMVARLU unsupported");
+            case LF_REFSYM: {
+              seek(typeStringOffset + 2);
+              return 4 + readShort();
+            }
+
+            case LF_BCLASS:  return 8 + numericLeafLengthAt(8);
+            case LF_VBCLASS:
+            case LF_IVBCLASS: {
+              int temp = 12 + numericLeafLengthAt(12);
+              return temp + numericLeafLengthAt(temp);
+            }
+            case LF_ENUMERATE: {
+              int temp = 4 + numericLeafLengthAt(4);
+              return temp + lengthPrefixedStringLengthAt(temp);
+            }
+            case LF_FRIENDFCN: return 8 + lengthPrefixedStringLengthAt(8);
+            case LF_INDEX: return 8;
+            case LF_MEMBER: {
+              int temp = 8 + numericLeafLengthAt(8);
+              return temp + lengthPrefixedStringLengthAt(temp);
+            }
+            case LF_STMEMBER: return 8 + lengthPrefixedStringLengthAt(8);
+            case LF_METHOD:   return 8 + lengthPrefixedStringLengthAt(8);
+            case LF_NESTTYPE: return 8 + lengthPrefixedStringLengthAt(8);
+            case LF_VFUNCTAB: return 8;
+            case LF_FRIENDCLS: return 8;
+            case LF_ONEMETHOD: {
+              int baseLen = 8 + (isOneMethodIntroducingVirtual() ? 4 : 0);
+              return baseLen + lengthPrefixedStringLengthAt(baseLen);
+            }
+            case LF_VFUNCOFF:  return 12;
+            case LF_NESTTYPEEX: return 8 + lengthPrefixedStringLengthAt(8);
+            case LF_MEMBERMODIFY: return 8 + lengthPrefixedStringLengthAt(8);
+
+            // Should not encounter numeric leaves with this routine
+            case LF_CHAR:
+            case LF_SHORT:
+            case LF_USHORT:
+            case LF_LONG:
+            case LF_ULONG:
+            case LF_REAL32:
+            case LF_REAL64:
+            case LF_REAL80:
+            case LF_REAL128:
+            case LF_QUADWORD:
+            case LF_UQUADWORD:
+            case LF_REAL48:
+            case LF_COMPLEX32:
+            case LF_COMPLEX64:
+            case LF_COMPLEX80:
+            case LF_COMPLEX128:
+            case LF_VARSTRING:  throw new RuntimeException("Unexpected numeric leaf " + typeStringLeaf +
+                                                           "in type string");
+            default:
+              throw new COFFException("Unrecognized leaf " + typeStringLeaf + " in type string at offset " +
+                                      typeStringOffset);
+            }
+          }
+
+          private boolean isIntroducingVirtual(int mprop) {
+            int masked = mprop & MEMATTR_MPROP_MASK;
+            return ((masked == MEMATTR_MPROP_INTRODUCING_VIRTUAL) ||
+                    (masked == MEMATTR_MPROP_PURE_INTRODUCING_VIRTUAL));
+          }
+
+          private int numericLeafLengthAt(int offset) {
+            return DebugVC50Impl.this.numericLeafLengthAt(typeStringOffset + offset);
+          }
+
+          private int readIntNumericLeafAt(int offset) {
+            return DebugVC50Impl.this.readIntNumericLeafAt(typeStringOffset + offset);
+          }
+
+          private int lengthPrefixedStringLengthAt(int offset) {
+            return DebugVC50Impl.this.lengthPrefixedStringLengthAt(typeStringOffset + offset);
+          }
+
+          private String readLengthPrefixedStringAt(int offset) {
+            return DebugVC50Impl.this.readLengthPrefixedStringAt(typeStringOffset + offset);
+          }
+        }
+
+        private int numericLeafLengthAt(int absoluteOffset) throws DebugVC50WrongNumericTypeException {
+          seek(absoluteOffset);
+          int leaf = readShort() & 0xFFFF;
+          if (leaf < 0x8000) return 2;
+          switch (leaf) {
+          case LF_CHAR:       return 3;
+          case LF_SHORT:
+          case LF_USHORT:     return 4;
+          case LF_LONG:
+          case LF_ULONG:      return 6;
+          case LF_REAL32:     return 6;
+          case LF_REAL64:     return 10;
+          case LF_REAL80:     return 12;
+          case LF_REAL128:    return 18;
+          case LF_QUADWORD:
+          case LF_UQUADWORD:  return 18;
+          case LF_REAL48:     return 8;
+          case LF_COMPLEX32:  return 10;
+          case LF_COMPLEX64:  return 18;
+          case LF_COMPLEX80:  return 26;
+          case LF_COMPLEX128: return 66;
+            // FIXME: figure out format of variable-length strings
+          case LF_VARSTRING:  return 4 + readIntNumericLeafAt(absoluteOffset + 2);
+
+          default:
+            throw new DebugVC50WrongNumericTypeException("Illegal numeric leaf index " + leaf +
+                                                         " at offset " + absoluteOffset);
+          }
+        }
+
+        private int readIntNumericLeafAt(int absoluteOffset) throws DebugVC50WrongNumericTypeException {
+          seek(absoluteOffset);
+          int leaf = readShort() & 0xFFFF;
+          if (leaf < 0x8000) return leaf;
+          switch (leaf) {
+          case LF_CHAR:       return readByte() & 0xFF;
+          case LF_SHORT:
+          case LF_USHORT:     return readShort() & 0xFFFF;
+          case LF_LONG:
+          case LF_ULONG:      return readInt();
+
+          default:
+            throw new DebugVC50WrongNumericTypeException("Illegal numeric leaf index " + leaf);
+          }
+        }
+
+        private long readLongNumericLeafAt(int absoluteOffset) throws DebugVC50WrongNumericTypeException {
+          seek(absoluteOffset);
+          int leaf = readShort() & 0xFFFF;
+          if (leaf < 0x8000) return leaf;
+          switch (leaf) {
+          case LF_CHAR:       return readByte() & 0xFF;
+          case LF_SHORT:
+          case LF_USHORT:     return readShort() & 0xFFFF;
+          case LF_LONG:
+          case LF_ULONG:      return readInt() & 0xFFFFFFFF;
+          case LF_QUADWORD:
+          case LF_UQUADWORD:  return readLong();
+
+          default:
+            throw new DebugVC50WrongNumericTypeException("Illegal numeric leaf index " + leaf);
+          }
+        }
+
+        private float readFloatNumericLeafAt(int absoluteOffset) throws DebugVC50WrongNumericTypeException {
+          seek(absoluteOffset);
+          int leaf = readShort() & 0xFFFF;
+          if (leaf != LF_REAL32) {
+            throw new DebugVC50WrongNumericTypeException("Illegal numeric leaf index " + leaf);
+          }
+          return readFloat();
+        }
+
+        private double readDoubleNumericLeafAt(int absoluteOffset) throws DebugVC50WrongNumericTypeException {
+          seek(absoluteOffset);
+          int leaf = readShort() & 0xFFFF;
+          if (leaf != LF_REAL64) {
+            throw new DebugVC50WrongNumericTypeException("Illegal numeric leaf index " + leaf);
+          }
+          return readDouble();
+        }
+
+        private int lengthPrefixedStringLengthAt(int absoluteOffset) {
+          // NOTE: the format of length-prefixed strings is not well
+          // specified. There is a LF_VARSTRING numeric leaf (the
+          // format of which is also not specified), but it seems that
+          // most length-prefixed strings are comprised of a single
+          // byte length followed by that many bytes of data.
+          seek(absoluteOffset);
+          int len = readByte() & 0xFF;
+          return 1 + len;
+        }
+
+        private String readLengthPrefixedStringAt(int absoluteOffset) {
+          // NOTE: it isn't clear whether LF_VARSTRING numeric leaves
+          // ever show up, or in general what happens when the length
+          // of the string is > 255 (FIXME)
+          seek(absoluteOffset);
+          int len = readByte() & 0xFF;
+          byte[] res = new byte[len];
+          int numRead = readBytes(res);
+          if (numRead != len) {
+            throw new COFFException("Error reading length prefixed string in symbol at offset " +
+                                    absoluteOffset);
+          }
+          try {
+            return new String(res, US_ASCII);
+          } catch (UnsupportedEncodingException e) {
+            throw new COFFException(e);
+          }
+        }
+
+        private int unbiasTypeIndex(int index) {
+          return index - 0x1000;
+        }
+
+        private int biasTypeIndex(int index) {
+          return index + 0x1000;
+        }
+      } // Class DebugVC50Impl
+
+      class SectionHeaderImpl implements SectionHeader {
+        private String name;
+        private int    virtualSize;
+        private int    virtualAddress;
+        private int    sizeOfRawData;
+        private int    pointerToRawData;
+        private int    pointerToRelocations;
+        private int    pointerToLineNumbers;
+        private short  numberOfRelocations;
+        private short  numberOfLineNumbers;
+        private int    characteristics;
+        private MemoizedObject[] relocations;
+        private MemoizedObject[] lineNumbers;
+
+        public SectionHeaderImpl(int offset) throws COFFException {
+          seek(offset);
+
+          // FIXME: compute name lazily
+
+          // Read name
+          byte[] tmpName = new byte[8];
+          int numRead = readBytes(tmpName);
+          if (numRead != 8) {
+            throw new COFFException("Error reading name of section header at offset " + offset);
+          }
+          if (tmpName[0] == (byte) '/') {
+            // Long name; must find real value in string table
+            int index = 0;
+            try {
+              index = Integer.parseInt(new String(tmpName, 1, tmpName.length - 1, US_ASCII));
+            } catch (NumberFormatException e) {
+              throw new COFFException("Error parsing string table index of name of section header " +
+                                      "at offset " + offset);
+            } catch (UnsupportedEncodingException e) {
+              throw new COFFException(e);
+            }
+            // Look up in string table
+            name = getStringTable().get(index);
+          } else {
+            try {
+              name = new String(tmpName, US_ASCII);
+            } catch (UnsupportedEncodingException e) {
+              throw new COFFException(e);
+            }
+          }
+          virtualSize          = readInt();
+          virtualAddress       = readInt();
+          sizeOfRawData        = readInt();
+          pointerToRawData     = readInt();
+          pointerToRelocations = readInt();
+          pointerToLineNumbers = readInt();
+          numberOfRelocations  = readShort();
+          numberOfLineNumbers  = readShort();
+          characteristics      = readInt();
+
+          // Set up relocations
+          relocations = new MemoizedObject[numberOfRelocations];
+          for (int i = 0; i < numberOfRelocations; i++) {
+            final int relocOffset = pointerToRelocations + i * RELOCATION_SIZE;
+            relocations[i] = new MemoizedObject() {
+                public Object computeValue() {
+                  return new COFFRelocationImpl(relocOffset);
+                }
+              };
+          }
+
+          // Set up line numbers
+          lineNumbers = new MemoizedObject[numberOfLineNumbers];
+          for (int i = 0; i < numberOfLineNumbers; i++) {
+            final int lineNoOffset = pointerToLineNumbers + i * LINE_NUMBER_SIZE;
+            lineNumbers[i] = new MemoizedObject() {
+                public Object computeValue() {
+                  return new COFFLineNumberImpl(lineNoOffset);
+                }
+              };
+          }
+        }
+
+        public String getName() { return name; }
+        public int getSize() { return virtualSize; }
+        public int getVirtualAddress() { return virtualAddress; }
+        public int getSizeOfRawData() { return sizeOfRawData; }
+        public int getPointerToRawData() { return pointerToRawData; }
+        public int getPointerToRelocations() { return pointerToRelocations; }
+        public int getPointerToLineNumbers() { return pointerToLineNumbers; }
+        public short getNumberOfRelocations() { return numberOfRelocations; }
+        public short getNumberOfLineNumbers() { return numberOfLineNumbers; }
+        public int getSectionFlags() { return characteristics; }
+        public boolean hasSectionFlag(int flag ) {
+          return ((characteristics & flag) != 0);
+        }
+        public COFFRelocation getCOFFRelocation(int index) {
+          return (COFFRelocation) relocations[index].getValue();
+        }
+        public COFFLineNumber getCOFFLineNumber(int index) {
+          return (COFFLineNumber) lineNumbers[index];
+        }
+      }
+
+      class COFFSymbolImpl implements COFFSymbol, COFFSymbolConstants {
+        private int    offset;
+        private String name;
+        private int    value;
+        private short  sectionNumber;
+        private short  type;
+        private byte   storageClass;
+        private byte   numberOfAuxSymbols;
+        private MemoizedObject auxFunctionDefinitionRecord = new MemoizedObject() {
+            public Object computeValue() {
+              return new AuxFunctionDefinitionRecordImpl(offset + SYMBOL_SIZE);
+            }
+          };
+        private MemoizedObject auxBfEfRecord = new MemoizedObject() {
+            public Object computeValue() {
+              return new AuxBfEfRecordImpl(offset + SYMBOL_SIZE);
+            }
+          };
+        private MemoizedObject auxWeakExternalRecord = new MemoizedObject() {
+            public Object computeValue() {
+              return new AuxWeakExternalRecordImpl(offset + SYMBOL_SIZE);
+            }
+          };
+        private MemoizedObject auxFileRecord = new MemoizedObject() {
+            public Object computeValue() {
+              return new AuxFileRecordImpl(offset + SYMBOL_SIZE);
+            }
+          };
+        private MemoizedObject auxSectionDefinitionsRecord = new MemoizedObject() {
+            public Object computeValue() {
+              return new AuxSectionDefinitionsRecordImpl(offset + SYMBOL_SIZE);
+            }
+          };
+
+        public COFFSymbolImpl(int offset) throws COFFException {
+          this.offset = offset;
+          seek(offset);
+
+          // Parse name
+          byte[] tmpName = new byte[8];
+          int numRead = readBytes(tmpName);
+          if (numRead != 8) {
+            throw new COFFException("Error reading name of symbol at offset " + offset);
+          }
+          if ((tmpName[0] == 0) &&
+              (tmpName[1] == 0) &&
+              (tmpName[2] == 0) &&
+              (tmpName[3] == 0)) {
+            // It's an offset into the string table.
+            // FIXME: not sure about byte ordering...
+            int stringOffset = (tmpName[4] << 24 |
+                                tmpName[5] << 16 |
+                                tmpName[6] <<  8 |
+                                tmpName[7]);
+            name = getStringTable().getAtOffset(stringOffset);
+          }
+
+          value = readInt();
+          sectionNumber = readShort();
+          type = readShort();
+          storageClass = readByte();
+          numberOfAuxSymbols = readByte();
+        }
+
+        public int getOffset()              { return offset; }
+        public String getName()             { return name; }
+        public int getValue()               { return value; }
+        public short getSectionNumber()     { return sectionNumber; }
+        public short getType()              { return type; }
+        public byte getStorageClass()       { return storageClass; }
+        public byte getNumberOfAuxSymbols() { return numberOfAuxSymbols; }
+        public boolean isFunctionDefinition() {
+          return ((getStorageClass() == IMAGE_SYM_CLASS_EXTERNAL) &&
+                  ((getType() >>> 8) == IMAGE_SYM_DTYPE_FUNCTION) &&
+                  (getSectionNumber() > 0));
+        }
+        public AuxFunctionDefinitionRecord getAuxFunctionDefinitionRecord() {
+          return (AuxFunctionDefinitionRecord) auxFunctionDefinitionRecord.getValue();
+        }
+        public boolean isBfOrEfSymbol() {
+          return ((getName().equals(".bf") || getName().equals(".ef")) &&
+                  (getStorageClass() == IMAGE_SYM_CLASS_FUNCTION));
+        }
+        public AuxBfEfRecord getAuxBfEfRecord() {
+          return (AuxBfEfRecord) auxBfEfRecord.getValue();
+        }
+        public boolean isWeakExternal() {
+          return ((getStorageClass() == IMAGE_SYM_CLASS_EXTERNAL) &&
+                  (getSectionNumber() == IMAGE_SYM_UNDEFINED) &&
+                  (getValue() == 0));
+        }
+        public AuxWeakExternalRecord getAuxWeakExternalRecord() {
+          return (AuxWeakExternalRecord) auxWeakExternalRecord.getValue();
+        }
+        public boolean isFile() {
+          return ((getName().equals(".file")) &&
+                  (getStorageClass() == IMAGE_SYM_CLASS_FILE));
+        }
+        public AuxFileRecord getAuxFileRecord() {
+          return (AuxFileRecord) auxFileRecord.getValue();
+        }
+        public boolean isSectionDefinition() {
+          // FIXME: not sure how to ensure that symbol name is the
+          // name of a section.
+          return ((getName().charAt(0) == '.') &&
+                  (getStorageClass() == IMAGE_SYM_CLASS_STATIC));
+        }
+        public AuxSectionDefinitionsRecord getAuxSectionDefinitionsRecord() {
+          return (AuxSectionDefinitionsRecord) auxSectionDefinitionsRecord.getValue();
+        }
+      }
+
+      class AuxFunctionDefinitionRecordImpl implements AuxFunctionDefinitionRecord {
+        private int tagIndex;
+        private int totalSize;
+        private int pointerToLineNumber;
+        private int pointerToNextFunction;
+
+        AuxFunctionDefinitionRecordImpl(int offset) {
+          seek(offset);
+          tagIndex              = readInt();
+          totalSize             = readInt();
+          // NOTE zero-basing of this index
+          pointerToLineNumber   = readInt() - 1;
+          pointerToNextFunction = readInt();
+        }
+
+        public int getTagIndex()              { return tagIndex; }
+        public int getTotalSize()             { return totalSize; }
+        public int getPointerToLineNumber()   { return pointerToLineNumber; }
+        public int getPointerToNextFunction() { return pointerToNextFunction; }
+        public int getType()                  { return FUNCTION_DEFINITION; }
+      }
+
+      class AuxBfEfRecordImpl implements AuxBfEfRecord {
+        private short lineNumber;
+        private int   pointerToNextFunction;
+
+        AuxBfEfRecordImpl(int offset) {
+          seek(offset);
+          readInt();
+          lineNumber = readShort();
+          readInt();
+          readShort();
+          pointerToNextFunction = readInt();
+        }
+
+        public short getLineNumber()          { return lineNumber; }
+        public int getPointerToNextFunction() { return pointerToNextFunction; }
+        public int getType()                  { return BF_EF_RECORD; }
+      }
+
+      class AuxWeakExternalRecordImpl implements AuxWeakExternalRecord {
+        private int tagIndex;
+        private int characteristics;
+
+        AuxWeakExternalRecordImpl(int offset) {
+          seek(offset);
+          tagIndex = readInt();
+          characteristics = readInt();
+        }
+
+        public int getTagIndex()        { return tagIndex; }
+        public int getCharacteristics() { return characteristics; }
+        public int getType()            { return WEAK_EXTERNAL; }
+      }
+
+      class AuxFileRecordImpl implements AuxFileRecord {
+        private String name;
+
+        AuxFileRecordImpl(int offset) {
+          seek(offset);
+          byte[] tmpName = new byte[18];
+          int numRead = readBytes(tmpName);
+          if (numRead != 18) {
+            throw new COFFException("Error reading auxiliary file record at offset " + offset);
+          }
+          try {
+            name = new String(tmpName, US_ASCII);
+          } catch (UnsupportedEncodingException e) {
+            throw new COFFException(e);
+          }
+        }
+
+        public String getName() { return name; }
+        public int getType()    { return FILE; }
+      }
+
+      class AuxSectionDefinitionsRecordImpl implements AuxSectionDefinitionsRecord {
+        private int length;
+        private short numberOfRelocations;
+        private short numberOfLineNumbers;
+        private int checkSum;
+        private short number;
+        private byte selection;
+
+        AuxSectionDefinitionsRecordImpl(int offset) {
+          seek(offset);
+          length = readInt();
+          numberOfRelocations = readShort();
+          numberOfLineNumbers = readShort();
+          checkSum = readInt();
+          number = readShort();
+          selection = readByte();
+        }
+
+        public int   getLength()              { return length; }
+        public short getNumberOfRelocations() { return numberOfRelocations; }
+        public short getNumberOfLineNumbers() { return numberOfLineNumbers; }
+        public int   getCheckSum()            { return checkSum; }
+        public short getNumber()              { return number; }
+        public byte  getSelection()           { return selection; }
+        public int getType()                  { return SECTION_DEFINITION; }
+      }
+
+      class COFFRelocationImpl implements COFFRelocation {
+        private int virtualAddress;
+        private int symbolTableIndex;
+        private short type;
+
+        COFFRelocationImpl(int offset) {
+          seek(offset);
+          virtualAddress   = readInt();
+          symbolTableIndex = readInt();
+          type             = readShort();
+        }
+
+        public int   getVirtualAddress()     { return virtualAddress; }
+        public int   getSymbolTableIndex()   { return symbolTableIndex; }
+        public short getType()               { return type; }
+      }
+
+      class COFFLineNumberImpl implements COFFLineNumber {
+        private int   type;
+        private short lineNumber;
+
+        COFFLineNumberImpl(int offset) {
+          seek(offset);
+          type       = readInt();
+          lineNumber = readShort();
+        }
+
+        public int getType() {
+          return type;
+        }
+
+        public short getLineNumber() {
+          return lineNumber;
+        }
+      }
+
+      class StringTable {
+        class COFFString {
+          String str;
+          int    offset;
+
+          COFFString(String str, int offset) {
+            this.str = str; this.offset = offset;
+          }
+        }
+
+        COFFString[] strings;
+
+        StringTable(int offset) {
+          if (offset == 0) {
+            strings = new COFFString[0];
+            return;
+          }
+
+          seek(offset);
+          int length = readInt();
+          byte[] data = new byte[length - 4];
+          int numBytesRead = readBytes(data);
+          if (numBytesRead != data.length) {
+            throw new COFFException("Error reading string table (read " +
+                                    numBytesRead + " bytes, expected to read " + data.length + ")");
+          }
+          int numStrings = 0;
+          int ptr = 0;
+          for (ptr = 0; ptr < data.length; ptr++) {
+            if (data[ptr] == 0) {
+              numStrings++;
+            }
+          }
+          strings = new COFFString[numStrings];
+          int lastPtr = 0;
+          ptr = 0;
+          for (int i = 0; i < numStrings; i++) {
+            while (data[ptr] != 0) {
+              ptr++;
+            }
+            try {
+              strings[i] = new COFFString(new String(data, lastPtr, ptr - lastPtr, US_ASCII),
+                                          offset + ptr + 4);
+            } catch (UnsupportedEncodingException e) {
+              throw new COFFException(e);
+            }
+            ptr++;
+            lastPtr = ptr;
+          }
+        }
+
+        int getNum() {
+          return strings.length;
+        }
+
+        String get(int i) {
+          return strings[i].str;
+        }
+
+        /** This version takes an absolute offset in the file */
+        String getAtOffset(int offset) {
+          int i = Arrays.binarySearch(strings, new COFFString(null, offset),
+                                      new Comparator() {
+                                          public int compare(Object o1, Object o2) {
+                                            COFFString s1 = (COFFString) o1;
+                                            COFFString s2 = (COFFString) o2;
+                                            if (s1.offset == s2.offset) {
+                                              return 0;
+                                            } else if (s1.offset < s2.offset) {
+                                              return -1;
+                                            } else {
+                                              return 1;
+                                            }
+                                          }
+                                        });
+          if (i < 0) {
+            throw new COFFException("No string found at file offset " + offset);
+          }
+          return strings[i].str;
+        }
+      }
+    }
+
+    void initialize() throws COFFException {
+      // Figure out whether this file is an object file or an image
+      // (either executable or DLL).
+      seek(0x3c); // Error here probably indicates file format error
+      try {
+        int peOffset = readInt();
+        seek(peOffset);
+        if ((readByte() == (byte) 'P') &&
+            (readByte() == (byte) 'E') &&
+            (readByte() == (byte) 0) &&
+            (readByte() == (byte) 0)) {
+          isImage = true;
+          imageHeaderOffset = getFilePointer();
+        }
+      }
+      catch (COFFException e) {
+        // Expect failures here if not image file.
+      }
+    }
+
+    byte readByteAt(long offset) throws COFFException {
+      seek(offset);
+      return readByte();
+    }
+
+    byte readByte() throws COFFException {
+      try {
+        return file.readByte();
+      } catch (IOException e) {
+        throw new COFFException(e.toString() + " at offset 0x" +
+                                Long.toHexString(filePos), e);
+      }
+    }
+
+    int readBytesAt(long offset, byte[] b) throws COFFException {
+      seek(offset);
+      return readBytes(b);
+    }
+
+    int readBytes(byte[] b) throws COFFException {
+      try {
+        return file.read(b);
+      } catch (IOException e) {
+        throw new COFFException(e.toString() + " at offset 0x" +
+                                Long.toHexString(filePos), e);
+      }
+    }
+
+    /** NOTE: reads little-endian short */
+    short readShortAt(long offset) throws COFFException {
+      seek(offset);
+      return readShort();
+    }
+
+    /** NOTE: reads little-endian short */
+    short readShort() throws COFFException {
+      try {
+        return byteSwap(file.readShort());
+      } catch (IOException e) {
+        throw new COFFException(e.toString() + " at offset 0x" +
+                                Long.toHexString(filePos), e);
+      }
+    }
+
+    /** NOTE: reads little-endian int */
+    int readIntAt(long offset) throws COFFException {
+      seek(offset);
+      return readInt();
+    }
+
+    /** NOTE: reads little-endian int */
+    int readInt() throws COFFException {
+      try {
+        return byteSwap(file.readInt());
+      } catch (IOException e) {
+        throw new COFFException(e.toString() + " at offset 0x" +
+                                Long.toHexString(filePos), e);
+      }
+    }
+
+    /** NOTE: reads little-endian long */
+    long readLongAt(long offset) throws COFFException {
+      seek(offset);
+      return readLong();
+    }
+
+    /** NOTE: reads little-endian long */
+    long readLong() throws COFFException {
+      try {
+        return byteSwap(file.readLong());
+      } catch (IOException e) {
+        throw new COFFException(e.toString() + " at offset 0x" +
+                                Long.toHexString(filePos), e);
+      }
+    }
+
+    /** NOTE: reads little-endian float */
+    float readFloat() throws COFFException {
+      int i = readInt();
+      return Float.intBitsToFloat(i);
+    }
+
+    /** NOTE: reads little-endian double */
+    double readDouble() throws COFFException {
+      long l = readLong();
+      return Double.longBitsToDouble(l);
+    }
+
+    String readCString() throws COFFException {
+      List data = new ArrayList();
+      byte b = 0;
+      while ((b = readByte()) != 0) {
+        data.add(new Byte(b));
+      }
+      byte[] bytes = new byte[data.size()];
+      for (int i = 0; i < data.size(); i++) {
+        bytes[i] = ((Byte) data.get(i)).byteValue();
+      }
+      try {
+        return new String(bytes, US_ASCII);
+      } catch (UnsupportedEncodingException e) {
+        throw new COFFException(e);
+      }
+    }
+
+    void seek(long offset) throws COFFException {
+      try {
+        filePos = offset;
+        file.seek(offset);
+      } catch (IOException e) {
+        throw new COFFException(e.toString() + " at offset 0x" +
+                                Long.toHexString(offset), e);
+      }
+    }
+
+    long getFilePointer() throws COFFException {
+      try {
+        return file.getFilePointer();
+      } catch (IOException e) {
+        throw new COFFException(e);
+      }
+    }
+
+    short byteSwap(short arg) {
+      return (short) ((arg << 8) | ((arg >>> 8) & 0xFF));
+    }
+
+    int byteSwap(int arg) {
+      return (((int) byteSwap((short) arg)) << 16) | (((int) (byteSwap((short) (arg >>> 16)))) & 0xFFFF);
+    }
+
+    long byteSwap(long arg) {
+      return ((((long) byteSwap((int) arg)) << 32) | (((long) byteSwap((int) (arg >>> 32))) & 0xFFFFFFFF));
+    }
+
+    public void close() throws COFFException {
+      try {
+        file.close();
+      } catch (IOException e) {
+        throw new COFFException(e);
+      }
+    }
+  }
+}