From c9ed561db438ba413ba8cfac0c292a615bda45a8 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Tue, 12 Mar 2024 20:01:40 -0700 Subject: [PATCH] debug/elf: avoid using binary.Read() in NewFile() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this change my test program that reads a tree of ELF files runs 1.71 ± 0.12 times faster without parallelism or 1.39 ± 0.06 times faster using 8 goroutines. Change-Id: I443d1a02736f16f5532ef28e1447c97aa87c7126 Reviewed-on: https://go-review.googlesource.com/c/go/+/571436 Auto-Submit: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI Reviewed-by: Ian Lance Taylor --- src/debug/elf/file.go | 207 +++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 104 deletions(-) diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go index 7228447c21..6b4de0c56e 100644 --- a/src/debug/elf/file.go +++ b/src/debug/elf/file.go @@ -27,6 +27,7 @@ import ( "io" "os" "strings" + "unsafe" ) // TODO: error reporting detail @@ -296,14 +297,16 @@ func NewFile(r io.ReaderAt) (*File, error) { } f.Data = Data(ident[EI_DATA]) + var bo binary.ByteOrder switch f.Data { case ELFDATA2LSB: - f.ByteOrder = binary.LittleEndian + bo = binary.LittleEndian case ELFDATA2MSB: - f.ByteOrder = binary.BigEndian + bo = binary.BigEndian default: return nil, &FormatError{0, "unknown ELF data encoding", f.Data} } + f.ByteOrder = bo f.Version = Version(ident[EI_VERSION]) if f.Version != EV_CURRENT { @@ -320,43 +323,43 @@ func NewFile(r io.ReaderAt) (*File, error) { var shentsize, shnum, shstrndx int switch f.Class { case ELFCLASS32: - hdr := new(Header32) - sr.Seek(0, io.SeekStart) - if err := binary.Read(sr, f.ByteOrder, hdr); err != nil { + var hdr Header32 + data := make([]byte, unsafe.Sizeof(hdr)) + if _, err := sr.ReadAt(data, 0); err != nil { return nil, err } - f.Type = Type(hdr.Type) - f.Machine = Machine(hdr.Machine) - f.Entry = uint64(hdr.Entry) - if v := Version(hdr.Version); v != f.Version { + f.Type = Type(bo.Uint16(data[unsafe.Offsetof(hdr.Type):])) + f.Machine = Machine(bo.Uint16(data[unsafe.Offsetof(hdr.Machine):])) + f.Entry = uint64(bo.Uint32(data[unsafe.Offsetof(hdr.Entry):])) + if v := Version(bo.Uint32(data[unsafe.Offsetof(hdr.Version):])); v != f.Version { return nil, &FormatError{0, "mismatched ELF version", v} } - phoff = int64(hdr.Phoff) - phentsize = int(hdr.Phentsize) - phnum = int(hdr.Phnum) - shoff = int64(hdr.Shoff) - shentsize = int(hdr.Shentsize) - shnum = int(hdr.Shnum) - shstrndx = int(hdr.Shstrndx) + phoff = int64(bo.Uint32(data[unsafe.Offsetof(hdr.Phoff):])) + phentsize = int(bo.Uint16(data[unsafe.Offsetof(hdr.Phentsize):])) + phnum = int(bo.Uint16(data[unsafe.Offsetof(hdr.Phnum):])) + shoff = int64(bo.Uint32(data[unsafe.Offsetof(hdr.Shoff):])) + shentsize = int(bo.Uint16(data[unsafe.Offsetof(hdr.Shentsize):])) + shnum = int(bo.Uint16(data[unsafe.Offsetof(hdr.Shnum):])) + shstrndx = int(bo.Uint16(data[unsafe.Offsetof(hdr.Shstrndx):])) case ELFCLASS64: - hdr := new(Header64) - sr.Seek(0, io.SeekStart) - if err := binary.Read(sr, f.ByteOrder, hdr); err != nil { + var hdr Header64 + data := make([]byte, unsafe.Sizeof(hdr)) + if _, err := sr.ReadAt(data, 0); err != nil { return nil, err } - f.Type = Type(hdr.Type) - f.Machine = Machine(hdr.Machine) - f.Entry = hdr.Entry - if v := Version(hdr.Version); v != f.Version { + f.Type = Type(bo.Uint16(data[unsafe.Offsetof(hdr.Type):])) + f.Machine = Machine(bo.Uint16(data[unsafe.Offsetof(hdr.Machine):])) + f.Entry = bo.Uint64(data[unsafe.Offsetof(hdr.Entry):]) + if v := Version(bo.Uint32(data[unsafe.Offsetof(hdr.Version):])); v != f.Version { return nil, &FormatError{0, "mismatched ELF version", v} } - phoff = int64(hdr.Phoff) - phentsize = int(hdr.Phentsize) - phnum = int(hdr.Phnum) - shoff = int64(hdr.Shoff) - shentsize = int(hdr.Shentsize) - shnum = int(hdr.Shnum) - shstrndx = int(hdr.Shstrndx) + phoff = int64(bo.Uint64(data[unsafe.Offsetof(hdr.Phoff):])) + phentsize = int(bo.Uint16(data[unsafe.Offsetof(hdr.Phentsize):])) + phnum = int(bo.Uint16(data[unsafe.Offsetof(hdr.Phnum):])) + shoff = int64(bo.Uint64(data[unsafe.Offsetof(hdr.Shoff):])) + shentsize = int(bo.Uint16(data[unsafe.Offsetof(hdr.Shentsize):])) + shnum = int(bo.Uint16(data[unsafe.Offsetof(hdr.Shnum):])) + shstrndx = int(bo.Uint16(data[unsafe.Offsetof(hdr.Shstrndx):])) } if shoff < 0 { @@ -389,47 +392,44 @@ func NewFile(r io.ReaderAt) (*File, error) { // Read program headers f.Progs = make([]*Prog, phnum) + phdata, err := saferio.ReadDataAt(sr, uint64(phnum)*uint64(phentsize), phoff) + if err != nil { + return nil, err + } for i := 0; i < phnum; i++ { - off := phoff + int64(i)*int64(phentsize) - sr.Seek(off, io.SeekStart) + off := uintptr(i) * uintptr(phentsize) p := new(Prog) switch f.Class { case ELFCLASS32: - ph := new(Prog32) - if err := binary.Read(sr, f.ByteOrder, ph); err != nil { - return nil, err - } + var ph Prog32 p.ProgHeader = ProgHeader{ - Type: ProgType(ph.Type), - Flags: ProgFlag(ph.Flags), - Off: uint64(ph.Off), - Vaddr: uint64(ph.Vaddr), - Paddr: uint64(ph.Paddr), - Filesz: uint64(ph.Filesz), - Memsz: uint64(ph.Memsz), - Align: uint64(ph.Align), + Type: ProgType(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Type):])), + Flags: ProgFlag(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Flags):])), + Off: uint64(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Off):])), + Vaddr: uint64(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Vaddr):])), + Paddr: uint64(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Paddr):])), + Filesz: uint64(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Filesz):])), + Memsz: uint64(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Memsz):])), + Align: uint64(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Align):])), } case ELFCLASS64: - ph := new(Prog64) - if err := binary.Read(sr, f.ByteOrder, ph); err != nil { - return nil, err - } + var ph Prog64 p.ProgHeader = ProgHeader{ - Type: ProgType(ph.Type), - Flags: ProgFlag(ph.Flags), - Off: ph.Off, - Vaddr: ph.Vaddr, - Paddr: ph.Paddr, - Filesz: ph.Filesz, - Memsz: ph.Memsz, - Align: ph.Align, + Type: ProgType(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Type):])), + Flags: ProgFlag(bo.Uint32(phdata[off+unsafe.Offsetof(ph.Flags):])), + Off: bo.Uint64(phdata[off+unsafe.Offsetof(ph.Off):]), + Vaddr: bo.Uint64(phdata[off+unsafe.Offsetof(ph.Vaddr):]), + Paddr: bo.Uint64(phdata[off+unsafe.Offsetof(ph.Paddr):]), + Filesz: bo.Uint64(phdata[off+unsafe.Offsetof(ph.Filesz):]), + Memsz: bo.Uint64(phdata[off+unsafe.Offsetof(ph.Memsz):]), + Align: bo.Uint64(phdata[off+unsafe.Offsetof(ph.Align):]), } } if int64(p.Off) < 0 { - return nil, &FormatError{off, "invalid program header offset", p.Off} + return nil, &FormatError{phoff + int64(off), "invalid program header offset", p.Off} } if int64(p.Filesz) < 0 { - return nil, &FormatError{off, "invalid program header file size", p.Filesz} + return nil, &FormatError{phoff + int64(off), "invalid program header file size", p.Filesz} } p.sr = io.NewSectionReader(r, int64(p.Off), int64(p.Filesz)) p.ReaderAt = p.sr @@ -446,7 +446,7 @@ func NewFile(r io.ReaderAt) (*File, error) { switch f.Class { case ELFCLASS32: sh := new(Section32) - if err := binary.Read(sr, f.ByteOrder, sh); err != nil { + if err := binary.Read(sr, bo, sh); err != nil { return nil, err } shnum = int(sh.Size) @@ -454,7 +454,7 @@ func NewFile(r io.ReaderAt) (*File, error) { link = sh.Link case ELFCLASS64: sh := new(Section64) - if err := binary.Read(sr, f.ByteOrder, sh); err != nil { + if err := binary.Read(sr, bo, sh); err != nil { return nil, err } shnum = int(sh.Size) @@ -493,51 +493,48 @@ func NewFile(r io.ReaderAt) (*File, error) { } f.Sections = make([]*Section, 0, c) names := make([]uint32, 0, c) + shdata, err := saferio.ReadDataAt(sr, uint64(shnum)*uint64(shentsize), shoff) + if err != nil { + return nil, err + } for i := 0; i < shnum; i++ { - off := shoff + int64(i)*int64(shentsize) - sr.Seek(off, io.SeekStart) + off := uintptr(i) * uintptr(shentsize) s := new(Section) switch f.Class { case ELFCLASS32: - sh := new(Section32) - if err := binary.Read(sr, f.ByteOrder, sh); err != nil { - return nil, err - } - names = append(names, sh.Name) + var sh Section32 + names = append(names, bo.Uint32(shdata[off+unsafe.Offsetof(sh.Name):])) s.SectionHeader = SectionHeader{ - Type: SectionType(sh.Type), - Flags: SectionFlag(sh.Flags), - Addr: uint64(sh.Addr), - Offset: uint64(sh.Off), - FileSize: uint64(sh.Size), - Link: sh.Link, - Info: sh.Info, - Addralign: uint64(sh.Addralign), - Entsize: uint64(sh.Entsize), + Type: SectionType(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Type):])), + Flags: SectionFlag(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Flags):])), + Addr: uint64(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Addr):])), + Offset: uint64(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Off):])), + FileSize: uint64(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Size):])), + Link: bo.Uint32(shdata[off+unsafe.Offsetof(sh.Link):]), + Info: bo.Uint32(shdata[off+unsafe.Offsetof(sh.Info):]), + Addralign: uint64(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Addralign):])), + Entsize: uint64(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Entsize):])), } case ELFCLASS64: - sh := new(Section64) - if err := binary.Read(sr, f.ByteOrder, sh); err != nil { - return nil, err - } - names = append(names, sh.Name) + var sh Section64 + names = append(names, bo.Uint32(shdata[off+unsafe.Offsetof(sh.Name):])) s.SectionHeader = SectionHeader{ - Type: SectionType(sh.Type), - Flags: SectionFlag(sh.Flags), - Offset: sh.Off, - FileSize: sh.Size, - Addr: sh.Addr, - Link: sh.Link, - Info: sh.Info, - Addralign: sh.Addralign, - Entsize: sh.Entsize, + Type: SectionType(bo.Uint32(shdata[off+unsafe.Offsetof(sh.Type):])), + Flags: SectionFlag(bo.Uint64(shdata[off+unsafe.Offsetof(sh.Flags):])), + Offset: bo.Uint64(shdata[off+unsafe.Offsetof(sh.Off):]), + FileSize: bo.Uint64(shdata[off+unsafe.Offsetof(sh.Size):]), + Addr: bo.Uint64(shdata[off+unsafe.Offsetof(sh.Addr):]), + Link: bo.Uint32(shdata[off+unsafe.Offsetof(sh.Link):]), + Info: bo.Uint32(shdata[off+unsafe.Offsetof(sh.Info):]), + Addralign: bo.Uint64(shdata[off+unsafe.Offsetof(sh.Addralign):]), + Entsize: bo.Uint64(shdata[off+unsafe.Offsetof(sh.Entsize):]), } } if int64(s.Offset) < 0 { - return nil, &FormatError{off, "invalid section offset", int64(s.Offset)} + return nil, &FormatError{shoff + int64(off), "invalid section offset", int64(s.Offset)} } if int64(s.FileSize) < 0 { - return nil, &FormatError{off, "invalid section size", int64(s.FileSize)} + return nil, &FormatError{shoff + int64(off), "invalid section size", int64(s.FileSize)} } s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.FileSize)) @@ -548,23 +545,25 @@ func NewFile(r io.ReaderAt) (*File, error) { // Read the compression header. switch f.Class { case ELFCLASS32: - ch := new(Chdr32) - if err := binary.Read(s.sr, f.ByteOrder, ch); err != nil { + var ch Chdr32 + chdata := make([]byte, unsafe.Sizeof(ch)) + if _, err := s.sr.ReadAt(chdata, 0); err != nil { return nil, err } - s.compressionType = CompressionType(ch.Type) - s.Size = uint64(ch.Size) - s.Addralign = uint64(ch.Addralign) - s.compressionOffset = int64(binary.Size(ch)) + s.compressionType = CompressionType(bo.Uint32(chdata[unsafe.Offsetof(ch.Type):])) + s.Size = uint64(bo.Uint32(chdata[unsafe.Offsetof(ch.Size):])) + s.Addralign = uint64(bo.Uint32(chdata[unsafe.Offsetof(ch.Addralign):])) + s.compressionOffset = int64(unsafe.Sizeof(ch)) case ELFCLASS64: - ch := new(Chdr64) - if err := binary.Read(s.sr, f.ByteOrder, ch); err != nil { + var ch Chdr64 + chdata := make([]byte, unsafe.Sizeof(ch)) + if _, err := s.sr.ReadAt(chdata, 0); err != nil { return nil, err } - s.compressionType = CompressionType(ch.Type) - s.Size = ch.Size - s.Addralign = ch.Addralign - s.compressionOffset = int64(binary.Size(ch)) + s.compressionType = CompressionType(bo.Uint32(chdata[unsafe.Offsetof(ch.Type):])) + s.Size = bo.Uint64(chdata[unsafe.Offsetof(ch.Size):]) + s.Addralign = bo.Uint64(chdata[unsafe.Offsetof(ch.Addralign):]) + s.compressionOffset = int64(unsafe.Sizeof(ch)) } }