Files
wazero/internal/logging/logging.go
Crypt Keeper 6243091dc2 renames exit log scope to proc and resolves gojs files to cwd (#1223)
Many tests failed in gojs due to needing to be resolved against the CWD,
which is atypically stored host side. This fixes that and renames the
"exit" scope to "proc" so we can use it for other proc concerns besides
exit.

This reduces known failures on GOOS=js from 23 to 14:
```bash
$ wazero run -mount=/usr/local/go/src/os:/:ro -mount=/tmp:/tmp -mount=/etc:/etc:ro -mount=/usr:/usr:ro -mount=/dev:/dev:ro os.wasm |grep '^--- FAIL'|wc -l
      14
```

See #1222

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-03-13 11:41:19 +08:00

291 lines
8.3 KiB
Go

// Package logging includes utilities used to log function calls. This is in
// an independent package to avoid dependency cycles.
package logging
import (
"context"
"encoding/hex"
"fmt"
"io"
"strconv"
"strings"
"github.com/tetratelabs/wazero/api"
)
// ValueType is an extended form of api.ValueType, used to control logging in
// cases such as bitmasks or strings.
type ValueType = api.ValueType
const (
ValueTypeI32 = api.ValueTypeI32
ValueTypeI64 = api.ValueTypeI64
ValueTypeF32 = api.ValueTypeF32
ValueTypeF64 = api.ValueTypeF64
ValueTypeV128 ValueType = 0x7b // same as wasm.ValueTypeV128
ValueTypeFuncref ValueType = 0x70 // same as wasm.ValueTypeFuncref
ValueTypeExternref = api.ValueTypeExternref
// ValueTypeMemI32 is a non-standard type which writes ValueTypeI32 from the memory offset.
ValueTypeMemI32 = 0xfd
// ValueTypeMemH64 is a non-standard type which writes 64-bits fixed-width hex from the memory offset.
ValueTypeMemH64 = 0xfe
// ValueTypeString is a non-standard type describing an offset/len pair of a string.
ValueTypeString = 0xff
)
type LogScopes uint64
const (
LogScopeNone = LogScopes(0)
LogScopeClock LogScopes = 1 << iota
LogScopeProc
LogScopeFilesystem
LogScopeMemory
LogScopePoll
LogScopeRandom
LogScopeAll = LogScopes(0xffffffffffffffff)
)
func scopeName(s LogScopes) string {
switch s {
case LogScopeClock:
return "clock"
case LogScopeProc:
return "proc"
case LogScopeFilesystem:
return "filesystem"
case LogScopeMemory:
return "memory"
case LogScopePoll:
return "poll"
case LogScopeRandom:
return "random"
default:
return fmt.Sprintf("<unknown=%d>", s)
}
}
// IsEnabled returns true if the scope (or group of scopes) is enabled.
func (f LogScopes) IsEnabled(scope LogScopes) bool {
return f&scope != 0
}
// String implements fmt.Stringer by returning each enabled log scope.
func (f LogScopes) String() string {
if f == LogScopeAll {
return "all"
}
var builder strings.Builder
for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance
target := LogScopes(1 << i)
if f.IsEnabled(target) {
if name := scopeName(target); name != "" {
if builder.Len() > 0 {
builder.WriteByte('|')
}
builder.WriteString(name)
}
}
}
return builder.String()
}
// LoggerKey is a context.Context Value key with a FunctionLogger value.
type LoggerKey struct{}
type ParamLogger func(ctx context.Context, mod api.Module, w Writer, params []uint64)
type ParamSampler func(ctx context.Context, mod api.Module, params []uint64) bool
type ResultLogger func(ctx context.Context, mod api.Module, w Writer, params, results []uint64)
type Writer interface {
io.Writer
io.StringWriter
io.ByteWriter
}
// ValWriter formats an indexed value. For example, if `vals[i]` is a
// ValueTypeI32, this would format it by default as signed. If a
// ValueTypeString, it would read `vals[i+1]` and write the string from memory.
type ValWriter func(ctx context.Context, mod api.Module, w Writer, i uint32, vals []uint64)
func Config(fnd api.FunctionDefinition) (paramLoggers []ParamLogger, resultLoggers []ResultLogger) {
types := fnd.ParamTypes()
names := fnd.ParamNames()
if paramLen := uint32(len(types)); paramLen > 0 {
paramLoggers = make([]ParamLogger, paramLen)
hasParamNames := len(names) > 0
for i, t := range types {
if hasParamNames {
paramLoggers[i] = NewParamLogger(uint32(i), names[i], t)
} else {
paramLoggers[i] = (&paramLogger{idx: uint32(i), valWriter: ValWriterForType(t)}).Log
}
}
}
if resultLen := uint32(len(fnd.ResultTypes())); resultLen > 0 {
resultLoggers = make([]ResultLogger, resultLen)
hasResultNames := len(fnd.ResultNames()) > 0
for i, t := range fnd.ResultTypes() {
if hasResultNames {
resultLoggers[i] = NewResultLogger(uint32(i), fnd.ResultNames()[i], t)
} else {
resultLoggers[i] = (&resultLogger{idx: uint32(i), valWriter: ValWriterForType(t)}).Log
}
}
}
return
}
type paramLogger struct {
idx uint32
valWriter ValWriter
}
func (n *paramLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) {
n.valWriter(ctx, mod, w, n.idx, params)
}
func NewParamLogger(idx uint32, name string, t ValueType) ParamLogger {
return (&namedParamLogger{idx: idx, name: name, valWriter: ValWriterForType(t)}).Log
}
type namedParamLogger struct {
idx uint32
name string
valWriter ValWriter
}
func (n *namedParamLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) {
w.WriteString(n.name) //nolint
w.WriteByte('=') //nolint
n.valWriter(ctx, mod, w, n.idx, params)
}
type resultLogger struct {
idx uint32
valWriter ValWriter
}
func (n *resultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) {
n.valWriter(ctx, mod, w, n.idx, results)
}
func NewResultLogger(idx uint32, name string, t ValueType) ResultLogger {
return (&namedResultLogger{idx, name, ValWriterForType(t)}).Log
}
type namedResultLogger struct {
idx uint32
name string
valWriter ValWriter
}
func (n *namedResultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) {
w.WriteString(n.name) //nolint
w.WriteByte('=') //nolint
n.valWriter(ctx, mod, w, n.idx, results)
}
func ValWriterForType(vt ValueType) ValWriter {
switch vt {
case ValueTypeI32:
return writeI32
case ValueTypeI64:
return writeI64
case ValueTypeF32:
return writeF32
case ValueTypeF64:
return writeF64
case ValueTypeV128:
return writeV128
case ValueTypeExternref, ValueTypeFuncref:
return writeRef
case ValueTypeMemI32:
return writeMemI32
case ValueTypeMemH64:
return writeMemH64
case ValueTypeString:
return writeString
default:
panic(fmt.Errorf("BUG: unsupported type %d", vt))
}
}
func writeI32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint
}
func writeI64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
w.WriteString(strconv.FormatInt(int64(v), 10)) //nolint
}
func writeF32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
s := strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32)
w.WriteString(s) //nolint
}
func writeF64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
s := strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64)
w.WriteString(s) //nolint
}
// logV128 logs in fixed-width hex
func writeV128(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v1, v2 := vals[i], vals[i+1]
w.WriteString(fmt.Sprintf("%016x%016x", v1, v2)) //nolint
}
// logRef logs in fixed-width hex
func writeRef(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
w.WriteString(fmt.Sprintf("%016x", v)) //nolint
}
func writeMemI32(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
offset := uint32(vals[i])
byteCount := uint32(4)
if v, ok := mod.Memory().ReadUint32Le(offset); ok {
w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint
} else { // log the positions that were out of memory
WriteOOM(w, offset, byteCount)
}
}
func writeMemH64(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
offset := uint32(vals[i])
byteCount := uint32(8)
if s, ok := mod.Memory().Read(offset, byteCount); ok {
hex.NewEncoder(w).Write(s) //nolint
} else { // log the positions that were out of memory
WriteOOM(w, offset, byteCount)
}
}
func writeString(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
offset, byteCount := uint32(vals[i]), uint32(vals[i+1])
WriteStringOrOOM(mod.Memory(), w, offset, byteCount)
}
func WriteStringOrOOM(mem api.Memory, w Writer, offset, byteCount uint32) {
if s, ok := mem.Read(offset, byteCount); ok {
w.Write(s) //nolint
} else { // log the positions that were out of memory
WriteOOM(w, offset, byteCount)
}
}
func WriteOOM(w Writer, offset uint32, byteCount uint32) {
w.WriteString("OOM(") //nolint
w.WriteString(strconv.Itoa(int(offset))) //nolint
w.WriteByte(',') //nolint
w.WriteString(strconv.Itoa(int(byteCount))) //nolint
w.WriteByte(')') //nolint
}