📜 ⬆️ ⬇️

Go panic (), runtime error and their implementation in your OS on Go + asm Part 0x000c03f. (Float32)

Hello! Recently I wrote about the implementation of empty interfaces in Go, that article, as you can guess, is directly related to the development of the OS on Go, but this topic is not abandoned and not forgotten, but was postponed for a long time.

Under the cat: “throwing out” asm proxy methods, implement panic () methods and support for runtime errors.

Remember all our stub methods and proxy methods on asm? Throw it out and forget it. Only two files should remain: multiboot.s and runtime.s The contents of multiboot.s will not change, and runtime.s should be brought to this:

global dummy dummy: ;   ret global __unsafe_get_addr; convert uint32 to pointer __unsafe_get_addr: push ebp mov ebp, esp mov eax, [ebp+8] mov esp, ebp pop ebp ret 

All the rest ruthlessly let under the knife.
')
Open link.ld in the text section, after the DATA task:

  __go_new = go.runtime.New; __go_new_nopointers = go.runtime.New; __go_print_string = go.screen.PrintStr; __go_print_empty_interface = go.screen.PrintInterface; __go_print_nl = go.screen.PrintNl; __go_print_pointer = go.screen.PrintHex; __go_print_uint64 = go.screen.PrintUint64; __go_runtime_error = go.runtime.RuntimeError; __go_panic = go.runtime.Panic; runtime.efacetype = go.runtime.InterfaceType; runtime.ifacetypeeq = go.runtime.InterfaceTypeEq; runtime.ifaceE2T2 = go.runtime.InterfaceE2T2; __go_type_hash_identity = go.runtime.TypeHashIdentity; __go_type_equal_identity = go.runtime.TypeEqualIdentity; __go_strcmp = go.runtime.StrCmp; __go_type_hash_error = dummy; __go_type_equal_error = dummy; __go_register_gc_roots = dummy; __go_type_hash_identity_descriptor = dummy; __go_type_equal_error_descriptor = dummy; __go_type_equal_identity_descriptor = dummy; __go_type_hash_error_descriptor = dummy; __go_type_hash_empty_interface = dummy; __go_empty_interface_compare = dummy; __go_type_hash_string = dummy; __go_type_equal_string = dummy; __go_type_equal_empty_interface = dummy; __go_type_hash_string_descriptor = dummy; __go_type_equal_string_descriptor = dummy; __go_type_hash_empty_interface_descriptor = dummy; __go_type_equal_empty_interface_descriptor = dummy; 

Yes, I hasten to warn you that some of the methods implemented by me are not yet described by me, so you will have to replace them with dummy or implement them yourself.

What's going on here? We create symbolic aliases; this is a much better solution than writing proxy methods on asm.

Well, as I promised asm-proxy we threw out, let's do the implementation of the panic method.
From this article, copy-paste the TypeDescriptor, EmptyInterface and Uncommon structures into the runtime.go file.

Add to it:

 //,    type PanicStack struct { Next *PanicStack //   Arg interface{} // WasRecovered bool //   IsForeign bool //  } //   panic() func Panic(arg interface{}) { //stackTrace(3) //,  ,  ,   =) p := PanicStack{} //     . :  ,    p.Arg = arg //   PrintPanic(&p) //  for { //    } } //     func PrintPanic(p *PanicStack) { if p.Next != nil { //     ,   ()   PrintPanic(p.Next) print("\t") } print("panic: ") //   print(p.Arg) //   if p.WasRecovered { //   print("[recovered]") //    } print("\n") } 

Honestly, if the panic arguments were a string, then this could have been completed, but alas - an empty interface, so we will have to implement printing an empty interface, and accordingly bring the type to the interface, and we will deal with it. Vorning: all the code written in this section is a vivid example of the initial stage of MVP, it works, of course, but it hurts too primitive and flawed.

Let me remind you, we are still working in runtime.go:

 //     func StrCmp(s1, s2 string) int { if len(s1) < len(s2) { return -1 } if len(s2) < len(s1) { return 1 } for i := 0; i < len(s1); i++ { if s1[i] < s2[i] { return -1 } if s2[i] < s1[i] { return 1 } } return 0 } //  TypeDescriptor     func InterfaceType(arg *EmptyInterface) TypeDescriptor { return *(arg.__type_descriptor) } //    ,       ,     func InterfaceTypeEq(arg1, arg2 *EmptyInterface) bool { return *(arg1.__type_descriptor.string) == *(arg2.__type_descriptor.string) } //     //iface -   TypeDescriptor   //e -     //ret - ,     // ok -    func InterfaceE2T2(iface *TypeDescriptor, e EmptyInterface, ret uint32) (ok bool) { if *(iface.string) == *(e.__type_descriptor.string) { //   memcpy(ret, e.__object, uint32(iface.size)) //        return true } else { return false } } 

