1package builder
2
3import (
4	"debug/elf"
5	"sort"
6	"strings"
7)
8
9// programSize contains size statistics per package of a compiled program.
10type programSize struct {
11	Packages map[string]*packageSize
12	Sum      *packageSize
13	Code     uint64
14	Data     uint64
15	BSS      uint64
16}
17
18// sortedPackageNames returns the list of package names (ProgramSize.Packages)
19// sorted alphabetically.
20func (ps *programSize) sortedPackageNames() []string {
21	names := make([]string, 0, len(ps.Packages))
22	for name := range ps.Packages {
23		names = append(names, name)
24	}
25	sort.Strings(names)
26	return names
27}
28
29// packageSize contains the size of a package, calculated from the linked object
30// file.
31type packageSize struct {
32	Code   uint64
33	ROData uint64
34	Data   uint64
35	BSS    uint64
36}
37
38// Flash usage in regular microcontrollers.
39func (ps *packageSize) Flash() uint64 {
40	return ps.Code + ps.ROData + ps.Data
41}
42
43// Static RAM usage in regular microcontrollers.
44func (ps *packageSize) RAM() uint64 {
45	return ps.Data + ps.BSS
46}
47
48type symbolList []elf.Symbol
49
50func (l symbolList) Len() int {
51	return len(l)
52}
53
54func (l symbolList) Less(i, j int) bool {
55	bind_i := elf.ST_BIND(l[i].Info)
56	bind_j := elf.ST_BIND(l[j].Info)
57	if l[i].Value == l[j].Value && bind_i != elf.STB_WEAK && bind_j == elf.STB_WEAK {
58		// sort weak symbols after non-weak symbols
59		return true
60	}
61	return l[i].Value < l[j].Value
62}
63
64func (l symbolList) Swap(i, j int) {
65	l[i], l[j] = l[j], l[i]
66}
67
68// loadProgramSize calculate a program/data size breakdown of each package for a
69// given ELF file.
70func loadProgramSize(path string) (*programSize, error) {
71	file, err := elf.Open(path)
72	if err != nil {
73		return nil, err
74	}
75	defer file.Close()
76
77	var sumCode uint64
78	var sumData uint64
79	var sumBSS uint64
80	for _, section := range file.Sections {
81		if section.Flags&elf.SHF_ALLOC == 0 {
82			continue
83		}
84		if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
85			continue
86		}
87		if section.Name == ".stack" {
88			// HACK: this works around a bug in ld.lld from LLVM 10. The linker
89			// marks sections with no input symbols (such as is the case for the
90			// .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
91			// doesn't affect the generated binaries (.hex and .bin), it does
92			// affect the reported size.
93			// https://bugs.llvm.org/show_bug.cgi?id=45336
94			// https://reviews.llvm.org/D76981
95			// It has been merged in master, but it has not (yet) been
96			// backported to the LLVM 10 release branch.
97			sumBSS += section.Size
98		} else if section.Type == elf.SHT_NOBITS {
99			sumBSS += section.Size
100		} else if section.Flags&elf.SHF_EXECINSTR != 0 {
101			sumCode += section.Size
102		} else if section.Flags&elf.SHF_WRITE != 0 {
103			sumData += section.Size
104		}
105	}
106
107	allSymbols, err := file.Symbols()
108	if err != nil {
109		return nil, err
110	}
111	symbols := make([]elf.Symbol, 0, len(allSymbols))
112	for _, symbol := range allSymbols {
113		symType := elf.ST_TYPE(symbol.Info)
114		if symbol.Size == 0 {
115			continue
116		}
117		if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
118			continue
119		}
120		if symbol.Section >= elf.SectionIndex(len(file.Sections)) {
121			continue
122		}
123		section := file.Sections[symbol.Section]
124		if section.Flags&elf.SHF_ALLOC == 0 {
125			continue
126		}
127		symbols = append(symbols, symbol)
128	}
129	sort.Sort(symbolList(symbols))
130
131	sizes := map[string]*packageSize{}
132	var lastSymbolValue uint64
133	for _, symbol := range symbols {
134		symType := elf.ST_TYPE(symbol.Info)
135		//bind := elf.ST_BIND(symbol.Info)
136		section := file.Sections[symbol.Section]
137		pkgName := "(bootstrap)"
138		symName := strings.TrimLeft(symbol.Name, "(*")
139		dot := strings.IndexByte(symName, '.')
140		if dot > 0 {
141			pkgName = symName[:dot]
142		}
143		pkgSize := sizes[pkgName]
144		if pkgSize == nil {
145			pkgSize = &packageSize{}
146			sizes[pkgName] = pkgSize
147		}
148		if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
149			if symType == elf.STT_FUNC {
150				pkgSize.Code += symbol.Size
151			} else if section.Flags&elf.SHF_WRITE != 0 {
152				if section.Type == elf.SHT_NOBITS {
153					pkgSize.BSS += symbol.Size
154				} else {
155					pkgSize.Data += symbol.Size
156				}
157			} else {
158				pkgSize.ROData += symbol.Size
159			}
160		}
161		lastSymbolValue = symbol.Value
162	}
163
164	sum := &packageSize{}
165	for _, pkg := range sizes {
166		sum.Code += pkg.Code
167		sum.ROData += pkg.ROData
168		sum.Data += pkg.Data
169		sum.BSS += pkg.BSS
170	}
171
172	return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
173}
174