aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/symbolizer/nm.go
blob: 1696fc0a3d31162cc22ffe85dec4ed3be7e4a8b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Copyright 2016 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package symbolizer

import (
	"debug/elf"
	"fmt"
	"sort"
)

type Symbol struct {
	Addr uint64
	Size int
}

// ReadTextSymbols returns list of text symbols in the binary bin.
func ReadTextSymbols(bin string) (map[string][]Symbol, error) {
	return read(bin, true)
}

// ReadRodataSymbols returns list of rodata symbols in the binary bin.
func ReadRodataSymbols(bin string) (map[string][]Symbol, error) {
	return read(bin, false)
}

func read(bin string, text bool) (map[string][]Symbol, error) {
	raw, err := load(bin, text)
	if err != nil {
		return nil, err
	}
	sort.Slice(raw, func(i, j int) bool {
		return raw[i].Value > raw[j].Value
	})
	symbols := make(map[string][]Symbol)
	// Function sizes reported by the Linux kernel do not match symbol tables.
	// The kernel computes size of a symbol based on the start of the next symbol.
	// We need to do the same to match kernel sizes to be able to find the right
	// symbol across multiple symbols with the same name.
	var prevAddr uint64
	var prevSize int
	for _, symb := range raw {
		size := int(symb.Size)
		if text {
			if symb.Value == prevAddr {
				size = prevSize
			} else if prevAddr != 0 {
				size = int(prevAddr - symb.Value)
			}
			prevAddr, prevSize = symb.Value, size
		}
		symbols[symb.Name] = append(symbols[symb.Name], Symbol{symb.Value, size})
	}
	return symbols, nil
}

func load(bin string, text bool) ([]elf.Symbol, error) {
	file, err := elf.Open(bin)
	if err != nil {
		return nil, fmt.Errorf("failed to open ELF file %v: %w", bin, err)
	}
	allSymbols, err := file.Symbols()
	if err != nil {
		return nil, fmt.Errorf("failed to read ELF symbols: %w", err)
	}
	var symbols []elf.Symbol
	for _, symb := range allSymbols {
		if symb.Size == 0 || symb.Section < 0 || int(symb.Section) >= len(file.Sections) {
			continue
		}
		sect := file.Sections[symb.Section]
		isText := sect.Type == elf.SHT_PROGBITS &&
			sect.Flags&elf.SHF_ALLOC != 0 &&
			sect.Flags&elf.SHF_EXECINSTR != 0
		// Note: x86_64 vmlinux .rodata is marked as writable and according to flags it looks like .data,
		// so we look at the name.
		if text && !isText || !text && sect.Name != ".rodata" {
			continue
		}
		symbols = append(symbols, symb)
	}
	return symbols, nil
}