mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 14:36:47 +08:00 
			
		
		
		
	Replaces the Thread.Location method with a ThreadLocation function. All backends implemented the Location method exactly the same way, this change deduplicates it.
		
			
				
	
	
		
			251 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package proc_test
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/binary"
 | 
						|
	"encoding/gob"
 | 
						|
	"flag"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/go-delve/delve/pkg/dwarf/op"
 | 
						|
	"github.com/go-delve/delve/pkg/proc"
 | 
						|
	"github.com/go-delve/delve/pkg/proc/core"
 | 
						|
 | 
						|
	protest "github.com/go-delve/delve/pkg/proc/test"
 | 
						|
)
 | 
						|
 | 
						|
var fuzzEvalExpressionSetup = flag.Bool("fuzzevalexpressionsetup", false, "Performs setup for FuzzEvalExpression")
 | 
						|
 | 
						|
const (
 | 
						|
	fuzzExecutable = "testdata/fuzzexe"
 | 
						|
	fuzzCoredump   = "testdata/fuzzcoredump"
 | 
						|
	fuzzInfoPath   = "testdata/fuzzinfo"
 | 
						|
)
 | 
						|
 | 
						|
type fuzzInfo struct {
 | 
						|
	Loc       *proc.Location
 | 
						|
	Memchunks []memchunk
 | 
						|
	Regs      op.DwarfRegisters
 | 
						|
	Fuzzbuf   []byte
 | 
						|
}
 | 
						|
 | 
						|
// FuzzEvalExpression fuzzes the variables loader and expression evaluator of Delve.
 | 
						|
// To run it, execute the setup first:
 | 
						|
//
 | 
						|
//	go test -run FuzzEvalExpression -fuzzevalexpressionsetup
 | 
						|
//
 | 
						|
// this will create some required files in testdata, the fuzzer can then be run with:
 | 
						|
//
 | 
						|
//	go test -run NONE -fuzz FuzzEvalExpression -v -fuzzminimizetime=0
 | 
						|
