mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Implement early printf functionality
The kfmt/early package provides a minimal printf implementation that does not use memory allocations (everything is allocated on the stack). This implementation can be used to emit debug messages before the memory manager is initialized.
This commit is contained in:
parent
d69c945019
commit
b6ad5c933d
270
kernel/kfmt/early/early_fmt.go
Normal file
270
kernel/kfmt/early/early_fmt.go
Normal file
@ -0,0 +1,270 @@
|
||||
package early
|
||||
|
||||
import "github.com/achilleasa/gopher-os/kernel/hal"
|
||||
|
||||
var (
|
||||
errMissingArg = []byte("(MISSING)")
|
||||
errWrongArgType = []byte("%!(WRONGTYPE)")
|
||||
errNoVerb = []byte("%!(NOVERB)")
|
||||
errExtraArg = []byte("%!(EXTRA)")
|
||||
padding = []byte{' '}
|
||||
trueValue = []byte("true")
|
||||
falseValue = []byte("false")
|
||||
)
|
||||
|
||||
// Printf provides a minimal Printf implementation that can be used before the
|
||||
// Go runtime has been properly initialized. This version of printf does not
|
||||
// allocate any memory and uses hal.ActiveTerminal for its output.
|
||||
//
|
||||
// Similar to fmt.Printf, this version of printf supports the following subset
|
||||
// of formatting verbs:
|
||||
//
|
||||
// Strings:
|
||||
// %s the uninterpreted bytes of the string or byte slice
|
||||
//
|
||||
// Integers:
|
||||
// %o base 8
|
||||
// %d base 10
|
||||
// %x base 16, with lower-case letters for a-f
|
||||
//
|
||||
// Booleans:
|
||||
// %t "true" or "false"
|
||||
//
|
||||
// Width is specified by an optional decimal number immediately preceding the verb.
|
||||
// If absent, the width is whatever is necessary to represent the value.
|
||||
//
|
||||
// String values with length less than the specified width will be left-padded with
|
||||
// spaces. Integer values formatted as base-10 will also be left-padded with spaces.
|
||||
// Finally, integer values formatted as base-16 will be left-padded with zeroes.
|
||||
//
|
||||
// Printf supports all built-in string and integer types but assumes that the
|
||||
// Go itables have not been initialized yet so it will not check whether its
|
||||
// arguments support io.Stringer if they don't match one of the supported tupes.
|
||||
//
|
||||
// This function does not provide support for printing pointers (%p) as this
|
||||
// requires importing the reflect package. By importing reflect, the go compiler
|
||||
// starts generating calls to runtime.convT2E (which calls runtime.newobject)
|
||||
// when assembling the argument slice which obviously will crash the kernel since
|
||||
// memory management is not yet available.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
var (
|
||||
nextCh byte
|
||||
nextArgIndex int
|
||||
blockStart, blockEnd, padLen int
|
||||
fmtLen = len(format)
|
||||
)
|
||||
|
||||
for blockEnd < fmtLen {
|
||||
nextCh = format[blockEnd]
|
||||
if nextCh != '%' {
|
||||
blockEnd++
|
||||
continue
|
||||
}
|
||||
|
||||
if blockStart < blockEnd {
|
||||
hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd]))
|
||||
}
|
||||
|
||||
// Scan til we hit the format character
|
||||
padLen = 0
|
||||
blockEnd++
|
||||
parseFmt:
|
||||
for ; blockEnd < fmtLen; blockEnd++ {
|
||||
nextCh = format[blockEnd]
|
||||
switch {
|
||||
case nextCh == '%':
|
||||
hal.ActiveTerminal.Write([]byte{'%'})
|
||||
break parseFmt
|
||||
case nextCh >= '0' && nextCh <= '9':
|
||||
padLen = (padLen * 10) + int(nextCh-'0')
|
||||
continue
|
||||
case nextCh == 'd' || nextCh == 'x' || nextCh == 'o' || nextCh == 's' || nextCh == 't':
|
||||
// Run out of args to print
|
||||
if nextArgIndex >= len(args) {
|
||||
hal.ActiveTerminal.Write(errMissingArg)
|
||||
break parseFmt
|
||||
}
|
||||
|
||||
switch nextCh {
|
||||
case 'o':
|
||||
fmtInt(args[nextArgIndex], 8, padLen)
|
||||
case 'd':
|
||||
fmtInt(args[nextArgIndex], 10, padLen)
|
||||
case 'x':
|
||||
fmtInt(args[nextArgIndex], 16, padLen)
|
||||
case 's':
|
||||
fmtString(args[nextArgIndex], padLen)
|
||||
case 't':
|
||||
fmtBool(args[nextArgIndex])
|
||||
}
|
||||
|
||||
nextArgIndex++
|
||||
break parseFmt
|
||||
}
|
||||
|
||||
// reached end of formatting string without finding a verb
|
||||
hal.ActiveTerminal.Write(errNoVerb)
|
||||
}
|
||||
blockStart, blockEnd = blockEnd+1, blockEnd+1
|
||||
}
|
||||
|
||||
if blockStart != blockEnd {
|
||||
hal.ActiveTerminal.Write([]byte(format[blockStart:blockEnd]))
|
||||
}
|
||||
|
||||
// Check for unused args
|
||||
for ; nextArgIndex < len(args); nextArgIndex++ {
|
||||
hal.ActiveTerminal.Write(errExtraArg)
|
||||
}
|
||||
}
|
||||
|
||||
// fmtBool prints a formatted version of boolean value v using hal.ActiveTerminal
|
||||
// for its output.
|
||||
func fmtBool(v interface{}) {
|
||||
switch bVal := v.(type) {
|
||||
case bool:
|
||||
switch bVal {
|
||||
case true:
|
||||
hal.ActiveTerminal.Write(trueValue)
|
||||
case false:
|
||||
hal.ActiveTerminal.Write(falseValue)
|
||||
}
|
||||
default:
|
||||
hal.ActiveTerminal.Write(errWrongArgType)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// fmtString prints a formatted version of string or []byte value v, applying the
|
||||
// padding specified by padLen. This function uses hal.ActiveTerminal for its
|
||||
// output.
|
||||
func fmtString(v interface{}, padLen int) {
|
||||
var sval []byte
|
||||
|
||||
switch castedVal := v.(type) {
|
||||
case string:
|
||||
sval = []byte(castedVal)
|
||||
case []byte:
|
||||
sval = castedVal
|
||||
default:
|
||||
hal.ActiveTerminal.Write(errWrongArgType)
|
||||
return
|
||||
}
|
||||
|
||||
for pad := padLen - len(sval); pad > 0; pad-- {
|
||||
hal.ActiveTerminal.Write(padding)
|
||||
}
|
||||
|
||||
hal.ActiveTerminal.Write(sval)
|
||||
}
|
||||
|
||||
// fmtInt prints out a formatted version of v in the requested base, applying the
|
||||
// padding specified by padLen. This function uses hal.ActiveTerminal for its
|
||||
// output, supports all built-in signed and unsigned integer types and supports
|
||||
// base 8, 10 and 16 output.
|
||||
func fmtInt(v interface{}, base, padLen int) {
|
||||
var (
|
||||
sval int64
|
||||
uval uint64
|
||||
divider uint64
|
||||
remainder uint64
|
||||
buf [20]byte
|
||||
padCh byte
|
||||
left, right, end int
|
||||
)
|
||||
|
||||
switch base {
|
||||
case 8:
|
||||
divider = 8
|
||||
padCh = '0'
|
||||
case 10:
|
||||
divider = 10
|
||||
padCh = ' '
|
||||
case 16:
|
||||
divider = 16
|
||||
padCh = '0'
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case uint8:
|
||||
uval = uint64(v.(uint8))
|
||||
case uint16:
|
||||
uval = uint64(v.(uint16))
|
||||
case uint32:
|
||||
uval = uint64(v.(uint32))
|
||||
case uint64:
|
||||
uval = v.(uint64)
|
||||
case uintptr:
|
||||
uval = uint64(v.(uintptr))
|
||||
case int8:
|
||||
sval = int64(v.(int8))
|
||||
case int16:
|
||||
sval = int64(v.(int16))
|
||||
case int32:
|
||||
sval = int64(v.(int32))
|
||||
case int64:
|
||||
sval = v.(int64)
|
||||
case int:
|
||||
sval = int64(v.(int))
|
||||
default:
|
||||
hal.ActiveTerminal.Write(errWrongArgType)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle signs
|
||||
if sval < 0 {
|
||||
uval = uint64(-sval)
|
||||
} else if sval > 0 {
|
||||
uval = uint64(sval)
|
||||
}
|
||||
|
||||
for {
|
||||
remainder = uval % divider
|
||||
if remainder < 10 {
|
||||
buf[right] = byte(remainder) + '0'
|
||||
} else {
|
||||
// map values from 10 to 15 -> a-f
|
||||
buf[right] = byte(remainder-10) + 'a'
|
||||
}
|
||||
|
||||
right++
|
||||
|
||||
uval /= divider
|
||||
if uval == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Apply padding if required
|
||||
for ; right-left < padLen; right++ {
|
||||
buf[right] = padCh
|
||||
}
|
||||
|
||||
// Apply hex prefix
|
||||
if base == 16 {
|
||||
buf[right] = 'x'
|
||||
buf[right+1] = '0'
|
||||
right += 2
|
||||
}
|
||||
|
||||
// Apply negative sign to the rightmost blank character (if using enough padding);
|
||||
// otherwise append the sign as a new char
|
||||
if sval < 0 {
|
||||
for end = right - 1; buf[end] == ' '; end-- {
|
||||
}
|
||||
|
||||
if end == right-1 {
|
||||
right++
|
||||
}
|
||||
|
||||
buf[end+1] = '-'
|
||||
}
|
||||
|
||||
// Reverse in place
|
||||
end = right
|
||||
for right = right - 1; left < right; left, right = left+1, right-1 {
|
||||
buf[left], buf[right] = buf[right], buf[left]
|
||||
}
|
||||
|
||||
hal.ActiveTerminal.Write(buf[0:end])
|
||||
}
|
181
kernel/kfmt/early/early_fmt_test.go
Normal file
181
kernel/kfmt/early/early_fmt_test.go
Normal file
@ -0,0 +1,181 @@
|
||||
package early
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel/driver/tty"
|
||||
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||
)
|
||||
|
||||
func TestPrintf(t *testing.T) {
|
||||
origTerm := hal.ActiveTerminal
|
||||
defer func() {
|
||||
hal.ActiveTerminal = origTerm
|
||||
}()
|
||||
|
||||
// mute vet warnings about malformed printf formatting strings
|
||||
printfn := Printf
|
||||
|
||||
ega := &console.Ega{}
|
||||
fb := make([]uint8, 160*25)
|
||||
ega.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||
|
||||
vt := &tty.Vt{}
|
||||
vt.AttachTo(ega)
|
||||
hal.ActiveTerminal = vt
|
||||
|
||||
specs := []struct {
|
||||
fn func()
|
||||
expOutput string
|
||||
}{
|
||||
{
|
||||
func() { printfn("no args") },
|
||||
"no args",
|
||||
},
|
||||
// bool values
|
||||
{
|
||||
func() { printfn("%t", true) },
|
||||
"true",
|
||||
},
|
||||
{
|
||||
func() { printfn("%41t", false) },
|
||||
"false",
|
||||
},
|
||||
// strings and byte slices
|
||||
{
|
||||
func() { printfn("%s arg", "STRING") },
|
||||
"STRING arg",
|
||||
},
|
||||
{
|
||||
func() { printfn("%s arg", []byte("BYTE SLICE")) },
|
||||
"BYTE SLICE arg",
|
||||
},
|
||||
{
|
||||
func() { printfn("'%4s' arg with padding", "ABC") },
|
||||
"' ABC' arg with padding",
|
||||
},
|
||||
{
|
||||
func() { printfn("'%4s' arg longer than padding", "ABCDE") },
|
||||
"'ABCDE' arg longer than padding",
|
||||
},
|
||||
// uints
|
||||
{
|
||||
func() { printfn("uint arg: %d", uint8(10)) },
|
||||
"uint arg: 10",
|
||||
},
|
||||
{
|
||||
func() { printfn("uint arg: %o", uint16(0777)) },
|
||||
"uint arg: 777",
|
||||
},
|
||||
{
|
||||
func() { printfn("uint arg: %x", uint32(0xbadf00d)) },
|
||||
"uint arg: 0xbadf00d",
|
||||
},
|
||||
{
|
||||
func() { printfn("uint arg with padding: '%10d'", uint64(123)) },
|
||||
"uint arg with padding: ' 123'",
|
||||
},
|
||||
{
|
||||
func() { printfn("uint arg with padding: '%4o'", uint64(0777)) },
|
||||
"uint arg with padding: '0777'",
|
||||
},
|
||||
{
|
||||
func() { printfn("uint arg with padding: '%10x'", uint64(0xbadf00d)) },
|
||||
"uint arg with padding: '0x000badf00d'",
|
||||
},
|
||||
{
|
||||
func() { printfn("uint arg longer than padding: '%5x'", int64(0xbadf00d)) },
|
||||
"uint arg longer than padding: '0xbadf00d'",
|
||||
},
|
||||
// pointers
|
||||
{
|
||||
func() { printfn("uintptr %x", uintptr(0xb8000)) },
|
||||
"uintptr 0xb8000",
|
||||
},
|
||||
// ints
|
||||
|
||||
{
|
||||
func() { printfn("int arg: %d", int8(-10)) },
|
||||
"int arg: -10",
|
||||
},
|
||||
{
|
||||
func() { printfn("int arg: %o", int16(0777)) },
|
||||
"int arg: 777",
|
||||
},
|
||||
{
|
||||
func() { printfn("int arg: %x", int32(-0xbadf00d)) },
|
||||
"int arg: -0xbadf00d",
|
||||
},
|
||||
{
|
||||
func() { printfn("int arg with padding: '%10d'", int64(-12345678)) },
|
||||
"int arg with padding: ' -12345678'",
|
||||
},
|
||||
{
|
||||
func() { printfn("int arg with padding: '%10d'", int64(-123456789)) },
|
||||
"int arg with padding: '-123456789'",
|
||||
},
|
||||
{
|
||||
func() { printfn("int arg with padding: '%10d'", int64(-1234567890)) },
|
||||
"int arg with padding: '-1234567890'",
|
||||
},
|
||||
{
|
||||
func() { printfn("int arg longer than padding: '%5x'", int(-0xbadf00d)) },
|
||||
"int arg longer than padding: '-0xbadf00d'",
|
||||
},
|
||||
// multiple arguments
|
||||
{
|
||||
func() { printfn("%%%s%d%t", "foo", 123, true) },
|
||||
`%foo123true`,
|
||||
},
|
||||
// errors
|
||||
{
|
||||
func() { printfn("more args", "foo", "bar", "baz") },
|
||||
`more args%!(EXTRA)%!(EXTRA)%!(EXTRA)`,
|
||||
},
|
||||
{
|
||||
func() { printfn("missing args %s") },
|
||||
`missing args (MISSING)`,
|
||||
},
|
||||
{
|
||||
func() { printfn("bad verb %Q") },
|
||||
`bad verb %!(NOVERB)`,
|
||||
},
|
||||
{
|
||||
func() { printfn("not bool %t", "foo") },
|
||||
`not bool %!(WRONGTYPE)`,
|
||||
},
|
||||
{
|
||||
func() { printfn("not int %d", "foo") },
|
||||
`not int %!(WRONGTYPE)`,
|
||||
},
|
||||
{
|
||||
func() { printfn("not string %s", 123) },
|
||||
`not string %!(WRONGTYPE)`,
|
||||
},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
for index := 0; index < len(fb); index++ {
|
||||
fb[index] = 0
|
||||
}
|
||||
vt.SetPosition(0, 0)
|
||||
|
||||
spec.fn()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for index := 0; ; index += 2 {
|
||||
if fb[index] == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteByte(fb[index])
|
||||
}
|
||||
|
||||
if got := buf.String(); got != spec.expOutput {
|
||||
t.Errorf("[spec %d] expected to get %q; got %q", specIndex, spec.expOutput, got)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user