Я создал загрузчик (asm), который загружает ядро (Си) по адресу в памяти. Реальный режим. Проблема со стеком. Если параметры функции, вызываемой в ядре, передаются через регистры, все ок. Но если нет - через стек - например, переменное количество аргументов, все летит. Как инициализировать стек? Куда должны указывать регистры? Спасибо.Вот kernel.c. В qemu код ничего не выводит, и выводит 'S', если '...' заменить на 'int x'. Отличие в передаче параметров через стек, как видно в ассемблерном коде.
__asm__(".code16gcc\n");
__asm__ ("jmp _start\n");int extern __attribute__((noinline)) __attribute__((regparm(3))) sprintf(char * buf, const char *fmt, ...)
{
__asm__ __volatile__ ("int 0x10" : : "a" (0x0E00 | *fmt), "b"(144));
}void __attribute__((noreturn)) _start()
{
char buf[128];
sprintf(buf, "Sasha%d", 123);
while(1);
}Нынешняя инициализация регистров:
mov ax, SETUP_ADDR>>4 ; SETUP_SEG
mov es, ax
mov ds, ax
mov cs, ax
mov ss, ax
mov sp, 0x400
jmp SETUP_ADDR>>4:0Компиляция и запуск:
#!/bin/bash
gcc -c -Wall -save-temps -march=i386 -ffreestanding -Wno-main -fno-builtin -masm=intel -O0 -o kernel.o kernel.c
ld -nostdlib -static -Ttext 0 --oformat binary -o kernel.bin kernel.omkdir diskc
mount -t msdos -o loop=/dev/loop3,blocksize=1024 dos.img diskc
rm diskc/BOOTOR
cp kernel.bin diskc/BOOTOR
umount diskc
rmdir diskcnasm boot.asm -o boot.bsr
dd if=boot.bsr of=dos.img bs=512 count=1 conv=notruncqemu -fda dos.img -boot a
> mov sp, 0x400может просто для стека мало 1кб и соответственно при использовании стека происходит затирание кода. Попробуйте поставить sp на конец сегмента.
К сожалению я не помню как он должен быть выравнен) попробуйте например 0xfffc
>> mov sp, 0x400
> может просто для стека мало 1кб и соответственно при использовании стека происходит
> затирание кода. Попробуйте поставить sp на конец сегмента.
> К сожалению я не помню как он должен быть выравнен) попробуйте например
> 0xfffcНет. Да и функция жрет 12 байт... Посмотрите, пожалуйста, я не ем, не сплю, не могу, когда что-то стоит из-за маленького незнания!
Вот полные исходники: http://zalil.ru/30246211
А вот ассемблер ядра:
.file "kernel1.c"
.intel_syntax noprefix
#APP
.code16gccjmp _start
#NO_APP
.text
.globl sprintf
.type sprintf, @function
sprintf:
push ebp
mov ebp, esp
push ebx
mov eax, DWORD PTR [ebp+12]
mov al, BYTE PTR [eax]
movsx eax, al
or ah, 14
mov edx, 144
mov ebx, edx
#APP
# 6 "kernel1.c" 1
int 0x10
# 0 "" 2
#NO_APP
pop ebx
leave
ret
.size sprintf, .-sprintf
.section .rodata
.LC0:
.string "Sasha%d"
.text
.globl _start
.type _start, @function
_start:
push ebp
mov ebp, esp
add esp, -128
push 123
push OFFSET FLAT:.LC0
lea eax, [ebp-128]
push eax
call sprintf
add esp, 12
.L4:
jmp .L4
.size _start, .-_start
.ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5"
.section .note.GNU-stack,"",@progbits
>[оверквотинг удален]
> lea eax, [ebp-128]
> push eax
> call sprintf
> add esp, 12
> .L4:
> jmp .L4
> .size _start, .-_start
> .ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5"
> .section .note.GNU-stack,"",@progbits
>
Извините нечаянно квотинг отправил.
Почему категорично нет?
На мой взгляд сейчас так:
код смещение 0
...
где-то тут конец кода
стэк смещение 400код отъедает от несчастного килобайта еще 128байт + сколько-то 32битных push
дальше зовете int10 - стэк стал еще ближе к коду. как оно там устроенно внутре хз. А если
еще и какоенибудь аппаратное прерывание?
Вообщем я бы таки попробовал подвинуть стек в более безопасное место.
>[оверквотинг удален]
> На мой взгляд сейчас так:
> код смещение 0
> ...
> где-то тут конец кода
> стэк смещение 400
> код отъедает от несчастного килобайта еще 128байт + сколько-то 32битных push
> дальше зовете int10 - стэк стал еще ближе к коду. как оно
> там устроенно внутре хз. А если
> еще и какоенибудь аппаратное прерывание?
> Вообщем я бы таки попробовал подвинуть стек в более безопасное место."Нет." = "Спасибо. Попробовал, но не помогло." :-) Ставил 0xfffc, 0xf00 и StackSeg (и метка в конце). А так Вы правы, хз почему написал 0x400
> "Нет." = "Спасибо. Попробовал, но не помогло." :-) Ставил 0xfffc, 0xf00 и
> StackSeg (и метка в конце). А так Вы правы, хз почему
> написал 0x400Тогда кроме как прицепить к quemu gdb и пройтись по шагово ничего посоветовать не могу.
Кажется нарыл. в двоичном файле ядра строка "Sasha%d" строго в конце файла, хотя OFFSET .LC0 по дизассемблеру есть 0. Разве по опции -Ttext оно не должно быть в начале???
Кажется нарыл. в двоичном файле ядра строка "Sasha%d" строго в конце файла, хотя OFFSET .LC0 по дизассемблеру есть 0. Разве по опции -Ttext оно не должно быть в начале???
Я вот че понял: все правильно, он берет смещения переменных от начала какого-то сегмента... Какого? Я просто привык, что мы пишем ASSUME, а GCC такого не пишет... Или это на linux всегда так? Так вот: базирование .LC0 ведь идет по DS? А кто его устанавливает? Сейчас - я, причем абсолютно неправильно!!! [u]А как GCC формирует сегменты?[/u] Один сегмент данных или как? [u]Как получить его адрес, и почему он не записывается в DS?[/u] [u]И где сегмент стека, какой из указанных опций я его отключил?[/u]Ответьте, пожалуйста, и простите за тупые вопросы! :)
> Я вот че понял: все правильно, он берет смещения переменных от начала
> какого-то сегмента... Какого? Я просто привык, что мы пишем ASSUME, а
> GCC такого не пишет... Или это на linux всегда так? Так
> вот: базирование .LC0 ведь идет по DS? А кто его устанавливает?
> Сейчас - я, причем абсолютно неправильно!!! [u]А как GCC формирует сегменты?[/u]
> Один сегмент данных или как? [u]Как получить его адрес, и почему
> он не записывается в DS?[/u] [u]И где сегмент стека, какой из
> указанных опций я его отключил?[/u]
> Ответьте, пожалуйста, и простите за тупые вопросы! :)GCC в обыденной жизни сегменты сам не привязывает, т.к полагает что это сделает ОС.
Для того чтобы это делалось, вам надо будет написать ассемблерную процедуру, которая будет вызываться перед _start() и будет устанавливать SS/DS/ES в нужные значения.
И еще вопрос, вы загружаете ELF-бинарник или plain (а-ля DOS *.COM файл)?
> И еще вопрос, вы загружаете ELF-бинарник или plain (а-ля DOS *.COM файл)?Второе.
>> И еще вопрос, вы загружаете ELF-бинарник или plain (а-ля DOS *.COM файл)?
> Второе.Значит тогда так.
После того как ваш бинарь считан с диска, он помещается по адресу 0x7c00:0000 (его туда кладет BIOS после загрузки через INT 19h) и устанавливает CS=DS=ES=SS=0x7c00.
После этого BIOS передает управление на этот адрес.
В boot.ld для меня есть пара подозрительных вещей:.text AT(0)
Вы хотите чтобы ваш сегмент кода лег в сегмент 0000h(точнее по линейному адресу 00000000h)?
Я правильно вас понял?
>[оверквотинг удален]
> Значит тогда так.
> После того как ваш бинарь считан с диска, он помещается по адресу
> 0x7c00:0000 (его туда кладет BIOS после загрузки через INT 19h) и
> устанавливает CS=DS=ES=SS=0x7c00.
> После этого BIOS передает управление на этот адрес.
> В boot.ld для меня есть пара подозрительных вещей:
> .text AT(0)
> Вы хотите чтобы ваш сегмент кода лег в сегмент 0000h(точнее по линейному
> адресу 00000000h)?
> Я правильно вас понял?Вот может тут и дыра! Я плохо знаю, что делает эта инструкция. Значение 0 выставил методом тыка...
Как правильно и что это делает (полнеый перечень можно пжл?)
>[оверквотинг удален]
>> устанавливает CS=DS=ES=SS=0x7c00.
>> После этого BIOS передает управление на этот адрес.
>> В boot.ld для меня есть пара подозрительных вещей:
>> .text AT(0)
>> Вы хотите чтобы ваш сегмент кода лег в сегмент 0000h(точнее по линейному
>> адресу 00000000h)?
>> Я правильно вас понял?
> Вот может тут и дыра! Я плохо знаю, что делает эта инструкция.
> Значение 0 выставил методом тыка...
> Как правильно и что это делает (полнеый перечень можно пжл?)Я вижу, что вы плохо ориентируетесь в архитектуре x86.
В сегменте с линейным адресом 00000000h лежит таблица векторов прерываний ( а точнее их адреса в формате сегмент:смещение). Эта таблица имеет 256 элементов по 4 байта каждый.
Когда вы делаете int xxh, адрес на который CPU при прерывании передаст управление как раз берется оттуда =), а вы туда пишете свой код =)) и как итог вместо передачи управления по определенному адресу, управление передается на случайную область памяти =) и у вас ничего не работает.Я бы вам посоветовал убрать вообще AT(0).Оно вам там ни к чему.
Это первое замечание.
Замечание номер два:
Если хотите сами вручную задать адрес сегмента стека и положение указателя стека (регистры SS и SP), запрещайте перед этим все прерывания, т.к это критическая секция, и вызванный обработчик прерывания может записать туда мусор.
т.е делаем так:
cli # Запрещаем прерывания
mov sp,...
mov ax,....
mov ss,ax
sti # Разрешаем прерыванияТретье:
Про скрипты LD читаем здесь: http://www.opennet.me/docs/RUS/gnu_ld/
>[оверквотинг удален]
>>> После этого BIOS передает управление на этот адрес.
>>> В boot.ld для меня есть пара подозрительных вещей:
>>> .text AT(0)
>>> Вы хотите чтобы ваш сегмент кода лег в сегмент 0000h(точнее по линейному
>>> адресу 00000000h)?
>>> Я правильно вас понял?
>> Вот может тут и дыра! Я плохо знаю, что делает эта инструкция.
>> Значение 0 выставил методом тыка...
>> Как правильно и что это делает (полнеый перечень можно пжл?)
> Я вижу, что вы плохо ориентируетесь в архитектуре x86.А я и не говорил, что хорошо. Я студент 2-го курса, да только там нам таких подробностей (пока) не рассказывали. Друзей-программистов у меня нет.
Те замечания, что Вы написали, я знал. Гружу я не в 0, а в 0x0700. Если убрать, не работает вообще. Спасибо огромное, но яснее не стало :)
>[оверквотинг удален]
>>> Вот может тут и дыра! Я плохо знаю, что делает эта инструкция.
>>> Значение 0 выставил методом тыка...
>>> Как правильно и что это делает (полнеый перечень можно пжл?)
>> Я вижу, что вы плохо ориентируетесь в архитектуре x86.
> А я и не говорил, что хорошо. Я студент 2-го курса, да
> только там нам таких подробностей (пока) не рассказывали. Друзей-программистов у меня
> нет.
> Те замечания, что Вы написали, я знал. Гружу я не в 0,
> а в 0x0700. Если убрать, не работает вообще. Спасибо огромное, но
> яснее не стало :)Грузите с FAT я так понимаю ( boot.asm смахиывает на копию оного из FreeDos =))?
как совет:
Скачайте исходники freedos и посмотрите как это сделано там