...

Source file src/cmd/vendor/github.com/google/pprof/internal/elfexec/elfexec.go

     1	// Copyright 2014 Google Inc. All Rights Reserved.
     2	//
     3	// Licensed under the Apache License, Version 2.0 (the "License");
     4	// you may not use this file except in compliance with the License.
     5	// You may obtain a copy of the License at
     6	//
     7	//     http://www.apache.org/licenses/LICENSE-2.0
     8	//
     9	// Unless required by applicable law or agreed to in writing, software
    10	// distributed under the License is distributed on an "AS IS" BASIS,
    11	// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12	// See the License for the specific language governing permissions and
    13	// limitations under the License.
    14	
    15	// Package elfexec provides utility routines to examine ELF binaries.
    16	package elfexec
    17	
    18	import (
    19		"bufio"
    20		"debug/elf"
    21		"encoding/binary"
    22		"fmt"
    23		"io"
    24	)
    25	
    26	const (
    27		maxNoteSize        = 1 << 20 // in bytes
    28		noteTypeGNUBuildID = 3
    29	)
    30	
    31	// elfNote is the payload of a Note Section in an ELF file.
    32	type elfNote struct {
    33		Name string // Contents of the "name" field, omitting the trailing zero byte.
    34		Desc []byte // Contents of the "desc" field.
    35		Type uint32 // Contents of the "type" field.
    36	}
    37	
    38	// parseNotes returns the notes from a SHT_NOTE section or PT_NOTE segment.
    39	func parseNotes(reader io.Reader, alignment int, order binary.ByteOrder) ([]elfNote, error) {
    40		r := bufio.NewReader(reader)
    41	
    42		// padding returns the number of bytes required to pad the given size to an
    43		// alignment boundary.
    44		padding := func(size int) int {
    45			return ((size + (alignment - 1)) &^ (alignment - 1)) - size
    46		}
    47	
    48		var notes []elfNote
    49		for {
    50			noteHeader := make([]byte, 12) // 3 4-byte words
    51			if _, err := io.ReadFull(r, noteHeader); err == io.EOF {
    52				break
    53			} else if err != nil {
    54				return nil, err
    55			}
    56			namesz := order.Uint32(noteHeader[0:4])
    57			descsz := order.Uint32(noteHeader[4:8])
    58			typ := order.Uint32(noteHeader[8:12])
    59	
    60			if uint64(namesz) > uint64(maxNoteSize) {
    61				return nil, fmt.Errorf("note name too long (%d bytes)", namesz)
    62			}
    63			var name string
    64			if namesz > 0 {
    65				// Documentation differs as to whether namesz is meant to include the
    66				// trailing zero, but everyone agrees that name is null-terminated.
    67				// So we'll just determine the actual length after the fact.
    68				var err error
    69				name, err = r.ReadString('\x00')
    70				if err == io.EOF {
    71					return nil, fmt.Errorf("missing note name (want %d bytes)", namesz)
    72				} else if err != nil {
    73					return nil, err
    74				}
    75				namesz = uint32(len(name))
    76				name = name[:len(name)-1]
    77			}
    78	
    79			// Drop padding bytes until the desc field.
    80			for n := padding(len(noteHeader) + int(namesz)); n > 0; n-- {
    81				if _, err := r.ReadByte(); err == io.EOF {
    82					return nil, fmt.Errorf(
    83						"missing %d bytes of padding after note name", n)
    84				} else if err != nil {
    85					return nil, err
    86				}
    87			}
    88	
    89			if uint64(descsz) > uint64(maxNoteSize) {
    90				return nil, fmt.Errorf("note desc too long (%d bytes)", descsz)
    91			}
    92			desc := make([]byte, int(descsz))
    93			if _, err := io.ReadFull(r, desc); err == io.EOF {
    94				return nil, fmt.Errorf("missing desc (want %d bytes)", len(desc))
    95			} else if err != nil {
    96				return nil, err
    97			}
    98	
    99			notes = append(notes, elfNote{Name: name, Desc: desc, Type: typ})
   100	
   101			// Drop padding bytes until the next note or the end of the section,
   102			// whichever comes first.
   103			for n := padding(len(desc)); n > 0; n-- {
   104				if _, err := r.ReadByte(); err == io.EOF {
   105					// We hit the end of the section before an alignment boundary.
   106					// This can happen if this section is at the end of the file or the next
   107					// section has a smaller alignment requirement.
   108					break
   109				} else if err != nil {
   110					return nil, err
   111				}
   112			}
   113		}
   114		return notes, nil
   115	}
   116	
   117	// GetBuildID returns the GNU build-ID for an ELF binary.
   118	//
   119	// If no build-ID was found but the binary was read without error, it returns
   120	// (nil, nil).
   121	func GetBuildID(binary io.ReaderAt) ([]byte, error) {
   122		f, err := elf.NewFile(binary)
   123		if err != nil {
   124			return nil, err
   125		}
   126	
   127		findBuildID := func(notes []elfNote) ([]byte, error) {
   128			var buildID []byte
   129			for _, note := range notes {
   130				if note.Name == "GNU" && note.Type == noteTypeGNUBuildID {
   131					if buildID == nil {
   132						buildID = note.Desc
   133					} else {
   134						return nil, fmt.Errorf("multiple build ids found, don't know which to use")
   135					}
   136				}
   137			}
   138			return buildID, nil
   139		}
   140	
   141		for _, p := range f.Progs {
   142			if p.Type != elf.PT_NOTE {
   143				continue
   144			}
   145			notes, err := parseNotes(p.Open(), int(p.Align), f.ByteOrder)
   146			if err != nil {
   147				return nil, err
   148			}
   149			if b, err := findBuildID(notes); b != nil || err != nil {
   150				return b, err
   151			}
   152		}
   153		for _, s := range f.Sections {
   154			if s.Type != elf.SHT_NOTE {
   155				continue
   156			}
   157			notes, err := parseNotes(s.Open(), int(s.Addralign), f.ByteOrder)
   158			if err != nil {
   159				return nil, err
   160			}
   161			if b, err := findBuildID(notes); b != nil || err != nil {
   162				return b, err
   163			}
   164		}
   165		return nil, nil
   166	}
   167	
   168	// GetBase determines the base address to subtract from virtual
   169	// address to get symbol table address. For an executable, the base
   170	// is 0. Otherwise, it's a shared library, and the base is the
   171	// address where the mapping starts. The kernel is special, and may
   172	// use the address of the _stext symbol as the mmap start. _stext
   173	// offset can be obtained with `nm vmlinux | grep _stext`
   174	func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint64, start, limit, offset uint64) (uint64, error) {
   175		const (
   176			pageSize = 4096
   177			// PAGE_OFFSET for PowerPC64, see arch/powerpc/Kconfig in the kernel sources.
   178			pageOffsetPpc64 = 0xc000000000000000
   179		)
   180	
   181		if start == 0 && offset == 0 && (limit == ^uint64(0) || limit == 0) {
   182			// Some tools may introduce a fake mapping that spans the entire
   183			// address space. Assume that the address has already been
   184			// adjusted, so no additional base adjustment is necessary.
   185			return 0, nil
   186		}
   187	
   188		switch fh.Type {
   189		case elf.ET_EXEC:
   190			if loadSegment == nil {
   191				// Assume fixed-address executable and so no adjustment.
   192				return 0, nil
   193			}
   194			if stextOffset == nil && start > 0 && start < 0x8000000000000000 {
   195				// A regular user-mode executable. Compute the base offset using same
   196				// arithmetics as in ET_DYN case below, see the explanation there.
   197				// Ideally, the condition would just be "stextOffset == nil" as that
   198				// represents the address of _stext symbol in the vmlinux image. Alas,
   199				// the caller may skip reading it from the binary (it's expensive to scan
   200				// all the symbols) and so it may be nil even for the kernel executable.
   201				// So additionally check that the start is within the user-mode half of
   202				// the 64-bit address space.
   203				return start - offset + loadSegment.Off - loadSegment.Vaddr, nil
   204			}
   205			// Various kernel heuristics and cases follow.
   206			if loadSegment.Vaddr == start-offset {
   207				return offset, nil
   208			}
   209			if start == 0 && limit != 0 {
   210				// ChromeOS remaps its kernel to 0. Nothing else should come
   211				// down this path. Empirical values:
   212				//       VADDR=0xffffffff80200000
   213				// stextOffset=0xffffffff80200198
   214				if stextOffset != nil {
   215					return -*stextOffset, nil
   216				}
   217				return -loadSegment.Vaddr, nil
   218			}
   219			if start >= loadSegment.Vaddr && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {
   220				// Some kernels look like:
   221				//       VADDR=0xffffffff80200000
   222				// stextOffset=0xffffffff80200198
   223				//       Start=0xffffffff83200000
   224				//       Limit=0xffffffff84200000
   225				//      Offset=0 (0xc000000000000000 for PowerPC64) (== Start for ASLR kernel)
   226				// So the base should be:
   227				if stextOffset != nil && (start%pageSize) == (*stextOffset%pageSize) {
   228					// perf uses the address of _stext as start. Some tools may
   229					// adjust for this before calling GetBase, in which case the page
   230					// alignment should be different from that of stextOffset.
   231					return start - *stextOffset, nil
   232				}
   233	
   234				return start - loadSegment.Vaddr, nil
   235			} else if start%pageSize != 0 && stextOffset != nil && *stextOffset%pageSize == start%pageSize {
   236				// ChromeOS remaps its kernel to 0 + start%pageSize. Nothing
   237				// else should come down this path. Empirical values:
   238				//       start=0x198 limit=0x2f9fffff offset=0
   239				//       VADDR=0xffffffff81000000
   240				// stextOffset=0xffffffff81000198
   241				return start - *stextOffset, nil
   242			}
   243	
   244			return 0, fmt.Errorf("don't know how to handle EXEC segment: %v start=0x%x limit=0x%x offset=0x%x", *loadSegment, start, limit, offset)
   245		case elf.ET_REL:
   246			if offset != 0 {
   247				return 0, fmt.Errorf("don't know how to handle mapping.Offset")
   248			}
   249			return start, nil
   250		case elf.ET_DYN:
   251			// The process mapping information, start = start of virtual address range,
   252			// and offset = offset in the executable file of the start address, tells us
   253			// that a runtime virtual address x maps to a file offset
   254			// fx = x - start + offset.
   255			if loadSegment == nil {
   256				return start - offset, nil
   257			}
   258			// The program header, if not nil, indicates the offset in the file where
   259			// the executable segment is located (loadSegment.Off), and the base virtual
   260			// address where the first byte of the segment is loaded
   261			// (loadSegment.Vaddr). A file offset fx maps to a virtual (symbol) address
   262			// sx = fx - loadSegment.Off + loadSegment.Vaddr.
   263			//
   264			// Thus, a runtime virtual address x maps to a symbol address
   265			// sx = x - start + offset - loadSegment.Off + loadSegment.Vaddr.
   266			return start - offset + loadSegment.Off - loadSegment.Vaddr, nil
   267		}
   268		return 0, fmt.Errorf("don't know how to handle FileHeader.Type %v", fh.Type)
   269	}
   270	
   271	// FindTextProgHeader finds the program segment header containing the .text
   272	// section or nil if the segment cannot be found.
   273	func FindTextProgHeader(f *elf.File) *elf.ProgHeader {
   274		for _, s := range f.Sections {
   275			if s.Name == ".text" {
   276				// Find the LOAD segment containing the .text section.
   277				for _, p := range f.Progs {
   278					if p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 && s.Addr >= p.Vaddr && s.Addr < p.Vaddr+p.Memsz {
   279						return &p.ProgHeader
   280					}
   281				}
   282			}
   283		}
   284		return nil
   285	}
   286	

View as plain text