Skip to content

Commit

Permalink
pkg,service: support 386 on linux
Browse files Browse the repository at this point in the history
Implement debugging function for 386 on linux with reference to AMD64.
There are a few remaining problems that need to be solved in another time.

1. The stacktrace of cgo are not exactly as expected.
2. Not implement `core` for now.
3. Not implement `call` for now. Can't not find `runtime·debugCallV1` or
   similar function in $GOROOT/src/runtime/asm_386.s.

Update go-delve#20
  • Loading branch information
chainhelen committed Feb 26, 2020
1 parent 7560c33 commit a2d3949
Show file tree
Hide file tree
Showing 44 changed files with 1,371 additions and 277 deletions.
2 changes: 2 additions & 0 deletions _fixtures/cgostacktest/hello.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#ifdef __amd64__
#define BREAKPOINT asm("int3;")
#elif __i386__
#define BREAKPOINT asm("int3;")
#elif __aarch64__
#define BREAKPOINT asm("brk 0;")
#endif
Expand Down
7 changes: 6 additions & 1 deletion pkg/dwarf/frame/entries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import (
"io/ioutil"
"os"
"testing"
"unsafe"
)

func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}

func TestFDEForPC(t *testing.T) {
frames := NewFrameIndex()
frames = append(frames,
Expand Down Expand Up @@ -62,7 +67,7 @@ func BenchmarkFDEForPC(b *testing.B) {
if err != nil {
b.Fatal(err)
}
fdes := Parse(data, binary.BigEndian, 0)
fdes := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())

for i := 0; i < b.N; i++ {
// bench worst case, exhaustive search
Expand Down
15 changes: 10 additions & 5 deletions pkg/dwarf/frame/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ type parseContext struct {
common *CommonInformationEntry
frame *FrameDescriptionEntry
length uint32
ptrSize int
}

// Parse takes in data (a byte slice) and returns a slice of
// commonInformationEntry structures. Each commonInformationEntry
// has a slice of frameDescriptionEntry structures.
func Parse(data []byte, order binary.ByteOrder, staticBase uint64) FrameDescriptionEntries {
func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int) FrameDescriptionEntries {
var (
buf = bytes.NewBuffer(data)
pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase}
pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase, ptrSize: ptrSize}
)

for fn := parselength; buf.Len() != 0; {
Expand Down Expand Up @@ -68,10 +69,14 @@ func parselength(ctx *parseContext) parsefunc {
}

func parseFDE(ctx *parseContext) parsefunc {
var num uint64
r := ctx.buf.Next(int(ctx.length))

ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) + ctx.staticBase
ctx.frame.size = binary.LittleEndian.Uint64(r[8:16])
reader := bytes.NewReader(r)
num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
ctx.frame.begin = num + ctx.staticBase
num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
ctx.frame.size = num

// Insert into the tree after setting address range begin
// otherwise compares won't work.
Expand All @@ -80,7 +85,7 @@ func parseFDE(ctx *parseContext) parsefunc {
// The rest of this entry consists of the instructions
// so we can just grab all of the data from the buffer
// cursor to length.
ctx.frame.Instructions = r[16:]
ctx.frame.Instructions = r[2*ctx.ptrSize:]
ctx.length = 0

return parselength
Expand Down
10 changes: 5 additions & 5 deletions pkg/dwarf/frame/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (

func TestParseCIE(t *testing.T) {
ctx := &parseContext{
buf : bytes.NewBuffer([]byte{3,0,1,124,16,12,7,8,5,16,2,0,36,0,0,0,0,0,0,0,0,16,64,0,0,0,0,0}),
common: &CommonInformationEntry{Length:12},
buf: bytes.NewBuffer([]byte{3, 0, 1, 124, 16, 12, 7, 8, 5, 16, 2, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 16, 64, 0, 0, 0, 0, 0}),
common: &CommonInformationEntry{Length: 12},
length: 12,
}
_ = parseCIE(ctx)
Expand All @@ -35,8 +35,8 @@ func TestParseCIE(t *testing.T) {
if common.ReturnAddressRegister != 16 {
t.Fatalf("Expected ReturnAddressRegister 16, but get %d", common.ReturnAddressRegister)
}
initialInstructions := []byte{12,7,8,5,16,2,0}
if !bytes.Equal(common.InitialInstructions, initialInstructions){
initialInstructions := []byte{12, 7, 8, 5, 16, 2, 0}
if !bytes.Equal(common.InitialInstructions, initialInstructions) {
t.Fatalf("Expected InitialInstructions %v, but get %v", initialInstructions, common.InitialInstructions)
}
}
Expand All @@ -56,6 +56,6 @@ func BenchmarkParse(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
Parse(data, binary.BigEndian, 0)
Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
}
}
8 changes: 5 additions & 3 deletions pkg/dwarf/line/line_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type DebugLineInfo struct {

// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
normalizeBackslash bool
ptrSize int
}

type FileEntry struct {
Expand All @@ -53,26 +54,27 @@ type FileEntry struct {
type DebugLines []*DebugLineInfo

// ParseAll parses all debug_line segments found in data
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) DebugLines {
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
)

// We have to parse multiple file name tables here.
for buf.Len() > 0 {
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash))
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash, ptrSize))
}

return lines
}

// Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) *DebugLineInfo {
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
dbl := new(DebugLineInfo)
dbl.Logf = logfn
dbl.staticBase = staticBase
dbl.ptrSize = ptrSize
dbl.Lookup = make(map[string]*FileEntry)
dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)

Expand Down
28 changes: 16 additions & 12 deletions pkg/dwarf/line/line_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"
"testing"
"time"
"unsafe"

"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/pkg/profile"
Expand Down Expand Up @@ -65,10 +66,13 @@ const (
opcodeBaseGo111 uint8 = 11
)

func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}

