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 pointer to

__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("[відновлені]") //то повідомимо про це
} 
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
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.