Switch to the screen.go file:

 //   ,         func PrintInterface(arg interface{}) { v, ok := arg.(string) if ok { print(v) } } 

Well, now we can write in our code (kernel.go, the Load method):

 panic("Habrahabr") 

and admire the output in qemu:

 panic: Habrahabr 

Already not bad, huh? But I also promised to handle runtime errors.

runtime.go:

 //           const ( SLICE_INDEX_OUT_OF_BOUNDS = uint32(iota) ARRAY_INDEX_OUT_OF_BOUNDS STRING_INDEX_OUT_OF_BOUNDS SLICE_SLICE_OUT_OF_BOUNDS ARRAY_SLICE_OUT_OF_BOUNDS STRING_SLICE_OUT_OF_BOUNDS NIL_DEREFERENCE MAKE_SLICE_OUT_OF_BOUNDS MAKE_MAP_OUT_OF_BOUNDS MAKE_CHAN_OUT_OF_BOUNDS DIVISION_BY_ZERO MSG_INDEX_OUT_OF_RANGE = "index out of range" MSG_SLICE_BOUNDS_OUT_OF_RANGE = "slice vounds out of range" MSG_NIL_DEREFERENCE = "nil pointer dereference" MSG_MAKE_SLICE_OUT_OF_BOUNDS = "make slice len or cap out of range" MSG_MAKE_MAP_OUT_OF_BOUNDS = "make map len out of range" MSG_MAKE_CHAN_OUT_OF_BOUNDS = "make chan len out of range" MSG_DIVISION_BY_ZERO = "integer divide by zero" MSG_UNKNOWN = "unknown" ) // ,  ,   ,   ,   func RuntimeError(i uint32) { switch i { case SLICE_INDEX_OUT_OF_BOUNDS, ARRAY_INDEX_OUT_OF_BOUNDS, STRING_INDEX_OUT_OF_BOUNDS: panic(MSG_INDEX_OUT_OF_RANGE) case SLICE_SLICE_OUT_OF_BOUNDS, ARRAY_SLICE_OUT_OF_BOUNDS, STRING_SLICE_OUT_OF_BOUNDS: panic(MSG_SLICE_BOUNDS_OUT_OF_RANGE) case NIL_DEREFERENCE: panic(MSG_NIL_DEREFERENCE) case MAKE_SLICE_OUT_OF_BOUNDS: panic(MSG_MAKE_SLICE_OUT_OF_BOUNDS) case MAKE_MAP_OUT_OF_BOUNDS: panic(MSG_MAKE_MAP_OUT_OF_BOUNDS) case MAKE_CHAN_OUT_OF_BOUNDS: panic(MSG_MAKE_CHAN_OUT_OF_BOUNDS) case DIVISION_BY_ZERO: panic(MSG_DIVISION_BY_ZERO) default: panic(MSG_UNKNOWN) } } 

Accordingly, now, if we make a mistake somewhere, we will see a message about it.

Warning: The code from the previous parts generates a bunch of runtime errors, but for now I will not show how to fix them, let it be homework.

Thanks for help in preparing the article:

Victorya1 - proofreading, roughness processing
kirill_danshin - discussion of those. parts interesting discussions

UPD: An interesting discussion of the problem of casting interfaces to a string type
Kirill:
look what my thoughts are

[12:09:22 PM] Ivan:
I read carefully)

[12:09:35 PM] Kirill:
lines in goshka are immutable

[12:09:45 PM] Ivan:
Yep
Change leads to the creation of a new

[12:10:25 PM] Kirill:
and InterfaceE2T2 can, in theory, and not copy data
rather refer to existing

[12:12:47 PM] Ivan:
It cannot, a) it should work not only with strings, b) we get the return address and have to write data, which is what is expected, but not a pointer

[12:13:31 PM] Kirill:
something I overlooked means
sorry i can't
I always rest on this restriction
it turns out here it will not work to fix

[12:16:05 PM] Ivan:
Why, it is possible, runtime extension, i.e. implement the InterfaceToString method (iface interface {}) (str * string, ok bool)
But this is not a runtime method, but a custom
Hmm ... add this little discussion to the article?

[12:17:01 PM] Kirill:
come on
This is an interesting task, it must be solved sooner or later.
exactly the same interface {} -> [] byte

UPD2: I tried to implement the function from the discussion with Cyril.

Source: https://habr.com/ru/post/315566/


All Articles