github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/refs/refs_map.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package refs 16 17 import ( 18 "fmt" 19 20 "github.com/nicocha30/gvisor-ligolo/pkg/log" 21 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 22 ) 23 24 var ( 25 // liveObjects is a global map of reference-counted objects. Objects are 26 // inserted when leak check is enabled, and they are removed when they are 27 // destroyed. It is protected by liveObjectsMu. 28 liveObjects map[CheckedObject]struct{} 29 liveObjectsMu sync.Mutex 30 ) 31 32 // CheckedObject represents a reference-counted object with an informative 33 // leak detection message. 34 type CheckedObject interface { 35 // RefType is the type of the reference-counted object. 36 RefType() string 37 38 // LeakMessage supplies a warning to be printed upon leak detection. 39 LeakMessage() string 40 41 // LogRefs indicates whether reference-related events should be logged. 42 LogRefs() bool 43 } 44 45 func init() { 46 liveObjects = make(map[CheckedObject]struct{}) 47 } 48 49 // LeakCheckEnabled returns whether leak checking is enabled. The following 50 // functions should only be called if it returns true. 51 func LeakCheckEnabled() bool { 52 mode := GetLeakMode() 53 return mode != NoLeakChecking 54 } 55 56 // leakCheckPanicEnabled returns whether DoLeakCheck() should panic when leaks 57 // are detected. 58 func leakCheckPanicEnabled() bool { 59 return GetLeakMode() == LeaksPanic 60 } 61 62 // Register adds obj to the live object map. 63 func Register(obj CheckedObject) { 64 if LeakCheckEnabled() { 65 liveObjectsMu.Lock() 66 if _, ok := liveObjects[obj]; ok { 67 panic(fmt.Sprintf("Unexpected entry in leak checking map: reference %p already added", obj)) 68 } 69 liveObjects[obj] = struct{}{} 70 liveObjectsMu.Unlock() 71 if LeakCheckEnabled() && obj.LogRefs() { 72 logEvent(obj, "registered") 73 } 74 } 75 } 76 77 // Unregister removes obj from the live object map. 78 func Unregister(obj CheckedObject) { 79 if LeakCheckEnabled() { 80 liveObjectsMu.Lock() 81 defer liveObjectsMu.Unlock() 82 if _, ok := liveObjects[obj]; !ok { 83 panic(fmt.Sprintf("Expected to find entry in leak checking map for reference %p", obj)) 84 } 85 delete(liveObjects, obj) 86 if LeakCheckEnabled() && obj.LogRefs() { 87 logEvent(obj, "unregistered") 88 } 89 } 90 } 91 92 // LogIncRef logs a reference increment. 93 func LogIncRef(obj CheckedObject, refs int64) { 94 if LeakCheckEnabled() && obj.LogRefs() { 95 logEvent(obj, fmt.Sprintf("IncRef to %d", refs)) 96 } 97 } 98 99 // LogTryIncRef logs a successful TryIncRef call. 100 func LogTryIncRef(obj CheckedObject, refs int64) { 101 if LeakCheckEnabled() && obj.LogRefs() { 102 logEvent(obj, fmt.Sprintf("TryIncRef to %d", refs)) 103 } 104 } 105 106 // LogDecRef logs a reference decrement. 107 func LogDecRef(obj CheckedObject, refs int64) { 108 if LeakCheckEnabled() && obj.LogRefs() { 109 logEvent(obj, fmt.Sprintf("DecRef to %d", refs)) 110 } 111 } 112 113 // logEvent logs a message for the given reference-counted object. 114 // 115 // obj.LogRefs() should be checked before calling logEvent, in order to avoid 116 // calling any text processing needed to evaluate msg. 117 func logEvent(obj CheckedObject, msg string) { 118 log.Infof("[%s %p] %s:\n%s", obj.RefType(), obj, msg, FormatStack(RecordStack())) 119 } 120 121 // checkOnce makes sure that leak checking is only done once. DoLeakCheck is 122 // called from multiple places (which may overlap) to cover different sandbox 123 // exit scenarios. 124 var checkOnce sync.Once 125 126 // DoLeakCheck iterates through the live object map and logs a message for each 127 // object. It should be called when no reference-counted objects are reachable 128 // anymore, at which point anything left in the map is considered a leak. On 129 // multiple calls, only the first call will perform the leak check. 130 func DoLeakCheck() { 131 if LeakCheckEnabled() { 132 checkOnce.Do(doLeakCheck) 133 } 134 } 135 136 // DoRepeatedLeakCheck is the same as DoLeakCheck except that it can be called 137 // multiple times by the caller to incrementally perform leak checking. 138 func DoRepeatedLeakCheck() { 139 if LeakCheckEnabled() { 140 doLeakCheck() 141 } 142 } 143 144 type leakCheckDisabled interface { 145 LeakCheckDisabled() bool 146 } 147 148 // CleanupSync is used to wait for async cleanup actions. 149 var CleanupSync sync.WaitGroup 150 151 func doLeakCheck() { 152 CleanupSync.Wait() 153 liveObjectsMu.Lock() 154 defer liveObjectsMu.Unlock() 155 leaked := len(liveObjects) 156 if leaked > 0 { 157 n := 0 158 msg := fmt.Sprintf("Leak checking detected %d leaked objects:\n", leaked) 159 for obj := range liveObjects { 160 skip := false 161 if o, ok := obj.(leakCheckDisabled); ok { 162 skip = o.LeakCheckDisabled() 163 } 164 if skip { 165 log.Debugf(obj.LeakMessage()) 166 continue 167 } 168 msg += obj.LeakMessage() + "\n" 169 n++ 170 } 171 if n == 0 { 172 return 173 } 174 if leakCheckPanicEnabled() { 175 panic(msg) 176 } 177 log.Warningf(msg) 178 } 179 }