aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/newlink/scan.go
blob: 7feb0d89002abf0b25c522fb79bf35032079f19d (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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2014 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Initial scan of packages making up a program.

// TODO(rsc): Rename goobj.SymID.Version to StaticID to avoid confusion with the ELF meaning of version.
// TODO(rsc): Fix file format so that SBSS/SNOPTRBSS with data is listed as SDATA/SNOPTRDATA.
// TODO(rsc): Parallelize scan to overlap file i/o where possible.

package main

import (
	"cmd/internal/goobj"
	"os"
	"sort"
	"strings"
)

// scan scans all packages making up the program, starting with package main defined in mainfile.
func (p *Prog) scan(mainfile string) {
	p.initScan()
	p.scanFile("main", mainfile)
	if len(p.Missing) > 0 && !p.omitRuntime {
		p.scanImport("runtime")
	}

	var missing []string
	for sym := range p.Missing {
		if !p.isAuto(sym) {
			missing = append(missing, sym.String())
		}
	}

	if missing != nil {
		sort.Strings(missing)
		for _, sym := range missing {
			p.errorf("undefined: %s", sym)
		}
	}

	// TODO(rsc): Walk import graph to diagnose cycles.
}

// initScan initializes the Prog fields needed by scan.
func (p *Prog) initScan() {
	p.Packages = make(map[string]*Package)
	p.Syms = make(map[goobj.SymID]*Sym)
	p.Missing = make(map[goobj.SymID]bool)
	p.Missing[p.startSym] = true
}

// scanFile reads file to learn about the package with the given import path.
func (p *Prog) scanFile(pkgpath string, file string) {
	pkg := &Package{
		File: file,
	}
	p.Packages[pkgpath] = pkg

	f, err := os.Open(file)
	if err != nil {
		p.errorf("%v", err)
		return
	}
	gp, err := goobj.Parse(f, pkgpath)
	f.Close()
	if err != nil {
		p.errorf("reading %s: %v", file, err)
		return
	}

	// TODO(rsc): Change cmd/internal/goobj to record package name as gp.Name.
	// TODO(rsc): If pkgpath == "main", check that gp.Name == "main".

	pkg.Package = gp

	for _, gs := range gp.Syms {
		// TODO(rsc): Fix file format instead of this workaround.
		if gs.Data.Size > 0 {
			switch gs.Kind {
			case goobj.SBSS:
				gs.Kind = goobj.SDATA
			case goobj.SNOPTRBSS:
				gs.Kind = goobj.SNOPTRDATA
			}
		}

		if gs.Version != 0 {
			gs.Version += p.MaxVersion
		}
		for i := range gs.Reloc {
			r := &gs.Reloc[i]
			if r.Sym.Version != 0 {
				r.Sym.Version += p.MaxVersion
			}
			if p.Syms[r.Sym] == nil {
				p.Missing[r.Sym] = true
			}
		}
		if gs.Func != nil {
			for i := range gs.Func.FuncData {
				fdata := &gs.Func.FuncData[i]
				if fdata.Sym.Name != "" {
					if fdata.Sym.Version != 0 {
						fdata.Sym.Version += p.MaxVersion
					}
					if p.Syms[fdata.Sym] == nil {
						p.Missing[fdata.Sym] = true
					}
				}
			}
		}
		if old := p.Syms[gs.SymID]; old != nil {
			// Duplicate definition of symbol. Is it okay?
			// TODO(rsc): Write test for this code.
			switch {
			// If both symbols are BSS (no data), take max of sizes
			// but otherwise ignore second symbol.
			case old.Data.Size == 0 && gs.Data.Size == 0:
				if old.Size < gs.Size {
					old.Size = gs.Size
				}
				continue

			// If one is in BSS and one is not, use the one that is not.
			case old.Data.Size > 0 && gs.Data.Size == 0:
				continue
			case gs.Data.Size > 0 && old.Data.Size == 0:
				break // install gs as new symbol below

			// If either is marked as DupOK, we can keep either one.
			// Keep the one that we saw first.
			case old.DupOK || gs.DupOK:
				continue

			// Otherwise, there's an actual conflict:
			default:
				p.errorf("symbol %s defined in both %s and %s %v %v", gs.SymID, old.Package.File, file, old.Data, gs.Data)
				continue
			}
		}
		s := &Sym{
			Sym:     gs,
			Package: pkg,
		}
		p.addSym(s)
		delete(p.Missing, gs.SymID)

		if s.Data.Size > int64(s.Size) {
			p.errorf("%s: initialized data larger than symbol (%d > %d)", s, s.Data.Size, s.Size)
		}
	}
	p.MaxVersion += pkg.MaxVersion

	for i, pkgpath := range pkg.Imports {
		// TODO(rsc): Fix file format to drop .a from recorded import path.
		pkgpath = strings.TrimSuffix(pkgpath, ".a")
		pkg.Imports[i] = pkgpath

		p.scanImport(pkgpath)
	}
}

func (p *Prog) addSym(s *Sym) {
	pkg := s.Package
	if pkg == nil {
		pkg = p.Packages[""]
		if pkg == nil {
			pkg = &Package{}
			p.Packages[""] = pkg
		}
		s.Package = pkg
	}
	pkg.Syms = append(pkg.Syms, s)
	p.Syms[s.SymID] = s
	p.SymOrder = append(p.SymOrder, s)
}

// scanImport finds the object file for the given import path and then scans it.
func (p *Prog) scanImport(pkgpath string) {
	if p.Packages[pkgpath] != nil {
		return // already loaded
	}

	// TODO(rsc): Implement correct search to find file.
	p.scanFile(pkgpath, p.pkgdir+"/"+pkgpath+".a")
}