alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (COFFFileParser.java)

This example Java source code file (COFFFileParser.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

coffexception, coffstring, datadirectory, debugvc50subsectionimpl, debugvc50symboliteratorimpl, debugvc50typeiteratorimpl, debugvc50wrongnumerictypeexception, header_size, ioexception, memoizedobject, nio, object, string, symbol_size, us_ascii, util

The COFFFileParser.java Java example source code

/*
 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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;

      // Init stringTable at decl time since other fields init'ed in the
      // constructor need the String Table.
      private MemoizedObject stringTable = new MemoizedObject() {
          public Object computeValue() {
            // the String Table follows the Symbol Table
            int ptr = getPointerToSymbolTable();
            if (ptr == 0) {
              // no Symbol Table so no String Table
              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();
        // String Table can be accessed at this point because
        // pointerToSymbolTable and numberOfSymbols fields are set.
        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;

        // We use an offset of 2 because OptionalHeaderStandardFieldsImpl doesn't
        // include the 'magic' field.
        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;  // only set in PE32

        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) {
            // only available in PE32
            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;
                }
                // ExportDirectoryTableImpl needs both the RVA and the
                // RVA converted to a file offset.
                return new
                    ExportDirectoryTableImpl(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 exportDataDirRVA;
        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 exportDataDirRVA, int size) {
          this.exportDataDirRVA = exportDataDirRVA;
          offset = rvaToFileOffset(exportDataDirRVA);
          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() {
                // number of ordinals is same as the number of name pointers
                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()));
                // The Export Address Table values are a union of two
                // possible values:
                //   Export RVA - The address of the exported symbol when
                //       loaded into memory, relative to the image base.
                //       This value doesn't get converted into a file offset.
                //   Forwarder RVA - The pointer to a null-terminated ASCII
                //       string in the export section. This value gets
                //       converted into a file offset because we have to
                //       fetch the string.
                for (int i = 0; i < addresses.length; i++) {
                  addresses[i] = readInt();
                }
                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 ((exportDataDirRVA <= addr) &&
              (addr < (exportDataDirRVA + size)));
        }

        public String getExportAddressForwarder(short ordinal) {
          seek(rvaToFileOffset(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
            // FIXME: this index value is assumed to be in the valid range
            name = getStringTable().get(index);
          } else {
            try {
              int length = 0;
              // find last non-NULL
              for (; length < tmpName.length && tmpName[length] != '\0';) {
                length++;
              }
              // don't include NULL chars in returned name String
              name = new String(tmpName, 0, length, 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]);
            // FIXME: stringOffset is assumed to be in the valid range
            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) {
            // no String Table
            strings = new COFFString[0];
            return;
          }

          seek(offset);
          int length = readInt();  // length includes itself
          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);
      }
    }
  }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java COFFFileParser.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.