Source file src/debug/pe/file.go

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

View as plain text