直接打印到文本视频存储器时出现意外输出 [英] Unexpected output when printing directly to text video memory

查看:84
本文介绍了直接打印到文本视频存储器时出现意外输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用C开发内核,并创建了一些要在屏幕上显示在视频内存上的东西.我希望视频存储器中的第一个字节是要打印的字符,第二个字节告诉颜色.但是我的程序有一些不同之处,但是可以用!这是非常意外和不寻常的.

I am developing a kernel in C and created something to print on screen on video memory. I expected that the first byte in video memory would be the character to print and the second byte tells the color. But my program has something different but it works!! It is very unexpected and unusual.

我的内核代码-

#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)上打印下一个字符.因此,颜色应该可以,但仍然可以使用.

It prints the character on *vid and the color on *(vid+2) and then increments the vid by 2. It should then replace and print the next char on *(vid+2). So, the color should go but it still works.

另外,颜色应该在*(vid+1)

当我使用*(vid+1)而不是*(vid+2)来打印字符串时,屏幕上会显示向下箭头字符(带有我想作为颜色的ACII代码0x1f)来替换整个字符串.

When I use *(vid+1) instead of *(vid+2) to print the string, the screen shows down arrow characters (with ACII code 0x1f which I wanted to be the color) replacing the entire string.

为什么代码的行为如此异常?

Why does the code behave so unusual??

任何人都可以帮忙吗?

编辑

我已经编辑了代码,现在可以打印字符串了.但是又出现了另一个问题.我增加了对在特定行号上打印的支持.但是现在这会将字符串向后移动一个字符.

I have edited my code and now it prints string. But another problem arose. I added a support for printing on particular line number. But now this shifts the string backwards by one character.

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行的最后一个字符上打印第一个字符,然后继续.

So, If I tell it to print on line 10, it prints the first character on the last character of the 9th line and then continues.

我还有一个字符打印功能,它只打印花括号(})而不是给定字符,并且在给定位置的后面也有一个字符(例如write_string函数中的错误).此外,它不会更改作为参数指定的字符背景颜色.

I also have a character printing function that justs prints curly braces (}) instead of the given character and that too one character backwards of the given position (like the error in the write_string function). Also it doen't change the character background color given as argument.

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;
}


编辑2

我的引导加载程序:

[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-

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-

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文件-

I also add a kernel_start.asm file just before the kernel while linking to call the main function -

[bits 32]
[extern main]
call main
jmp $

这是我的制作文件-

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

推荐答案

使用其他答案和评论中建议的更改,您的问题似乎对我来说是无法重现的.以下代码对我有用.我试图维护您的编码方式,以便对您有意义:

With the changes suggested in other answer and comments, your problem doesn't seem to be reproducible for me. The following code works for me. I've tried to maintain how you coded it just so it makes sense to you:

#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 main\r\n" \
         "cli\r\n" \
         "hlt\r\n"
         );

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__,以确保该代码是第一个出现在生成的目标文件中的代码.没有它,它可能会起作用.我已将所有<​​c0>指针修改为volatile.由于视频是内存映射的IO,因此您不希望编译器在优化时潜在地删除屏幕写操作.您的代码可能不需要volatile就可以工作,但是在此处添加代码以避免潜在的问题是适当的.

I've added the __asm__ at the beginning to make sure that code is the first to appear in the generated object file. It likely works without it. I've modified all your *vid pointers to be volatile . Since video is memory mapped IO you don't want to have the compiler potentially remove screen writes when it optimizes. Likely your code will work without volatile, but it is proper to add it here to avoid potential problems.

运行 BOCHS 时,此代码将产生以下屏幕输出:

When run BOCHS this code produces this screen output:

如果您使用此处提供的代码,但无法正常工作,这表明您遇到的问题很可能与您在引导加载程序中编写的读取磁盘的代码有关,已启用A20,设置了GDT,并输入了保护模式,然后调用您的 C 代码.根据您编译和链接内核的方式,也可能会出现问题.

If you use the code provided here and it doesn't work that would suggest the issue you are having is likely related to the a code you write in your bootloader that read the disk, enabled A20, set the GDT, entered protected mode, and then called into your C code. It is also possible problems could occur depending on how you compile and link your kernel.

EDIT 2 中提供了所有代码和make文件之后,很明显的一个重要问题是,大多数代码都已编译并链接到64位对象和可执行文件.该代码无法在32位保护模式下工作.

After all the code and the make file were made available in EDIT 2 it became clear that one significant problem was that most of the code was compiled and linked to 64-bit objects and executables. That code won't work in 32-bit protected mode.

在make文件中进行以下调整:

In the make file make these adjustments:

  • 使用 GCC 进行编译时,您需要添加-m32选项
  • 使用针对32位对象的 GNU汇编器( as )进行组装时,您需要使用--32
  • LD 链接时,您需要添加-melf_i386选项
  • 与针对32位对象的 NASM 组装时,需要将-f elf64更改为-f elf32
  • When compiling with GCC you need to add -m32 option
  • When assembling with GNU Assembler (as) targeting 32-bit objects you need to use --32
  • When linking with LD you need to add the -melf_i386 option
  • When assembling with NASM targeting 32-bit objects you need to change -f elf64 to -f elf32

从主机环境中使用64位编译器和工具链的一个更好的选择是创建适用于i686或i386的交叉编译器工具链.

A preferable option to using a 64-bit compiler and tool chain from the host environment is to create a cross compiler toolchain for i686 or i386.

这篇关于直接打印到文本视频存储器时出现意外输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