2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3  След.
 
 Re: Стек и bootloader
Сообщение04.01.2011, 10:39 
Заслуженный участник


26/07/09
1559
Алматы
Эх, что-то ничего не получается с вашим кодом... Что я пытался сделать. Ну сначала я вот этот код инициализации регистров в 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. Я немного его переписал, в частности вместо вашей функции, использующей прерывания, я написал простенькую функцию, печатающую прямо в память; Также я заменил ваши ассемблерные вставки, чтобы можно было попробовать настроить стек. В общем вот:
код: [ скачать ] [ спрятать ]
Используется синтаксис 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? :)

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 12:12 


08/11/09
156
СПАСИБО ОГРОМНОЕ!!! Пока времени сейчас нет (экзамены), но еще вернусь...

P.S. Еще дельное обсуждение со мной завели тут: http://www.opennet.ru/openforum/vsluhforumID9/8980.html
Обратите внимание на последние сообщения... Говорят, что .text надо класть не в 0-й адрес? Действительно, почему 0?

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 12:32 
Заслуженный участник


26/07/09
1559
Алматы
Спасибо за ссылку.

kuraga писал(а):
Действительно, почему 0?

Потому, что так удобнее. :)

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 13:05 
Аватара пользователя


31/10/08
1244
Circiter
У вас всё не правильно. Удивительно что работает!!!

Пойдем с начало. Компилируете в виндоусе или в линуксе?

Вы пишите под реальный режим. С чего в друг у вас 32 битные регистры появились?

Полез разбираться наткнулся на http://gcc.gnu.org/install/specific.html#dos
Не компилирует gcc под реальный режим, да и вообще 16 бит не поддерживает.
Дальше разговор бессмысленный.

Цитата:
Возвращаемое значение должно взяться со стека, т.е. из [esp], и записаться в eax, но gcc почему-то сгенерировал код, берущий значение параметра не из [esp], а из [esp+4], т.е. из-за пределов стека!

Во первых компилятор прав. А если он не прав смотри пункт первый.
Во вторых за приделы стека это не выходит так как стек растет в низ.
esp=0x1000
Ты положил в стек значение. esp уменьшилось на 4. esp=0xFFC
[esp] указывает на твою тройку.
Потом ты сделал вызов call он положил адрес возврата в стек.
esp=0xFF8
Теперь [esp] это адресс возврата, а [esp+4] указывает на твою тройку.


Цитата:
Во-вторых, чтобы получить смещение видеопамяти, пришлось отнимать от её линейного адреса, т.е. от 0xb8000, адрес ядра, т.е. 0x1000; некрасиво.

Ну а чего ты хотел? Система x86 некрасивая. В реальном режиме действует сегментная модель памяти.
А в третьих видимо эмулятор глючит. Так как лимит сегмента DS = 0xFFFF 64КБ и обращение к данным выше этого придела должно приводить к исключению.

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 14:12 
Заслуженный участник


26/07/09
1559
Алматы
2Pavia
Ну насчет поддержки gcc'ом 16 бит в интернете сведения противоречивые. К тому-же, по-факту, код-то генерируется. Хотя мнение ваше понятно, я топикстартеру тоже намекал, что 16b в gcc -- это что-то очень странное.

А вот насчет call, это спасибо. Почему-то я не учитывал, что эта инструкция тоже работает со стеком. Теперь-то понятно... Просто я пытался найти причину работы кода в одном случае, и не работы -- в другом. Ухватился за такое-вот наблюдение, а оказалось, что ошибся.

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

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 15:02 
Аватара пользователя


31/10/08
1244
Circiter
Есть порт djgpp который таки компилирует под Dos. Но там есть какие-то нюансы. Надо изучать.

У вас скорее всего дело в том что код скомпилирован как 32 битный, а запускаете вы его из 16 битного режима. Соответственно он разбирает все неправильно. До какой-то инструкции это будет работать, а потом все пойдет на перекосяк.
К примеру процессор push будет интепритировать как 16 битный, а смещение рассчитывалось как +4 то да тут будет выход за приделы стека. Хотя тут вопрос дойдет ли он вообще до Call

