在不使用 C 库的情况下在 0xb8000 处显示文本视频内存
Displaying text video memory at 0xb8000 without using the C library
我一直在用 C 编写内核。我一直在使用 GCC 交叉编译器,在 Windows 系统上编写并针对 16 位实模式。我没有可用于编写内核的 C 库。我从一些代码开始,这些代码假设将一个字符直接打印到屏幕上。这是 kernel.c
:
中的函数
int main()
{
char *src = (char *)0xB8000000L;
*src = 'M';
src += 2;
*src = 'D';
return 0;
}
我使用带有参数 -m16
的 GCC 编译我的代码以生成将在实模式下 运行 的代码。我使用这些命令来生成我的 kernel.bin
:
gcc -ffreestanding -c -m16 kernel.c -o kernel.o
ld -Ttext 0x10000 -o kernel.pe kernel.o
objcopy -O binary kernel.pe kernel.bin
Stack Overflow 用户 Michael Petch solved my 但评论代码本身不正确。他发表了这样的评论:
Besides the linker problem, are you trying to convert old TurboC/MSVC 16-bit code to GCC? I find (char *)0xB8000000L suspicious. If it was a true 16-bit C compiler it might be okay if it was (char far *)0xB8000000L. GCC is not a true 16-bit C compiler and doesn't have a notion of old style far pointers. So even if you get this code to compile this may not do what you think it does, I'm assuming from the -m16 option with GCC you are trying to create a real-mode 16-bit kernel (rather than a protected mode one)?
我一直在尝试用 C 语言为我自己的操作系统实现我自己的 printf
类函数。我上面提供的代码只是我所理解的一小部分。我在 assembly(8086) 中创建了一个引导加载程序。
迈克尔是对的吗?如果是这样,如何解决这个问题并直接写入0xb8000
处的显存?
如果您打算将 GCC 与 -m16
一起使用,那么它会自动假定您将 运行ning 在 80386+ 上。我怎么强调都不为过,使用 GCC 创建 16 位代码充满了陷阱。如果您选择将内核放在内存中的 0x10000 处,情况会变得更糟。 0x10000 不能表示为 16 位偏移量,这可能会导致 GCC 发出可能无法运行的代码,尤其是当您想要使用 -O1
打开优化时, -O2
, -O3
等。即使访问全局变量也可能导致问题!
强烈推荐(几乎是避免大多数麻烦所必需的):如果将内核及其数据放在内存的前 64kb 中,问题可能会更少。内存地址0x00520处的原点正好在BIOS数据区和低位内存保留区之上。
预先警告:GCC 以 -m16
为目标实模式 使用风险自负。你也可能会失去理智。使用 flat memory model(从 0 扩展到 0xffffffff)将处理器置于 32 位保护模式,其中 CS=DS=ES 是 GCC
的理想选择
此代码假设您未处于 unreal mode,尽管您的系统可能处于该模式。
GCC 假定 CS=DS=ES,并且内存模型是平坦的。更改 ES 通常不是一个好主意。可以使用 ES 如果你保存它,做工作,然后全部恢复 而无需 干预 C之间的代码。由于 GCC 需要 80386,我们可以使用其他段寄存器之一:FS 和 GS。在此示例中,我们将使用 FS.
另一个先决条件是您了解 Real Mode Segmentation。我假设你这样做了,因为你已经创建了一个引导加载程序。物理内存地址的计算是:
Physical memory address = (segment << 4) + offset
Text mode (color)显存在物理地址0xb8000。该内存的基数可以表示为 segment:offset 对 0xb800:0x0000,因为:
(0xb800 << 4) + 0x0000 = 0xb8000
可见屏幕上的每个单元格都是一个 WORD(16 位)。 WORD 的高 8 位是属性,低 8 位是字符,详见 link。 Wiki page.
中描述了调色板
如果我们使用FS作为我们的段,我们可以将它设置为0xb800并用它来引用显存。由于您的代码最终可能会使用 FS 来做各种事情,我们将使用一些内联汇编代码保存它,处理视频内存,然后恢复 FS 到以前的样子。
由于我使用的是内联汇编程序,您可能希望查看 Peter Corde 关于该主题的 列表。
考虑到上述情况的代码,并提供了一种机制,用于通过我们设置为 0xb800 的 FS 段寄存器连续更新屏幕,col 具有属性.
代码比您喜欢的要多,但我想展示的不仅仅是输出单个字符。代码注释可能会帮助您继续前进。
#include <stdint.h>
/* use regparm(3) to use convention where first three
* integer sized parameters are passed in registers (EAX, EDX, ECX) rather
* than the stack. regparm(0) is default CDECL stack based
* parameter passing. regparm(3) is generally faster overall, compared
* to passing all parameters on the stack. Internally, the Linux kernel
* uses this convention to reduce stack overhead when functions
* are called across different kernel modules.
*/
#define fastcall __attribute__((regparm(3)))
#define asmlinkage __attribute__((regparm(0)))
/* Global functions that will be exported */
extern fastcall void dispchar(uint16_t celldata, uint16_t offset);
extern fastcall void dispstring(const char *outstring, uint8_t attr,
uint16_t offset);
extern fastcall void dispchar_nofsupd(uint16_t celldata, uint16_t offset);
extern fastcall void dispstring_nofsupd(const char *outstring, uint8_t attr,
uint16_t offset);
extern fastcall uint32_t getset_fs(uint32_t segment);
extern fastcall void set_fs(uint32_t segment);
extern fastcall uint32_t set_videomode_fs(void);
static inline uint16_t
tm_rowcol_to_vidoffset(uint16_t row, uint16_t col, uint16_t numcols);
static inline uint16_t
tm_charattr_to_celldata(uint8_t ochar, uint8_t attr);
/*----------------------------------------------------------*/
#define COLSPERROW 80
#define ROW 3
#define COL 40
#define RED_ON_BLACK 4 /* attribute= Red character on black background */
#define MAGENTA_ON_BLACK 5 /* attribute= Magenta character on black background */
/* Color text mode memory segment */
#define VIDEO_SEG 0xb800
/* Place main before all other code */
int
_main()
{
/* Set FS to video mode segment and save previous value of FS */
uint32_t oldfs = set_videomode_fs();
dispchar_nofsupd(tm_charattr_to_celldata('A', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata('B', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 1, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata(' ', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 2, COLSPERROW));
dispstring_nofsupd("Hello World", RED_ON_BLACK,
tm_rowcol_to_vidoffset(ROW, COL + 3, COLSPERROW));
/* Restore FS to original value when finished doing video mode work */
set_fs(oldfs);
/* Display Hello World using version dispstring
* that saves/restores FS automatically */
dispstring("Hello World", MAGENTA_ON_BLACK,
tm_rowcol_to_vidoffset(ROW+1, COL + 3, COLSPERROW));
return 0;
}
/* Convert Text Mode(TM) row, col, numcols
* to a video offset. numcols is the number of columns
* per row. Return value is a BYTE offset (not WORD)
*/
static inline uint16_t
tm_rowcol_to_vidoffset(uint16_t row, uint16_t col, uint16_t numcols)
{
return ((row * numcols + col) * 2);
}
static inline uint16_t
tm_charattr_to_celldata(uint8_t ochar, uint8_t attr)
{
return (uint16_t) (attr << 8) | (uint8_t) ochar;
}
/* Display character with FS change */
fastcall void
dispchar(uint16_t celldata, uint16_t offset)
{
uint32_t oldfs = set_videomode_fs();
dispchar_nofsupd(celldata, offset);
set_fs(oldfs);
}
/* Display character with no FS change */
fastcall void
dispchar_nofsupd(uint16_t celldata, uint16_t offset)
{
__asm__ ("movw %w[wordval], %%fs:%[memloc]\n\t"
:
:[wordval]"ri"(celldata),
[memloc] "m"(*(uint32_t *)(uint32_t)offset)
:"memory");
}
/* Set FS segment and return previous value */
fastcall uint32_t
getset_fs(uint32_t segment)
{
uint32_t origfs;
__asm__ __volatile__("mov %%fs, %w[origfs]\n\t"
"mov %w[segment], %%fs\n\t"
:[origfs] "=&rm"(origfs)
:[segment] "rm"(segment));
return origfs;
}
/* Set FS segment */
fastcall void
set_fs(uint32_t segment)
{
__asm__("mov %w[segment], %%fs\n\t"
:
:[segment]"rm"(segment));
}
/* Set FS to video mode segment 0xb800 */
fastcall uint32_t
set_videomode_fs(void)
{
return getset_fs(VIDEO_SEG);
}
/* Display string with FS change */
fastcall void
dispstring(const char *outstring, uint8_t attr, uint16_t offset)
{
uint32_t oldfs = set_videomode_fs();
dispstring_nofsupd(outstring, attr, offset);
set_fs(oldfs);
}
/* Display string with FS change */
fastcall void
dispstring_nofsupd(const char *outstring, uint8_t attr, uint16_t offset)
{
const char *curchar = outstring;
int i = 0;
for (; *curchar; curchar++, i++)
dispchar_nofsupd(tm_charattr_to_celldata(*curchar, attr),
offset + i * 2);
}
Windows
上的 GCC 链接器脚本
在 windows 下使用 GCC 时,您的 kernel.bin
可能会比您预期的要大。这是因为 GCC 使用的默认对齐规则。以下 linker script 可能有助于减小尺寸:
ENTRY(__main);
OUTPUT(i386pe);
SECTIONS
{
__kernelbase = 0x520;
. = __kernelbase;
.text : SUBALIGN(4) {
*(.text.st);
*(.text);
}
.data :
SUBALIGN(4) {
__data_start = .;
*(.rdata*);
*(.data);
__data_end = .;
__bss_start = .;
*(COMMON);
*(.bss);
__bss_end = .;
}
}
此脚本设置为 0x520(不是 0x10000)的 ORG。如前所述,强烈建议不要使用 0x10000 的来源,因为您已经使用了 16 位 GCC 生成的代码。将 linker 脚本命名为 linker.ld
然后你可以使用这些命令来汇编和 link 内核:
gcc -ffreestanding -c -m16 kernel.c -o kernel.o -O3
ld -o kernel.pe kernel.o -Tlinker.ld
objcopy -O binary kernel.pe kernel.bin
您必须修改引导加载程序才能将内核扇区读入从地址 0x520 开始的内存中。
使用简单的引导加载程序和使用提供的 code/linker 脚本构建的内核,这就是 Bochs 在 运行 时显示的内容:
查看一些生成的代码
前几行函数main
保存当前FS寄存器,设置FS为视频段0xb800和打印出 3 个字符:
int
_main()
{
/* Set FS to video mode segment and save previous value of FS */
uint32_t oldfs = set_videomode_fs();
dispchar_nofsupd(tm_charattr_to_celldata('A', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata('B', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 1, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata(' ', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 2, COLSPERROW));
dispstring_nofsupd("Hello World", RED_ON_BLACK,
tm_rowcol_to_vidoffset(ROW, COL + 3, COLSPERROW));
[code that prints strings has been snipped for brevity]
set_fs(oldfs);
可以使用这个objdump
命令查看生成的代码:
objdump -Dx kernel.pe --no-show-raw-insn -mi8086 -Mintel
我的编译器的 Intel 语法输出如下(使用 -O3
优化):
00000520 <__main>:
520: push esi ; Save register contents
522: mov eax,0xb800
528: push ebx ; Save register contents
52a: mov si,fs ; Save old FS to SI
52d: mov fs,ax ; Update FS with 0xb800 (segment of video)
52f: mov WORD PTR fs:0x230,0x441 ; 0x441 = Red on black Letter 'A'
; Write to offset 0x230 ((80*3+40)*2) row=3,col=40
536: mov WORD PTR fs:0x232,0x442 ; 0x442 = Red on black Letter 'B'
; Write to offset 0x232 ((80*3+41)*2) row=3,col=41
53d: mov WORD PTR fs:0x234,0x420 ; 0x420 = Red on black space char
; Write to offset 0x234 ((80*3+42)*2) row=3,col=42
这行C代码恢复了FS:
set_fs(oldfs);
稍后使用此说明:
571: mov fs,si ; Restore original value previously saved in SI
我用注释对反汇编进行了注释,以显示每个 WORD 值是如何在视频显示内存中更新的。 C代码行很多,但是输出很简单
我一直在用 C 编写内核。我一直在使用 GCC 交叉编译器,在 Windows 系统上编写并针对 16 位实模式。我没有可用于编写内核的 C 库。我从一些代码开始,这些代码假设将一个字符直接打印到屏幕上。这是 kernel.c
:
int main()
{
char *src = (char *)0xB8000000L;
*src = 'M';
src += 2;
*src = 'D';
return 0;
}
我使用带有参数 -m16
的 GCC 编译我的代码以生成将在实模式下 运行 的代码。我使用这些命令来生成我的 kernel.bin
:
gcc -ffreestanding -c -m16 kernel.c -o kernel.o
ld -Ttext 0x10000 -o kernel.pe kernel.o
objcopy -O binary kernel.pe kernel.bin
Stack Overflow 用户 Michael Petch solved my
Besides the linker problem, are you trying to convert old TurboC/MSVC 16-bit code to GCC? I find (char *)0xB8000000L suspicious. If it was a true 16-bit C compiler it might be okay if it was (char far *)0xB8000000L. GCC is not a true 16-bit C compiler and doesn't have a notion of old style far pointers. So even if you get this code to compile this may not do what you think it does, I'm assuming from the -m16 option with GCC you are trying to create a real-mode 16-bit kernel (rather than a protected mode one)?
我一直在尝试用 C 语言为我自己的操作系统实现我自己的 printf
类函数。我上面提供的代码只是我所理解的一小部分。我在 assembly(8086) 中创建了一个引导加载程序。
迈克尔是对的吗?如果是这样,如何解决这个问题并直接写入0xb8000
处的显存?
如果您打算将 GCC 与 -m16
一起使用,那么它会自动假定您将 运行ning 在 80386+ 上。我怎么强调都不为过,使用 GCC 创建 16 位代码充满了陷阱。如果您选择将内核放在内存中的 0x10000 处,情况会变得更糟。 0x10000 不能表示为 16 位偏移量,这可能会导致 GCC 发出可能无法运行的代码,尤其是当您想要使用 -O1
打开优化时, -O2
, -O3
等。即使访问全局变量也可能导致问题!
强烈推荐(几乎是避免大多数麻烦所必需的):如果将内核及其数据放在内存的前 64kb 中,问题可能会更少。内存地址0x00520处的原点正好在BIOS数据区和低位内存保留区之上。
预先警告:GCC 以 -m16
为目标实模式 使用风险自负。你也可能会失去理智。使用 flat memory model(从 0 扩展到 0xffffffff)将处理器置于 32 位保护模式,其中 CS=DS=ES 是 GCC
此代码假设您未处于 unreal mode,尽管您的系统可能处于该模式。
GCC 假定 CS=DS=ES,并且内存模型是平坦的。更改 ES 通常不是一个好主意。可以使用 ES 如果你保存它,做工作,然后全部恢复 而无需 干预 C之间的代码。由于 GCC 需要 80386,我们可以使用其他段寄存器之一:FS 和 GS。在此示例中,我们将使用 FS.
另一个先决条件是您了解 Real Mode Segmentation。我假设你这样做了,因为你已经创建了一个引导加载程序。物理内存地址的计算是:
Physical memory address = (segment << 4) + offset
Text mode (color)显存在物理地址0xb8000。该内存的基数可以表示为 segment:offset 对 0xb800:0x0000,因为:
(0xb800 << 4) + 0x0000 = 0xb8000
可见屏幕上的每个单元格都是一个 WORD(16 位)。 WORD 的高 8 位是属性,低 8 位是字符,详见 link。 Wiki page.
中描述了调色板如果我们使用FS作为我们的段,我们可以将它设置为0xb800并用它来引用显存。由于您的代码最终可能会使用 FS 来做各种事情,我们将使用一些内联汇编代码保存它,处理视频内存,然后恢复 FS 到以前的样子。
由于我使用的是内联汇编程序,您可能希望查看 Peter Corde 关于该主题的
考虑到上述情况的代码,并提供了一种机制,用于通过我们设置为 0xb800 的 FS 段寄存器连续更新屏幕,col 具有属性.
代码比您喜欢的要多,但我想展示的不仅仅是输出单个字符。代码注释可能会帮助您继续前进。
#include <stdint.h>
/* use regparm(3) to use convention where first three
* integer sized parameters are passed in registers (EAX, EDX, ECX) rather
* than the stack. regparm(0) is default CDECL stack based
* parameter passing. regparm(3) is generally faster overall, compared
* to passing all parameters on the stack. Internally, the Linux kernel
* uses this convention to reduce stack overhead when functions
* are called across different kernel modules.
*/
#define fastcall __attribute__((regparm(3)))
#define asmlinkage __attribute__((regparm(0)))
/* Global functions that will be exported */
extern fastcall void dispchar(uint16_t celldata, uint16_t offset);
extern fastcall void dispstring(const char *outstring, uint8_t attr,
uint16_t offset);
extern fastcall void dispchar_nofsupd(uint16_t celldata, uint16_t offset);
extern fastcall void dispstring_nofsupd(const char *outstring, uint8_t attr,
uint16_t offset);
extern fastcall uint32_t getset_fs(uint32_t segment);
extern fastcall void set_fs(uint32_t segment);
extern fastcall uint32_t set_videomode_fs(void);
static inline uint16_t
tm_rowcol_to_vidoffset(uint16_t row, uint16_t col, uint16_t numcols);
static inline uint16_t
tm_charattr_to_celldata(uint8_t ochar, uint8_t attr);
/*----------------------------------------------------------*/
#define COLSPERROW 80
#define ROW 3
#define COL 40
#define RED_ON_BLACK 4 /* attribute= Red character on black background */
#define MAGENTA_ON_BLACK 5 /* attribute= Magenta character on black background */
/* Color text mode memory segment */
#define VIDEO_SEG 0xb800
/* Place main before all other code */
int
_main()
{
/* Set FS to video mode segment and save previous value of FS */
uint32_t oldfs = set_videomode_fs();
dispchar_nofsupd(tm_charattr_to_celldata('A', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata('B', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 1, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata(' ', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 2, COLSPERROW));
dispstring_nofsupd("Hello World", RED_ON_BLACK,
tm_rowcol_to_vidoffset(ROW, COL + 3, COLSPERROW));
/* Restore FS to original value when finished doing video mode work */
set_fs(oldfs);
/* Display Hello World using version dispstring
* that saves/restores FS automatically */
dispstring("Hello World", MAGENTA_ON_BLACK,
tm_rowcol_to_vidoffset(ROW+1, COL + 3, COLSPERROW));
return 0;
}
/* Convert Text Mode(TM) row, col, numcols
* to a video offset. numcols is the number of columns
* per row. Return value is a BYTE offset (not WORD)
*/
static inline uint16_t
tm_rowcol_to_vidoffset(uint16_t row, uint16_t col, uint16_t numcols)
{
return ((row * numcols + col) * 2);
}
static inline uint16_t
tm_charattr_to_celldata(uint8_t ochar, uint8_t attr)
{
return (uint16_t) (attr << 8) | (uint8_t) ochar;
}
/* Display character with FS change */
fastcall void
dispchar(uint16_t celldata, uint16_t offset)
{
uint32_t oldfs = set_videomode_fs();
dispchar_nofsupd(celldata, offset);
set_fs(oldfs);
}
/* Display character with no FS change */
fastcall void
dispchar_nofsupd(uint16_t celldata, uint16_t offset)
{
__asm__ ("movw %w[wordval], %%fs:%[memloc]\n\t"
:
:[wordval]"ri"(celldata),
[memloc] "m"(*(uint32_t *)(uint32_t)offset)
:"memory");
}
/* Set FS segment and return previous value */
fastcall uint32_t
getset_fs(uint32_t segment)
{
uint32_t origfs;
__asm__ __volatile__("mov %%fs, %w[origfs]\n\t"
"mov %w[segment], %%fs\n\t"
:[origfs] "=&rm"(origfs)
:[segment] "rm"(segment));
return origfs;
}
/* Set FS segment */
fastcall void
set_fs(uint32_t segment)
{
__asm__("mov %w[segment], %%fs\n\t"
:
:[segment]"rm"(segment));
}
/* Set FS to video mode segment 0xb800 */
fastcall uint32_t
set_videomode_fs(void)
{
return getset_fs(VIDEO_SEG);
}
/* Display string with FS change */
fastcall void
dispstring(const char *outstring, uint8_t attr, uint16_t offset)
{
uint32_t oldfs = set_videomode_fs();
dispstring_nofsupd(outstring, attr, offset);
set_fs(oldfs);
}
/* Display string with FS change */
fastcall void
dispstring_nofsupd(const char *outstring, uint8_t attr, uint16_t offset)
{
const char *curchar = outstring;
int i = 0;
for (; *curchar; curchar++, i++)
dispchar_nofsupd(tm_charattr_to_celldata(*curchar, attr),
offset + i * 2);
}
Windows
上的 GCC 链接器脚本在 windows 下使用 GCC 时,您的 kernel.bin
可能会比您预期的要大。这是因为 GCC 使用的默认对齐规则。以下 linker script 可能有助于减小尺寸:
ENTRY(__main);
OUTPUT(i386pe);
SECTIONS
{
__kernelbase = 0x520;
. = __kernelbase;
.text : SUBALIGN(4) {
*(.text.st);
*(.text);
}
.data :
SUBALIGN(4) {
__data_start = .;
*(.rdata*);
*(.data);
__data_end = .;
__bss_start = .;
*(COMMON);
*(.bss);
__bss_end = .;
}
}
此脚本设置为 0x520(不是 0x10000)的 ORG。如前所述,强烈建议不要使用 0x10000 的来源,因为您已经使用了 16 位 GCC 生成的代码。将 linker 脚本命名为 linker.ld
然后你可以使用这些命令来汇编和 link 内核:
gcc -ffreestanding -c -m16 kernel.c -o kernel.o -O3
ld -o kernel.pe kernel.o -Tlinker.ld
objcopy -O binary kernel.pe kernel.bin
您必须修改引导加载程序才能将内核扇区读入从地址 0x520 开始的内存中。
使用简单的引导加载程序和使用提供的 code/linker 脚本构建的内核,这就是 Bochs 在 运行 时显示的内容:
查看一些生成的代码
前几行函数main
保存当前FS寄存器,设置FS为视频段0xb800和打印出 3 个字符:
int
_main()
{
/* Set FS to video mode segment and save previous value of FS */
uint32_t oldfs = set_videomode_fs();
dispchar_nofsupd(tm_charattr_to_celldata('A', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata('B', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 1, COLSPERROW));
dispchar_nofsupd(tm_charattr_to_celldata(' ', RED_ON_BLACK),
tm_rowcol_to_vidoffset(ROW, COL + 2, COLSPERROW));
dispstring_nofsupd("Hello World", RED_ON_BLACK,
tm_rowcol_to_vidoffset(ROW, COL + 3, COLSPERROW));
[code that prints strings has been snipped for brevity]
set_fs(oldfs);
可以使用这个objdump
命令查看生成的代码:
objdump -Dx kernel.pe --no-show-raw-insn -mi8086 -Mintel
我的编译器的 Intel 语法输出如下(使用 -O3
优化):
00000520 <__main>:
520: push esi ; Save register contents
522: mov eax,0xb800
528: push ebx ; Save register contents
52a: mov si,fs ; Save old FS to SI
52d: mov fs,ax ; Update FS with 0xb800 (segment of video)
52f: mov WORD PTR fs:0x230,0x441 ; 0x441 = Red on black Letter 'A'
; Write to offset 0x230 ((80*3+40)*2) row=3,col=40
536: mov WORD PTR fs:0x232,0x442 ; 0x442 = Red on black Letter 'B'
; Write to offset 0x232 ((80*3+41)*2) row=3,col=41
53d: mov WORD PTR fs:0x234,0x420 ; 0x420 = Red on black space char
; Write to offset 0x234 ((80*3+42)*2) row=3,col=42
这行C代码恢复了FS:
set_fs(oldfs);
稍后使用此说明:
571: mov fs,si ; Restore original value previously saved in SI
我用注释对反汇编进行了注释,以显示每个 WORD 值是如何在视频显示内存中更新的。 C代码行很多,但是输出很简单