mirror of
https://github.com/golang/go
synced 2024-11-12 09:20:22 -07:00
cmd/link: pclntab generation
R=iant CC=golang-codereviews https://golang.org/cl/53820043
This commit is contained in:
parent
0dd26f276d
commit
a453f28c70
480
src/cmd/link/pclntab.go
Normal file
480
src/cmd/link/pclntab.go
Normal file
@ -0,0 +1,480 @@
|
||||
// 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.
|
||||
|
||||
// Generation of runtime function information (pclntab).
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/goobj"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var zerofunc goobj.Func
|
||||
|
||||
// pclntab collects the runtime function data for each function that will
|
||||
// be listed in the binary and builds a single table describing all functions.
|
||||
// This table is used at run time for stack traces and to look up PC-specific
|
||||
// information during garbage collection. The symbol created is named
|
||||
// "pclntab" for historical reasons; the scope of the table has grown to
|
||||
// include more than just PC/line number correspondences.
|
||||
// The table format is documented at http://golang.org/s/go12symtab.
|
||||
func (p *Prog) pclntab() {
|
||||
// Count number of functions going into the binary,
|
||||
// so that we can size the initial index correctly.
|
||||
nfunc := 0
|
||||
for _, sym := range p.SymOrder {
|
||||
if sym.Kind != goobj.STEXT {
|
||||
continue
|
||||
}
|
||||
nfunc++
|
||||
}
|
||||
|
||||
// Table header.
|
||||
buf := new(SymBuffer)
|
||||
buf.Init(p)
|
||||
buf.SetSize(8 + p.ptrsize)
|
||||
off := 0
|
||||
off = buf.Uint32(off, 0xfffffffb)
|
||||
off = buf.Uint8(off, 0)
|
||||
off = buf.Uint8(off, 0)
|
||||
off = buf.Uint8(off, uint8(p.pcquantum))
|
||||
off = buf.Uint8(off, uint8(p.ptrsize))
|
||||
off = buf.Uint(off, uint64(nfunc), p.ptrsize)
|
||||
indexOff := off
|
||||
off += (nfunc*2 + 1) * p.ptrsize // function index, to be filled in
|
||||
off += 4 // file table start offset, to be filled in
|
||||
buf.SetSize(off)
|
||||
|
||||
// One-file cache for reading PCData tables from package files.
|
||||
// TODO(rsc): Better I/O strategy.
|
||||
var (
|
||||
file *os.File
|
||||
fname string
|
||||
)
|
||||
|
||||
// Files gives the file numbering for source file names recorded
|
||||
// in the binary.
|
||||
files := make(map[string]int)
|
||||
|
||||
// Build the table, build the index, and build the file name numbering.
|
||||
// The loop here must visit functions in the same order that they will
|
||||
// be stored in the binary, or else binary search over the index will fail.
|
||||
// The runtime checks that the index is sorted properly at program start time.
|
||||
var lastSym *Sym
|
||||
for _, sym := range p.SymOrder {
|
||||
if sym.Kind != goobj.STEXT {
|
||||
continue
|
||||
}
|
||||
lastSym = sym
|
||||
|
||||
// Treat no recorded function information same as all zeros.
|
||||
f := sym.Func
|
||||
if f == nil {
|
||||
f = &zerofunc
|
||||
}
|
||||
|
||||
// Open package file if needed, for reading PC data.
|
||||
if fname != sym.Package.File {
|
||||
if file != nil {
|
||||
file.Close()
|
||||
}
|
||||
var err error
|
||||
file, err = os.Open(sym.Package.File)
|
||||
if err != nil {
|
||||
p.errorf("%v: %v", sym, err)
|
||||
return
|
||||
}
|
||||
fname = sym.Package.File
|
||||
}
|
||||
|
||||
// off is the offset of the table entry where we're going to write
|
||||
// the encoded form of Func.
|
||||
// indexOff is the current position in the table index;
|
||||
// we add an entry in the index pointing at off.
|
||||
off = (buf.Size() + p.ptrsize - 1) &^ (p.ptrsize - 1)
|
||||
indexOff = buf.Addr(indexOff, sym.SymID, 0)
|
||||
indexOff = buf.Uint(indexOff, uint64(off), p.ptrsize)
|
||||
|
||||
// The Func encoding starts with a header giving offsets
|
||||
// to data blobs, and then the data blobs themselves.
|
||||
// end gives the current write position for the data blobs.
|
||||
end := off + p.ptrsize + 3*4 + 5*4 + len(f.PCData)*4 + len(f.FuncData)*p.ptrsize
|
||||
if len(f.FuncData) > 0 {
|
||||
end += -end & (p.ptrsize - 1)
|
||||
}
|
||||
buf.SetSize(end)
|
||||
|
||||
// entry uintptr
|
||||
// name int32
|
||||
// args int32
|
||||
// frame int32
|
||||
//
|
||||
// The frame recorded in the object file is
|
||||
// the frame size used in an assembly listing, which does
|
||||
// not include the caller PC on the stack.
|
||||
// The frame size we want to list here is the delta from
|
||||
// this function's SP to its caller's SP, which does include
|
||||
// the caller PC. Add p.ptrsize to f.Frame to adjust.
|
||||
// TODO(rsc): Record the same frame size in the object file.
|
||||
off = buf.Addr(off, sym.SymID, 0)
|
||||
off = buf.Uint32(off, uint32(addString(buf, sym.Name)))
|
||||
off = buf.Uint32(off, uint32(f.Args))
|
||||
off = buf.Uint32(off, uint32(f.Frame+p.ptrsize))
|
||||
|
||||
// pcdata
|
||||
off = buf.Uint32(off, uint32(addPCTable(p, buf, file, f.PCSP)))
|
||||
off = buf.Uint32(off, uint32(addPCFileTable(p, buf, file, f.PCFile, sym, files)))
|
||||
off = buf.Uint32(off, uint32(addPCTable(p, buf, file, f.PCLine)))
|
||||
off = buf.Uint32(off, uint32(len(f.PCData)))
|
||||
off = buf.Uint32(off, uint32(len(f.FuncData)))
|
||||
for _, pcdata := range f.PCData {
|
||||
off = buf.Uint32(off, uint32(addPCTable(p, buf, file, pcdata)))
|
||||
}
|
||||
|
||||
// funcdata
|
||||
if len(f.FuncData) > 0 {
|
||||
off += -off & (p.ptrsize - 1) // must be pointer-aligned
|
||||
for _, funcdata := range f.FuncData {
|
||||
if funcdata.Sym.Name == "" {
|
||||
off = buf.Uint(off, uint64(funcdata.Offset), p.ptrsize)
|
||||
} else {
|
||||
off = buf.Addr(off, funcdata.Sym, funcdata.Offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if off != end {
|
||||
p.errorf("internal error: invalid math in pclntab: off=%#x end=%#x", off, end)
|
||||
break
|
||||
}
|
||||
}
|
||||
if file != nil {
|
||||
file.Close()
|
||||
}
|
||||
|
||||
// Final entry of index is end PC of last function.
|
||||
indexOff = buf.Addr(indexOff, lastSym.SymID, int64(lastSym.Size))
|
||||
|
||||
// Start file table.
|
||||
// Function index is immediately followed by offset to file table.
|
||||
off = (buf.Size() + p.ptrsize - 1) &^ (p.ptrsize - 1)
|
||||
buf.Uint32(indexOff, uint32(off))
|
||||
|
||||
// File table is an array of uint32s.
|
||||
// The first entry gives 1+n, the size of the array.
|
||||
// The following n entries hold offsets to string data.
|
||||
// File number n uses the string pointed at by entry n.
|
||||
// File number 0 is invalid.
|
||||
buf.SetSize(off + (1+len(files))*4)
|
||||
buf.Uint32(off, uint32(1+len(files)))
|
||||
var filestr []string
|
||||
for file := range files {
|
||||
filestr = append(filestr, file)
|
||||
}
|
||||
sort.Strings(filestr)
|
||||
for _, file := range filestr {
|
||||
id := files[file]
|
||||
buf.Uint32(off+4*id, uint32(addString(buf, file)))
|
||||
}
|
||||
|
||||
pclntab := &Sym{
|
||||
Sym: &goobj.Sym{
|
||||
SymID: goobj.SymID{Name: "pclntab"},
|
||||
Kind: goobj.SPCLNTAB,
|
||||
Size: buf.Size(),
|
||||
Reloc: buf.Reloc(),
|
||||
},
|
||||
Bytes: buf.Bytes(),
|
||||
}
|
||||
p.addSym(pclntab)
|
||||
}
|
||||
|
||||
// addString appends the string s to the buffer b.
|
||||
// It returns the offset of the beginning of the string in the buffer.
|
||||
func addString(b *SymBuffer, s string) int {
|
||||
off := b.Size()
|
||||
b.SetSize(off + len(s) + 1)
|
||||
copy(b.data[off:], s)
|
||||
return off
|
||||
}
|
||||
|
||||
// addPCTable appends the PC-data table stored in the file f at the location loc
|
||||
// to the symbol buffer b. It returns the offset of the beginning of the table
|
||||
// in the buffer.
|
||||
func addPCTable(p *Prog, b *SymBuffer, f *os.File, loc goobj.Data) int {
|
||||
if loc.Size == 0 {
|
||||
return 0
|
||||
}
|
||||
off := b.Size()
|
||||
b.SetSize(off + int(loc.Size))
|
||||
_, err := f.ReadAt(b.data[off:off+int(loc.Size)], loc.Offset)
|
||||
if err != nil {
|
||||
p.errorf("%v", err)
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
// addPCFileTable is like addPCTable, but it renumbers the file names referred to by the table
|
||||
// to use the global numbering maintained in the files map. It adds new files to the
|
||||
// map as necessary.
|
||||
func addPCFileTable(p *Prog, b *SymBuffer, f *os.File, loc goobj.Data, sym *Sym, files map[string]int) int {
|
||||
if loc.Size == 0 {
|
||||
return 0
|
||||
}
|
||||
off := b.Size()
|
||||
|
||||
src := make([]byte, loc.Size)
|
||||
_, err := f.ReadAt(src, loc.Offset)
|
||||
if err != nil {
|
||||
p.errorf("%v", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
filenum := make([]int, len(sym.Func.File))
|
||||
for i, name := range sym.Func.File {
|
||||
num := files[name]
|
||||
if num == 0 {
|
||||
num = len(files) + 1
|
||||
files[name] = num
|
||||
}
|
||||
filenum[i] = num
|
||||
}
|
||||
|
||||
var dst []byte
|
||||
newval := int32(-1)
|
||||
var it PCIter
|
||||
for it.Init(p, src); !it.Done; it.Next() {
|
||||
// value delta
|
||||
oldval := it.Value
|
||||
val := oldval
|
||||
if oldval != -1 {
|
||||
if oldval < 0 || int(oldval) >= len(filenum) {
|
||||
p.errorf("%s: corrupt pc-file table", sym)
|
||||
break
|
||||
}
|
||||
val = int32(filenum[oldval])
|
||||
}
|
||||
dv := val - newval
|
||||
newval = val
|
||||
uv := uint32(dv<<1) ^ uint32(dv>>31)
|
||||
dst = appendVarint(dst, uv)
|
||||
|
||||
// pc delta
|
||||
dst = appendVarint(dst, it.NextPC-it.PC)
|
||||
}
|
||||
if it.Corrupt {
|
||||
p.errorf("%s: corrupt pc-file table", sym)
|
||||
}
|
||||
|
||||
// terminating value delta
|
||||
dst = appendVarint(dst, 0)
|
||||
|
||||
b.SetSize(off + len(dst))
|
||||
copy(b.data[off:], dst)
|
||||
return off
|
||||
}
|
||||
|
||||
// A SymBuffer is a buffer for preparing the data image of a
|
||||
// linker-generated symbol.
|
||||
type SymBuffer struct {
|
||||
data []byte
|
||||
reloc []goobj.Reloc
|
||||
order binary.ByteOrder
|
||||
ptrsize int
|
||||
}
|
||||
|
||||
// Init initializes the buffer for writing.
|
||||
func (b *SymBuffer) Init(p *Prog) {
|
||||
b.data = nil
|
||||
b.reloc = nil
|
||||
b.order = p.byteorder
|
||||
b.ptrsize = p.ptrsize
|
||||
}
|
||||
|
||||
// Bytes returns the buffer data.
|
||||
func (b *SymBuffer) Bytes() []byte {
|
||||
return b.data
|
||||
}
|
||||
|
||||
// SetSize sets the buffer's data size to n bytes.
|
||||
func (b *SymBuffer) SetSize(n int) {
|
||||
for cap(b.data) < n {
|
||||
b.data = append(b.data[:cap(b.data)], 0)
|
||||
}
|
||||
b.data = b.data[:n]
|
||||
}
|
||||
|
||||
// Size returns the buffer's data size.
|
||||
func (b *SymBuffer) Size() int {
|
||||
return len(b.data)
|
||||
}
|
||||
|
||||
// Reloc returns the buffered relocations.
|
||||
func (b *SymBuffer) Reloc() []goobj.Reloc {
|
||||
return b.reloc
|
||||
}
|
||||
|
||||
// Uint8 sets the uint8 at offset off to v.
|
||||
// It returns the offset just beyond v.
|
||||
func (b *SymBuffer) Uint8(off int, v uint8) int {
|
||||
b.data[off] = v
|
||||
return off + 1
|
||||
}
|
||||
|
||||
// Uint16 sets the uint16 at offset off to v.
|
||||
// It returns the offset just beyond v.
|
||||
func (b *SymBuffer) Uint16(off int, v uint16) int {
|
||||
b.order.PutUint16(b.data[off:], v)
|
||||
return off + 2
|
||||
}
|
||||
|
||||
// Uint32 sets the uint32 at offset off to v.
|
||||
// It returns the offset just beyond v.
|
||||
func (b *SymBuffer) Uint32(off int, v uint32) int {
|
||||
b.order.PutUint32(b.data[off:], v)
|
||||
return off + 4
|
||||
}
|
||||
|
||||
// Uint64 sets the uint64 at offset off to v.
|
||||
// It returns the offset just beyond v.
|
||||
func (b *SymBuffer) Uint64(off int, v uint64) int {
|
||||
b.order.PutUint64(b.data[off:], v)
|
||||
return off + 8
|
||||
}
|
||||
|
||||
// Uint sets the size-byte unsigned integer at offset off to v.
|
||||
// It returns the offset just beyond v.
|
||||
func (b *SymBuffer) Uint(off int, v uint64, size int) int {
|
||||
switch size {
|
||||
case 1:
|
||||
return b.Uint8(off, uint8(v))
|
||||
case 2:
|
||||
return b.Uint16(off, uint16(v))
|
||||
case 4:
|
||||
return b.Uint32(off, uint32(v))
|
||||
case 8:
|
||||
return b.Uint64(off, v)
|
||||
}
|
||||
panic("invalid use of SymBuffer.SetUint")
|
||||
}
|
||||
|
||||
// Addr sets the pointer-sized address at offset off to refer
|
||||
// to symoff bytes past the start of sym. It returns the offset
|
||||
// just beyond the address.
|
||||
func (b *SymBuffer) Addr(off int, sym goobj.SymID, symoff int64) int {
|
||||
b.reloc = append(b.reloc, goobj.Reloc{
|
||||
Offset: off,
|
||||
Size: b.ptrsize,
|
||||
Sym: sym,
|
||||
Add: int(symoff),
|
||||
Type: D_ADDR,
|
||||
})
|
||||
return off + b.ptrsize
|
||||
}
|
||||
|
||||
// A PCIter implements iteration over PC-data tables.
|
||||
//
|
||||
// var it PCIter
|
||||
// it.Init(p, data)
|
||||
// for it.Init(p, data); !it.Done; it.Next() {
|
||||
// it.Value holds from it.PC up to (but not including) it.NextPC
|
||||
// }
|
||||
// if it.Corrupt {
|
||||
// data was malformed
|
||||
// }
|
||||
//
|
||||
type PCIter struct {
|
||||
PC uint32
|
||||
NextPC uint32
|
||||
Value int32
|
||||
Done bool
|
||||
Corrupt bool
|
||||
p []byte
|
||||
start bool
|
||||
pcquantum uint32
|
||||
}
|
||||
|
||||
// Init initializes the iteration.
|
||||
// On return, if it.Done is true, the iteration is over.
|
||||
// Otherwise it.Value applies in the pc range [it.PC, it.NextPC).
|
||||
func (it *PCIter) Init(p *Prog, buf []byte) {
|
||||
it.p = buf
|
||||
it.PC = 0
|
||||
it.NextPC = 0
|
||||
it.Value = -1
|
||||
it.start = true
|
||||
it.pcquantum = uint32(p.pcquantum)
|
||||
it.Done = false
|
||||
it.Next()
|
||||
}
|
||||
|
||||
// Next steps forward one entry in the table.
|
||||
// On return, if it.Done is true, the iteration is over.
|
||||
// Otherwise it.Value applies in the pc range [it.PC, it.NextPC).
|
||||
func (it *PCIter) Next() {
|
||||
it.PC = it.NextPC
|
||||
if it.Done {
|
||||
return
|
||||
}
|
||||
if len(it.p) == 0 {
|
||||
it.Done = true
|
||||
return
|
||||
}
|
||||
|
||||
// value delta
|
||||
uv, p, ok := decodeVarint(it.p)
|
||||
if !ok {
|
||||
it.Done = true
|
||||
it.Corrupt = true
|
||||
return
|
||||
}
|
||||
it.p = p
|
||||
if uv == 0 && !it.start {
|
||||
it.Done = true
|
||||
return
|
||||
}
|
||||
it.start = false
|
||||
sv := int32(uv)>>1 ^ int32(uv)<<31>>31
|
||||
it.Value += sv
|
||||
|
||||
// pc delta
|
||||
uv, it.p, ok = decodeVarint(it.p)
|
||||
if !ok {
|
||||
it.Done = true
|
||||
it.Corrupt = true
|
||||
return
|
||||
}
|
||||
it.NextPC = it.PC + uv*it.pcquantum
|
||||
}
|
||||
|
||||
// decodeVarint decodes an unsigned varint from p,
|
||||
// reporting the value, the remainder of the data, and
|
||||
// whether the decoding was successful.
|
||||
func decodeVarint(p []byte) (v uint32, rest []byte, ok bool) {
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
c := uint32(p[0])
|
||||
p = p[1:]
|
||||
v |= (c & 0x7F) << shift
|
||||
if c&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, p, true
|
||||
}
|
||||
|
||||
// appendVarint appends an unsigned varint encoding of v to p
|
||||
// and returns the resulting slice.
|
||||
func appendVarint(p []byte, v uint32) []byte {
|
||||
for ; v >= 0x80; v >>= 7 {
|
||||
p = append(p, byte(v)|0x80)
|
||||
}
|
||||
p = append(p, byte(v))
|
||||
return p
|
||||
}
|
334
src/cmd/link/pclntab_test.go
Normal file
334
src/cmd/link/pclntab_test.go
Normal file
@ -0,0 +1,334 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/goobj"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test of pcln table encoding.
|
||||
// testdata/genpcln.go generates an assembly file with
|
||||
// pseudorandom values for the data that pclntab stores.
|
||||
// This test recomputes the same pseudorandom stream
|
||||
// and checks that the final linked binary uses those values
|
||||
// as well.
|
||||
func TestPclntab(t *testing.T) {
|
||||
p := &Prog{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "amd64",
|
||||
Error: func(s string) { t.Error(s) },
|
||||
StartSym: "start",
|
||||
omitRuntime: true,
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
p.link(&buf, "testdata/pclntab.6")
|
||||
if p.NumError > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// The algorithm for computing values here must match
|
||||
// the one in testdata/genpcln.go.
|
||||
for f := 0; f < 3; f++ {
|
||||
file := "input"
|
||||
line := 1
|
||||
rnd := rand.New(rand.NewSource(int64(f)))
|
||||
args := rnd.Intn(100) * 8
|
||||
frame := 32 + rnd.Intn(32)/8*8
|
||||
size := 200 + rnd.Intn(100)*8
|
||||
|
||||
name := fmt.Sprintf("func%d", f)
|
||||
r, off, fargs, fframe, ok := findFunc(t, p, name)
|
||||
if !ok {
|
||||
continue // error already printed
|
||||
}
|
||||
if fargs != args {
|
||||
t.Errorf("%s: args=%d, want %d", name, fargs, args)
|
||||
}
|
||||
if fframe != frame+8 {
|
||||
t.Errorf("%s: frame=%d, want %d", name, fframe, frame+8)
|
||||
}
|
||||
|
||||
// Check FUNCDATA 1.
|
||||
fdata, ok := loadFuncdata(t, r, name, off, 1)
|
||||
if ok {
|
||||
fsym := p.Syms[goobj.SymID{Name: fmt.Sprintf("funcdata%d", f)}]
|
||||
if fsym == nil {
|
||||
t.Errorf("funcdata%d is missing in binary", f)
|
||||
} else if fdata != fsym.Addr {
|
||||
t.Errorf("%s: funcdata 1 = %#x, want %#x", name, fdata, fsym.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Walk code checking pcdata values.
|
||||
spadj := 0
|
||||
pcdata1 := -1
|
||||
pcdata2 := -1
|
||||
|
||||
checkPCSP(t, r, name, off, 0, 0)
|
||||
checkPCData(t, r, name, off, 0, 0, -1)
|
||||
checkPCData(t, r, name, off, 0, 1, -1)
|
||||
checkPCData(t, r, name, off, 0, 2, -1)
|
||||
|
||||
firstpc := 4
|
||||
for i := 0; i < size; i++ {
|
||||
pc := firstpc + i // skip SP adjustment to allocate frame
|
||||
if i >= 0x100 && t.Failed() {
|
||||
break
|
||||
}
|
||||
// Possible SP adjustment.
|
||||
checkPCSP(t, r, name, off, pc, frame+spadj)
|
||||
if rnd.Intn(100) == 0 {
|
||||
checkPCFileLine(t, r, name, off, pc, file, line)
|
||||
checkPCData(t, r, name, off, pc, 1, pcdata1)
|
||||
checkPCData(t, r, name, off, pc, 2, pcdata2)
|
||||
i += 1
|
||||
pc = firstpc + i
|
||||
checkPCFileLine(t, r, name, off, pc-1, file, line)
|
||||
checkPCData(t, r, name, off, pc-1, 1, pcdata1)
|
||||
checkPCData(t, r, name, off, pc-1, 2, pcdata2)
|
||||
checkPCSP(t, r, name, off, pc-1, frame+spadj)
|
||||
|
||||
if spadj <= -32 || spadj < 32 && rnd.Intn(2) == 0 {
|
||||
spadj += 8
|
||||
} else {
|
||||
spadj -= 8
|
||||
}
|
||||
checkPCSP(t, r, name, off, pc, frame+spadj)
|
||||
}
|
||||
|
||||
// Possible PCFile change.
|
||||
if rnd.Intn(100) == 0 {
|
||||
file = fmt.Sprintf("file%d.s", rnd.Intn(10))
|
||||
line = rnd.Intn(100) + 1
|
||||
}
|
||||
|
||||
// Possible PCLine change.
|
||||
if rnd.Intn(10) == 0 {
|
||||
line = rnd.Intn(1000) + 1
|
||||
}
|
||||
|
||||
// Possible PCData $1 change.
|
||||
if rnd.Intn(100) == 0 {
|
||||
pcdata1 = rnd.Intn(1000)
|
||||
}
|
||||
|
||||
// Possible PCData $2 change.
|
||||
if rnd.Intn(100) == 0 {
|
||||
pcdata2 = rnd.Intn(1000)
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
checkPCFileLine(t, r, name, off, 0, file, line)
|
||||
checkPCFileLine(t, r, name, off, pc-1, file, line)
|
||||
}
|
||||
checkPCFileLine(t, r, name, off, pc, file, line)
|
||||
checkPCData(t, r, name, off, pc, 1, pcdata1)
|
||||
checkPCData(t, r, name, off, pc, 2, pcdata2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findFunc finds the function information in the pclntab of p
|
||||
// for the function with the given name.
|
||||
// It returns a symbol reader for pclntab, the offset of the function information
|
||||
// within that symbol, and the args and frame values read out of the information.
|
||||
func findFunc(t *testing.T, p *Prog, name string) (r *SymReader, off, args, frame int, ok bool) {
|
||||
tabsym := p.Syms[goobj.SymID{Name: "pclntab"}]
|
||||
if tabsym == nil {
|
||||
t.Errorf("pclntab is missing in binary")
|
||||
return
|
||||
}
|
||||
|
||||
r = new(SymReader)
|
||||
r.Init(p, tabsym)
|
||||
|
||||
// pclntab must with 8-byte header
|
||||
if r.Uint32(0) != 0xfffffffb || r.Uint8(4) != 0 || r.Uint8(5) != 0 || r.Uint8(6) != uint8(p.pcquantum) || r.Uint8(7) != uint8(p.ptrsize) {
|
||||
t.Errorf("pclntab has incorrect header %.8x", r.data[:8])
|
||||
return
|
||||
}
|
||||
|
||||
sym := p.Syms[goobj.SymID{Name: name}]
|
||||
if sym == nil {
|
||||
t.Errorf("%s is missing in the binary", name)
|
||||
return
|
||||
}
|
||||
|
||||
// index is nfunc addr0 off0 addr1 off1 ... addr_nfunc (sentinel)
|
||||
nfunc := int(r.Addr(8))
|
||||
i := sort.Search(nfunc, func(i int) bool {
|
||||
return r.Addr(8+p.ptrsize*(1+2*i)) >= sym.Addr
|
||||
})
|
||||
if entry := r.Addr(8 + p.ptrsize*(1+2*i)); entry != sym.Addr {
|
||||
indexTab := make([]Addr, 2*nfunc+1)
|
||||
for j := range indexTab {
|
||||
indexTab[j] = r.Addr(8 + p.ptrsize*(1+j))
|
||||
}
|
||||
t.Errorf("pclntab is missing entry for %s (%#x): %#x", name, sym.Addr, indexTab)
|
||||
return
|
||||
}
|
||||
|
||||
off = int(r.Addr(8 + p.ptrsize*(1+2*i+1)))
|
||||
|
||||
// func description at off is
|
||||
// entry addr
|
||||
// nameoff uint32
|
||||
// args uint32
|
||||
// frame uint32
|
||||
// pcspoff uint32
|
||||
// pcfileoff uint32
|
||||
// pclineoff uint32
|
||||
// npcdata uint32
|
||||
// nfuncdata uint32
|
||||
// pcdata npcdata*uint32
|
||||
// funcdata nfuncdata*addr
|
||||
//
|
||||
if entry := r.Addr(off); entry != sym.Addr {
|
||||
t.Errorf("pclntab inconsistent: entry for %s addr=%#x has entry=%#x", name, sym.Addr, entry)
|
||||
return
|
||||
}
|
||||
nameoff := int(r.Uint32(off + p.ptrsize))
|
||||
args = int(r.Uint32(off + p.ptrsize + 1*4))
|
||||
frame = int(r.Uint32(off + p.ptrsize + 2*4))
|
||||
|
||||
fname := r.String(nameoff)
|
||||
if fname != name {
|
||||
t.Errorf("pclntab inconsistent: entry for %s addr=%#x has name %q", name, sym.Addr, fname)
|
||||
}
|
||||
|
||||
ok = true // off, args, frame are usable
|
||||
return
|
||||
}
|
||||
|
||||
// loadFuncdata returns the funcdata #fnum value
|
||||
// loaded from the function information for name.
|
||||
func loadFuncdata(t *testing.T, r *SymReader, name string, off int, fnum int) (Addr, bool) {
|
||||
npcdata := int(r.Uint32(off + r.p.ptrsize + 6*4))
|
||||
nfuncdata := int(r.Uint32(off + r.p.ptrsize + 7*4))
|
||||
if fnum >= nfuncdata {
|
||||
t.Errorf("pclntab(%s): no funcdata %d (only < %d)", name, fnum, nfuncdata)
|
||||
return 0, false
|
||||
}
|
||||
fdataoff := off + r.p.ptrsize + (8+npcdata)*4 + fnum*r.p.ptrsize
|
||||
fdataoff += fdataoff & 4
|
||||
return r.Addr(fdataoff), true
|
||||
}
|
||||
|
||||
// checkPCSP checks that the PCSP table in the function information at off
|
||||
// lists spadj as the sp delta for pc.
|
||||
func checkPCSP(t *testing.T, r *SymReader, name string, off, pc, spadj int) {
|
||||
pcoff := r.Uint32(off + r.p.ptrsize + 3*4)
|
||||
pcval, ok := readPCData(t, r, name, "PCSP", pcoff, pc)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if pcval != spadj {
|
||||
t.Errorf("pclntab(%s): at pc=+%#x, pcsp=%d, want %d", name, pc, pcval, spadj)
|
||||
}
|
||||
}
|
||||
|
||||
// checkPCSP checks that the PCFile and PCLine tables in the function information at off
|
||||
// list file, line as the file name and line number for pc.
|
||||
func checkPCFileLine(t *testing.T, r *SymReader, name string, off, pc int, file string, line int) {
|
||||
pcfileoff := r.Uint32(off + r.p.ptrsize + 4*4)
|
||||
pclineoff := r.Uint32(off + r.p.ptrsize + 5*4)
|
||||
pcfilenum, ok1 := readPCData(t, r, name, "PCFile", pcfileoff, pc)
|
||||
pcline, ok2 := readPCData(t, r, name, "PCLine", pclineoff, pc)
|
||||
if !ok1 || !ok2 {
|
||||
return
|
||||
}
|
||||
nfunc := int(r.Addr(8))
|
||||
filetaboff := r.Uint32(8 + r.p.ptrsize*2*(nfunc+1))
|
||||
nfile := int(r.Uint32(int(filetaboff)))
|
||||
if pcfilenum <= 0 || pcfilenum >= nfile {
|
||||
t.Errorf("pclntab(%s): at pc=+%#x, filenum=%d (invalid; nfile=%d)", name, pc, pcfilenum, nfile)
|
||||
}
|
||||
pcfile := r.String(int(r.Uint32(int(filetaboff) + pcfilenum*4)))
|
||||
if !strings.HasSuffix(pcfile, file) {
|
||||
t.Errorf("pclntab(%s): at pc=+%#x, file=%q, want %q", name, pc, pcfile, file)
|
||||
}
|
||||
if pcline != line {
|
||||
t.Errorf("pclntab(%s): at pc=+%#x, line=%d, want %d", name, pc, pcline, line)
|
||||
}
|
||||
}
|
||||
|
||||
// checkPCData checks that the PCData#pnum table in the function information at off
|
||||
// list val as the value for pc.
|
||||
func checkPCData(t *testing.T, r *SymReader, name string, off, pc, pnum, val int) {
|
||||
pcoff := r.Uint32(off + r.p.ptrsize + (8+pnum)*4)
|
||||
pcval, ok := readPCData(t, r, name, fmt.Sprintf("PCData#%d", pnum), pcoff, pc)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if pcval != val {
|
||||
t.Errorf("pclntab(%s): at pc=+%#x, pcdata#%d=%d, want %d", name, pc, pnum, pcval, val)
|
||||
}
|
||||
}
|
||||
|
||||
// readPCData reads the PCData table offset off
|
||||
// to obtain and return the value associated with pc.
|
||||
func readPCData(t *testing.T, r *SymReader, name, pcdataname string, pcoff uint32, pc int) (int, bool) {
|
||||
var it PCIter
|
||||
for it.Init(r.p, r.data[pcoff:]); !it.Done; it.Next() {
|
||||
if it.PC <= uint32(pc) && uint32(pc) < it.NextPC {
|
||||
return int(it.Value), true
|
||||
}
|
||||
}
|
||||
if it.Corrupt {
|
||||
t.Errorf("pclntab(%s): %s: corrupt pcdata table", name, pcdataname)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// A SymReader provides typed access to the data for a symbol.
|
||||
type SymReader struct {
|
||||
p *Prog
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (r *SymReader) Init(p *Prog, sym *Sym) {
|
||||
seg := sym.Section.Segment
|
||||
off := sym.Addr - seg.VirtAddr
|
||||
data := seg.Data[off : off+Addr(sym.Size)]
|
||||
r.p = p
|
||||
r.data = data
|
||||
}
|
||||
|
||||
func (r *SymReader) Uint8(off int) uint8 {
|
||||
return r.data[off]
|
||||
}
|
||||
|
||||
func (r *SymReader) Uint16(off int) uint16 {
|
||||
return r.p.byteorder.Uint16(r.data[off:])
|
||||
}
|
||||
|
||||
func (r *SymReader) Uint32(off int) uint32 {
|
||||
return r.p.byteorder.Uint32(r.data[off:])
|
||||
}
|
||||
|
||||
func (r *SymReader) Uint64(off int) uint64 {
|
||||
return r.p.byteorder.Uint64(r.data[off:])
|
||||
}
|
||||
|
||||
func (r *SymReader) Addr(off int) Addr {
|
||||
if r.p.ptrsize == 4 {
|
||||
return Addr(r.Uint32(off))
|
||||
}
|
||||
return Addr(r.Uint64(off))
|
||||
}
|
||||
|
||||
func (r *SymReader) String(off int) string {
|
||||
end := off
|
||||
for r.data[end] != '\x00' {
|
||||
end++
|
||||
}
|
||||
return string(r.data[off:end])
|
||||
}
|
@ -66,6 +66,7 @@ type Prog struct {
|
||||
type arch struct {
|
||||
byteorder binary.ByteOrder
|
||||
ptrsize int
|
||||
pcquantum int
|
||||
}
|
||||
|
||||
// A formatter takes care of the details of generating a particular
|
||||
@ -214,5 +215,6 @@ var arches = map[string]arch{
|
||||
"amd64": {
|
||||
byteorder: binary.LittleEndian,
|
||||
ptrsize: 8,
|
||||
pcquantum: 1,
|
||||
},
|
||||
}
|
||||
|
@ -7,5 +7,22 @@
|
||||
|
||||
package main
|
||||
|
||||
import "debug/goobj"
|
||||
|
||||
func (p *Prog) runtime() {
|
||||
p.pclntab()
|
||||
|
||||
// TODO: Implement garbage collection data.
|
||||
p.addSym(&Sym{
|
||||
Sym: &goobj.Sym{
|
||||
SymID: goobj.SymID{Name: "gcdata"},
|
||||
Kind: goobj.SRODATA,
|
||||
},
|
||||
})
|
||||
p.addSym(&Sym{
|
||||
Sym: &goobj.Sym{
|
||||
SymID: goobj.SymID{Name: "gcbss"},
|
||||
Kind: goobj.SRODATA,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
10
src/cmd/link/testdata/Makefile
vendored
Normal file
10
src/cmd/link/testdata/Makefile
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
all: hello.6 pclntab.6
|
||||
|
||||
hello.6: hello.s
|
||||
go tool 6a hello.s
|
||||
|
||||
pclntab.6: pclntab.s
|
||||
go tool 6a pclntab.s
|
||||
|
||||
pclntab.s: genpcln.go
|
||||
go run genpcln.go >pclntab.s
|
112
src/cmd/link/testdata/genpcln.go
vendored
Normal file
112
src/cmd/link/testdata/genpcln.go
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
// 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.
|
||||
|
||||
// This program generates a .s file using a pseudorandom
|
||||
// value stream for the runtime function data.
|
||||
// The pclntab test checks that the linked copy
|
||||
// still has the same pseudorandom value stream.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("// generated by genpcln.go; do not edit\n\n")
|
||||
for f := 0; f < 3; f++ {
|
||||
r := rand.New(rand.NewSource(int64(f)))
|
||||
file := "input"
|
||||
line := 1
|
||||
args := r.Intn(100) * 8
|
||||
frame := 32 + r.Intn(32)/8*8
|
||||
fmt.Printf("#line %d %q\n", line, file)
|
||||
fmt.Printf("TEXT func%d(SB),7,$%d-%d\n", f, frame, args)
|
||||
fmt.Printf("\tFUNCDATA $1, funcdata%d(SB)\n", f)
|
||||
fmt.Printf("#line %d %q\n", line, file)
|
||||
size := 200 + r.Intn(100)*8
|
||||
spadj := 0
|
||||
flushed := 0
|
||||
firstpc := 4
|
||||
flush := func(i int) {
|
||||
for i-flushed >= 10 {
|
||||
fmt.Printf("#line %d %q\n", line, file)
|
||||
fmt.Printf("/*%#04x*/\tMOVQ $0x123456789, AX\n", firstpc+flushed)
|
||||
flushed += 10
|
||||
}
|
||||
for i-flushed >= 5 {
|
||||
fmt.Printf("#line %d %q\n", line, file)
|
||||
fmt.Printf("/*%#04x*/\tMOVL $0x1234567, AX\n", firstpc+flushed)
|
||||
flushed += 5
|
||||
}
|
||||
for i-flushed > 0 {
|
||||
fmt.Printf("#line %d %q\n", line, file)
|
||||
fmt.Printf("/*%#04x*/\tBYTE $0\n", firstpc+flushed)
|
||||
flushed++
|
||||
}
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
// Possible SP adjustment.
|
||||
if r.Intn(100) == 0 {
|
||||
flush(i)
|
||||
fmt.Printf("#line %d %q\n", line, file)
|
||||
if spadj <= -32 || spadj < 32 && r.Intn(2) == 0 {
|
||||
spadj += 8
|
||||
fmt.Printf("/*%#04x*/\tPUSHQ AX\n", firstpc+i)
|
||||
} else {
|
||||
spadj -= 8
|
||||
fmt.Printf("/*%#04x*/\tPOPQ AX\n", firstpc+i)
|
||||
}
|
||||
i += 1
|
||||
flushed = i
|
||||
}
|
||||
|
||||
// Possible PCFile change.
|
||||
if r.Intn(100) == 0 {
|
||||
flush(i)
|
||||
file = fmt.Sprintf("file%d.s", r.Intn(10))
|
||||
line = r.Intn(100) + 1
|
||||
}
|
||||
|
||||
// Possible PCLine change.
|
||||
if r.Intn(10) == 0 {
|
||||
flush(i)
|
||||
line = r.Intn(1000) + 1
|
||||
}
|
||||
|
||||
// Possible PCData $1 change.
|
||||
if r.Intn(100) == 0 {
|
||||
flush(i)
|
||||
fmt.Printf("/*%6s*/\tPCDATA $1, $%d\n", "", r.Intn(1000))
|
||||
}
|
||||
|
||||
// Possible PCData $2 change.
|
||||
if r.Intn(100) == 0 {
|
||||
flush(i)
|
||||
fmt.Printf("/*%6s*/\tPCDATA $2, $%d\n", "", r.Intn(1000))
|
||||
}
|
||||
}
|
||||
flush(size)
|
||||
for spadj < 0 {
|
||||
fmt.Printf("\tPUSHQ AX\n")
|
||||
spadj += 8
|
||||
}
|
||||
for spadj > 0 {
|
||||
fmt.Printf("\tPOPQ AX\n")
|
||||
spadj -= 8
|
||||
}
|
||||
fmt.Printf("\tRET\n")
|
||||
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf("GLOBL funcdata%d(SB), $16\n", f)
|
||||
}
|
||||
|
||||
fmt.Printf("\nTEXT start(SB),7,$0\n")
|
||||
for f := 0; f < 3; f++ {
|
||||
fmt.Printf("\tCALL func%d(SB)\n", f)
|
||||
}
|
||||
fmt.Printf("\tMOVQ $pclntab(SB), AX\n")
|
||||
fmt.Printf("\n\tRET\n")
|
||||
}
|
54
src/cmd/link/testdata/link.hello.darwin.amd64
vendored
54
src/cmd/link/testdata/link.hello.darwin.amd64
vendored
@ -1,35 +1,57 @@
|
||||
00000000 cf fa ed fe 07 00 00 01 03 00 00 00 02 00 00 00 |................|
|
||||
00000010 04 00 00 00 30 02 00 00 01 00 00 00 00 00 00 00 |....0...........|
|
||||
00000010 04 00 00 00 d0 02 00 00 01 00 00 00 00 00 00 00 |................|
|
||||
00000020 19 00 00 00 48 00 00 00 5f 5f 50 41 47 45 5a 45 |....H...__PAGEZE|
|
||||
00000030 52 4f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |RO..............|
|
||||
00000040 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
||||
*
|
||||
00000060 00 00 00 00 00 00 00 00 19 00 00 00 98 00 00 00 |................|
|
||||
00000060 00 00 00 00 00 00 00 00 19 00 00 00 38 01 00 00 |............8...|
|
||||
00000070 5f 5f 54 45 58 54 00 00 00 00 00 00 00 00 00 00 |__TEXT..........|
|
||||
00000080 00 10 00 00 00 00 00 00 20 10 00 00 00 00 00 00 |........ .......|
|
||||
00000090 00 00 00 00 00 00 00 00 20 10 00 00 00 00 00 00 |........ .......|
|
||||
000000a0 07 00 00 00 05 00 00 00 01 00 00 00 00 00 00 00 |................|
|
||||
00000080 00 10 00 00 00 00 00 00 d6 10 00 00 00 00 00 00 |................|
|
||||
00000090 00 00 00 00 00 00 00 00 d6 10 00 00 00 00 00 00 |................|
|
||||
000000a0 07 00 00 00 05 00 00 00 03 00 00 00 00 00 00 00 |................|
|
||||
000000b0 5f 5f 74 65 78 74 00 00 00 00 00 00 00 00 00 00 |__text..........|
|
||||
000000c0 5f 5f 54 45 58 54 00 00 00 00 00 00 00 00 00 00 |__TEXT..........|
|
||||
000000d0 00 20 00 00 00 00 00 00 20 00 00 00 00 00 00 00 |. ...... .......|
|
||||
000000e0 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
||||
000000f0 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
||||
00000100 19 00 00 00 98 00 00 00 5f 5f 44 41 54 41 00 00 |........__DATA..|
|
||||
00000110 00 00 00 00 00 00 00 00 00 30 00 00 00 00 00 00 |.........0......|
|
||||
00000120 0c 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
|
||||
00000130 0c 00 00 00 00 00 00 00 03 00 00 00 03 00 00 00 |................|
|
||||
00000140 01 00 00 00 00 00 00 00 5f 5f 64 61 74 61 00 00 |........__data..|
|
||||
00000150 00 00 00 00 00 00 00 00 5f 5f 44 41 54 41 00 00 |........__DATA..|
|
||||
00000160 00 00 00 00 00 00 00 00 00 30 00 00 00 00 00 00 |.........0......|
|
||||
00000170 0c 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
|
||||
00000100 5f 5f 72 6f 64 61 74 61 00 00 00 00 00 00 00 00 |__rodata........|
|
||||
00000110 5f 5f 54 45 58 54 00 00 00 00 00 00 00 00 00 00 |__TEXT..........|
|
||||
00000120 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ..............|
|
||||
00000130 20 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ...............|
|
||||
*
|
||||
00000190 00 00 00 00 00 00 00 00 05 00 00 00 b8 00 00 00 |................|
|
||||
000001a0 04 00 00 00 2a 00 00 00 00 00 00 00 00 00 00 00 |....*...........|
|
||||
00000150 5f 5f 66 75 6e 63 74 61 62 00 00 00 00 00 00 00 |__functab.......|
|
||||
00000160 5f 5f 54 45 58 54 00 00 00 00 00 00 00 00 00 00 |__TEXT..........|
|
||||
00000170 20 20 00 00 00 00 00 00 b6 00 00 00 00 00 00 00 | ..............|
|
||||
00000180 20 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ...............|
|
||||
*
|
||||
00000220 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
|
||||
000001a0 19 00 00 00 98 00 00 00 5f 5f 44 41 54 41 00 00 |........__DATA..|
|
||||
000001b0 00 00 00 00 00 00 00 00 00 30 00 00 00 00 00 00 |.........0......|
|
||||
000001c0 0c 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
|
||||
000001d0 0c 00 00 00 00 00 00 00 03 00 00 00 03 00 00 00 |................|
|
||||
000001e0 01 00 00 00 00 00 00 00 5f 5f 64 61 74 61 00 00 |........__data..|
|
||||
000001f0 00 00 00 00 00 00 00 00 5f 5f 44 41 54 41 00 00 |........__DATA..|
|
||||
00000200 00 00 00 00 00 00 00 00 00 30 00 00 00 00 00 00 |.........0......|
|
||||
00000210 0c 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
|
||||
*
|
||||
00000230 00 00 00 00 00 00 00 00 05 00 00 00 b8 00 00 00 |................|
|
||||
00000240 04 00 00 00 2a 00 00 00 00 00 00 00 00 00 00 00 |....*...........|
|
||||
*
|
||||
000002c0 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
|
||||
*
|
||||
00001000 bf 01 00 00 00 be 00 30 00 00 ba 0c 00 00 00 b8 |.......0........|
|
||||
00001010 04 00 00 02 0f 05 31 ff b8 01 00 00 02 0f 05 c3 |......1.........|
|
||||
00001020 fb ff ff ff 00 00 01 08 01 00 00 00 00 00 00 00 |................|
|
||||
00001030 00 20 00 00 00 00 00 00 30 00 00 00 00 00 00 00 |. ......0.......|
|
||||
00001040 20 20 00 00 00 00 00 00 80 00 00 00 00 00 00 00 | ..............|
|
||||
00001050 00 20 00 00 00 00 00 00 58 00 00 00 00 00 00 80 |. ......X.......|
|
||||
00001060 08 00 00 00 60 00 00 00 63 00 00 00 66 00 00 00 |....`...c...f...|
|
||||
00001070 00 00 00 00 00 00 00 00 5f 72 74 30 5f 67 6f 00 |........_rt0_go.|
|
||||
00001080 02 20 00 04 20 00 06 05 02 05 02 05 02 05 02 02 |. .. ...........|
|
||||
00001090 02 02 02 05 02 02 02 01 00 00 00 00 00 00 00 00 |................|
|
||||
000010a0 02 00 00 00 88 00 00 00 2f 55 73 65 72 73 2f 72 |......../Users/r|
|
||||
000010b0 73 63 2f 72 73 63 67 6f 2f 73 72 63 2f 63 6d 64 |sc/rscgo/src/cmd|
|
||||
000010c0 2f 6c 64 32 2f 74 65 73 74 64 61 74 61 2f 68 65 |/ld2/testdata/he|
|
||||
000010d0 6c 6c 6f 2e 73 00 00 00 00 00 00 00 00 00 00 00 |llo.s...........|
|
||||
*
|
||||
00002000 68 65 6c 6c 6f 20 77 6f 72 6c 64 0a |hello world.|
|
||||
0000200c
|
||||
|
1751
src/cmd/link/testdata/pclntab.s
vendored
Normal file
1751
src/cmd/link/testdata/pclntab.s
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user