github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/debug/pe/file.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6  Package pe implements access to PE (Microsoft Windows Portable Executable) files.
     7  
     8  # Security
     9  
    10  This package is not designed to be hardened against adversarial inputs, and is
    11  outside the scope of https://go.dev/security/policy. In particular, only basic
    12  validation is done when parsing object files. As such, care should be taken when
    13  parsing untrusted inputs, as parsing malformed files may consume significant
    14  resources, or cause panics.
    15  */
    16  package pe
    17  
    18  import (
    19  	"bytes"
    20  	"compress/zlib"
    21  	"debug/dwarf"
    22  	"encoding/binary"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"strings"
    28  )
    29  
    30  // Avoid use of post-Go 1.4 io features, to make safe for toolchain bootstrap.
    31  const seekStart = 0
    32  
    33  // A File represents an open PE file.
    34  type File struct {
    35  	FileHeader
    36  	OptionalHeader any // of type *OptionalHeader32 or *OptionalHeader64
    37  	Sections       []*Section
    38  	Symbols        []*Symbol    // COFF symbols with auxiliary symbol records removed
    39  	COFFSymbols    []COFFSymbol // all COFF symbols (including auxiliary symbol records)
    40  	StringTable    StringTable
    41  
    42  	closer io.Closer
    43  }
    44  
    45  // Open opens the named file using os.Open and prepares it for use as a PE binary.
    46  func Open(name string) (*File, error) {
    47  	f, err := os.Open(name)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	ff, err := NewFile(f)
    52  	if err != nil {
    53  		f.Close()
    54  		return nil, err
    55  	}
    56  	ff.closer = f
    57  	return ff, nil
    58  }
    59  
    60  // Close closes the File.
    61  // If the File was created using NewFile directly instead of Open,
    62  // Close has no effect.
    63  func (f *File) Close() error {
    64  	var err error
    65  	if f.closer != nil {
    66  		err = f.closer.Close()
    67  		f.closer = nil
    68  	}
    69  	return err
    70  }
    71  
    72  // TODO(brainman): add Load function, as a replacement for NewFile, that does not call removeAuxSymbols (for performance)
    73  
    74  // NewFile creates a new File for accessing a PE binary in an underlying reader.
    75  func NewFile(r io.ReaderAt) (*File, error) {
    76  	f := new(File)
    77  	sr := io.NewSectionReader(r, 0, 1<<63-1)
    78  
    79  	var dosheader [96]byte
    80  	if _, err := r.ReadAt(dosheader[0:], 0); err != nil {
    81  		return nil, err
    82  	}
    83  	var base int64
    84  	if dosheader[0] == 'M' && dosheader[1] == 'Z' {
    85  		signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:]))
    86  		var sign [4]byte
    87  		r.ReadAt(sign[:], signoff)
    88  		if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) {
    89  			return nil, fmt.Errorf("invalid PE file signature: % x", sign)
    90  		}
    91  		base = signoff + 4
    92  	} else {
    93  		base = int64(0)
    94  	}
    95  	sr.Seek(base, seekStart)
    96  	if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
    97  		return nil, err
    98  	}
    99  	switch f.FileHeader.Machine {
   100  	case IMAGE_FILE_MACHINE_AMD64,
   101  		IMAGE_FILE_MACHINE_ARM64,
   102  		IMAGE_FILE_MACHINE_ARMNT,
   103  		IMAGE_FILE_MACHINE_I386,
   104  		IMAGE_FILE_MACHINE_RISCV32,
   105  		IMAGE_FILE_MACHINE_RISCV64,
   106  		IMAGE_FILE_MACHINE_RISCV128,
   107  		IMAGE_FILE_MACHINE_UNKNOWN:
   108  		// ok
   109  	default:
   110  		return nil, fmt.Errorf("unrecognized PE machine: %#x", f.FileHeader.Machine)
   111  	}
   112  
   113  	var err error
   114  
   115  	// Read string table.
   116  	f.StringTable, err = readStringTable(&f.FileHeader, sr)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	// Read symbol table.
   122  	f.COFFSymbols, err = readCOFFSymbols(&f.FileHeader, sr)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	f.Symbols, err = removeAuxSymbols(f.COFFSymbols, f.StringTable)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	// Seek past file header.
   132  	_, err = sr.Seek(base+int64(binary.Size(f.FileHeader)), seekStart)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	// Read optional header.
   138  	f.OptionalHeader, err = readOptionalHeader(sr, f.FileHeader.SizeOfOptionalHeader)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	// Process sections.
   144  	f.Sections = make([]*Section, f.FileHeader.NumberOfSections)
   145  	for i := 0; i < int(f.FileHeader.NumberOfSections); i++ {
   146  		sh := new(SectionHeader32)
   147  		if err := binary.Read(sr, binary.LittleEndian, sh); err != nil {
   148  			return nil, err
   149  		}
   150  		name, err := sh.fullName(f.StringTable)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		s := new(Section)
   155  		s.SectionHeader = SectionHeader{
   156  			Name:                 name,
   157  			VirtualSize:          sh.VirtualSize,
   158  			VirtualAddress:       sh.VirtualAddress,
   159  			Size:                 sh.SizeOfRawData,
   160  			Offset:               sh.PointerToRawData,
   161  			PointerToRelocations: sh.PointerToRelocations,
   162  			PointerToLineNumbers: sh.PointerToLineNumbers,
   163  			NumberOfRelocations:  sh.NumberOfRelocations,
   164  			NumberOfLineNumbers:  sh.NumberOfLineNumbers,
   165  			Characteristics:      sh.Characteristics,
   166  		}
   167  		r2 := r
   168  		if sh.PointerToRawData == 0 { // .bss must have all 0s
   169  			r2 = &nobitsSectionReader{}
   170  		}
   171  		s.sr = io.NewSectionReader(r2, int64(s.SectionHeader.Offset), int64(s.SectionHeader.Size))
   172  		s.ReaderAt = s.sr
   173  		f.Sections[i] = s
   174  	}
   175  	for i := range f.Sections {
   176  		var err error
   177  		f.Sections[i].Relocs, err = readRelocs(&f.Sections[i].SectionHeader, sr)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  	}
   182  
   183  	return f, nil
   184  }
   185  
   186  type nobitsSectionReader struct{}
   187  
   188  func (*nobitsSectionReader) ReadAt(p []byte, off int64) (n int, err error) {
   189  	return 0, errors.New("unexpected read from section with uninitialized data")
   190  }
   191  
   192  // getString extracts a string from symbol string table.
   193  func getString(section []byte, start int) (string, bool) {
   194  	if start < 0 || start >= len(section) {
   195  		return "", false
   196  	}
   197  
   198  	for end := start; end < len(section); end++ {
   199  		if section[end] == 0 {
   200  			return string(section[start:end]), true
   201  		}
   202  	}
   203  	return "", false
   204  }
   205  
   206  // Section returns the first section with the given name, or nil if no such
   207  // section exists.
   208  func (f *File) Section(name string) *Section {
   209  	for _, s := range f.Sections {
   210  		if s.Name == name {
   211  			return s
   212  		}
   213  	}
   214  	return nil
   215  }
   216  
   217  func (f *File) DWARF() (*dwarf.Data, error) {
   218  	dwarfSuffix := func(s *Section) string {
   219  		switch {
   220  		case strings.HasPrefix(s.Name, ".debug_"):
   221  			return s.Name[7:]
   222  		case strings.HasPrefix(s.Name, ".zdebug_"):
   223  			return s.Name[8:]
   224  		default:
   225  			return ""
   226  		}
   227  
   228  	}
   229  
   230  	// sectionData gets the data for s and checks its size.
   231  	sectionData := func(s *Section) ([]byte, error) {
   232  		b, err := s.Data()
   233  		if err != nil && uint32(len(b)) < s.Size {
   234  			return nil, err
   235  		}
   236  
   237  		if 0 < s.VirtualSize && s.VirtualSize < s.Size {
   238  			b = b[:s.VirtualSize]
   239  		}
   240  
   241  		if len(b) >= 12 && string(b[:4]) == "ZLIB" {
   242  			dlen := binary.BigEndian.Uint64(b[4:12])
   243  			dbuf := make([]byte, dlen)
   244  			r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
   245  			if err != nil {
   246  				return nil, err
   247  			}
   248  			if _, err := io.ReadFull(r, dbuf); err != nil {
   249  				return nil, err
   250  			}
   251  			if err := r.Close(); err != nil {
   252  				return nil, err
   253  			}
   254  			b = dbuf
   255  		}
   256  		return b, nil
   257  	}
   258  
   259  	// There are many other DWARF sections, but these
   260  	// are the ones the debug/dwarf package uses.
   261  	// Don't bother loading others.
   262  	var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
   263  	for _, s := range f.Sections {
   264  		suffix := dwarfSuffix(s)
   265  		if suffix == "" {
   266  			continue
   267  		}
   268  		if _, ok := dat[suffix]; !ok {
   269  			continue
   270  		}
   271  
   272  		b, err := sectionData(s)
   273  		if err != nil {
   274  			return nil, err
   275  		}
   276  		dat[suffix] = b
   277  	}
   278  
   279  	d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	// Look for DWARF4 .debug_types sections and DWARF5 sections.
   285  	for i, s := range f.Sections {
   286  		suffix := dwarfSuffix(s)
   287  		if suffix == "" {
   288  			continue
   289  		}
   290  		if _, ok := dat[suffix]; ok {
   291  			// Already handled.
   292  			continue
   293  		}
   294  
   295  		b, err := sectionData(s)
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  
   300  		if suffix == "types" {
   301  			err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
   302  		} else {
   303  			err = d.AddSection(".debug_"+suffix, b)
   304  		}
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  	}
   309  
   310  	return d, nil
   311  }
   312  
   313  // TODO(brainman): document ImportDirectory once we decide what to do with it.
   314  
   315  type ImportDirectory struct {
   316  	OriginalFirstThunk uint32
   317  	TimeDateStamp      uint32
   318  	ForwarderChain     uint32
   319  	Name               uint32
   320  	FirstThunk         uint32
   321  
   322  	dll string
   323  }
   324  
   325  // ImportedSymbols returns the names of all symbols
   326  // referred to by the binary f that are expected to be
   327  // satisfied by other libraries at dynamic load time.
   328  // It does not return weak symbols.
   329  func (f *File) ImportedSymbols() ([]string, error) {
   330  	if f.OptionalHeader == nil {
   331  		return nil, nil
   332  	}
   333  
   334  	_, pe64 := f.OptionalHeader.(*OptionalHeader64)
   335  
   336  	// grab the number of data directory entries
   337  	var dd_length uint32
   338  	if pe64 {
   339  		dd_length = f.OptionalHeader.(*OptionalHeader64).NumberOfRvaAndSizes
   340  	} else {
   341  		dd_length = f.OptionalHeader.(*OptionalHeader32).NumberOfRvaAndSizes
   342  	}
   343  
   344  	// check that the length of data directory entries is large
   345  	// enough to include the imports directory.
   346  	if dd_length < IMAGE_DIRECTORY_ENTRY_IMPORT+1 {
   347  		return nil, nil
   348  	}
   349  
   350  	// grab the import data directory entry
   351  	var idd DataDirectory
   352  	if pe64 {
   353  		idd = f.OptionalHeader.(*OptionalHeader64).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
   354  	} else {
   355  		idd = f.OptionalHeader.(*OptionalHeader32).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
   356  	}
   357  
   358  	// figure out which section contains the import directory table
   359  	var ds *Section
   360  	ds = nil
   361  	for _, s := range f.Sections {
   362  		if s.Offset == 0 {
   363  			continue
   364  		}
   365  		// We are using distance between s.VirtualAddress and idd.VirtualAddress
   366  		// to avoid potential overflow of uint32 caused by addition of s.VirtualSize
   367  		// to s.VirtualAddress.
   368  		if s.VirtualAddress <= idd.VirtualAddress && idd.VirtualAddress-s.VirtualAddress < s.VirtualSize {
   369  			ds = s
   370  			break
   371  		}
   372  	}
   373  
   374  	// didn't find a section, so no import libraries were found
   375  	if ds == nil {
   376  		return nil, nil
   377  	}
   378  
   379  	d, err := ds.Data()
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  
   384  	// seek to the virtual address specified in the import data directory
   385  	d = d[idd.VirtualAddress-ds.VirtualAddress:]
   386  
   387  	// start decoding the import directory
   388  	var ida []ImportDirectory
   389  	for len(d) >= 20 {
   390  		var dt ImportDirectory
   391  		dt.OriginalFirstThunk = binary.LittleEndian.Uint32(d[0:4])
   392  		dt.TimeDateStamp = binary.LittleEndian.Uint32(d[4:8])
   393  		dt.ForwarderChain = binary.LittleEndian.Uint32(d[8:12])
   394  		dt.Name = binary.LittleEndian.Uint32(d[12:16])
   395  		dt.FirstThunk = binary.LittleEndian.Uint32(d[16:20])
   396  		d = d[20:]
   397  		if dt.OriginalFirstThunk == 0 {
   398  			break
   399  		}
   400  		ida = append(ida, dt)
   401  	}
   402  	// TODO(brainman): this needs to be rewritten
   403  	//  ds.Data() returns contents of section containing import table. Why store in variable called "names"?
   404  	//  Why we are retrieving it second time? We already have it in "d", and it is not modified anywhere.
   405  	//  getString does not extracts a string from symbol string table (as getString doco says).
   406  	//  Why ds.Data() called again and again in the loop?
   407  	//  Needs test before rewrite.
   408  	names, _ := ds.Data()
   409  	var all []string
   410  	for _, dt := range ida {
   411  		dt.dll, _ = getString(names, int(dt.Name-ds.VirtualAddress))
   412  		d, _ = ds.Data()
   413  		// seek to OriginalFirstThunk
   414  		d = d[dt.OriginalFirstThunk-ds.VirtualAddress:]
   415  		for len(d) > 0 {
   416  			if pe64 { // 64bit
   417  				va := binary.LittleEndian.Uint64(d[0:8])
   418  				d = d[8:]
   419  				if va == 0 {
   420  					break
   421  				}
   422  				if va&0x8000000000000000 > 0 { // is Ordinal
   423  					// TODO add dynimport ordinal support.
   424  				} else {
   425  					fn, _ := getString(names, int(uint32(va)-ds.VirtualAddress+2))
   426  					all = append(all, fn+":"+dt.dll)
   427  				}
   428  			} else { // 32bit
   429  				va := binary.LittleEndian.Uint32(d[0:4])
   430  				d = d[4:]
   431  				if va == 0 {
   432  					break
   433  				}
   434  				if va&0x80000000 > 0 { // is Ordinal
   435  					// TODO add dynimport ordinal support.
   436  					//ord := va&0x0000FFFF
   437  				} else {
   438  					fn, _ := getString(names, int(va-ds.VirtualAddress+2))
   439  					all = append(all, fn+":"+dt.dll)
   440  				}
   441  			}
   442  		}
   443  	}
   444  
   445  	return all, nil
   446  }
   447  
   448  // ImportedLibraries returns the names of all libraries
   449  // referred to by the binary f that are expected to be
   450  // linked with the binary at dynamic link time.
   451  func (f *File) ImportedLibraries() ([]string, error) {
   452  	// TODO
   453  	// cgo -dynimport don't use this for windows PE, so just return.
   454  	return nil, nil
   455  }
   456  
   457  // FormatError is unused.
   458  // The type is retained for compatibility.
   459  type FormatError struct {
   460  }
   461  
   462  func (e *FormatError) Error() string {
   463  	return "unknown error"
   464  }
   465  
   466  // readOptionalHeader accepts an io.ReadSeeker pointing to optional header in the PE file
   467  // and its size as seen in the file header.
   468  // It parses the given size of bytes and returns optional header. It infers whether the
   469  // bytes being parsed refer to 32 bit or 64 bit version of optional header.
   470  func readOptionalHeader(r io.ReadSeeker, sz uint16) (any, error) {
   471  	// If optional header size is 0, return empty optional header.
   472  	if sz == 0 {
   473  		return nil, nil
   474  	}
   475  
   476  	var (
   477  		// First couple of bytes in option header state its type.
   478  		// We need to read them first to determine the type and
   479  		// validity of optional header.
   480  		ohMagic   uint16
   481  		ohMagicSz = binary.Size(ohMagic)
   482  	)
   483  
   484  	// If optional header size is greater than 0 but less than its magic size, return error.
   485  	if sz < uint16(ohMagicSz) {
   486  		return nil, fmt.Errorf("optional header size is less than optional header magic size")
   487  	}
   488  
   489  	// read reads from io.ReadSeeke, r, into data.
   490  	var err error
   491  	read := func(data any) bool {
   492  		err = binary.Read(r, binary.LittleEndian, data)
   493  		return err == nil
   494  	}
   495  
   496  	if !read(&ohMagic) {
   497  		return nil, fmt.Errorf("failure to read optional header magic: %v", err)
   498  
   499  	}
   500  
   501  	switch ohMagic {
   502  	case 0x10b: // PE32
   503  		var (
   504  			oh32 OptionalHeader32
   505  			// There can be 0 or more data directories. So the minimum size of optional
   506  			// header is calculated by subtracting oh32.DataDirectory size from oh32 size.
   507  			oh32MinSz = binary.Size(oh32) - binary.Size(oh32.DataDirectory)
   508  		)
   509  
   510  		if sz < uint16(oh32MinSz) {
   511  			return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) of PE32 optional header", sz, oh32MinSz)
   512  		}
   513  
   514  		// Init oh32 fields
   515  		oh32.Magic = ohMagic
   516  		if !read(&oh32.MajorLinkerVersion) ||
   517  			!read(&oh32.MinorLinkerVersion) ||
   518  			!read(&oh32.SizeOfCode) ||
   519  			!read(&oh32.SizeOfInitializedData) ||
   520  			!read(&oh32.SizeOfUninitializedData) ||
   521  			!read(&oh32.AddressOfEntryPoint) ||
   522  			!read(&oh32.BaseOfCode) ||
   523  			!read(&oh32.BaseOfData) ||
   524  			!read(&oh32.ImageBase) ||
   525  			!read(&oh32.SectionAlignment) ||
   526  			!read(&oh32.FileAlignment) ||
   527  			!read(&oh32.MajorOperatingSystemVersion) ||
   528  			!read(&oh32.MinorOperatingSystemVersion) ||
   529  			!read(&oh32.MajorImageVersion) ||
   530  			!read(&oh32.MinorImageVersion) ||
   531  			!read(&oh32.MajorSubsystemVersion) ||
   532  			!read(&oh32.MinorSubsystemVersion) ||
   533  			!read(&oh32.Win32VersionValue) ||
   534  			!read(&oh32.SizeOfImage) ||
   535  			!read(&oh32.SizeOfHeaders) ||
   536  			!read(&oh32.CheckSum) ||
   537  			!read(&oh32.Subsystem) ||
   538  			!read(&oh32.DllCharacteristics) ||
   539  			!read(&oh32.SizeOfStackReserve) ||
   540  			!read(&oh32.SizeOfStackCommit) ||
   541  			!read(&oh32.SizeOfHeapReserve) ||
   542  			!read(&oh32.SizeOfHeapCommit) ||
   543  			!read(&oh32.LoaderFlags) ||
   544  			!read(&oh32.NumberOfRvaAndSizes) {
   545  			return nil, fmt.Errorf("failure to read PE32 optional header: %v", err)
   546  		}
   547  
   548  		dd, err := readDataDirectories(r, sz-uint16(oh32MinSz), oh32.NumberOfRvaAndSizes)
   549  		if err != nil {
   550  			return nil, err
   551  		}
   552  
   553  		copy(oh32.DataDirectory[:], dd)
   554  
   555  		return &oh32, nil
   556  	case 0x20b: // PE32+
   557  		var (
   558  			oh64 OptionalHeader64
   559  			// There can be 0 or more data directories. So the minimum size of optional
   560  			// header is calculated by subtracting oh64.DataDirectory size from oh64 size.
   561  			oh64MinSz = binary.Size(oh64) - binary.Size(oh64.DataDirectory)
   562  		)
   563  
   564  		if sz < uint16(oh64MinSz) {
   565  			return nil, fmt.Errorf("optional header size(%d) is less minimum size (%d) for PE32+ optional header", sz, oh64MinSz)
   566  		}
   567  
   568  		// Init oh64 fields
   569  		oh64.Magic = ohMagic
   570  		if !read(&oh64.MajorLinkerVersion) ||
   571  			!read(&oh64.MinorLinkerVersion) ||
   572  			!read(&oh64.SizeOfCode) ||
   573  			!read(&oh64.SizeOfInitializedData) ||
   574  			!read(&oh64.SizeOfUninitializedData) ||
   575  			!read(&oh64.AddressOfEntryPoint) ||
   576  			!read(&oh64.BaseOfCode) ||
   577  			!read(&oh64.ImageBase) ||
   578  			!read(&oh64.SectionAlignment) ||
   579  			!read(&oh64.FileAlignment) ||
   580  			!read(&oh64.MajorOperatingSystemVersion) ||
   581  			!read(&oh64.MinorOperatingSystemVersion) ||
   582  			!read(&oh64.MajorImageVersion) ||
   583  			!read(&oh64.MinorImageVersion) ||
   584  			!read(&oh64.MajorSubsystemVersion) ||
   585  			!read(&oh64.MinorSubsystemVersion) ||
   586  			!read(&oh64.Win32VersionValue) ||
   587  			!read(&oh64.SizeOfImage) ||
   588  			!read(&oh64.SizeOfHeaders) ||
   589  			!read(&oh64.CheckSum) ||
   590  			!read(&oh64.Subsystem) ||
   591  			!read(&oh64.DllCharacteristics) ||
   592  			!read(&oh64.SizeOfStackReserve) ||
   593  			!read(&oh64.SizeOfStackCommit) ||
   594  			!read(&oh64.SizeOfHeapReserve) ||
   595  			!read(&oh64.SizeOfHeapCommit) ||
   596  			!read(&oh64.LoaderFlags) ||
   597  			!read(&oh64.NumberOfRvaAndSizes) {
   598  			return nil, fmt.Errorf("failure to read PE32+ optional header: %v", err)
   599  		}
   600  
   601  		dd, err := readDataDirectories(r, sz-uint16(oh64MinSz), oh64.NumberOfRvaAndSizes)
   602  		if err != nil {
   603  			return nil, err
   604  		}
   605  
   606  		copy(oh64.DataDirectory[:], dd)
   607  
   608  		return &oh64, nil
   609  	default:
   610  		return nil, fmt.Errorf("optional header has unexpected Magic of 0x%x", ohMagic)
   611  	}
   612  }
   613  
   614  // readDataDirectories accepts an io.ReadSeeker pointing to data directories in the PE file,
   615  // its size and number of data directories as seen in optional header.
   616  // It parses the given size of bytes and returns given number of data directories.
   617  func readDataDirectories(r io.ReadSeeker, sz uint16, n uint32) ([]DataDirectory, error) {
   618  	ddSz := uint64(binary.Size(DataDirectory{}))
   619  	if uint64(sz) != uint64(n)*ddSz {
   620  		return nil, fmt.Errorf("size of data directories(%d) is inconsistent with number of data directories(%d)", sz, n)
   621  	}
   622  
   623  	dd := make([]DataDirectory, n)
   624  	if err := binary.Read(r, binary.LittleEndian, dd); err != nil {
   625  		return nil, fmt.Errorf("failure to read data directories: %v", err)
   626  	}
   627  
   628  	return dd, nil
   629  }