delve/pkg/proc/variables_fuzz_test.go
Alessandro Arzilli d963eb1057
proc: read context from sigtrampgo, fixes TestCgoStacktrace2 on 1.21 (#3401)
* logflags,proc: flag to log stacktrace execution

Add a log flag to write logs about what the stacktracer does.

* proc: read context from sigtrampgo, fixes TestCgoStacktrace2 on 1.21

Changes stacktrace code to read the signal context from the arguments
of sigtrampgo.
Also changes the automatic fatalthrow breakpoint for go 1.21.
In combination these two changes fix TestCgoStacktrace2 on Go 1.21 on
various platforms.
2023-06-27 09:33:07 -07:00

246 lines
6.7 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() {
scope.EvalExpression(tc.name, pnormalLoadConfig)
}
})
}
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, _ := c.CurrentThread().Location()
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")
}