直接打印到文本视频内存时出现意外输出
Unexpected output when printing directly to text video memory
我正在用 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。然后它应该替换并在 [=21= 上打印下一个字符].所以,颜色应该消失了,但它仍然有效。
另外,颜色应该在*(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 文件,同时链接以调用主函数 -
[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
您使用 *(vid) 作为颜色的第一个视频字符
这应该有效。
每个 VGA 单元有 2 个字节长,第一个字节存储字符,第二个字节存储颜色。
还要确保标记为易失性指针。避免编译器对该本地字段进行任何类型的意外更改(或优化)。
void write_string( int colour, const unsigned char *string )
{
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
while( *string != 0 )
{
*vid++ = *string++;
*vid++ = colour;
}
}
根据其他答案和评论中建议的更改,您的问题对我来说似乎无法重现。以下代码对我有用。我试图维护您的编码方式,以使其对您有意义:
#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代码。也可能会出现问题,具体取决于您的编译方式和 link 您的内核。
未定义行为的可能原因
在 EDIT 2 中提供了所有代码和 make 文件之后,很明显一个重要的问题是大部分代码都已编译并且 link ed 到 64 位对象和可执行文件。该代码在 32 位保护模式下不起作用。
在 make 文件中进行这些调整:
- 使用 GCC 编译时需要添加
-m32
选项
- 当使用 GNU 汇编程序 (as) 以 32 位对象为目标进行汇编时,您需要使用
--32
- 当link使用LD时,您需要添加
-melf_i386
选项
- 使用 NASM 以 32 位对象为目标进行汇编时,您需要将
-f elf64
更改为 -f elf32
从主机环境使用 64 位编译器和工具链的一个更好的选择是创建一个 cross compiler toolchain for i686 or i386.
我正在用 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。然后它应该替换并在 [=21= 上打印下一个字符].所以,颜色应该消失了,但它仍然有效。
另外,颜色应该在*(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 文件,同时链接以调用主函数 -
[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
您使用 *(vid) 作为颜色的第一个视频字符
这应该有效。 每个 VGA 单元有 2 个字节长,第一个字节存储字符,第二个字节存储颜色。 还要确保标记为易失性指针。避免编译器对该本地字段进行任何类型的意外更改(或优化)。
void write_string( int colour, const unsigned char *string )
{
volatile unsigned char *vid = (unsigned char*) VIDEO_MEM;
while( *string != 0 )
{
*vid++ = *string++;
*vid++ = colour;
}
}
根据其他答案和评论中建议的更改,您的问题对我来说似乎无法重现。以下代码对我有用。我试图维护您的编码方式,以使其对您有意义:
#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代码。也可能会出现问题,具体取决于您的编译方式和 link 您的内核。
未定义行为的可能原因
在 EDIT 2 中提供了所有代码和 make 文件之后,很明显一个重要的问题是大部分代码都已编译并且 link ed 到 64 位对象和可执行文件。该代码在 32 位保护模式下不起作用。
在 make 文件中进行这些调整:
- 使用 GCC 编译时需要添加
-m32
选项 - 当使用 GNU 汇编程序 (as) 以 32 位对象为目标进行汇编时,您需要使用
--32
- 当link使用LD时,您需要添加
-melf_i386
选项 - 使用 NASM 以 32 位对象为目标进行汇编时,您需要将
-f elf64
更改为-f elf32
从主机环境使用 64 位编译器和工具链的一个更好的选择是创建一个 cross compiler toolchain for i686 or i386.