func FuzzEvalExpression(f *testing.F) {
 | 
						|
	if *fuzzEvalExpressionSetup {
 | 
						|
		doFuzzEvalExpressionSetup(f)
 | 
						|
	}
 | 
						|
	_, err := os.Stat(fuzzExecutable)
 | 
						|
	if os.IsNotExist(err) {
 | 
						|
		f.Skip("not setup")
 | 
						|
	}
 | 
						|
	bi := proc.NewBinaryInfo("linux", "amd64")
 | 
						|
	assertNoError(bi.LoadBinaryInfo(fuzzExecutable, 0, nil), f, "LoadBinaryInfo")
 | 
						|
	fh, err := os.Open(fuzzInfoPath)
 | 
						|
	assertNoError(err, f, "Open fuzzInfoPath")
 | 
						|
	defer fh.Close()
 | 
						|
	var fi fuzzInfo
 | 
						|
	gob.NewDecoder(fh).Decode(&fi)
 | 
						|
	fi.Regs.ByteOrder = binary.LittleEndian
 | 
						|
	fns, err := bi.FindFunction("main.main")
 | 
						|
	assertNoError(err, f, "FindFunction main.main")
 | 
						|
	fi.Loc.Fn = fns[0]
 | 
						|
	f.Add(fi.Fuzzbuf)
 | 
						|
	f.Fuzz(func(t *testing.T, fuzzbuf []byte) {
 | 
						|
		t.Log("fuzzbuf len", len(fuzzbuf))
 | 
						|
		mem := &core.SplicedMemory{}
 | 
						|
 | 
						|
		// can't work with shrunk input fuzzbufs provided by the fuzzer, resize it
 | 
						|
		// so it is always at least the size we want.
 | 
						|
		lastMemchunk := fi.Memchunks[len(fi.Memchunks)-1]
 | 
						|
		fuzzbufsz := lastMemchunk.Idx + int(lastMemchunk.Sz)
 | 
						|
		if fuzzbufsz > len(fuzzbuf) {
 | 
						|
			newfuzzbuf := make([]byte, fuzzbufsz)
 | 
						|
			copy(newfuzzbuf, fuzzbuf)
 | 
						|
			fuzzbuf = newfuzzbuf
 | 
						|
		}
 | 
						|
 | 
						|
		end := uint64(0)
 | 
						|
 | 
						|
		for _, memchunk := range fi.Memchunks {
 | 
						|
			if end != memchunk.Addr {
 | 
						|
				mem.Add(&zeroReader{}, end, memchunk.Addr-end)
 | 
						|
			}
 | 
						|
			mem.Add(&offsetReader{fuzzbuf[memchunk.Idx:][:memchunk.Sz], memchunk.Addr}, memchunk.Addr, memchunk.Sz)
 | 
						|
			end = memchunk.Addr + memchunk.Sz
 | 
						|
		}
 | 
						|
 | 
						|
		scope := &proc.EvalScope{Location: *fi.Loc, Regs: fi.Regs, Mem: memoryReaderWithFailingWrites{mem}, BinInfo: bi}
 | 
						|
		for _, tc := range getEvalExpressionTestCases() {
 | 
						|
			_, err := scope.EvalExpression(tc.name, pnormalLoadConfig)
 | 
						|
			if err != nil {
 | 
						|
				if strings.Contains(err.Error(), "internal debugger error") {
 | 
						|
					panic(err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func doFuzzEvalExpressionSetup(f *testing.F) {
 | 
						|
	os.Mkdir("testdata", 0700)
 | 
						|
	withTestProcess("testvariables2", f, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		exePath := fixture.Path
 | 
						|
		assertNoError(grp.Continue(), f, "Continue")
 | 
						|
		fh, err := os.Create(fuzzCoredump)
 | 
						|
		assertNoError(err, f, "Creating coredump")
 | 
						|
		var state proc.DumpState
 | 
						|
		p.Dump(fh, 0, &state)
 | 
						|
		assertNoError(state.Err, f, "Dump()")
 | 
						|
		out, err := exec.Command("cp", exePath, fuzzExecutable).CombinedOutput()
 | 
						|
		f.Log(string(out))
 | 
						|
		assertNoError(err, f, "cp")
 | 
						|
	})
 | 
						|
 | 
						|
	// 2. Open the core file and search for the correct goroutine
 | 
						|
 | 
						|
	cgrp, err := core.OpenCore(fuzzCoredump, fuzzExecutable, nil)
 | 
						|
	c := cgrp.Selected
 | 
						|
	assertNoError(err, f, "OpenCore")
 | 
						|
	gs, _, err := proc.GoroutinesInfo(c, 0, 0)
 | 
						|
	assertNoError(err, f, "GoroutinesInfo")
 | 
						|
	found := false
 | 
						|
	for _, g := range gs {
 | 
						|
		if strings.Contains(g.UserCurrent().File, "testvariables2") {
 | 
						|
			c.SwitchGoroutine(g)
 | 
						|
			found = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if !found {
 | 
						|
		f.Errorf("could not find testvariables2 goroutine")
 | 
						|
	}
 | 
						|
 | 
						|
	// 3. Run all the test cases on the core file, register which memory addresses are read
 | 
						|
 | 
						|
	frames, err := proc.GoroutineStacktrace(c, c.SelectedGoroutine(), 2, 0)
 | 
						|
	assertNoError(err, f, "Stacktrace")
 | 
						|
 | 
						|
	mem := c.Memory()
 | 
						|
	loc, _ := proc.ThreadLocation(c.CurrentThread())
 | 
						|
	tmem := &tracingMem{make(map[uint64]int), mem}
 | 
						|
 | 
						|
	scope := &proc.EvalScope{Location: *loc, Regs: frames[0].Regs, Mem: tmem, BinInfo: c.BinInfo()}
 | 
						|
 | 
						|
	for _, tc := range getEvalExpressionTestCases() {
 | 
						|
		scope.EvalExpression(tc.name, pnormalLoadConfig)
 | 
						|
	}
 | 
						|
 | 
						|
	// 4. Copy all the memory we read into a buffer to use as fuzz example (if
 | 
						|
	// we try to use the whole core dump as fuzz example the Go fuzzer crashes)
 | 
						|
 | 
						|
	memchunks, fuzzbuf := tmem.memoryReadsCondensed()
 | 
						|
 | 
						|
	fh, err := os.Create(fuzzInfoPath)
 | 
						|
	assertNoError(err, f, "os.Create")
 | 
						|
	frames[0].Regs.ByteOrder = nil
 | 
						|
	loc.Fn = nil
 | 
						|
	assertNoError(gob.NewEncoder(fh).Encode(fuzzInfo{
 | 
						|
		Loc:       loc,
 | 
						|
		Memchunks: memchunks,
 | 
						|
		Regs:      frames[0].Regs,
 | 
						|
		Fuzzbuf:   fuzzbuf,
 | 
						|
	}), f, "Encode")
 | 
						|
	assertNoError(fh.Close(), f, "Close")
 | 
						|
}
 | 
						|
 | 
						|
type tracingMem struct {
 | 
						|
	read map[uint64]int
 | 
						|
	mem  proc.MemoryReadWriter
 | 
						|
}
 | 
						|
 | 
						|
func (tmem *tracingMem) ReadMemory(b []byte, n uint64) (int, error) {
 | 
						|
	if len(b) > tmem.read[n] {
 | 
						|
		tmem.read[n] = len(b)
 | 
						|
	}
 | 
						|
	return tmem.mem.ReadMemory(b, n)
 | 
						|
}
 | 
						|
 | 
						|
func (tmem *tracingMem) WriteMemory(uint64, []byte) (int, error) {
 | 
						|
	panic("should not be called")
 | 
						|
}
 | 
						|
 | 
						|
type memchunk struct {
 | 
						|
	Addr, Sz uint64
 | 
						|
	Idx      int
 | 
						|
}
 | 
						|
 | 
						|
func (tmem *tracingMem) memoryReadsCondensed() ([]memchunk, []byte) {
 | 
						|
	memoryReads := make([]memchunk, 0, len(tmem.read))
 | 
						|
	for addr, sz := range tmem.read {
 | 
						|
		memoryReads = append(memoryReads, memchunk{addr, uint64(sz), 0})
 | 
						|
	}
 | 
						|
	sort.Slice(memoryReads, func(i, j int) bool { return memoryReads[i].Addr < memoryReads[j].Addr })
 | 
						|
 | 
						|
	memoryReadsCondensed := make([]memchunk, 0, len(memoryReads))
 | 
						|
	for _, v := range memoryReads {
 | 
						|
		if len(memoryReadsCondensed) != 0 {
 | 
						|
			last := &memoryReadsCondensed[len(memoryReadsCondensed)-1]
 | 
						|
			if last.Addr+last.Sz >= v.Addr {
 | 
						|
				end := v.Addr + v.Sz
 | 
						|
				sz2 := end - last.Addr
 | 
						|
				if sz2 > last.Sz {
 | 
						|
					last.Sz = sz2
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
		memoryReadsCondensed = append(memoryReadsCondensed, v)
 | 
						|
	}
 | 
						|
 | 
						|
	fuzzbuf := []byte{}
 | 
						|
	for i := range memoryReadsCondensed {
 | 
						|
		buf := make([]byte, memoryReadsCondensed[i].Sz)
 | 
						|
		tmem.mem.ReadMemory(buf, memoryReadsCondensed[i].Addr)
 | 
						|
		memoryReadsCondensed[i].Idx = len(fuzzbuf)
 | 
						|
		fuzzbuf = append(fuzzbuf, buf...)
 | 
						|
	}
 | 
						|
 | 
						|
	return memoryReadsCondensed, fuzzbuf
 | 
						|
}
 | 
						|
 | 
						|
type offsetReader struct {
 | 
						|
	buf  []byte
 | 
						|
	addr uint64
 | 
						|
}
 | 
						|
 | 
						|
func (or *offsetReader) ReadMemory(buf []byte, addr uint64) (int, error) {
 | 
						|
	copy(buf, or.buf[addr-or.addr:][:len(buf)])
 | 
						|
	return len(buf), nil
 | 
						|
}
 | 
						|
 | 
						|
type memoryReaderWithFailingWrites struct {
 | 
						|
	proc.MemoryReader
 | 
						|
}
 | 
						|
 | 
						|
func (w memoryReaderWithFailingWrites) WriteMemory(uint64, []byte) (int, error) {
 | 
						|
	panic("should not be called")
 | 
						|
}
 | 
						|
 | 
						|
type zeroReader struct{}
 | 
						|
 | 
						|
func (*zeroReader) ReadMemory(b []byte, addr uint64) (int, error) {
 | 
						|
	for i := range b {
 | 
						|
		b[i] = 0
 | 
						|
	}
 | 
						|
	return len(b), nil
 | 
						|
}
 | 
						|
 | 
						|
func (*zeroReader) WriteMemory(b []byte, addr uint64) (int, error) {
 | 
						|
	panic("should not be called")
 | 
						|
}
 |