diff --git a/cmd/grf/cmds/commands.go b/cmd/grf/cmds/commands.go index 616a793..b22910f 100644 --- a/cmd/grf/cmds/commands.go +++ b/cmd/grf/cmds/commands.go @@ -41,6 +41,9 @@ var ( // verbose is whether to log verbose info, like debug logs. verbose bool + + // useMarkStub is whether to use mark stub for object reference analysis. + useMarkStub bool ) // New returns an initialized command tree. @@ -75,6 +78,7 @@ You'll have to wait for goref until it outputs 'successfully output to ...', or } attachCommand.Flags().IntVar(&maxRefDepth, "max-depth", 0, "max reference depth shown by pprof") attachCommand.Flags().StringVarP(&outFile, "out", "o", "grf.out", "output file name") + attachCommand.Flags().BoolVar(&useMarkStub, "usemarkstub", false, "use mark stub for partial object reference analysis") rootCommand.AddCommand(attachCommand) coreCommand := &cobra.Command{ @@ -130,14 +134,14 @@ func attachCmd(_ *cobra.Command, args []string) { if len(args) > 1 { exeFile = args[1] } - os.Exit(execute(pid, exeFile, "", outFile, conf)) + os.Exit(execute(pid, exeFile, "", outFile, useMarkStub, conf)) } func coreCmd(_ *cobra.Command, args []string) { - os.Exit(execute(0, args[0], args[1], outFile, conf)) + os.Exit(execute(0, args[0], args[1], outFile, useMarkStub, conf)) } -func execute(attachPid int, exeFile, coreFile, outFile string, conf *config.Config) int { +func execute(attachPid int, exeFile, coreFile, outFile string, useMarkStub bool, conf *config.Config) int { if verbose { if err := logflags.Setup(verbose, "", ""); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) @@ -175,7 +179,7 @@ func execute(attachPid int, exeFile, coreFile, outFile string, conf *config.Conf func() { tg, unlock := dbg.LockTargetGroup() defer unlock() - _, err = myproc.ObjectReference(tg.Selected, outFile) + _, err = myproc.ObjectReference(tg.Selected, outFile, useMarkStub) }() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) diff --git a/cmd/grf/cmds/self_attach.go b/cmd/grf/cmds/self_attach.go new file mode 100644 index 0000000..1f14703 --- /dev/null +++ b/cmd/grf/cmds/self_attach.go @@ -0,0 +1,27 @@ +package cmds + +import ( + "fmt" + "os" + "os/exec" + "runtime" + "strconv" +) + +// AttachSelf create new grf process to attach to caller's pid +func AttachSelf(filename string) error { + // check grf first + binName := "grf" + if runtime.GOOS == "windows" { + binName = "grf.exe" + } + + grfPath, err := exec.LookPath(binName) + if err != nil { + return fmt.Errorf("grf not found in PATH: %v", err) + } + + // execute grf + cmd := exec.Command(grfPath, "attach", strconv.Itoa(os.Getpid()), "-o", filename) + return cmd.Run() +} diff --git a/pkg/proc/heap.go b/pkg/proc/heap.go index 527f680..b7fd7c9 100644 --- a/pkg/proc/heap.go +++ b/pkg/proc/heap.go @@ -165,6 +165,8 @@ type HeapScope struct { greenTeaGCEnabled bool spanInlineMarkBitsSize int64 inlineMarkSpanPages map[Address]struct{} + // stub address + markStubAddr Address // address for pkg/stub.markStub } func (s *HeapScope) readHeap() error { @@ -422,7 +424,7 @@ func (s *HeapScope) readBitmapFunc(heapArena *region) func(heapArena *region, mi } } -// base must be the base address of an object in then span +// base must be the base address of an object in the span func (s *HeapScope) copyGCMask(sp *spanInfo, base Address) Address { if !s.enableAllocHeader { return base @@ -866,3 +868,13 @@ func (s *HeapScope) rtConstant(name string) int64 { } return 0 } + +func (s *HeapScope) readMarkStubAddr() error { + tmp, err := s.scope.EvalExpression("stub.markStub", loadSingleValue) + if err != nil { + return err + } + markStub := toRegion(tmp, s.bi) + s.markStubAddr = markStub.a + return nil +} diff --git a/pkg/proc/reference.go b/pkg/proc/reference.go index ee7dc0e..b76ac82 100644 --- a/pkg/proc/reference.go +++ b/pkg/proc/reference.go @@ -21,6 +21,7 @@ import ( "os" "reflect" "regexp" + "strings" "github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/logflags" @@ -99,12 +100,13 @@ func (s *ObjRefScope) findObject(addr Address, typ godwarf.Type, mem proc.Memory return } -func (s *HeapScope) markObject(addr Address, mem proc.MemoryReadWriter) (size, count int64) { +func (s *HeapScope) markObject(addr Address, mem proc.MemoryReadWriter, mc markContext) (size, count int64) { type stackEntry struct { - addr Address + addr Address + enableRecord bool } var stack []stackEntry - stack = append(stack, stackEntry{addr}) + stack = append(stack, stackEntry{addr, !mc.useMarkStub || mc.enableRecord}) for len(stack) > 0 { entry := stack[len(stack)-1] @@ -120,11 +122,15 @@ func (s *HeapScope) markObject(addr Address, mem proc.MemoryReadWriter) (size, c continue // already found } realBase := s.copyGCMask(sp, base) - size += sp.elemSize - count++ + if entry.enableRecord { + size += sp.elemSize + count++ + } hb := newGCBitsIterator(realBase, sp.elemEnd(base), sp.base, sp.ptrMask) var cmem proc.MemoryReadWriter + stubInObj := false // whether object contains mark stub. + startIdx := len(stack) for { ptr := hb.nextPtr(true) if ptr == 0 { @@ -137,7 +143,20 @@ func (s *HeapScope) markObject(addr Address, mem proc.MemoryReadWriter) (size, c if err != nil { continue } - stack = append(stack, stackEntry{Address(nptr)}) + if mc.useMarkStub { + if nptr == uint64(s.markStubAddr) { + stubInObj = true + } + stack = append(stack, stackEntry{Address(nptr), entry.enableRecord}) + } else { + stack = append(stack, stackEntry{Address(nptr), true}) + } + } + if stubInObj { + // mark all objects referenced by current object + for i := startIdx; i < len(stack); i++ { + stack[i].enableRecord = true + } } } return @@ -185,7 +204,7 @@ func (s *ObjRefScope) readFuncValueInfo(addr Address, mem proc.MemoryReadWriter) return s.readClosureInfo(Address(closureAddr), proc.DereferenceMemory(mem)) } -func (s *ObjRefScope) scanClosureInfo(x *ReferenceVariable, info *funcValueInfo, mem proc.MemoryReadWriter, idx *pprofIndex) { +func (s *ObjRefScope) scanClosureInfo(x *ReferenceVariable, info *funcValueInfo, mem proc.MemoryReadWriter, idx *pprofIndex, mc markContext) { if info == nil || info.closureAddr == 0 { return } @@ -198,17 +217,17 @@ func (s *ObjRefScope) scanClosureInfo(x *ReferenceVariable, info *funcValueInfo, if info.name != "" { closure.Name = info.name } - _ = s.findRef(closure, idx) + _ = s.findRef(closure, idx, mc) x.size += closure.size x.count += closure.count rvpool.Put(closure) } } -func (s *ObjRefScope) scanIndirectFuncValue(x *ReferenceVariable, addr Address, mem proc.MemoryReadWriter, idx *pprofIndex) bool { +func (s *ObjRefScope) scanIndirectFuncValue(x *ReferenceVariable, addr Address, mem proc.MemoryReadWriter, idx *pprofIndex, mc markContext) bool { if info, err := s.readFuncValueInfo(addr, mem); err == nil && info != nil && info.name != "" { if y := s.findObject(addr, new(godwarf.FuncType), mem); y != nil { - _ = s.findRef(y, idx) + _ = s.findRef(y, idx, mc) x.size += y.size x.count += y.count rvpool.Put(y) @@ -216,13 +235,13 @@ func (s *ObjRefScope) scanIndirectFuncValue(x *ReferenceVariable, addr Address, return true } if info, err := s.readClosureInfo(addr, mem); err == nil && info != nil && info.name != "" { - s.scanClosureInfo(x, info, mem, idx) + s.scanClosureInfo(x, info, mem, idx, mc) return true } return false } -func (s *ObjRefScope) scanFuncValue(x *ReferenceVariable, idx *pprofIndex) error { +func (s *ObjRefScope) scanFuncValue(x *ReferenceVariable, idx *pprofIndex, mc markContext) error { if _, err := x.readPointer(x.Addr); err != nil { return err } @@ -230,11 +249,16 @@ func (s *ObjRefScope) scanFuncValue(x *ReferenceVariable, idx *pprofIndex) error if err != nil || info == nil || info.closureAddr == 0 { return err } - s.scanClosureInfo(x, info, proc.DereferenceMemory(x.mem), idx) + s.scanClosureInfo(x, info, proc.DereferenceMemory(x.mem), idx, mc) return nil } -func (s *ObjRefScope) finalMark(idx *pprofIndex, hb *gcMaskBitIterator) { +type markContext struct { + useMarkStub bool + enableRecord bool +} + +func (s *ObjRefScope) finalMark(idx *pprofIndex, hb *gcMaskBitIterator, mc markContext) { var ptr Address var size, count int64 var cmem proc.MemoryReadWriter @@ -250,7 +274,7 @@ func (s *ObjRefScope) finalMark(idx *pprofIndex, hb *gcMaskBitIterator) { if err != nil { continue } - size_, count_ := s.markObject(Address(ptr), cmem) + size_, count_ := s.markObject(Address(ptr), cmem, mc) size += size_ count += count_ } @@ -258,7 +282,7 @@ func (s *ObjRefScope) finalMark(idx *pprofIndex, hb *gcMaskBitIterator) { } // findRef finds sub refs of x, and records them to pprof buffer. -func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) { +func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex, mc markContext) (err error) { if x.Name != "" { if idx != nil && idx.depth >= maxRefDepth { // No scan for depth >= maxRefDepth, as it could lead to uncontrollable reference chain depths. @@ -267,7 +291,9 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) } // For array elem / map kv / struct field type, record them. idx = idx.pushHead(s.pb, x.Name) - defer func() { s.record(idx, x.size, x.count) }() + if !mc.useMarkStub || mc.enableRecord { + defer func() { s.record(idx, x.size, x.count) }() + } } else { // For newly found heap objects, check if all pointers have been scanned by the DWARF searching. defer func() { @@ -285,7 +311,7 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) return } if y := s.findObject(Address(ptrval), godwarf.ResolveTypedef(typ.Type), proc.DereferenceMemory(x.mem)); y != nil { - _ = s.findRef(y, idx) + _ = s.findRef(y, idx, mc) // flatten reference x.size += y.size x.count += y.count @@ -318,7 +344,7 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) } } if z := s.findObject(Address(zptrval), fakeArrayType(chanLen, typ.ElemType), y.mem); z != nil { - _ = s.findRef(z, idx) + _ = s.findRef(z, idx, mc) x.size += z.size x.count += z.count rvpool.Put(z) @@ -339,7 +365,7 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) // find key ref if key := it.key(); key != nil { key.Name = "$mapkey. (" + key.RealType.String() + ")" - if err := s.findRef(key, idx); errors.Is(err, errOutOfRange) { + if err := s.findRef(key, idx, mc); errors.Is(err, errOutOfRange) { continue } rvpool.Put(key) @@ -347,7 +373,7 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) // find val ref if val := it.value(); val != nil { val.Name = "$mapval. (" + val.RealType.String() + ")" - if err := s.findRef(val, idx); errors.Is(err, errOutOfRange) { + if err := s.findRef(val, idx, mc); errors.Is(err, errOutOfRange) { continue } rvpool.Put(val) @@ -375,7 +401,7 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) return } if y := s.findObject(Address(strAddr), fakeArrayType(strLen, &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "byte", ReflectKind: reflect.Uint8}, BitSize: 8, BitOffset: 0}}), proc.DereferenceMemory(x.mem)); y != nil { - _ = s.findRef(y, idx) + _ = s.findRef(y, idx, mc) x.size += y.size x.count += y.count rvpool.Put(y) @@ -394,7 +420,7 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) } } if y := s.findObject(Address(base), fakeArrayType(cap_, typ.ElemType), proc.DereferenceMemory(x.mem)); y != nil { - _ = s.findRef(y, idx) + _ = s.findRef(y, idx, mc) x.size += y.size x.count += y.count rvpool.Put(y) @@ -427,23 +453,38 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) } rvpool.Put(data) mem := proc.DereferenceMemory(x.mem) - if s.scanIndirectFuncValue(x, Address(ptrval), mem, idx) { + if s.scanIndirectFuncValue(x, Address(ptrval), mem, idx, mc) { return } if ityp == nil { ityp = new(godwarf.VoidType) } if y := s.findObject(Address(ptrval), ityp, mem); y != nil { - _ = s.findRef(y, idx) + _ = s.findRef(y, idx, mc) x.size += y.size x.count += y.count rvpool.Put(y) } case *godwarf.StructType: typ = s.specialStructTypes(typ) + newmc := mc + if newmc.useMarkStub { + for _, field := range typ.Field { + if _, ok := field.Type.(*godwarf.PtrType); ok && field.Name == "markStub" { + y := x.toField(field) + ptrval, err := x.readPointer(y.Addr) + if err != nil { + continue + } + if ptrval == uint64(s.markStubAddr) { + newmc.enableRecord = true + } + } + } + } for _, field := range typ.Field { y := x.toField(field) - err = s.findRef(y, idx) + err = s.findRef(y, idx, newmc) rvpool.Put(y) if errors.Is(err, errOutOfRange) { break @@ -456,17 +497,17 @@ func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) (err error) } for i := int64(0); i < typ.Count; i++ { y := x.arrayAccess(i) - err = s.findRef(y, idx) + err = s.findRef(y, idx, mc) rvpool.Put(y) if errors.Is(err, errOutOfRange) { break } } case *godwarf.FuncType: - err = s.scanFuncValue(x, idx) + err = s.scanFuncValue(x, idx, mc) case *finalizePtrType: if y := s.findObject(x.Addr, new(godwarf.VoidType), x.mem); y != nil { - _ = s.findRef(y, idx) + _ = s.findRef(y, idx, mc) x.size += y.size x.count += y.count rvpool.Put(y) @@ -539,7 +580,7 @@ var loadSingleValue = proc.LoadConfig{} // ObjectReference scanning goroutine stack and global vars to search all heap objects they reference, // and outputs the reference relationship to the filename with pprof format. // Returns the ObjRefScope for testing purposes. -func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { +func ObjectReference(t *proc.Target, filename string, useMarkStub bool) (*ObjRefScope, error) { scope, err := proc.ThreadScope(t, t.CurrentThread()) if err != nil { return nil, err @@ -550,6 +591,12 @@ func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { if err != nil { return nil, err } + if useMarkStub { + err = heapScope.readMarkStubAddr() + if err != nil { + return nil, err + } + } f, err := os.Create(filename) if err != nil { @@ -574,7 +621,13 @@ func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { continue } rv := ToReferenceVariable(pv) - s.findRef(rv, nil) + s.findRef(rv, nil, markContext{useMarkStub: useMarkStub, enableRecord: false}) + if useMarkStub { + if strings.Contains(rv.Name, "stub.markStub") { + idx := (*pprofIndex)(nil).pushHead(s.pb, rv.Name) + s.record(idx, 1, 1) + } + } rvpool.Put(rv) } @@ -608,7 +661,7 @@ func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { } l.Name = sf[i].Current.Fn.Name + "." + l.Name rv := ToReferenceVariable(l) - s.findRef(rv, nil) + s.findRef(rv, nil, markContext{useMarkStub: useMarkStub, enableRecord: false}) rvpool.Put(rv) } } @@ -621,24 +674,24 @@ func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { for _, fin := range heapScope.finalizers { // scan object rv := newReferenceVariable(fin.p, "runtime.SetFinalizer.obj", new(finalizePtrType), s.mem, nil) - s.findRef(rv, nil) + s.findRef(rv, nil, markContext{useMarkStub: useMarkStub, enableRecord: false}) rvpool.Put(rv) // scan finalizer rv = newReferenceVariable(fin.fn, "runtime.SetFinalizer.fn", new(godwarf.FuncType), s.mem, nil) - s.findRef(rv, nil) + s.findRef(rv, nil, markContext{useMarkStub: useMarkStub, enableRecord: false}) rvpool.Put(rv) } // Cleanups for _, clu := range heapScope.cleanups { // scan cleanup rv := newReferenceVariable(clu.fn, "runtime.AddCleanup.fn", new(godwarf.FuncType), s.mem, nil) - s.findRef(rv, nil) + s.findRef(rv, nil, markContext{useMarkStub: useMarkStub, enableRecord: false}) rvpool.Put(rv) } // final mark with gc mask bits for _, param := range s.finalMarks { - s.finalMark(param.idx, param.hb) + s.finalMark(param.idx, param.hb, markContext{useMarkStub: useMarkStub, enableRecord: false}) } s.finalMarks = nil @@ -651,7 +704,7 @@ func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { for j := len(g.frames) - 1; j >= i; j-- { idx = idx.pushHead(s.pb, g.frames[j].funcName) } - s.finalMark(idx, it) + s.finalMark(idx, it, markContext{useMarkStub: useMarkStub, enableRecord: false}) } } } @@ -661,14 +714,14 @@ func ObjectReference(t *proc.Target, filename string) (*ObjRefScope, error) { it := &(seg.gcMaskBitIterator) if it.nextPtr(false) != 0 { idx := (*pprofIndex)(nil).pushHead(s.pb, fmt.Sprintf("bss segment[%d]", i)) - s.finalMark(idx, it) + s.finalMark(idx, it, markContext{useMarkStub: useMarkStub, enableRecord: false}) } } for i, seg := range s.data { it := &(seg.gcMaskBitIterator) if it.nextPtr(false) != 0 { idx := (*pprofIndex)(nil).pushHead(s.pb, fmt.Sprintf("data segment[%d]", i)) - s.finalMark(idx, it) + s.finalMark(idx, it, markContext{useMarkStub: useMarkStub, enableRecord: false}) } } diff --git a/pkg/stub/stub.go b/pkg/stub/stub.go new file mode 100644 index 0000000..7296bbf --- /dev/null +++ b/pkg/stub/stub.go @@ -0,0 +1,8 @@ +package stub + +var markStub bool + +// GetMarkStubAddr return address of markStub. Objects with this value will be handled specially +func GetMarkStubAddr() *bool { + return &markStub +} diff --git a/test/framework.go b/test/framework.go index c2458ca..45823ae 100644 --- a/test/framework.go +++ b/test/framework.go @@ -43,6 +43,7 @@ type TestScenario struct { // AllowExtraChildren relaxes strict tree matching by allowing actual nodes // to have children not listed in Expected. AllowExtraChildren bool + UseMarkStub bool } // TestFramework manages integration test execution @@ -95,7 +96,7 @@ func (tf *TestFramework) runScenario(scenario TestScenario) { } outputFile := tf.tempDir + "/" + scenario.Name + ".out" - scope, err := tf.attachAndAnalyze(program.GetPID(), outputFile, program.Binary) + scope, err := tf.attachAndAnalyze(program.GetPID(), outputFile, program.Binary, scenario.UseMarkStub) if err != nil { tf.t.Fatalf("Failed to attach and analyze: %v", err) } @@ -135,7 +136,7 @@ func (tf *TestFramework) createTestProgram(scenario TestScenario) (*TestProgram, } // attachAndAnalyze attaches to the target process and analyzes references -func (tf *TestFramework) attachAndAnalyze(pid int, outputFile, binary string) (*gorefproc.ObjRefScope, error) { +func (tf *TestFramework) attachAndAnalyze(pid int, outputFile, binary string, useMarkStub bool) (*gorefproc.ObjRefScope, error) { tf.t.Logf("Attaching to PID %d", pid) // Create debugger config @@ -160,7 +161,7 @@ func (tf *TestFramework) attachAndAnalyze(pid int, outputFile, binary string) (* func() { tg, unlock := dbg.LockTargetGroup() defer unlock() - scope, err = gorefproc.ObjectReference(tg.Selected, outputFile) + scope, err = gorefproc.ObjectReference(tg.Selected, outputFile, useMarkStub) }() if err != nil { return nil, fmt.Errorf("failed to analyze references: %w", err) @@ -384,6 +385,9 @@ func (tf *TestFramework) buildMemoryTreeFromNodes(nodes map[string]ProfileNodeIn break } } + if strings.HasSuffix(leaf, "stub.markStub") { + tf.createOrUpdateNode(root, nodePath, node.GetCount(), node.GetSize()) + } } tf.t.Logf(" Found %d matched root nodes", matchedRootNodes) diff --git a/test/integration_test.go b/test/integration_test.go index 740c9f0..112a673 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -33,6 +33,8 @@ var testCases = []TestScenario{ ChannelScenario, MallocHeaderHiddenTypeScenario, CircularReferenceScenario, + MarkStubScenario, + StubAddrScenario, } // TestScenarios runs individual test scenarios using table-driven approach diff --git a/test/scenarios.go b/test/scenarios.go index 7293e3b..a01a2f2 100644 --- a/test/scenarios.go +++ b/test/scenarios.go @@ -14,7 +14,10 @@ package test -import "time" +import ( + "fmt" + "time" +) // LocalStringScenario tests local string allocation var LocalStringScenario = TestScenario{ @@ -809,3 +812,142 @@ func main() { }, Timeout: 30 * time.Second, } + +var MarkStubScenario = TestScenario{ + Name: "markStub reference scenario", + Code: `package main + +import ( + "fmt" + "time" + + "github.com/cloudwego/goref/pkg/stub" +) + +func main() { + stubAddr := stub.GetMarkStubAddr() + _ = stubAddr + fmt.Println("READY") + time.Sleep(100 * time.Second) +} +`, + Expected: &MemoryNode{ + Children: []*MemoryNode{ + { + Name: "github.com/cloudwego/goref/pkg/stub.markStub", + Size: ExactValue(1), + Count: ExactValue(1), + }, + }, + }, + Timeout: 30 * time.Second, + UseMarkStub: true, +} + +var StubAddrScenario = TestScenario{ + Name: "stub address reference scenario", + Code: `package main + +import ( + "fmt" + "time" + + "github.com/cloudwego/goref/pkg/stub" +) + +type Session struct { + sid int + markStub *bool + objs []*Obj +} + +type Obj struct { + data [30]int +} + +func (obj *Obj) do() int { + sum := 0 + for _, elem := range obj.data { + sum += elem + } + return sum +} + +func main() { + var markStub *bool = nil + markStub = stub.GetMarkStubAddr() + s := &Session{ + markStub: markStub, + sid: 0, + objs: make([]*Obj, 0, 10), + } + runSession(s) + s2 := &Session{ + sid: 1, + markStub: nil, + objs: make([]*Obj, 0, 10), + } + runSession(s2) + fmt.Println("READY") + time.Sleep(100 * time.Second) + fmt.Println(s.objs[0]) +} + +func runSession(s *Session) { + for i := range 10 { + if i%5 == 0 { + s.objs = append(s.objs, nil) + continue + } + obj := Obj{} + for j := i; j < i+30; j++ { + obj.data[j-i] = j + } + s.objs = append(s.objs, &obj) + } + for _, obj := range s.objs { + if obj != nil { + fmt.Println("sid:", s.sid, "do:", obj.do()) + } + } +}`, + Expected: &MemoryNode{ + Children: []*MemoryNode{ + { + Name: "main.main.s", + Size: nil, + Count: nil, + Children: []*MemoryNode{ + { + Name: "objs", + Size: ExactValue(80), + Count: ExactValue(1), + Children: stubAddrArrayElem(), + }, + }, + }, + { + Name: "github.com/cloudwego/goref/pkg/stub.markStub", + Size: ExactValue(1), + Count: ExactValue(1), + }, + }, + }, + Timeout: 30 * time.Second, + UseMarkStub: true, +} + +func stubAddrArrayElem() []*MemoryNode { + res := make([]*MemoryNode, 0, 8) + for i := range 10 { + if i%5 == 0 { + continue + } + res = append(res, &MemoryNode{ + Name: fmt.Sprintf("[%d]", i), + Size: ExactValue(240), + Count: ExactValue(1), + }) + } + return res +} diff --git a/test/testdata/.gitignore b/test/testdata/.gitignore new file mode 100644 index 0000000..65a5b54 --- /dev/null +++ b/test/testdata/.gitignore @@ -0,0 +1,3 @@ +connNotSetNil/connNotSetNil +stubaddr/stubaddr +stubaddrIsNil/stubaddrIsNil diff --git a/test/testdata/connNotSetNil/main.go b/test/testdata/connNotSetNil/main.go new file mode 100644 index 0000000..e422a9d --- /dev/null +++ b/test/testdata/connNotSetNil/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "time" + + "github.com/cloudwego/goref/cmd/grf/cmds" +) + +type Conn struct { + state int + data []int +} + +func (c *Conn) handle() { + c.state += 1 +} + +func move[T any, PT *T](pp *PT) (res PT) { + res = *pp + *pp = nil + return +} + +func setnil() { + for range 20 { + c := &Conn{} + c.data = make([]int, 10) + go func(c2 *Conn) { + c2.handle() + }(move(&c)) + } + complexFunc() + cmds.AttachSelf("setnil.out") +} + +func complexFunc() { + sum := 0 + for i := range 10000000 { + sum += i + } + fmt.Println(sum) +} + +func nosetnil() { + for range 20 { + c := &Conn{} + c.data = make([]int, 30) + go func() { + c.handle() + }() + } + complexFunc() + cmds.AttachSelf("nosetnil.out") +} + +func main() { + setnil() + nosetnil() + time.Sleep(3 * time.Second) +} diff --git a/test/testdata/stubaddr/main.go b/test/testdata/stubaddr/main.go new file mode 100644 index 0000000..6b53afa --- /dev/null +++ b/test/testdata/stubaddr/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/cloudwego/goref/pkg/stub" +) + +type Session struct { + sid int + markStub *bool + objs []*Obj +} + +type Obj struct { + data [30]int +} + +func (obj *Obj) do() int { + sum := 0 + for _, elem := range obj.data { + sum += elem + } + return sum +} + +func main() { + var markStub *bool = nil + markStub = stub.GetMarkStubAddr() + s := &Session{ + markStub: markStub, + sid: 0, + objs: make([]*Obj, 0, 10), + } + runSession(s) + s2 := &Session{ + sid: 1, + markStub: nil, + objs: make([]*Obj, 0, 10), + } + runSession(s2) + fmt.Println("pid:", os.Getpid()) + fmt.Println("READY") + time.Sleep(100 * time.Second) + fmt.Println(s.objs[0]) +} + +func runSession(s *Session) { + for i := range 10 { + if i%5 == 0 { + s.objs = append(s.objs, nil) + continue + } + obj := Obj{} + for j := i; j < i+30; j++ { + obj.data[j-i] = j + } + s.objs = append(s.objs, &obj) + } + for _, obj := range s.objs { + if obj != nil { + fmt.Println("sid:", s.sid, "do:", obj.do()) + } + } +} diff --git a/test/testdata/stubaddrIsNil/main.go b/test/testdata/stubaddrIsNil/main.go new file mode 100644 index 0000000..d9efaa5 --- /dev/null +++ b/test/testdata/stubaddrIsNil/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "os" + + "github.com/cloudwego/goref/pkg/stub" +) + +type Session struct { + markStub *bool + sid int + objs []*Obj +} + +type Obj struct { + data [30]int +} + +func (obj *Obj) do() int { + sum := 0 + for _, elem := range obj.data { + sum += elem + } + return sum +} + +func main() { + var markStub *bool = nil + markStub = stub.GetMarkStubAddr() + ss := make([]*Session, 0) + for i := 0; i < 4; i++ { + tmpStub := markStub + if i%2 == 0 { + tmpStub = nil + } + s := &Session{ + markStub: tmpStub, + sid: 0, + objs: make([]*Obj, 0, 10), + } + runSession(s) + ss = append(ss, s) + } + fmt.Println("pid:", os.Getpid()) + fmt.Scanln() + for _, s := range ss { + fmt.Println(s.sid) + } +} + +func runSession(s *Session) { + for i := range 10 { + if i%5 == 0 { + s.objs = append(s.objs, nil) + continue + } + obj := Obj{} + for j := i; j < i+30; j++ { + obj.data[j-i] = j + } + s.objs = append(s.objs, &obj) + } + for _, obj := range s.objs { + if obj != nil { + fmt.Println("sid:", s.sid, "do:", obj.do()) + } + } +}