gpt4 book ai didi

assembly - 在不使用C库的情况下以0xb8000显示文本视频存储器

转载 作者:行者123 更新时间:2023-12-02 19:19:46 27 4
gpt4 key购买 nike

我一直在用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


堆栈溢出用户 Michael Petch解决了我的 linker problem,但评论了代码本身不正确。他发表了这样的评论:


除了链接器问题之外,您是否还在尝试将旧的TurboC / MSVC 16位代码转换为GCC?我发现(char *)0xB8000000L可疑。如果它是一个真正的16位C编译器,则它可以是(char far *)0xB8000000L。 GCC不是真正的16位C编译器,也没有旧式远指针的概念。因此,即使您可以编译该代码,也可能无法按照您的想法做,但我假设从GCC的-m16选项中您正在尝试创建实模式16位内核(而不是受保护模式) )?


我一直在尝试为自己的操作系统在C中实现自己的 printf类似功能。我上面提供的代码只是我所理解的一小部分。我已经在Assembly(8086)中创建了一个引导加载程序。

迈克尔正确吗?如果是这样,如何解决此问题并直接写入 0xb8000的视频存储器?

最佳答案

如果您打算将GCC与-m16一起使用,则会自动假定您将在80386+上运行。我不能太强调这一点,使用GCC创建16位代码充满了陷阱。您选择将内核的内存放在0x10000会变得更糟。 0x10000不能表示为16位偏移量,这可能导致GCC发出可能无法工作的代码,尤其是如果您想使用-O1-O2-O3等打开优化时。甚至访问全局变量可能会导致问题!

高度推荐(几乎是避免大多数麻烦所必需的):如果将内核及其数据放入前64kb内存中,则问题可能会更少。内存地址0x00520的原点位于BIOS数据区和较低内存的保留区的正上方。

注意:以-m16定位为实模式的GCC可能会导致您的使用风险。您也可能会失去理智。使用flat memory model(从0扩展到0xffffffff)将处理器置于32位保护模式下,其中CS = DS = ES是GCC的理想选择



尽管您的系统可能处于该模式,但是此代码假设您不在unreal mode中。

GCC假定CS = DS = ES,并且内存模型是平坦的。更改ES通常不是一个好主意。如果保存,工作并还原它们,而无需在其间插入C代码,则可以使用ES。由于GCC需要80386,因此我们可以使用其他段寄存器之一:FS和GS。在此示例中,我们将使用FS。

另一个前提条件是您了解Real Mode Segmentation。我假设您已经这样做,因为您已经创建了引导加载程序。物理内存地址的计算为:

Physical memory address = (segment << 4) + offset


Text mode (color)视频内存位于物理地址0xb8000。该内存的基数可以表示为0xb800:0x0000的segment:offset对,因为:

(0xb800 << 4) + 0x0000 = 0xb8000


可见屏幕上的每个单元格都是一个WORD(16位)。 WORD的高8位是属性,而低位是该链接中详细介绍的字符。此 Wiki page中描述了调色板。

如果我们使用FS作为段,则可以将其设置为0xb800并以此引用视频内存。由于您的代码最终可能会将FS用于多种用途,因此我们将使用一些内联汇编代码将其保存,在视频内存上进行处理,并将FS恢复到以前的状态。

由于我使用的是内联汇编程序,因此您不妨查看Peter Corde关于该主题的 useful links列表。

该代码考虑了上述情况,并提供了一种用于连续更新屏幕的机制,并通过我们设置为0xb800的FS段寄存器与属性进行了关联。

有比您可能喜欢的更多的代码,但是我想展示的不仅仅是输出单个字符。代码注释可以帮助您上路。

#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 = .;
}
}


该脚本的ORG设置为0x520(不是0x10000)。如前所述,强烈建议您不要使用0x10000的原点,因为您使用的是16位GCC生成的代码。将链接脚本命名为 linker.ld,然后可以使用以下命令来汇编和链接内核:

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开始)。

通过一个简单的引导程序,并使用提供的代码/链接程序脚本构建了该内核,这是Bochs在运行时显示的内容:

Bochs output



看一些生成的代码

函数 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


这行恢复了FS的C代码:

 set_fs(oldfs);


稍后使用此说明:

 571:   mov    fs,si                   ; Restore original value previously saved in SI


我用注释注释了反汇编,以显示如何在视频显示内存中更新每个WORD值。 C代码很多行,但是输出非常简单。

关于assembly - 在不使用C库的情况下以0xb8000显示文本视频存储器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37354717/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com