package logging import ( "context" "fmt" "io/fs" "os" "strconv" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/gojs" "github.com/tetratelabs/wazero/internal/gojs/custom" "github.com/tetratelabs/wazero/internal/gojs/goarch" "github.com/tetratelabs/wazero/internal/gojs/goos" "github.com/tetratelabs/wazero/internal/logging" "github.com/tetratelabs/wazero/internal/sys" ) // IsInLogScope returns true if the current function is in any of the scopes. func IsInLogScope(fnd api.FunctionDefinition, scopes logging.LogScopes) bool { if scopes.IsEnabled(logging.LogScopeClock) { switch fnd.Name() { case custom.NameRuntimeNanotime1, custom.NameRuntimeWalltime: return true case custom.NameSyscallValueCall: // e.g. Date.getTimezoneOffset return true } } if scopes.IsEnabled(logging.LogScopeProc) { switch fnd.Name() { case custom.NameRuntimeWasmExit: return true case custom.NameSyscallValueCall: // e.g. proc.* return true } } if scopes.IsEnabled(logging.LogScopeFilesystem) { if fnd.Name() == custom.NameSyscallValueCall { return true // e.g. fs.open } } if scopes.IsEnabled(logging.LogScopeMemory) { switch fnd.Name() { case custom.NameRuntimeResetMemoryDataView: return true } } if scopes.IsEnabled(logging.LogScopePoll) { switch fnd.Name() { case custom.NameRuntimeScheduleTimeoutEvent, custom.NameRuntimeClearTimeoutEvent: return true } } if scopes.IsEnabled(logging.LogScopeRandom) { switch fnd.Name() { case custom.NameRuntimeGetRandomData: return true case custom.NameSyscallValueCall: // e.g. crypto.getRandomValues return true } } return scopes == logging.LogScopeAll } func Config(fnd api.FunctionDefinition, scopes logging.LogScopes) (pSampler logging.ParamSampler, pLoggers []logging.ParamLogger, rLoggers []logging.ResultLogger) { switch fnd.Name() { case custom.NameRuntimeWasmExit: pLoggers = []logging.ParamLogger{runtimeWasmExitParamLogger} // no results case custom.NameRuntimeWasmWrite: return // Don't log NameRuntimeWasmWrite as it is used in panics case custom.NameRuntimeResetMemoryDataView: // no params or results case custom.NameRuntimeNanotime1: // no params rLoggers = []logging.ResultLogger{runtimeNanotime1ResultLogger} case custom.NameRuntimeWalltime: // no params rLoggers = []logging.ResultLogger{runtimeWalltimeResultLogger} case custom.NameRuntimeScheduleTimeoutEvent: pLoggers = []logging.ParamLogger{runtimeScheduleTimeoutEventParamLogger} rLoggers = []logging.ResultLogger{runtimeScheduleTimeoutEventResultLogger} case custom.NameRuntimeClearTimeoutEvent: pLoggers = []logging.ParamLogger{runtimeClearTimeoutEventParamLogger} // no results case custom.NameRuntimeGetRandomData: pLoggers = []logging.ParamLogger{runtimeGetRandomDataParamLogger} // no results case custom.NameSyscallValueCall: p := &syscallValueCallParamSampler{scopes: scopes} pSampler = p.isSampled pLoggers = []logging.ParamLogger{syscallValueCallParamLogger} rLoggers = []logging.ResultLogger{syscallValueCallResultLogger} default: // TODO: make generic logger for gojs } return } func runtimeGetRandomDataParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { paramIdx := 1 /* there are two params, only write the length */ writeParameter(w, custom.NameRuntimeGetRandomData, mod, params, paramIdx) } func runtimeScheduleTimeoutEventParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { writeParameter(w, custom.NameRuntimeScheduleTimeoutEvent, mod, params, 0) } func runtimeClearTimeoutEventParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { writeParameter(w, custom.NameRuntimeClearTimeoutEvent, mod, params, 0) } func runtimeWasmExitParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { writeParameter(w, custom.NameRuntimeWasmExit, mod, params, 0) } func writeParameter(w logging.Writer, funcName string, mod api.Module, params []uint64, paramIdx int) { paramNames := custom.NameSection[funcName].ParamNames stack := goos.NewStack(funcName, mod.Memory(), uint32(params[0])) w.WriteString(paramNames[paramIdx]) //nolint w.WriteByte('=') //nolint writeI32(w, stack.ParamUint32(paramIdx)) } func runtimeNanotime1ResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { writeResults(w, custom.NameRuntimeNanotime1, mod, params, 0) } func runtimeWalltimeResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { writeResults(w, custom.NameRuntimeWalltime, mod, params, 0) } func runtimeScheduleTimeoutEventResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { writeResults(w, custom.NameRuntimeScheduleTimeoutEvent, mod, params, 1) } func writeResults(w logging.Writer, funcName string, mod api.Module, params []uint64, resultOffset int) { stack := goos.NewStack(funcName, mod.Memory(), uint32(params[0])) resultNames := custom.NameSection[funcName].ResultNames results := make([]interface{}, len(resultNames)) for i := range resultNames { results[i] = stack.ParamUint32(i + resultOffset) } w.WriteByte('(') //nolint writeVals(w, resultNames, results) w.WriteByte(')') //nolint } type syscallValueCallParamSampler struct { scopes logging.LogScopes } func (s *syscallValueCallParamSampler) isSampled(ctx context.Context, mod api.Module, params []uint64) bool { vRef, m, args := syscallValueCallParams(ctx, mod, params) switch vRef { case goos.RefJsCrypto: return logging.LogScopeRandom.IsEnabled(s.scopes) case goos.RefJsDate: return logging.LogScopeClock.IsEnabled(s.scopes) case goos.RefJsfs: if !logging.LogScopeFilesystem.IsEnabled(s.scopes) { return false } // Don't amplify logs with stdio reads or writes switch m { case custom.NameFsWrite, custom.NameFsRead: fd := goos.ValueToInt32(args[0]) return fd > sys.FdStderr } return true case goos.RefJsProcess: return logging.LogScopeProc.IsEnabled(s.scopes) } return s.scopes == logging.LogScopeAll } func syscallValueCallParamLogger(ctx context.Context, mod api.Module, w logging.Writer, params []uint64) { vRef, m, args := syscallValueCallParams(ctx, mod, params) switch vRef { case goos.RefJsCrypto: logSyscallValueCallArgs(w, custom.NameCrypto, m, args) case goos.RefJsDate: logSyscallValueCallArgs(w, custom.NameDate, m, args) case goos.RefJsfs: logFsParams(m, w, args) case goos.RefJsProcess: logSyscallValueCallArgs(w, custom.NameProcess, m, args) default: // TODO: other scopes } } func logFsParams(m string, w logging.Writer, args []interface{}) { if m == custom.NameFsOpen { w.WriteString("fs.open(") //nolint w.WriteString("path=") //nolint w.WriteString(args[0].(string)) //nolint w.WriteString(",flags=") //nolint writeOFlags(w, int(args[1].(float64))) w.WriteString(",perm=") //nolint w.WriteString(fs.FileMode(uint32(args[2].(float64))).String()) //nolint w.WriteByte(')') //nolint return } logSyscallValueCallArgs(w, custom.NameFs, m, args) } func logSyscallValueCallArgs(w logging.Writer, n, m string, args []interface{}) { argNames := custom.NameSectionSyscallValueCall[n][m].ParamNames w.WriteString(n) //nolint w.WriteByte('.') //nolint w.WriteString(m) //nolint w.WriteByte('(') //nolint writeVals(w, argNames, args) w.WriteByte(')') //nolint } func syscallValueCallParams(ctx context.Context, mod api.Module, params []uint64) (goos.Ref, string, []interface{}) { mem := mod.Memory() funcName := custom.NameSyscallValueCall stack := goos.NewStack(funcName, mem, uint32(params[0])) vRef := stack.ParamRef(0) //nolint m := stack.ParamString(mem, 1 /*, 2 */) //nolint args := stack.ParamVals(ctx, mem, 3 /*, 4 */, gojs.LoadValue) return vRef, m, args } func syscallValueCallResultLogger(ctx context.Context, mod api.Module, w logging.Writer, params, results []uint64) { mem := mod.Memory() funcName := custom.NameSyscallValueCall stack := goos.NewStack(funcName, mem, goarch.GetSP(mod)) vRef := stack.ParamRef(0) //nolint m := stack.ParamString(mem, 1 /*, 2 */) //nolint var resultNames []string var resultVals []interface{} switch vRef { case goos.RefJsCrypto: resultNames = custom.CryptoNameSection[m].ResultNames rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding resultVals = []interface{}{rRef} case goos.RefJsDate: resultNames = custom.DateNameSection[m].ResultNames rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding resultVals = []interface{}{rRef} case goos.RefJsfs: resultNames = custom.FsNameSection[m].ResultNames resultVals = gojs.GetLastEventArgs(ctx) case goos.RefJsProcess: resultNames = custom.ProcessNameSection[m].ResultNames rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding resultVals = []interface{}{rRef} default: // TODO: other scopes } w.WriteByte('(') //nolint writeVals(w, resultNames, resultVals) w.WriteByte(')') //nolint } func writeVals(w logging.Writer, names []string, vals []interface{}) { valLen := len(vals) if valLen > 0 { writeVal(w, names[0], vals[0]) for i := 1; i < valLen; i++ { name := names[i] val := vals[i] switch name { case custom.NameCallback: return // last val case "buf": // always equal size with byteCount continue } w.WriteByte(',') //nolint writeVal(w, name, val) } } } func writeVal(w logging.Writer, name string, val interface{}) { if b, ok := val.(*goos.ByteArray); ok { // Write the length instead of a byte array. w.WriteString(name) //nolint w.WriteString("_len=") //nolint writeI32(w, uint32(len(b.Unwrap()))) //nolint return } switch name { case "uid", "gid": w.WriteString(name) //nolint w.WriteByte('=') //nolint id := int(goos.ValueToInt32(val)) w.WriteString(strconv.Itoa(id)) //nolint case "mask", "mode", "oldmask", "perm": w.WriteString(name) //nolint w.WriteByte('=') //nolint perm := custom.FromJsMode(goos.ValueToUint32(val), 0) w.WriteString(perm.String()) //nolint default: w.WriteString(name) //nolint w.WriteByte('=') //nolint w.WriteString(fmt.Sprintf("%v", val)) //nolint } } func writeOFlags(w logging.Writer, f int) { // Iterate a subflagset in order to avoid OS differences, notably for windows first := true for i, sf := range oFlags { if f&sf != 0 { if !first { w.WriteByte('|') //nolint } else { first = false } w.WriteString(oflagToString[i]) //nolint } } } var oFlags = [...]int{ os.O_RDONLY, os.O_WRONLY, os.O_RDWR, os.O_APPEND, os.O_CREATE, os.O_EXCL, os.O_SYNC, os.O_TRUNC, } var oflagToString = [...]string{ "RDONLY", "WRONLY", "RDWR", "APPEND", "CREATE", "EXCL", "SYNC", "TRUNC", } func writeI32(w logging.Writer, v uint32) { w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint }