Эх, что-то ничего не получается с вашим кодом... Что я пытался сделать. Ну сначала я вот этот код инициализации регистров в boot.asm'е:
Код:
mov ax, SETUP_ADDR>>4
mov es, ax
mov ds, ax
mov cs, ax
mov ss, ax
mov sp, 0x400
заменил на:
Код:
cli
mov ax, SETUP_ADDR>>4
mov ds, ax
mov ss, ax
sti
То есть, убрал настройку
cs,
es и
sp. Кстати насчет
cs'а. Значение в этом регистре все-таки можно изменить, например, как я уже говорил, это можно сделать
jmp'ом, а также
call'ом (с предварительным запихиванием нужного значения
cs в стек), генерированием прерывания (через
int; значение берется из таблицы векторов прерываний) и возвратами
ret* (с предварительным запихиванием в стек двух значений, для
cs и для
ip; ну и ещё одного, при использовании
iret). То есть,
cs настраивается сам при прыжке в другой сегмент (перечисленными выше способами). Правда, есть ещё парочка способов изменить этот регистр, но они относятся к pm (шлюзы или tss). И в данной ситуации ничего такого вроде как и не нужно.
Потом я похимичил немного с boot.ld. Мне хотелось, чтобы все секции были объединены в одну, в результате получилось что-то вроде этого:
Код:
SECTIONS{
.text 0 :
{
*(.text)
*(.rodata)
*(.data)
*(COMMON)
*(.bss)
}
}
Я пытался сделать нечто подобное с помощью gcc-опции -Wa,-R, но ничего не получилось...
Потом я принялся за kernel.c. Я немного его переписал, в частности вместо вашей функции, использующей прерывания, я написал простенькую функцию, печатающую прямо в память; Также я заменил ваши ассемблерные вставки, чтобы можно было попробовать настроить стек. В общем вот:
__asm__
(
".code16\n"
"addr32 mov esp, stack+0x1000\n"
"jmp _start\n"
"stack: .space 0x1000"
);
void __attribute__((regparm(3))) putch(char c, int x, int y)
{
char *video=(char*)(0xb8000-0x1000);
video[(80*y+x)*2]=c;
video[(80*y+x)*2+1]=' ';
}
void __attribute__((regparm(1))) print(char *s)
{
int x=0, y=0;
while(*s)
{
putch(*s++, x, y);
if(++x==80) {x=0; y++;}
}
}
char h[]="I'm here.";
void start()
{
print(h);
while(1);
}
Во-первых, я решил зарезервировать 4K (0x1000) под стек я настроить
esp. Во-вторых, чтобы получить смещение видеопамяти, пришлось отнимать от её линейного адреса, т.е. от 0xb8000, адрес ядра, т.е. 0x1000; некрасиво. В-третьих, почему-то, несмотря на сливание всех секций, не удается делать что-то вроде
print("I'm here."), поэтому пришлось ввести глобальную переменную
h. В-четвертых, как вы видите, все это было абсолютно зря сделано и изначальную проблему это не решило -- по прежнему на самых видных местах красуются директивы
__attribute__((regparm(...))) и без них строка на экран не выводится.
Я ещё больше упростил основной код kernel'я, как-то так (ассемблерная заглушка осталась прежней):
Код:
int f(int a) {return a;}
void start()
{
f(3);
while(1);
}
Скомпилировал его с отключенной оптимизацией и опцией -fomit-frame-pointer. Получилось такое:
Код:
_f:
mov eax, DWORD PTR [esp+4]
ret
_start:
push 3
call _f
add esp, 4
L: jmp L
Видите, сначала
push 3 кладет параметр 3 на стек, уменьшая при этом
esp на 4. Потом происходит вызов
f. Возвращаемое значение должно взяться со стека, т.е. из
[esp], и записаться в
eax, но gcc почему-то сгенерировал код, берущий значение параметра не из
[esp], а из
[esp+4], т.е. из-за пределов стека!
А ежели снабздить
f директивой
__attribute__((regparm(1))), то код создается вполне здоровый (тупой, но здоровый):
Код:
_f:
sub esp, 4
mov DWORD PTR [esp], eax
mov eax, DWORD PTR [esp]
add esp, 4
ret
_start:
mov eax, 3
call _f
L: jmp L
Теперь, параметр передается
f через
eax. Уже в самой
f указатель стека уменьшается на 4 и по этому адресу, то есть прямо в
[esp], записывается значение из
eax. Потом это значение пересылается обратно из
[esp] в
eax и
esp восстанавливается (увеличивается на 4). Все понятно и логично.
Почему gcc так делает? Может что не так с выравниванием или ещё с чем. Может быть проблема все-таки именно в смешивании 16b x 32b. Было бы неплохо, если в этой теме ответил бы кто-нибудь хорошо разбирающийся в поднятых вопросах... Воть...
P.S.: Как решите проблему, отпишитесь (сюда), пожалуйста, Ok? :)