Go panic(), runtime error и их реализации в своей ОС на Go+asm Part 0x000c03f.(float32)

от автора

Всем привет!
Недавно я писал про реализацию пустых интерфейсов в Go, та статья, как можно догадаться имеет прямое отношение к разработке ОС на Go, да данная тема не заброшена и не забыта, но была отложена на долгий срок.
Под катом: «выкидываем» asm прокси-методы, имплементируем методы panic() и поддержку рантаймовых ошибок

Помните все наши методы-заглушки и методы прокси на asm? Выкиньте и забудьте.
Должны остаться только два файла: multiboot.s и runtime.s
Содержимое multiboot.s не изменится, а runtime.s должен быть приведен вот к такому:

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  

Всё остальное безжалостно пускаем под нож

Открываем link.ld и в текстовой секции, после задачи DATA:

__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;

Да, спешу предупредить, некоторые методы, реализованные у меня, пока не описаны мной, поэтому придется вам их заменить на dummy или реализовать самостоятельно

Что здесь происходит? Мы создаем символьные алиасы, это гораздо лучшее решение, чем писать прокси методы на asm.

Что ж, как я и обещал asm-прокси мы выкинули, давайте займемся имплементацией метода panic.
Из этой статьи копипастим структуры TypeDescriptor, EmptyInterface и Uncommon в файл runtime.go

Добавляем в него:

//Интерфейс, описывающий стэк паники 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") } 

Честно говоря, если бы аргументы паники были строкой, то на этом можно было бы и закончить, но увы — пустой интерфейс, так что нам придется реализовать печать пустого интерфейса, а соответсенно приведение интерфейса к типу, этим и займемся. Ворнинг: весь код, написанный в данной секции, является ярким примером начальной стадии MVP, работает, конечно, но больно уж примитивен и ущербен

Напомню, мы по прежнему работаем в 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   } } 

Переключаемся на файл screen.go

//метод печати пустого интерфейса, на самом деле пока умеет только в строки func PrintInterface(arg interface{}) {   v, ok := arg.(string)   if ok {     print(v)   } } 

Что ж, теперь мы можем написать в своем коде (kernel.go, метод Load)

panic("Habrahabr") 

и полюбоваться на вывод в qemu

panic: Habrahabr 

Уже не плохо, да? Но я обещал ещё и обработку рантаймовых ошибок

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)   } } 

Соответственно теперь, если мы где-то допустим ошибку, то увидим о ней сообщение

Ворнинг: Код из прошлых частей генерирует кучу рантаймовых ошибок, но пока что я не буду показывать как их исправить, пусть это будет домашним заданием.

Благодарности за помощь в подготовке статьи:
Victorya1 — вычитка, обработка шероховатостей
kirill_danshin — обсуждение тех. частей, интересные дискуссии

UPD: Интересное обсуждение проблемы приведения интерфейсов к строковому типу

Kirill:
смотри, какие у меня мысли

[12:09:22 PM] Ivan:
Читаю внимательно )

[12:09:35 PM] Kirill:
строки в гошке иммутабельны

[12:09:45 PM] Ivan:
Угу
Изменение ведёт к созданию новой

[12:10:25 PM] Kirill:
и InterfaceE2T2 может, в теории, и не копировать данные
а ссылаться на существующие

[12:12:47 PM] Ivan:
Не может, а) он должен работать не только со строками, б) мы адрес возврата получаем и туда должны писать данные, при чём того типа которые ожидаются, а не указатель

[12:13:31 PM] Kirill:
что-то я проглядел, значит
жаль, что не может
я в это ограничение в проде вечно упираюсь
выходит, тут это не получится исправить

[12:16:05 PM] Ivan:
Почему, можно, расширением рантайма, т.е. реализовать метода InterfaceToString(iface interface{}) (str *string, ok bool)
Но это не рантаймовый метод будет, а пользовательский
Хм… добавлю это маленькое обсуждение в статью?

[12:17:01 PM] Kirill:
да, давай
это интересная задача, ее обязательно рано или поздно нужно решать
точно также interface{} -> []byte

ссылка на оригинал статьи https://habrahabr.ru/post/315566/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *