1
2
3
4
5
6
7 package obj
8
9 import (
10 "bytes"
11 "github.com/twitchyliquid64/golang-asm/bio"
12 "github.com/twitchyliquid64/golang-asm/goobj"
13 "github.com/twitchyliquid64/golang-asm/objabi"
14 "github.com/twitchyliquid64/golang-asm/sys"
15 "crypto/sha1"
16 "encoding/binary"
17 "fmt"
18 "io"
19 "path/filepath"
20 "sort"
21 "strings"
22 )
23
24
25 func WriteObjFile(ctxt *Link, b *bio.Writer) {
26
27 debugAsmEmit(ctxt)
28
29 genFuncInfoSyms(ctxt)
30
31 w := writer{
32 Writer: goobj.NewWriter(b),
33 ctxt: ctxt,
34 pkgpath: objabi.PathToPrefix(ctxt.Pkgpath),
35 }
36
37 start := b.Offset()
38 w.init()
39
40
41
42 flags := uint32(0)
43 if ctxt.Flag_shared {
44 flags |= goobj.ObjFlagShared
45 }
46 if w.pkgpath == "" {
47 flags |= goobj.ObjFlagNeedNameExpansion
48 }
49 if ctxt.IsAsm {
50 flags |= goobj.ObjFlagFromAssembly
51 }
52 h := goobj.Header{
53 Magic: goobj.Magic,
54 Fingerprint: ctxt.Fingerprint,
55 Flags: flags,
56 }
57 h.Write(w.Writer)
58
59
60 w.StringTable()
61
62
63 h.Offsets[goobj.BlkAutolib] = w.Offset()
64 for i := range ctxt.Imports {
65 ctxt.Imports[i].Write(w.Writer)
66 }
67
68
69 h.Offsets[goobj.BlkPkgIdx] = w.Offset()
70 for _, pkg := range w.pkglist {
71 w.StringRef(pkg)
72 }
73
74
75 h.Offsets[goobj.BlkFile] = w.Offset()
76 for _, f := range ctxt.PosTable.FileTable() {
77 w.StringRef(filepath.ToSlash(f))
78 }
79
80
81 h.Offsets[goobj.BlkSymdef] = w.Offset()
82 for _, s := range ctxt.defs {
83 w.Sym(s)
84 }
85
86
87 h.Offsets[goobj.BlkHashed64def] = w.Offset()
88 for _, s := range ctxt.hashed64defs {
89 w.Sym(s)
90 }
91
92
93 h.Offsets[goobj.BlkHasheddef] = w.Offset()
94 for _, s := range ctxt.hasheddefs {
95 w.Sym(s)
96 }
97
98
99 h.Offsets[goobj.BlkNonpkgdef] = w.Offset()
100 for _, s := range ctxt.nonpkgdefs {
101 w.Sym(s)
102 }
103
104
105 h.Offsets[goobj.BlkNonpkgref] = w.Offset()
106 for _, s := range ctxt.nonpkgrefs {
107 w.Sym(s)
108 }
109
110
111 h.Offsets[goobj.BlkRefFlags] = w.Offset()
112 w.refFlags()
113
114
115 h.Offsets[goobj.BlkHash64] = w.Offset()
116 for _, s := range ctxt.hashed64defs {
117 w.Hash64(s)
118 }
119 h.Offsets[goobj.BlkHash] = w.Offset()
120 for _, s := range ctxt.hasheddefs {
121 w.Hash(s)
122 }
123
124
125
126 h.Offsets[goobj.BlkRelocIdx] = w.Offset()
127 nreloc := uint32(0)
128 lists := [][]*LSym{ctxt.defs, ctxt.hashed64defs, ctxt.hasheddefs, ctxt.nonpkgdefs}
129 for _, list := range lists {
130 for _, s := range list {
131 w.Uint32(nreloc)
132 nreloc += uint32(len(s.R))
133 }
134 }
135 w.Uint32(nreloc)
136
137
138 h.Offsets[goobj.BlkAuxIdx] = w.Offset()
139 naux := uint32(0)
140 for _, list := range lists {
141 for _, s := range list {
142 w.Uint32(naux)
143 naux += uint32(nAuxSym(s))
144 }
145 }
146 w.Uint32(naux)
147
148
149 h.Offsets[goobj.BlkDataIdx] = w.Offset()
150 dataOff := uint32(0)
151 for _, list := range lists {
152 for _, s := range list {
153 w.Uint32(dataOff)
154 dataOff += uint32(len(s.P))
155 }
156 }
157 w.Uint32(dataOff)
158
159
160 h.Offsets[goobj.BlkReloc] = w.Offset()
161 for _, list := range lists {
162 for _, s := range list {
163 for i := range s.R {
164 w.Reloc(&s.R[i])
165 }
166 }
167 }
168
169
170 h.Offsets[goobj.BlkAux] = w.Offset()
171 for _, list := range lists {
172 for _, s := range list {
173 w.Aux(s)
174 }
175 }
176
177
178 h.Offsets[goobj.BlkData] = w.Offset()
179 for _, list := range lists {
180 for _, s := range list {
181 w.Bytes(s.P)
182 }
183 }
184
185
186 h.Offsets[goobj.BlkPcdata] = w.Offset()
187 for _, s := range ctxt.Text {
188 if s.Func != nil {
189 pc := &s.Func.Pcln
190 w.Bytes(pc.Pcsp.P)
191 w.Bytes(pc.Pcfile.P)
192 w.Bytes(pc.Pcline.P)
193 w.Bytes(pc.Pcinline.P)
194 for i := range pc.Pcdata {
195 w.Bytes(pc.Pcdata[i].P)
196 }
197 }
198 }
199
200
201
202
203 h.Offsets[goobj.BlkRefName] = w.Offset()
204 w.refNames()
205
206 h.Offsets[goobj.BlkEnd] = w.Offset()
207
208
209 end := start + int64(w.Offset())
210 b.MustSeek(start, 0)
211 h.Write(w.Writer)
212 b.MustSeek(end, 0)
213 }
214
215 type writer struct {
216 *goobj.Writer
217 ctxt *Link
218 pkgpath string
219 pkglist []string
220 }
221
222
223 func (w *writer) init() {
224 w.pkglist = make([]string, len(w.ctxt.pkgIdx)+1)
225 w.pkglist[0] = ""
226 for pkg, i := range w.ctxt.pkgIdx {
227 w.pkglist[i] = pkg
228 }
229 }
230
231 func (w *writer) StringTable() {
232 w.AddString("")
233 for _, p := range w.ctxt.Imports {
234 w.AddString(p.Pkg)
235 }
236 for _, pkg := range w.pkglist {
237 w.AddString(pkg)
238 }
239 w.ctxt.traverseSyms(traverseAll, func(s *LSym) {
240
241
242
243 if w.pkgpath != "" {
244 s.Name = strings.Replace(s.Name, "\"\".", w.pkgpath+".", -1)
245 }
246
247
248 if s.PkgIdx == goobj.PkgIdxBuiltin {
249 return
250 }
251 w.AddString(s.Name)
252 })
253
254
255 for _, f := range w.ctxt.PosTable.FileTable() {
256 w.AddString(filepath.ToSlash(f))
257 }
258 }
259
260 func (w *writer) Sym(s *LSym) {
261 abi := uint16(s.ABI())
262 if s.Static() {
263 abi = goobj.SymABIstatic
264 }
265 flag := uint8(0)
266 if s.DuplicateOK() {
267 flag |= goobj.SymFlagDupok
268 }
269 if s.Local() {
270 flag |= goobj.SymFlagLocal
271 }
272 if s.MakeTypelink() {
273 flag |= goobj.SymFlagTypelink
274 }
275 if s.Leaf() {
276 flag |= goobj.SymFlagLeaf
277 }
278 if s.NoSplit() {
279 flag |= goobj.SymFlagNoSplit
280 }
281 if s.ReflectMethod() {
282 flag |= goobj.SymFlagReflectMethod
283 }
284 if s.TopFrame() {
285 flag |= goobj.SymFlagTopFrame
286 }
287 if strings.HasPrefix(s.Name, "type.") && s.Name[5] != '.' && s.Type == objabi.SRODATA {
288 flag |= goobj.SymFlagGoType
289 }
290 flag2 := uint8(0)
291 if s.UsedInIface() {
292 flag2 |= goobj.SymFlagUsedInIface
293 }
294 if strings.HasPrefix(s.Name, "go.itab.") && s.Type == objabi.SRODATA {
295 flag2 |= goobj.SymFlagItab
296 }
297 name := s.Name
298 if strings.HasPrefix(name, "gofile..") {
299 name = filepath.ToSlash(name)
300 }
301 var align uint32
302 if s.Func != nil {
303 align = uint32(s.Func.Align)
304 }
305 if s.ContentAddressable() {
306
307
308
309
310
311
312 if s.Size != 0 && !strings.HasPrefix(s.Name, "go.string.") {
313 switch {
314 case w.ctxt.Arch.PtrSize == 8 && s.Size%8 == 0:
315 align = 8
316 case s.Size%4 == 0:
317 align = 4
318 case s.Size%2 == 0:
319 align = 2
320 }
321
322 }
323 }
324 var o goobj.Sym
325 o.SetName(name, w.Writer)
326 o.SetABI(abi)
327 o.SetType(uint8(s.Type))
328 o.SetFlag(flag)
329 o.SetFlag2(flag2)
330 o.SetSiz(uint32(s.Size))
331 o.SetAlign(align)
332 o.Write(w.Writer)
333 }
334
335 func (w *writer) Hash64(s *LSym) {
336 if !s.ContentAddressable() || len(s.R) != 0 {
337 panic("Hash of non-content-addresable symbol")
338 }
339 b := contentHash64(s)
340 w.Bytes(b[:])
341 }
342
343 func (w *writer) Hash(s *LSym) {
344 if !s.ContentAddressable() {
345 panic("Hash of non-content-addresable symbol")
346 }
347 b := w.contentHash(s)
348 w.Bytes(b[:])
349 }
350
351 func contentHash64(s *LSym) goobj.Hash64Type {
352 var b goobj.Hash64Type
353 copy(b[:], s.P)
354 return b
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 func (w *writer) contentHash(s *LSym) goobj.HashType {
374 h := sha1.New()
375
376
377 h.Write(bytes.TrimRight(s.P, "\x00"))
378 var tmp [14]byte
379 for i := range s.R {
380 r := &s.R[i]
381 binary.LittleEndian.PutUint32(tmp[:4], uint32(r.Off))
382 tmp[4] = r.Siz
383 tmp[5] = uint8(r.Type)
384 binary.LittleEndian.PutUint64(tmp[6:14], uint64(r.Add))
385 h.Write(tmp[:])
386 rs := r.Sym
387 switch rs.PkgIdx {
388 case goobj.PkgIdxHashed64:
389 h.Write([]byte{0})
390 t := contentHash64(rs)
391 h.Write(t[:])
392 case goobj.PkgIdxHashed:
393 h.Write([]byte{1})
394 t := w.contentHash(rs)
395 h.Write(t[:])
396 case goobj.PkgIdxNone:
397 h.Write([]byte{2})
398 io.WriteString(h, rs.Name)
399 case goobj.PkgIdxBuiltin:
400 h.Write([]byte{3})
401 binary.LittleEndian.PutUint32(tmp[:4], uint32(rs.SymIdx))
402 h.Write(tmp[:4])
403 case goobj.PkgIdxSelf:
404 io.WriteString(h, w.pkgpath)
405 binary.LittleEndian.PutUint32(tmp[:4], uint32(rs.SymIdx))
406 h.Write(tmp[:4])
407 default:
408 io.WriteString(h, rs.Pkg)
409 binary.LittleEndian.PutUint32(tmp[:4], uint32(rs.SymIdx))
410 h.Write(tmp[:4])
411 }
412 }
413 var b goobj.HashType
414 copy(b[:], h.Sum(nil))
415 return b
416 }
417
418 func makeSymRef(s *LSym) goobj.SymRef {
419 if s == nil {
420 return goobj.SymRef{}
421 }
422 if s.PkgIdx == 0 || !s.Indexed() {
423 fmt.Printf("unindexed symbol reference: %v\n", s)
424 panic("unindexed symbol reference")
425 }
426 return goobj.SymRef{PkgIdx: uint32(s.PkgIdx), SymIdx: uint32(s.SymIdx)}
427 }
428
429 func (w *writer) Reloc(r *Reloc) {
430 var o goobj.Reloc
431 o.SetOff(r.Off)
432 o.SetSiz(r.Siz)
433 o.SetType(uint8(r.Type))
434 o.SetAdd(r.Add)
435 o.SetSym(makeSymRef(r.Sym))
436 o.Write(w.Writer)
437 }
438
439 func (w *writer) aux1(typ uint8, rs *LSym) {
440 var o goobj.Aux
441 o.SetType(typ)
442 o.SetSym(makeSymRef(rs))
443 o.Write(w.Writer)
444 }
445
446 func (w *writer) Aux(s *LSym) {
447 if s.Gotype != nil {
448 w.aux1(goobj.AuxGotype, s.Gotype)
449 }
450 if s.Func != nil {
451 w.aux1(goobj.AuxFuncInfo, s.Func.FuncInfoSym)
452
453 for _, d := range s.Func.Pcln.Funcdata {
454 w.aux1(goobj.AuxFuncdata, d)
455 }
456
457 if s.Func.dwarfInfoSym != nil && s.Func.dwarfInfoSym.Size != 0 {
458 w.aux1(goobj.AuxDwarfInfo, s.Func.dwarfInfoSym)
459 }
460 if s.Func.dwarfLocSym != nil && s.Func.dwarfLocSym.Size != 0 {
461 w.aux1(goobj.AuxDwarfLoc, s.Func.dwarfLocSym)
462 }
463 if s.Func.dwarfRangesSym != nil && s.Func.dwarfRangesSym.Size != 0 {
464 w.aux1(goobj.AuxDwarfRanges, s.Func.dwarfRangesSym)
465 }
466 if s.Func.dwarfDebugLinesSym != nil && s.Func.dwarfDebugLinesSym.Size != 0 {
467 w.aux1(goobj.AuxDwarfLines, s.Func.dwarfDebugLinesSym)
468 }
469 }
470 }
471
472
473 func (w *writer) refFlags() {
474 seen := make(map[*LSym]bool)
475 w.ctxt.traverseSyms(traverseRefs, func(rs *LSym) {
476 switch rs.PkgIdx {
477 case goobj.PkgIdxNone, goobj.PkgIdxHashed64, goobj.PkgIdxHashed, goobj.PkgIdxBuiltin, goobj.PkgIdxSelf:
478 return
479 case goobj.PkgIdxInvalid:
480 panic("unindexed symbol reference")
481 }
482 if seen[rs] {
483 return
484 }
485 seen[rs] = true
486 symref := makeSymRef(rs)
487 flag2 := uint8(0)
488 if rs.UsedInIface() {
489 flag2 |= goobj.SymFlagUsedInIface
490 }
491 if flag2 == 0 {
492 return
493 }
494 var o goobj.RefFlags
495 o.SetSym(symref)
496 o.SetFlag2(flag2)
497 o.Write(w.Writer)
498 })
499 }
500
501
502
503 func (w *writer) refNames() {
504 seen := make(map[*LSym]bool)
505 w.ctxt.traverseSyms(traverseRefs, func(rs *LSym) {
506 switch rs.PkgIdx {
507 case goobj.PkgIdxNone, goobj.PkgIdxHashed64, goobj.PkgIdxHashed, goobj.PkgIdxBuiltin, goobj.PkgIdxSelf:
508 return
509 case goobj.PkgIdxInvalid:
510 panic("unindexed symbol reference")
511 }
512 if seen[rs] {
513 return
514 }
515 seen[rs] = true
516 symref := makeSymRef(rs)
517 var o goobj.RefName
518 o.SetSym(symref)
519 o.SetName(rs.Name, w.Writer)
520 o.Write(w.Writer)
521 })
522
523
524
525
526
527 }
528
529
530 func nAuxSym(s *LSym) int {
531 n := 0
532 if s.Gotype != nil {
533 n++
534 }
535 if s.Func != nil {
536
537 n += 1 + len(s.Func.Pcln.Funcdata)
538 if s.Func.dwarfInfoSym != nil && s.Func.dwarfInfoSym.Size != 0 {
539 n++
540 }
541 if s.Func.dwarfLocSym != nil && s.Func.dwarfLocSym.Size != 0 {
542 n++
543 }
544 if s.Func.dwarfRangesSym != nil && s.Func.dwarfRangesSym.Size != 0 {
545 n++
546 }
547 if s.Func.dwarfDebugLinesSym != nil && s.Func.dwarfDebugLinesSym.Size != 0 {
548 n++
549 }
550 }
551 return n
552 }
553
554
555 func genFuncInfoSyms(ctxt *Link) {
556 infosyms := make([]*LSym, 0, len(ctxt.Text))
557 var pcdataoff uint32
558 var b bytes.Buffer
559 symidx := int32(len(ctxt.defs))
560 for _, s := range ctxt.Text {
561 if s.Func == nil {
562 continue
563 }
564 o := goobj.FuncInfo{
565 Args: uint32(s.Func.Args),
566 Locals: uint32(s.Func.Locals),
567 FuncID: objabi.FuncID(s.Func.FuncID),
568 }
569 pc := &s.Func.Pcln
570 o.Pcsp = pcdataoff
571 pcdataoff += uint32(len(pc.Pcsp.P))
572 o.Pcfile = pcdataoff
573 pcdataoff += uint32(len(pc.Pcfile.P))
574 o.Pcline = pcdataoff
575 pcdataoff += uint32(len(pc.Pcline.P))
576 o.Pcinline = pcdataoff
577 pcdataoff += uint32(len(pc.Pcinline.P))
578 o.Pcdata = make([]uint32, len(pc.Pcdata))
579 for i, pcd := range pc.Pcdata {
580 o.Pcdata[i] = pcdataoff
581 pcdataoff += uint32(len(pcd.P))
582 }
583 o.PcdataEnd = pcdataoff
584 o.Funcdataoff = make([]uint32, len(pc.Funcdataoff))
585 for i, x := range pc.Funcdataoff {
586 o.Funcdataoff[i] = uint32(x)
587 }
588 i := 0
589 o.File = make([]goobj.CUFileIndex, len(pc.UsedFiles))
590 for f := range pc.UsedFiles {
591 o.File[i] = f
592 i++
593 }
594 sort.Slice(o.File, func(i, j int) bool { return o.File[i] < o.File[j] })
595 o.InlTree = make([]goobj.InlTreeNode, len(pc.InlTree.nodes))
596 for i, inl := range pc.InlTree.nodes {
597 f, l := getFileIndexAndLine(ctxt, inl.Pos)
598 o.InlTree[i] = goobj.InlTreeNode{
599 Parent: int32(inl.Parent),
600 File: goobj.CUFileIndex(f),
601 Line: l,
602 Func: makeSymRef(inl.Func),
603 ParentPC: inl.ParentPC,
604 }
605 }
606
607 o.Write(&b)
608 isym := &LSym{
609 Type: objabi.SDATA,
610 PkgIdx: goobj.PkgIdxSelf,
611 SymIdx: symidx,
612 P: append([]byte(nil), b.Bytes()...),
613 }
614 isym.Set(AttrIndexed, true)
615 symidx++
616 infosyms = append(infosyms, isym)
617 s.Func.FuncInfoSym = isym
618 b.Reset()
619
620 dwsyms := []*LSym{s.Func.dwarfRangesSym, s.Func.dwarfLocSym, s.Func.dwarfDebugLinesSym, s.Func.dwarfInfoSym}
621 for _, s := range dwsyms {
622 if s == nil || s.Size == 0 {
623 continue
624 }
625 s.PkgIdx = goobj.PkgIdxSelf
626 s.SymIdx = symidx
627 s.Set(AttrIndexed, true)
628 symidx++
629 infosyms = append(infosyms, s)
630 }
631 }
632 ctxt.defs = append(ctxt.defs, infosyms...)
633 }
634
635
636 func writeAuxSymDebug(ctxt *Link, par *LSym, aux *LSym) {
637
638
639 if aux.Type != objabi.SDWARFLOC &&
640 aux.Type != objabi.SDWARFFCN &&
641 aux.Type != objabi.SDWARFABSFCN &&
642 aux.Type != objabi.SDWARFLINES &&
643 aux.Type != objabi.SDWARFRANGE {
644 return
645 }
646 ctxt.writeSymDebugNamed(aux, "aux for "+par.Name)
647 }
648
649 func debugAsmEmit(ctxt *Link) {
650 if ctxt.Debugasm > 0 {
651 ctxt.traverseSyms(traverseDefs, ctxt.writeSymDebug)
652 if ctxt.Debugasm > 1 {
653 fn := func(par *LSym, aux *LSym) {
654 writeAuxSymDebug(ctxt, par, aux)
655 }
656 ctxt.traverseAuxSyms(traverseAux, fn)
657 }
658 }
659 }
660
661 func (ctxt *Link) writeSymDebug(s *LSym) {
662 ctxt.writeSymDebugNamed(s, s.Name)
663 }
664
665 func (ctxt *Link) writeSymDebugNamed(s *LSym, name string) {
666 ver := ""
667 if ctxt.Debugasm > 1 {
668 ver = fmt.Sprintf("<%d>", s.ABI())
669 }
670 fmt.Fprintf(ctxt.Bso, "%s%s ", name, ver)
671 if s.Type != 0 {
672 fmt.Fprintf(ctxt.Bso, "%v ", s.Type)
673 }
674 if s.Static() {
675 fmt.Fprint(ctxt.Bso, "static ")
676 }
677 if s.DuplicateOK() {
678 fmt.Fprintf(ctxt.Bso, "dupok ")
679 }
680 if s.CFunc() {
681 fmt.Fprintf(ctxt.Bso, "cfunc ")
682 }
683 if s.NoSplit() {
684 fmt.Fprintf(ctxt.Bso, "nosplit ")
685 }
686 if s.TopFrame() {
687 fmt.Fprintf(ctxt.Bso, "topframe ")
688 }
689 fmt.Fprintf(ctxt.Bso, "size=%d", s.Size)
690 if s.Type == objabi.STEXT {
691 fmt.Fprintf(ctxt.Bso, " args=%#x locals=%#x funcid=%#x", uint64(s.Func.Args), uint64(s.Func.Locals), uint64(s.Func.FuncID))
692 if s.Leaf() {
693 fmt.Fprintf(ctxt.Bso, " leaf")
694 }
695 }
696 fmt.Fprintf(ctxt.Bso, "\n")
697 if s.Type == objabi.STEXT {
698 for p := s.Func.Text; p != nil; p = p.Link {
699 fmt.Fprintf(ctxt.Bso, "\t%#04x ", uint(int(p.Pc)))
700 if ctxt.Debugasm > 1 {
701 io.WriteString(ctxt.Bso, p.String())
702 } else {
703 p.InnermostString(ctxt.Bso)
704 }
705 fmt.Fprintln(ctxt.Bso)
706 }
707 }
708 for i := 0; i < len(s.P); i += 16 {
709 fmt.Fprintf(ctxt.Bso, "\t%#04x", uint(i))
710 j := i
711 for ; j < i+16 && j < len(s.P); j++ {
712 fmt.Fprintf(ctxt.Bso, " %02x", s.P[j])
713 }
714 for ; j < i+16; j++ {
715 fmt.Fprintf(ctxt.Bso, " ")
716 }
717 fmt.Fprintf(ctxt.Bso, " ")
718 for j = i; j < i+16 && j < len(s.P); j++ {
719 c := int(s.P[j])
720 b := byte('.')
721 if ' ' <= c && c <= 0x7e {
722 b = byte(c)
723 }
724 ctxt.Bso.WriteByte(b)
725 }
726
727 fmt.Fprintf(ctxt.Bso, "\n")
728 }
729
730 sort.Sort(relocByOff(s.R))
731 for _, r := range s.R {
732 name := ""
733 ver := ""
734 if r.Sym != nil {
735 name = r.Sym.Name
736 if ctxt.Debugasm > 1 {
737 ver = fmt.Sprintf("<%d>", s.ABI())
738 }
739 } else if r.Type == objabi.R_TLS_LE {
740 name = "TLS"
741 }
742 if ctxt.Arch.InFamily(sys.ARM, sys.PPC64) {
743 fmt.Fprintf(ctxt.Bso, "\trel %d+%d t=%d %s%s+%x\n", int(r.Off), r.Siz, r.Type, name, ver, uint64(r.Add))
744 } else {
745 fmt.Fprintf(ctxt.Bso, "\trel %d+%d t=%d %s%s+%d\n", int(r.Off), r.Siz, r.Type, name, ver, r.Add)
746 }
747 }
748 }
749
750
751 type relocByOff []Reloc
752
753 func (x relocByOff) Len() int { return len(x) }
754 func (x relocByOff) Less(i, j int) bool { return x[i].Off < x[j].Off }
755 func (x relocByOff) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
756
View as plain text