直接打印到文本显存时出现意外输出

Posted

技术标签:

【中文标题】直接打印到文本显存时出现意外输出【英文标题】:Unexpected output when printing directly to text video memory 【发布时间】:2017-02-09 23:42:09 【问题描述】:

我正在用 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;


编辑 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 -

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

这是我的制作文件 -

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

【问题讨论】:

@MichaelPetch 我纠正了错误。现在又出现了一个问题。我已经在 EDIT 中指定了它。可以看看吗? 但是上面的代码至少打印了写的东西——尽管我已经将第一个字节设置为属性,第二个字节设置为字符。问题是write_string 函数不会在写入位置打印,而putChar 不会打印任何内容,只是放置了。它甚至没有将颜色放在角色的记忆中。 @MichaelPetch VIDEO_MEM 是 0xb8000 @MichaelPetch 【参考方案1】:

根据其他答案和 cmets 中建议的更改,您的问题对我来说似乎无法重现。以下代码适用于我。我试图维护您的编码方式,以便对您有意义:

#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__,以确保代码首先出现在生成的目标文件中。没有它它可能会起作用。我已将您所有的 *vid 指针修改为 volatile 。由于视频是内存映射 IO,因此您不希望编译器在优化时可能会删除屏幕写入。您的代码可能在没有volatile 的情况下也能正常工作,但最好在此处添加它以避免潜在问题。

当运行 BOCHS 时,这段代码会产生这个屏幕输出:

如果您使用此处提供的代码但它不起作用,则表明您遇到的问题可能与您在引导加载程序中编写的读取磁盘、启用 A20、设置 GDT、输入受保护的代码有关模式,然后调用到您的 C 代码中。根据您编译和链接内核的方式,也可能会出现问题。


未定义行为的可能原因

EDIT 2 中提供了所有代码和make 文件之后,很明显一个重大问题是大部分代码都被编译并链接到64 位对象和可执行文件。该代码无法在 32 位保护模式下运行。

在 make 文件中进行这些调整:

使用GCC编译时需要添加-m32选项 当使用 GNU Assembler (as) 针对 32 位对象进行组装时,您需要使用 --32 当与 LD 链接时,您需要添加 -melf_i386 选项 当使用 NASM 针对 32 位对象进行组装时,您需要将 -f elf64 更改为 -f elf32

在主机环境中使用 64 位编译器和工具链的更好选择是创建一个cross compiler toolchain for i686 or i386。

【讨论】:

这不起作用。用于清除屏幕的代码打印向下箭头字符(ASCII = 0x1f)并打印向上箭头而不是两条消息。 putChar 函数以灰色背景打印闪烁的花括号。我的清除屏幕代码工作正常,但休息没有。 @AneeshSharma 这证实了我的怀疑,即您遇到的任何问题都可能与 C 代码本身无关。可能是您在将内核文件加载到内存时做错了,或者在段、GDT、您用来组装/构建图像的参数等方面做错了。事实上,这种简单的代码对您来说是失败的,而对我来说却是我自己的引导加载程序很好地表明您的引导加载程序做错了,或者没有做应该做的事情。如果您提供了所有代码以及用于构建它的命令,那么我可能会为您提供帮助。 @AneeshSharma :我的代码也适用于 Bochs。顺便说一句,您的编辑和屏幕图像应该添加到您的问题中,而不是我的答案中。 好的,我将添加所有代码。我在 Windows 10 上使用 Ubuntu bash shell 来使用 make 文件编译我的代码。对不起图片 这行得通!谢谢@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) 作为颜色的第一个视频字符

【讨论】:

以上是关于直接打印到文本显存时出现意外输出的主要内容,如果未能解决你的问题,请参考以下文章

打印 UIWebView 时出现意外的分页符

当我已经定义了变量时出现意外的令牌错误

打印 json 对象时出现意外的令牌 L 使用高级休息 chrome

使用jq选择对象时出现意外结果

替换文本文件中的单个字符时出现意外结果

在 PostgreSQL 中将双精度转换为文本时出现意外行为