Это все-равно что взять bmp и переименовать в com. Что нибудь да получится.
Или код с ARM запустить на x86.

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 15:25 
Заслуженный участник


26/07/09
1559
Алматы
2Pavia
Цитата:
У вас скорее всего дело в том что код скомпилирован как 32 битный, а запускаете вы его из 16 битного режима. Соответственно он разбирает все неправильно. До какой-то инструкции это будет работать, а потом все пойдет на перекосяк.

Да не, тут все не так страшно. Ведь gcc выплевывает ассемблерный листинг, который уже потом транслируется в машинный код с помощью ассемблера gas. Так вот, хоть поддержка 16 битного кода в gcc строго лимитирована (вы вообще утверждаете, что такой поддержки и вовсе нет; хотя существуют коммерческие 16b кодогенераторы для gcc), но gas от этого не страдает и директивы .code16 часто бывает достаточно. К тому-же дизассемблер правильно разбирает код (даже обычный objdump -d -M i8086 -M intel kernel.bin)

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 15:52 
Аватара пользователя


31/10/08
1244
Circiter
Как раз таки ".code16" недостаточно
Дело в том что кодо-генератор создает инструкции push call ret и др. У 32 и 16 одинаковые мнемоники.

Так вот дальше идет компиляция в нормальных условиях компилятор считает что это 32 битные инструкции.
А вы берете и обзываете их 16 битными. Ряд инструкций ассемблер может вычислить что они 32 битные, а ряд нет.

Можно попробовать как-то извратиться и заставить кодо-генератор выдавать код в синтаксис AT&T там насколько помню все инструкции строго определяют размерность.

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 16:14 
Заслуженный участник


26/07/09
1559
Алматы
Кстати, та небольшая проблема с невозможнотью передавать строковые литералы непосредственно, легко решается добавлением в boot.ld помимо *(.rodata) ещё и строки *(.rdata). Теперь можно писать print("My message").

2Pavia
Цитата:
Можно попробовать как-то извратиться и заставить кодо-генератор выдавать код в синтаксис AT&T там насколько помню все инструкции строго определяют размерность.

Наоборот, пришлось "извращаться", чтобы он НЕ выдавал ассемблерный код в, имхо малочитабельном (но подробном), at&t-синтаксисе.
А в intel синтаксисе размерность определяется по операндам. Если угадать размерность не получается, gas матерится. Ok, я посмотрю как именно выглядит push в готовом бинарном коде, вдруг и в правду все дела в разрядности.

Тут видите-ли ещё что... Вот например если взять нерабочий вариант кода, который вообще ничего на экран не выводить и в скрипте компоновщика спозиционировать секцию .text на адрес 0x1000, то какая-то абракадабра все-таки выводится (но не нужная строка).

Не могли бы вы рассказать поподробнее об этой возможности. Для уточнения: сейчас ядро грузится в сегмент 0x100, со смещением 0, то есть по линейному адресу 0x1000, а .text позиционируется на 0...

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 17:40 
Аватара пользователя


31/10/08
1244
Circiter
Ликеру ты говоришь куда будет загружена секция ".text" к примеру 210h. Твой код находится в этой секции. Ты его грузишь куда-то в память. К примеру по линейному адресу 200h.
К примеру в CS=30h:0= линейному 300h

Так вот что делает линкер. Он считает что секция ".text" находится по смещению 210h от адреса cs:0
т.е. равно cs:210h. И все указатели рассчитывает относительно этого адреса.

Так вот вопрос какую ошибку мы имеем?
200h- (cs*10h+210h)

Вот поэтому надо было грузить секцию выши xxx. И cs выбрать такой что cs=(xxx-210h)/10h:0

А данные аналогично. Только расчет относительно DS.

