Неожиданный вывод при печати непосредственно в текстовую видеопамять

#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 } . Он даже не помещает цвет в память для символа. @MichaelPetch

3. 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) для первого символа видео для цвета