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

我正在用C开发一个内核,并在屏幕上创建了一些可以在video内存上打印的内容。 我预计video内存中的第一个字节将是要打印的字符,第二个字节会告诉颜色。 但我的程序有一些不同,但它的工作原理! 这是非常意外和不寻常的。

我的内核代码 –

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

我还有一个字符打印function,只是打印花括号( } )而不是给定的字符,并且给定位置的后面也是一个字符(就像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 $ 

这是我的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 

通过其他答案和评论中建议的更改,您的问题似乎对我来说似乎不可重现。 以下代码适用于我。 我试图保持你的编码方式,这样才对你有意义:

 #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 。 由于video是内存映射IO,因此您不希望编译器在优化时可能会删除屏幕写入。 可能你的代码在没有volatile情况下工作,但是在这里添加它以避免潜在的问题是合适的。

运行BOCHS时,此代码生成此屏幕输出:

在此处输入图像描述

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


未定义行为的可能原因

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

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

  • 使用GCC进行编译时,需要添加-m32选项
  • GNU Assembleras )一起使用32位对象进行组装时,需要使用--32
  • LD链接时,需要添加-melf_i386选项
  • 使用针对32位对象的NASM进行组装时,需要将-f elf64更改为-f elf32

在主机环境中使用64位编译器和工具链的首选方法是为i686或i386创建交叉编译器工具链 。

这应该工作。 每个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; } } 

您使用*(vid)作为颜色的第一个video字符