Проще всего секции проверить и прочее через отладчик возьмите td (turbo debugger)

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение04.01.2011, 19:40 
Заслуженный участник


26/07/09
1559
Алматы
2Pavia
Так, ну сейчас у меня и код и данные в одной секции лежат, все как в .com файлах, ds=cs.

Насчет формул. Что есть сейчас:

xxx=1000h (сюда загрузчик считывает ядро),
.text позиционируется компоновщиком на 0,
cs=(1000h-0)/10h=100h (может быть здесь ошибка, вы под делением на 10h:0 имели ввиду деление на 10h или на 100h? Кажется, первый вариант...).

Передача управления ядру осуществляется инструкцией jmp 100h:0, т.е. после этого cs действительно содержит 100h. Вроде все по вашим формулкам... Где противоречие, что нужно изменить? Вот вы сказали "выше xxx", как это понимать?

Кстати, спасибо большое, что участвуете в обсуждении, а то я тоже вслед за участником kuraga запутался окончательно. :)

-- Вт янв 04, 2011 23:04:45 --

Ну это совсем уж, теперь абракадабра выводится нерабочим вариантом кода даже при позиционировании на .text на 0, хотя раньше экран оставался пустым... Может быть это все-таки из-за того, что я стучусь за пределы всего сегмента при доступе к видеопамяти... Надо попробовать сделать это более аккуратно...

-- Вт янв 04, 2011 23:09:35 --

Насчет push'а: в бинарнике он представлен опкодом 0x68, что это? То самое? :) Или там ещё какой-нибудь байтик-префикс должен быть?

-- Вт янв 04, 2011 23:19:25 --

В общем, как выглядят 16b и 32b push'ы? Ведь тут ещё разница в том, какой регистр задействован, sp или esp... Ну точно придется отладчиком туда лезть. :)

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение05.01.2011, 11:27 
Заслуженный участник


26/07/09
1559
Алматы
Насчет отладчика. Честно говоря, я ненавижу интерактивную отладку и всегда предпочитаю писать отладочный код (и не потому, что не умею пользоваться штангенциркулем дебагером), среди прочего это в дальнейшем облегчает юнит-тестирование. Вот и сейчас, для отслеживания значения esp я хотел бы написать пару строчек кода, вычисляющего разницу между указателями стека до и после использования push'а. Поэтому такой вот ламерский вопрос задаю, допустим разница лежит в eax, как это число отобразить на экране? Это число маленькое (0, 2 или 4) и фактически вопрос сводится к пересылке содержимого eax в, ну скажем, al (потом к al можно будет прибавить код символа '0' и отправить результат в видеобуфер). Я просто не уверен, что сейчас я это делаю правильно...

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение05.01.2011, 12:12 


08/11/09
156
Код:
// число в n
__asm__ __volatile__ ("int 0x10" : : "a" (0x0A00 | n+'0'), "c"(1));

Код:
; число в al
mov ah, 0xA
mov cx, 1
add al, '0'
int 0x10

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение05.01.2011, 12:34 
Заслуженный участник


26/07/09
1559
Алматы
2kuraga
Я вообще-то спрашивал, как на самом деле должна выглядеть конструкция mov al, eax... :) Я все ещё надеюсь, что допустил какую-то ошибку. А то сейчас все указывает на то, что push уменьшает esp всего на 2...

 Профиль  
                  
 
 Re: Стек и bootloader
Сообщение05.01.2011, 12:43 
Аватара пользователя


31/10/08
1244
Circiter
Al это младшая часть регистра eax поэтому ничего перемещать не надо.

Цитата:
А то сейчас все указывает на то, что push уменьшает esp всего на 2...

Всё правильно. Как я и писал здесь и ошибка. Вам надо будет препроцессор написать
Которая заменяет push на push DWord ptr или как там в as выглядит приведение типов.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 36 ]  На страницу Пред.  1, 2, 3  След.

Модераторы: maxal, Toucan, PAV, Karan, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group