github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/logging/logging.go (about) 1 // Package logging includes utilities used to log function calls. This is in 2 // an independent package to avoid dependency cycles. 3 package logging 4 5 import ( 6 "context" 7 "encoding/hex" 8 "fmt" 9 "io" 10 "strconv" 11 "strings" 12 13 "github.com/tetratelabs/wazero/api" 14 ) 15 16 // ValueType is an extended form of api.ValueType, used to control logging in 17 // cases such as bitmasks or strings. 18 type ValueType = api.ValueType 19 20 const ( 21 ValueTypeI32 = api.ValueTypeI32 22 ValueTypeI64 = api.ValueTypeI64 23 ValueTypeF32 = api.ValueTypeF32 24 ValueTypeF64 = api.ValueTypeF64 25 ValueTypeV128 ValueType = 0x7b // same as wasm.ValueTypeV128 26 ValueTypeFuncref ValueType = 0x70 // same as wasm.ValueTypeFuncref 27 ValueTypeExternref = api.ValueTypeExternref 28 29 // ValueTypeMemI32 is a non-standard type which writes ValueTypeI32 from the memory offset. 30 ValueTypeMemI32 = 0xfd 31 // ValueTypeMemH64 is a non-standard type which writes 64-bits fixed-width hex from the memory offset. 32 ValueTypeMemH64 = 0xfe 33 // ValueTypeString is a non-standard type describing an offset/len pair of a string. 34 ValueTypeString = 0xff 35 ) 36 37 type LogScopes uint64 38 39 const ( 40 LogScopeNone = LogScopes(0) 41 LogScopeClock LogScopes = 1 << iota 42 LogScopeProc 43 LogScopeFilesystem 44 LogScopeMemory 45 LogScopePoll 46 LogScopeRandom 47 LogScopeSock 48 LogScopeAll = LogScopes(0xffffffffffffffff) 49 ) 50 51 func scopeName(s LogScopes) string { 52 switch s { 53 case LogScopeClock: 54 return "clock" 55 case LogScopeProc: 56 return "proc" 57 case LogScopeFilesystem: 58 return "filesystem" 59 case LogScopeMemory: 60 return "memory" 61 case LogScopePoll: 62 return "poll" 63 case LogScopeRandom: 64 return "random" 65 case LogScopeSock: 66 return "sock" 67 default: 68 return fmt.Sprintf("<unknown=%d>", s) 69 } 70 } 71 72 // IsEnabled returns true if the scope (or group of scopes) is enabled. 73 func (f LogScopes) IsEnabled(scope LogScopes) bool { 74 return f&scope != 0 75 } 76 77 // String implements fmt.Stringer by returning each enabled log scope. 78 func (f LogScopes) String() string { 79 if f == LogScopeAll { 80 return "all" 81 } 82 var builder strings.Builder 83 for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance 84 target := LogScopes(1 << i) 85 if f.IsEnabled(target) { 86 if name := scopeName(target); name != "" { 87 if builder.Len() > 0 { 88 builder.WriteByte('|') 89 } 90 builder.WriteString(name) 91 } 92 } 93 } 94 return builder.String() 95 } 96 97 type ParamLogger func(ctx context.Context, mod api.Module, w Writer, params []uint64) 98 99 type ParamSampler func(ctx context.Context, mod api.Module, params []uint64) bool 100 101 type ResultLogger func(ctx context.Context, mod api.Module, w Writer, params, results []uint64) 102 103 type Writer interface { 104 io.Writer 105 io.StringWriter 106 io.ByteWriter 107 } 108 109 // ValWriter formats an indexed value. For example, if `vals[i]` is a 110 // ValueTypeI32, this would format it by default as signed. If a 111 // ValueTypeString, it would read `vals[i+1]` and write the string from memory. 112 type ValWriter func(ctx context.Context, mod api.Module, w Writer, i uint32, vals []uint64) 113 114 func Config(fnd api.FunctionDefinition) (paramLoggers []ParamLogger, resultLoggers []ResultLogger) { 115 types := fnd.ParamTypes() 116 names := fnd.ParamNames() 117 if paramLen := uint32(len(types)); paramLen > 0 { 118 paramLoggers = make([]ParamLogger, paramLen) 119 hasParamNames := len(names) > 0 120 var offset int64 121 for i, t := range types { 122 if hasParamNames { 123 paramLoggers[i] = NewParamLogger(uint32(offset), names[i], t) 124 } else { 125 paramLoggers[i] = (¶mLogger{offsetInStack: uint32(offset), valWriter: ValWriterForType(t)}).Log 126 } 127 offset++ 128 if t == ValueTypeV128 { 129 offset++ 130 } 131 } 132 } 133 if resultLen := uint32(len(fnd.ResultTypes())); resultLen > 0 { 134 resultLoggers = make([]ResultLogger, resultLen) 135 hasResultNames := len(fnd.ResultNames()) > 0 136 var offset int64 137 for i, t := range fnd.ResultTypes() { 138 if hasResultNames { 139 resultLoggers[i] = NewResultLogger(uint32(offset), fnd.ResultNames()[i], t) 140 } else { 141 resultLoggers[i] = (&resultLogger{offsetInStack: uint32(offset), valWriter: ValWriterForType(t)}).Log 142 } 143 offset++ 144 if t == ValueTypeV128 { 145 offset++ 146 } 147 } 148 } 149 return 150 } 151 152 type paramLogger struct { 153 offsetInStack uint32 154 valWriter ValWriter 155 } 156 157 func (n *paramLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) { 158 n.valWriter(ctx, mod, w, n.offsetInStack, params) 159 } 160 161 func NewParamLogger(offsetInStack uint32, name string, t ValueType) ParamLogger { 162 return (&namedParamLogger{offsetInStack: offsetInStack, name: name, valWriter: ValWriterForType(t)}).Log 163 } 164 165 type namedParamLogger struct { 166 offsetInStack uint32 167 name string 168 valWriter ValWriter 169 } 170 171 func (n *namedParamLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) { 172 w.WriteString(n.name) //nolint 173 w.WriteByte('=') //nolint 174 n.valWriter(ctx, mod, w, n.offsetInStack, params) 175 } 176 177 type resultLogger struct { 178 offsetInStack uint32 179 valWriter ValWriter 180 } 181 182 func (n *resultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) { 183 n.valWriter(ctx, mod, w, n.offsetInStack, results) 184 } 185 186 func NewResultLogger(idx uint32, name string, t ValueType) ResultLogger { 187 return (&namedResultLogger{idx, name, ValWriterForType(t)}).Log 188 } 189 190 type namedResultLogger struct { 191 offsetInStack uint32 192 name string 193 valWriter ValWriter 194 } 195 196 func (n *namedResultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) { 197 w.WriteString(n.name) //nolint 198 w.WriteByte('=') //nolint 199 n.valWriter(ctx, mod, w, n.offsetInStack, results) 200 } 201 202 func ValWriterForType(vt ValueType) ValWriter { 203 switch vt { 204 case ValueTypeI32: 205 return writeI32 206 case ValueTypeI64: 207 return writeI64 208 case ValueTypeF32: 209 return writeF32 210 case ValueTypeF64: 211 return writeF64 212 case ValueTypeV128: 213 return writeV128 214 case ValueTypeExternref, ValueTypeFuncref: 215 return writeRef 216 case ValueTypeMemI32: 217 return writeMemI32 218 case ValueTypeMemH64: 219 return writeMemH64 220 case ValueTypeString: 221 return writeString 222 default: 223 panic(fmt.Errorf("BUG: unsupported type %d", vt)) 224 } 225 } 226 227 func writeI32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 228 v := vals[i] 229 w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint 230 } 231 232 func writeI64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 233 v := vals[i] 234 w.WriteString(strconv.FormatInt(int64(v), 10)) //nolint 235 } 236 237 func writeF32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 238 v := vals[i] 239 s := strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32) 240 w.WriteString(s) //nolint 241 } 242 243 func writeF64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 244 v := vals[i] 245 s := strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64) 246 w.WriteString(s) //nolint 247 } 248 249 // logV128 logs in fixed-width hex 250 func writeV128(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 251 v1, v2 := vals[i], vals[i+1] 252 w.WriteString(fmt.Sprintf("%016x%016x", v1, v2)) //nolint 253 } 254 255 // logRef logs in fixed-width hex 256 func writeRef(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 257 v := vals[i] 258 w.WriteString(fmt.Sprintf("%016x", v)) //nolint 259 } 260 261 func writeMemI32(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) { 262 offset := uint32(vals[i]) 263 byteCount := uint32(4) 264 if v, ok := mod.Memory().ReadUint32Le(offset); ok { 265 w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint 266 } else { // log the positions that were out of memory 267 WriteOOM(w, offset, byteCount) 268 } 269 } 270 271 func writeMemH64(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) { 272 offset := uint32(vals[i]) 273 byteCount := uint32(8) 274 if s, ok := mod.Memory().Read(offset, byteCount); ok { 275 hex.NewEncoder(w).Write(s) //nolint 276 } else { // log the positions that were out of memory 277 WriteOOM(w, offset, byteCount) 278 } 279 } 280 281 func writeString(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) { 282 offset, byteCount := uint32(vals[i]), uint32(vals[i+1]) 283 WriteStringOrOOM(mod.Memory(), w, offset, byteCount) 284 } 285 286 func WriteStringOrOOM(mem api.Memory, w Writer, offset, byteCount uint32) { 287 if s, ok := mem.Read(offset, byteCount); ok { 288 w.Write(s) //nolint 289 } else { // log the positions that were out of memory 290 WriteOOM(w, offset, byteCount) 291 } 292 } 293 294 func WriteOOM(w Writer, offset uint32, byteCount uint32) { 295 w.WriteString("OOM(") //nolint 296 w.WriteString(strconv.Itoa(int(offset))) //nolint 297 w.WriteByte(',') //nolint 298 w.WriteString(strconv.Itoa(int(byteCount))) //nolint 299 w.WriteByte(')') //nolint 300 }