/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

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;

import static java.nio.charset.StandardCharsets.US_ASCII;

/** 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 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 (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) {
          Objects.checkIndex(i, getNumEntries());
          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) {
          Objects.checkIndex(i, getSizeOfData());
          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);
                  return new String(res, US_ASCII);
                }
              };
          }

          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<String> data = new ArrayList<>();
                  while (i < size) {
                    String s = readCString();
                    data.add(s);
                    i += s.length();
                  }
                  String[] res = new String[data.size()];
                  res = 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);
          }
          return new String(res, US_ASCII);
        }

        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);
            }
            // Look up in string table
            // FIXME: this index value is assumed to be in the valid range
            name = getStringTable().get(index);
          } else {
            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);
          }
          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);
          }
          name = new String(tmpName, US_ASCII);
        }

        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++;
            }
            strings[i] = new COFFString(new String(data, lastPtr, ptr - lastPtr, US_ASCII),
                                        offset + ptr + 4);
            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(COFFString s1, COFFString s2) {
                                            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<Byte> data = new ArrayList<>();
      byte b = 0;
      while ((b = readByte()) != 0) {
        data.add(b);
      }
      byte[] bytes = new byte[data.size()];
      for (int i = 0; i < data.size(); i++) {
        bytes[i] = (data.get(i)).byteValue();
      }
      return new String(bytes, US_ASCII);
    }

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