#c #x86 #kernel #bootloader #osdev
#c #x86 #ядро #загрузчик #экранное меню
Вопрос:
Я разрабатываю ядро на C и создал что-то для печати на экране в видеопамяти. Я ожидал, что первый байт в видеопамяти будет символом для печати, а второй байт определяет цвет. Но в моей программе есть что-то другое, но это работает!! Это очень неожиданно и необычно.
Мой код ядра —
#define VIDEO_MEM 0xb8000
void write_string( int colour, const unsigned char *string );
void main()
{
unsigned char *vid = (unsigned char*) VIDEO_MEM;
int i=0;
for (i = 0; i < 2000; i )
{
*vid = ' ';
*(vid 2) = 0x1f;
vid = 2;
}
write_string(0x1f,"The Kernel has been loaded successfully!!");
}
void write_string( int colour, const unsigned char *string ) {
unsigned char *vid = (unsigned char*) VIDEO_MEM;
while(*string != 0)
{
*(vid) = *string;
*(vid 2) = colour;
string;
vid =2;
}
}
Он печатает символ *vid
и цвет *(vid 2)
, а затем увеличивает значение vid
на 2. Затем он должен заменить и напечатать следующий символ *(vid 2)
. Итак, цвет должен исчезнуть, но он все еще работает.
Кроме того, цвет должен быть включен *(vid 1)
Когда я использую *(vid 1)
вместо *(vid 2)
для печати строки, на экране отображаются символы со стрелкой вниз (с кодом ACII 0x1f
, который я хотел использовать как цвет), заменяющие всю строку.
Почему код ведет себя так необычно??
Кто-нибудь может помочь?
Редактировать
Я отредактировал свой код, и теперь он печатает строку. Но возникла другая проблема. Я добавил поддержку печати по определенному номеру строки. Но теперь это сдвигает строку назад на один символ.
void write_string( int colour, const unsigned char *string, int pos ) {
unsigned char *vid = (unsigned char*) VIDEO_MEM;
vid =pos*160;
while(*string != 0)
{
*vid = colour;
*(vid 1) = *string;
string;
vid =2;
}
}
Итак, если я скажу ему печатать в строке 10, он напечатает первый символ в последнем символе 9-й строки, а затем продолжит.
У меня также есть функция печати символов, которая просто печатает фигурные скобки ( }
) вместо заданного символа, и это тоже на один символ назад от заданной позиции (например, ошибка в write_string
функции). Также не изменяется цвет фона символа, указанный в качестве аргумента.
void putChar(char character, short col, short row, char attr) {
unsigned char* vid_mem = (unsigned char *) VIDEO_MEM;
int offset = (row*80 col)*2;
vid_mem = offset;
if(!attr) {
attr = 0x0f;
}
*vid_mem = (attr<<8) character;
}
EDIT 2
My Boot Loader:
[org 0x7c00]
KERNEL equ 0x1000
mov [BOOT_DRIVE],dl
mov bp,0x9000
mov sp,bp
mov bx, msgReal
call print_string
call load_kernel
call switch_to_pm
jmp $
%include 'boot/bios.ASM'
%include 'boot/gdt.ASM'
%include 'boot/protected_mode.ASM'
%include 'boot/print32.ASM'
[bits 16]
load_kernel:
mov bx,msgKernel
call print_string
mov bx, KERNEL
mov dh, 15
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, msgProt
call print_string32
call KERNEL
jmp $
BOOT_DRIVE db 0
msgReal db "Booted in 16-bit mode",0
msgProt db "Successfully switched to 32-bit mode",0
msgKernel db "Loading the kernel onto memory",0
times 510-($-$$) db 0
dw 0xaa55
bios.ASM —
;BIOS Functions
[bits 16]
print_string:
pusha
mov cx,bx
mov ah,0x0e
printStringStart:
mov al,[bx]
cmp al,0
je done
int 0x10
inc bx
jmp printStringStart
done:
popa
ret
print_word:
pusha
mov ax,0x0000
mov cl,0x10
mov al,bh
div cl
call printDig
mov al,bh
and al,0x0f
call printDig
mov ax,0x0000
mov al,bl
div cl
call printDig
mov al,bl
and al,0x0f
call printDig
popa
ret
printDig:
cmp al,0x9
jg alpha
add al,'0'
mov ah,0x0e
int 0x10
jmp pDigDone
alpha:
sub al,0xa
add al,'A'
mov ah,0x0e
int 0x10
pDigDone:
ret
hex_prefix: db '0x',0
disk_load:
push dx
mov ah,0x02
mov al,dh
mov ch,0x00
mov dh,0x00
mov cl,0x02
int 0x13
jc disk_error
pop dx
cmp dh,al
jne disk_error
ret
disk_error:
mov ah,0x0e
mov al,'X'
int 0x10
mov bx,errMsg
call print_string
jmp $
errMsg:
db "Disk Read Error....."
times 80-20 db " "
db 0
gdt.ASM —
gdt_start:
gdt_null:
dd 0x0
dd 0x0
gdt_code:
dw 0xffff
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
protected_mode.ASM —
[bits 16]
switch_to_pm:
cli
lgdt [gdt_descriptor]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:init_pm
[bits 32]
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp,0x90000
mov esp,0x90000
call BEGIN_PM
print32.ASM —
[bits 32]
VIDEO_MEM equ 0xb8000
DEF_COLOR equ 0x0f
print_string32:
pusha
mov edx,VIDEO_MEM
print_string32_loop:
mov al, [ebx]
mov ah, DEF_COLOR
cmp al,0
je print_string32_end
mov [edx],ax
inc ebx
add edx,2
jmp print_string32_loop
print_string32_end:
popa
ret
Я также добавляю файл kernel_start.asm непосредственно перед ядром при связывании для вызова функции main —
[bits 32]
[extern main]
call main
jmp $
И вот мой файл make —
C_SOURCES = $(wildcard drivers/*.c kernel/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)
OBJ = ${C_SOURCES:.c=.o}
all: os-image
os-image: boot/boot_sector.bin kernel.bin
cat $^ > $@
kernel.bin: kernel/kernel_start.o ${OBJ}
ld -o $@ -Ttext 0x1000 $^ --oformat binary
%.o : %.c
gcc -std=c99 -Wall -pedantic -ffreestanding -c $< -o $@
%.o : %.asm
nasm $< -f elf64 -o $@
%.bin : %.asm
nasm $< -f bin -o $@
clean:
rm -fr kernel/*.o
rm -fr drivers/*.o
rm -fr boot/*.bin
rm -fr os-image *.bin *.o
Комментарии:
1. @MichaelPetch Я исправил ошибку. Теперь возникла другая проблема. Я указал это при РЕДАКТИРОВАНИИ. Не могли бы вы это проверить?
2. Но приведенный выше код, по крайней мере, печатает записи, хотя я установил первый байт в качестве атрибута, а второй — в качестве символа. Проблема в том, что
write_string
функция не печатает в позиции записи иputChar
ничего не печатает, а просто помещает a}
. Он даже не помещает цвет в память для символа. @MichaelPetch3. VIDEO_MEM равен 0xb8000 @MichaelPetch
Ответ №1:
С изменениями, предложенными в других ответах и комментариях, ваша проблема, похоже, не воспроизводима для меня. Следующий код работает для меня. Я попытался сохранить то, как вы его закодировали, чтобы это имело смысл для вас:
#define VIDEO_MEM 0xb8000
void write_string( unsigned char colour, const char *string );
void write_string_line( unsigned char colour, const char *string, int pos );
void putChar(char character, short col, short row, unsigned char attr);
/* Place this at top of file as first code in kernel.o */
__asm__ ("call mainrn"
"clirn"
"hltrn"
);
void main()
{
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
int i=0;
for (i = 0; i < 2000; i )
{
*vid = ' ';
*(vid 1) = 0x1f;
vid = 2;
}
write_string(0x1f,"The Kernel has been loaded successfully!!");
write_string_line(0x1f,"Testing Here!!",1);
putChar('Z',3,3,0xf3);
}
void write_string( unsigned char colour, const char *string ) {
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
while(*string != 0)
{
*(vid) = *string;
*(vid 1) = colour;
string;
vid =2;
}
}
void write_string_line( unsigned char colour, const char *string, int pos ) {
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
vid =pos*160;
while(*string != 0)
{
*vid = *string;
*(vid 1) = colour;
string;
vid =2;
}
}
void putChar(char character, short col, short row, unsigned char attr) {
volatile unsigned char* vid_mem = (unsigned char *) VIDEO_MEM;
int offset = (row*80 col)*2;
vid_mem = offset;
if(!attr) {
attr = 0x0f;
}
*(unsigned short int *)vid_mem = (attr<<8) character;
/* This would do the same as line above
*vid_mem = character;
*(vid_mem 1) = attr;
*/
}
Я добавил __asm__
в начале, чтобы убедиться, что код появляется первым в сгенерированном объектном файле. Вероятно, он работает без него. Я изменил все ваши *vid
указатели на volatile
. Поскольку видео — это ввод-вывод с отображением в память, вы не хотите, чтобы компилятор потенциально удалял записи экрана при оптимизации. Вероятно, ваш код будет работать без volatile
него, но его следует добавить здесь, чтобы избежать потенциальных проблем.
При запуске BOCH этот код выдает этот вывод на экран:
Если вы используете приведенный здесь код, и он не работает, это наводит на мысль, что проблема, с которой вы столкнулись, скорее всего, связана с кодом, который вы пишете в своем загрузчике, который считывает диск, включает A20, устанавливает GDT, переходит в защищенный режим, а затем вызывается в вашем C-коде. Также возможны проблемы в зависимости от того, как вы компилируете и связываете свое ядро.
Вероятная причина неопределенного поведения
После того, как весь код и файл make стали доступны в EDIT 2, стало ясно, что одной из существенных проблем было то, что большая часть кода была скомпилирована и связана с 64-разрядными объектами и исполняемыми файлами. Этот код не будет работать в 32-разрядном защищенном режиме.
В файле make выполните следующие настройки:
- При компиляции с помощью GCC необходимо добавить
-m32
опцию - При сборке с помощью GNU Assembler (as), ориентированной на 32-разрядные объекты, необходимо использовать
--32
- При связывании с LD необходимо добавить
-melf_i386
опцию - При сборке с использованием NASM, ориентированной на 32-разрядные объекты, необходимо изменить
-f elf64
на-f elf32
Предпочтительным вариантом использования 64-разрядного компилятора и цепочки инструментов из среды хоста является создание кросс-компиляторной цепочки инструментов для i686 или i386.
Комментарии:
1. Это не работает. Код для очистки экрана печатает символ стрелки вниз (с ASCII = 0x1f), а вместо двух сообщений печатаются стрелки вверх. Функция putChar выводит мигающие фигурные скобки
}
на сером фоне. Мой код для очистки экрана работает нормально, а rest — нет.2. @AneeshSharma Это подтверждает мое подозрение, что любая проблема, с которой вы столкнулись, скорее всего, не связана с самим кодом C. Вероятно, вы что-то сделали неправильно при загрузке файла ядра в память или что-то с сегментами, GDT-параметрами, которые вы используете для сборки / сборки изображений и т. Д. Тот факт, что такой простой код не работает для вас и работает для меня с моим собственным загрузчиком, является довольно хорошим признаком того, что ваш загрузчик сделал что-то не так или не сделал то, что должен был. Если вы сделали доступным ВЕСЬ свой код и команды, которые вы используете для его создания, я мог бы вам помочь.
3. @AneeshSharma: Мой код также работает с Boch. Кстати, ваше редактирование и изображение экрана должны быть добавлены к вашему вопросу, а не к моему ответу.
4. Хорошо, я добавлю весь код. И я использую оболочку Ubuntu bash в Windows 10 для компиляции моего кода с использованием файла make. И извините за изображение
5. Это работает!! Спасибо @MichaelPetch. Теперь мой код работает нормально.
Ответ №2:
Это должно сработать. Каждая ячейка VGA имеет длину 2 байта, первый байт хранит символ, а второй байт хранит цвет. Также убедитесь, что вы отметили указатель volatile. Чтобы избежать любых неожиданных изменений (или оптимизаций), внесенных компилятором в это локальное поле.
void write_string( int colour, const unsigned char *string )
{
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
while( *string != 0 )
{
*vid = *string ;
*vid = colour;
}
}
Ответ №3:
Вы используете * (vid) для первого символа видео для цвета