func testDebugLinePrologueParser(p string, t *testing.T) {
data := grabDebugLineSection(p, t)
debugLines := ParseAll(data, nil, 0, true)

debugLines := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
mainFileFound := false

for _, dbl := range debugLines {
Expand Down Expand Up @@ -117,7 +121,7 @@ func testDebugLinePrologueParser(p string, t *testing.T) {
if ln.Path == "<autogenerated>" {
continue
}
if _, err := os.Stat(ln.Path); err != nil {
if _, err := os.Stat(ln.Path); err != nil {
t.Fatalf("Invalid input path %s: %s\n", ln.Path, err)
}
}
Expand Down Expand Up @@ -173,7 +177,7 @@ func BenchmarkLineParser(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ParseAll(data, nil, 0, true)
_ = ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
}
}

Expand All @@ -188,15 +192,15 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
tb.Fatal("Could not read test data", err)
}

return ParseAll(data, nil, 0, true)
return ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
}

func BenchmarkStateMachine(b *testing.B) {
lineInfos := loadBenchmarkData(b)
b.ResetTimer()

for i := 0; i < b.N; i++ {
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions, ptrSizeByRuntimeArch())

for {
if err := sm.next(); err != nil {
Expand All @@ -223,7 +227,7 @@ func setupTestPCToLine(t testing.TB, lineInfos DebugLines) ([]pctolineEntry, []u
entries := []pctolineEntry{}
basePCs := []uint64{}

sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions, ptrSizeByRuntimeArch())
for {
if err := sm.next(); err != nil {
break
Expand Down Expand Up @@ -299,7 +303,7 @@ func BenchmarkPCToLine(b *testing.B) {
}
}

func TestDebugLineC(t * testing.T) {
func TestDebugLineC(t *testing.T) {
p, err := filepath.Abs("../../../_fixtures/debug_line_c_data")
if err != nil {
t.Fatal("Could not find test data", p, err)
Expand All @@ -310,15 +314,15 @@ func TestDebugLineC(t * testing.T) {
t.Fatal("Could not read test data", err)
}

parsed := ParseAll(data, nil, 0, true)
parsed := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())

if len(parsed) == 0 {
t.Fatal("Parser result is empty")
}

file := []string{"main.c", "/mnt/c/develop/delve/_fixtures/main.c" ,"/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h",
"/usr/include/x86_64-linux-gnu/bits/types.h" ,"/usr/include/x86_64-linux-gnu/bits/libio.h", "/usr/include/stdio.h",
"/usr/include/x86_64-linux-gnu/bits/sys_errlist.h"}
file := []string{"main.c", "/mnt/c/develop/delve/_fixtures/main.c", "/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h",
"/usr/include/x86_64-linux-gnu/bits/types.h", "/usr/include/x86_64-linux-gnu/bits/libio.h", "/usr/include/stdio.h",
"/usr/include/x86_64-linux-gnu/bits/sys_errlist.h"}

for _, ln := range parsed {
if len(ln.FileNames) == 0 {
Expand Down
36 changes: 23 additions & 13 deletions pkg/dwarf/line/state_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type StateMachine struct {
lastAddress uint64
lastFile string
lastLine int
ptrSize int
}

type opcodeKind uint8
Expand Down Expand Up @@ -102,13 +103,23 @@ var extendedopcodes = map[byte]opcodefn{
DW_LINE_define_file: definefile,
}

func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *StateMachine {
opcodes := make([]opcodefn, len(standardopcodes)+1)
opcodes[0] = execExtendedOpcode
for op := range standardopcodes {
opcodes[op] = standardopcodes[op]
}
sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase, lastAddress: ^uint64(0)}
sm := &StateMachine{
dbl: dbl,
file: dbl.FileNames[0].Path,
line: 1,
buf: bytes.NewBuffer(instructions),
opcodes: opcodes,
isStmt: dbl.Prologue.InitialIsStmt == uint8(1),
address: dbl.staticBase,
lastAddress: ^uint64(0),
ptrSize: ptrSize,
}
return sm
}

Expand All @@ -121,7 +132,7 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64)

var (
lastAddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
)

for {
Expand Down Expand Up @@ -153,7 +164,7 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile stri
var (
pcs []uint64
lastaddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
)

for {
Expand Down Expand Up @@ -189,7 +200,7 @@ func (sm *StateMachine) copy() *StateMachine {
func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) {
sm = lineInfo.stateMachineCache[basePC]
if sm == nil {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
sm.PCToLine(basePC)
lineInfo.stateMachineCache[basePC] = sm
}
Expand Down Expand Up @@ -219,7 +230,7 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
func (lineInfo *DebugLineInfo) stateMachineFor(basePC, pc uint64) *StateMachine {
var sm *StateMachine
if basePC == 0 {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
} else {
// Try to use the last state machine that we used for this function, if
// there isn't one or it's already past pc try to clone the cached state
Expand Down Expand Up @@ -274,7 +285,7 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
return 0
}

sm := newStateMachine(lineInfo, lineInfo.Instructions)
sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)

// if no instruction marked is_stmt is found fallback to the first
// instruction assigned to the filename:line.
Expand Down Expand Up @@ -393,7 +404,7 @@ func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, f
}

func (lineInfo *DebugLineInfo) FirstFile() string {
sm := newStateMachine(lineInfo, lineInfo.Instructions)
sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
for {
if sm.valid {
return sm.file
Expand Down Expand Up @@ -530,11 +541,10 @@ func endsequence(sm *StateMachine, buf *bytes.Buffer) {
}

func setaddress(sm *StateMachine, buf *bytes.Buffer) {
//TODO: this needs to be changed to support 32bit architectures (addr must be target arch pointer sized) -- also target endianness
var addr uint64

binary.Read(buf, binary.LittleEndian, &addr)

addr, err := util.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize)
if err != nil {
panic(err)
}
sm.address = addr + sm.dbl.staticBase
}

Expand Down
Loading

0 comments on commit a2d3949

Please sign in to comment.