首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >在受保护模式下将字符串放入屏幕的任何尝试都会导致重新启动

在受保护模式下将字符串放入屏幕的任何尝试都会导致重新启动
EN

Stack Overflow用户
提问于 2017-12-21 06:42:52
回答 1查看 257关注 0票数 0

我最近刚进入保护模式,当开发一个操作系统从零开始。我已经成功地进入C,并使函数打印字符到屏幕上(感谢Michael帮助我达到这个阶段)。不管怎么说,每当我尝试做一个例程,循环一个字符串文字,并打印其中的每个字符,嗯,有一点问题。QEMU只是进入一个引导循环,一次又一次地重新启动,我永远无法看到我美丽的绿色对黑色视频模式。如果我将它从一个例程中移开,并在kmain()函数中逐个打印它(我已经删除了这个部分),那么一切都很好。下面是我尝试实现字符串打印函数的文件:

vga.c -

代码语言:javascript
运行
AI代码解释
复制
#include <vga.h>

size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t *terminal_buffer;

volatile uint16_t * const VIDMEM = (volatile uint16_t *) 0xB8000;

size_t strlen(const char *s)
{
    size_t len = 0;
    while(s[len]) {
        len++;
    }
    return len;
}

void terminal_init(void) 
{
    terminal_row = 0;
    terminal_column = 0;
    terminal_color = vga_entry_color(LGREEN, BLACK);
    for(size_t y = 0; y < VGA_HEIGHT; y++) {
        for(size_t x = 0; x < VGA_WIDTH; x++) {
            const size_t index = y * VGA_WIDTH + x;
            VIDMEM[index] = vga_entry(' ', terminal_color);
        } 
    }
}

void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
    const size_t index = y * VGA_WIDTH + x;
    VIDMEM[index] = vga_entry(c, color);
}

void terminal_putchar(char c)
{
    terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
    if(++terminal_column == VGA_WIDTH) {
        terminal_column = 0;
        if(++terminal_row == VGA_HEIGHT) {
            terminal_row = 0;
        }
    }
}

void terminal_puts(const char *s)
{
    size_t n = strlen(s);
    for (size_t i=0; i < n; i++) {
        terminal_putchar(s[i]);
    }
}

我使用引导加载程序代码将内核读入内存:

代码语言:javascript
运行
AI代码解释
复制
extern kernel_start             ; External label for start of kernel
global boot_start               ; Make this global to suppress linker warning
bits 16

boot_start:
    xor ax, ax                  ; Set DS to 0. xor register to itselfzeroes register
    mov ds, ax
    mov ss, ax                  ; Stack just below bootloader SS:SP=0x0000:0x7c00
    mov sp, 0x7c00

    mov ah, 0x00
    mov al, 0x03
    int 0x10

load_kernel:
    mov ah, 0x02                ; call function 0x02 of int 13h (read sectors)
    mov al, 0x01                ; read one sector (512 bytes)
    mov ch, 0x00                ; track 0
    mov cl, 0x02                ; sector 2
    mov dh, 0x00                ; head 0
;    mov dl, 0x00               ; drive 0, floppy 1. Comment out DL passed to bootloader
    xor bx, bx                  ; segment 0x0000
    mov es, bx                  ; segments must be loaded from non immediate data
    mov bx, 0x7E00              ; load the kernel right after the bootloader in memory 
.readsector:
    int 13h                     ; call int 13h
    jc .readsector              ; error? try again

    jmp 0x0000:kernel_start     ; jump to the kernel at 0x0000:0x7e00

在内核开始时,我有一个程序集存根,它进入保护模式,对BSS部分进行0,发出一个CLD并调用我的C代码:

代码语言:javascript
运行
AI代码解释
复制
; These symbols are defined by the linker. We use them to zero BSS section
extern __bss_start
extern __bss_sizel

; Export kernel entry point
global kernel_start

; This is the C entry point defined in kmain.c
extern kmain               ; kmain is C entry point
bits 16

section .text
kernel_start:

    cli    

    in al, 0x92
    or al, 2
    out 0x92, al

    lgdt[toc]

    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp 0x08:start32     ; The FAR JMP is simplified since our segment is 0

section .rodata
gdt32:
    dd 0
    dd 0

    dw 0x0FFFF
    dw 0
    db 0
    db 0x9A
    db 0xCF
    db 0

    dw 0x0FFFF
    dw 0
    db 0
    db 0x92
    db 0xCF
    db 0
gdt_end:
toc:
    dw gdt_end - gdt32 - 1
    dd gdt32             ; The GDT base is simplified since our segment is now 0

bits 32
section .text
start32:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x9c000    ; Set the stack to grow down from area under BDA/Video memory

    ; We need to zero out the BSS section. We'll do it a DWORD at a time
    cld
    lea edi, [__bss_start] ; Start address of BSS
    lea ecx, [__bss_sizel] ; Lenght of BSS in DWORDS
    xor eax, eax           ; Set to 0x00000000
    rep stosd              ; Do clear using string store instruction

    call kmain

我有一个专门的链接器脚本,它将引导程序放在0x7c00,内核放在0x7e00。

有什么问题,我该怎么解决呢?如果需要更多的信息,我已经提供了我的git回购

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-12-21 12:17:48

TL;DR:您还没有用start.asm中的引导加载程序将整个内核读入内存。缺少代码和/或数据会导致内核出现三重故障,从而导致重新启动。随着内核的增长,您需要读取更多的扇区。

我注意到您生成的lunaos.img大于1024字节。引导加载程序为512字节,后面的内核略大于512字节。这意味着内核现在跨越多个扇区。在kernel.asm中,使用以下代码加载单个512字节扇区:

代码语言:javascript
运行
AI代码解释
复制
load_kernel:
    mov ah, 0x02                ; call function 0x02 of int 13h (read sectors)
    mov al, 0x18                ; read one sector (512 bytes)
    mov ch, 0x00                ; track 0
    mov cl, 0x02                ; sector 2
    mov dh, 0x00                ; head 0
;    mov dl, 0x00               ; drive 0, floppy 1. Comment out DL passed to bootloader
    xor bx, bx                  ; segment 0x0000
    mov es, bx                  ; segments must be loaded from non immediate data
    mov bx, 0x7E00              ; load the kernel right after the bootloader in memory
.readsector:
    int 13h                     ; call int 13h
    jc .readsector              ; error? try again

特别是:

代码语言:javascript
运行
AI代码解释
复制
mov al, 0x01                ; read one sector (512 bytes)

这是你问题的核心。由于您是作为软盘引导,我建议生成一个1.44MiB文件,并将引导加载程序和内核放在其中:

代码语言:javascript
运行
AI代码解释
复制
dd if=/dev/zero of=bin/lunaos.img bs=1024 count=1440
dd if=bin/os.bin of=bin/lunaos.img bs=512 conv=notrunc seek=0

第一个命令生成一个1.44MiB文件,其中填充了零。第二种方法是在编写完文件后使用conv=notrunc告诉DD不要截断文件。seek=0告诉DD开始编写文件中的第一个逻辑扇区。结果是,os.bin放在1.44MiB映像中,从逻辑扇区0开始,完成时不截断原始文件。

具有已知软盘大小的适当大小的磁盘映像使其更易于在某些模拟器中使用。

A 1.44 per软盘每个磁道有36个扇区 (每头18个扇区,每个磁道2个磁头)。如果您在实际硬件上运行代码,一些BIOSes可能不会跨轨道边界加载。用磁盘读取35个扇区可能是安全的。第一个扇区被BIOS从0头0读取。第一轨道上还有35个行业。我会把上面的一行修改为:

代码语言:javascript
运行
AI代码解释
复制
mov al, 35                ; read 35 sectors (35*512 = 17920 bytes)

这将允许您的内核长度为35*512字节= 17920字节,即使在实际硬件上也是如此。如果大于此,您将不得不考虑使用尝试读取多个轨道的循环来修改引导加载程序。让事情变得更复杂的是,你必须担心更大的内核最终会超过64k的限制。磁盘读取可能需要修改才能使用不是0的段(ES)。如果您的内核有那么大,那么此时可以修复引导加载程序。

调试

由于您处于保护模式并使用QEMU,我强烈建议您考虑使用调试器。QEMU支持使用GDB进行远程调试。设置并不困难,而且由于您已经为内核生成了一个ELF可执行文件,所以您也可以使用符号调试。

您将希望将-Fdwarf添加到-felf32之后的NASM程序集命令中,以启用调试信息。在GCC命令中添加-g选项以启用调试信息。下面的命令应该启动引导程序/内核;在kmain上自动中断;使用os.elf作为调试符号;并在终端中显示源代码和寄存器。

代码语言:javascript
运行
AI代码解释
复制
qemu-system-i386 -fda bin/lunaos.img -S -s &

gdb bin/os.elf \
        -ex 'target remote localhost:1234' \
        -ex 'layout src' \
        -ex 'layout regs' \
        -ex 'break *kmain' \
        -ex 'continue'

如果你用谷歌搜索,有很多关于使用GDB的教程。有一个备忘单描述了大多数基本命令及其语法。

如果您发现自己将来遇到中断、GDT或分页问题,我建议您使用Bochs来调试操作系统的这些方面。虽然Bochs没有符号调试器,但它弥补了它比QEMU更容易识别低级别问题的缺点。在Bochs中调试真正的模式代码(如引导程序)更容易,因为它能够理解20位段:偏移地址与QEMU不同。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47927290

复制
相关文章
如何在Visual Studio中调试.NET源码
In order to configure Visual Studio 2013 do the following in the Tools -> Options -> Debugging -> General menu:
卡尔曼和玻尔兹曼谁曼
2019/01/22
2.1K0
如何在Visual Studio中调试.NET源码
BTrace 告诉你如何在不重启 JVM 的情况下在线调试
Hello 大家好, 我是阿粉,不知道你有没有遇到过这种场景,线上服务跑了一段时间过后偶尔会出现问题,光靠代码和数据分析找不到原因,而且这种情况也不是很常见所以对应的代码也没有加日志输出,如果说重新加上日志进行发布的话,就会破坏现场只能再等一段时间了,或者有的时候想看下接口的参数,从而判断接口参数有没有问题。
Java极客技术
2022/12/02
4450
BTrace 告诉你如何在不重启 JVM 的情况下在线调试
Visual Studio远程调试
网址:https://visualstudio.microsoft.com/zh-hans/downloads/
FlyLolo
2021/11/29
1.4K0
Visual Studio远程调试
如何在 Visual Studio 编译调试 Windows 版本的 Nginx 源码?
在我们的《C/C++ 网络编程实战训练营》第二课,我们给大家布置了两个作业,其中之一是为 Nginx 源码制作一个 Visual Studio 工程文件,可以在 Visual Studio 中调试 Windows 版本的 Nginx,这个作业的目的是:
范蠡
2023/01/04
2.1K0
如何在 Visual Studio 编译调试 Windows 版本的 Nginx 源码?
Visual Studio 调试(系列文章)
  Visual Studio 调试程序有助于你观察程序的运行时行为并发现问题。 该调试器可用于所有 Visual Studio 编程语言及其关联的库。 使用调试程序时,可以中断程序的执行以检查代码、检查和编辑变量、查看寄存器、查看从源代码创建的指令,以及查看应用程序占用的内存空间。
张传宁IT讲堂
2019/09/17
8260
Visual Studio 调试(系列文章)
Visual Studio Code调试PHP
这两天有点时间,花时间学习了一下PHP基础语法和语句。因为个人比较喜欢Visual Studio Code这款编辑器,所以学习我主要记录一下使用VSCode学习PHP的遇到的一些问题。其中最主要的就是怎样使用VSCode调试PHP代码。
siberiawolf
2020/03/24
3.4K0
Visual Studio Code调试PHP
Visual Studio 调试系列11 远程调试
你可以调试已部署在另一台计算机的 Visual Studio 应用程序。 要进行此操作,可使用 Visual Studio 远程调试器。
张传宁IT讲堂
2019/09/17
3.7K0
Visual Studio 调试系列11 远程调试
Visual Studio 调试系列2 基本调试方法
在 Visual Studio 上下文中,当调试应用时,这通常意味着你在附加了调试器的情况下(即在调试器模式下)运行应用程序。 执行此操作时,调试器在运行过程中可提供许多方法让你查看代码的情况。 你可以逐步执行代码、查看变量中存储的值、设置对变量的监视以查看值何时改变、检查代码的执行路径等。
张传宁IT讲堂
2019/09/17
4.6K0
Visual Studio 调试系列2 基本调试方法
Visual Studio 2010的并行调试支持
Visual Studio 2010大幅度的提升了并行调试的用户体验。Visual Stuido 2010的并行调试非常简单,,它提供了两个新的调试工具窗口,以帮助完成基于任务的并行编程。 在 Vis
张善友
2018/01/31
9430
Visual Studio 2010的并行调试支持
Visual Studio 调试系列3 断点
断点是开发人员的工具箱中最重要的调试技术之一。 若要暂停调试程序执行所需的位置设置断点。 例如,你可能想要查看代码变量的状态或查看调用堆栈的某些断点。
张传宁IT讲堂
2019/09/17
5.5K0
Visual Studio 调试系列3 断点
用Visual Studio调试linux程序
用Visual Studio调试linux程序?你真的没看错,这个是真的,不是标题党。当然如果你说VS2015及以上版本自带的linux调试插件,那就算了。这些自带的插件调试一个有简单的main函数程序还凑合,稍微复杂点的程序,根本无法编译调试。 而本文介绍的主角是VS的另外一款插件Visual GDB,让我们欢迎主角登场,下面是正文。 使用Visual Studio+VisualGDB调试远程linux程序 需要工具: Visual Studio 2013或以上版本(以下简称VS) VisualGDB(一
范蠡
2018/04/04
5.8K1
用Visual Studio调试linux程序
使用 Visual Studio 调试多进程的程序
当你的编写的是一个多进程的程序的时候,调试起来可能会比较困难,因为 Visual Studio 默认只会把你当前设置的启动项目的启动调试。
walterlv
2020/02/10
2.7K0
使用Visual Studio Code调试Golang工程
Visual Studio Code - Code Editing. Redefined**
上善若水.夏
2018/09/28
5.1K0
Visual Studio 系列调试方法整理
学编程不会调试,这简直就跟做买卖不会数钱一样好笑,有时候程序出了bug,思维受限的时候,肉眼是很难判断出错误的地方的,虽然调试不一定非得用Debug模式来进行,但是使用vs系列的Debug模式的确是个比较方便的好办法。
mythsman
2022/11/14
7500
Visual Studio 系列调试方法整理
visual studio 不能进入调试状态
解决Windows操作系统在处理回环地址 1、 第一种解决方案是禁用环回检查。 步骤如下 a) 依次单击“开始”和“运行”,键入 regedit,然后单击“确定” b) 在注册表编辑器中,找到并单击下面的注册表项: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa c) 右键单击“Lsa”,指向“新建”,然后单击“DWORD 值” d) 键入 DisableLoopbackCheck,然后按 Enter e) 右键单击“Disabl
hbbliyong
2018/03/06
1.3K0
visual studio code 调试php方法(图文详解)
简介 php是动态语言没有调试器的话排错起来很是麻烦。vscode可以说是程序员的福音,启动速度快,插件越来越多,跨平台。现在说一下vscode上调试php文件 所需文件
用户2323866
2021/07/07
4K0
Visual Studio 调试系列9 调试器提示和技巧
如果你在调试时,经常将鼠标悬停在数据提示上,就可能想固定变量的数据提示,方便自己随时查看。 即使在重新启动后,固定的变量也能保持不动。 要固定数据提示,请在鼠标悬停其上时单击固定图标。 你可以固定多个变量。
张传宁IT讲堂
2019/09/17
3.3K0
Visual Studio 调试系列9 调试器提示和技巧
visual Studio 无法调试,提示程序跟踪已退出
今天在打码出现了vs无法调试,我在网上查了很久没有发现一个方法。 vs点击启动时,出现了一下提示
林德熙
2022/08/04
1.2K0
visual Studio 无法调试,提示程序跟踪已退出
使用 Visual Studio 2022 调试Dapr 应用程序
使用Dapr 编写的是一个多进程的程序, 两个进程之间依赖于启动顺序来组成父子进程,使用Visual Studio 调试起来可能会比较困难,因为 Visual Studio 默认只会把你当前设置的启动项目的启动调试。 好在有Visual Studio 扩展(Microsoft Child Process Debugging Power Tool 插件)可以支持。这个思路来自 https://github.com/dapr/dotnet-sdk/issues/401#issuecomment-747563695
张善友
2022/12/13
1K0
使用 Visual Studio 2022 调试Dapr 应用程序
visual Studio 无法调试,提示程序跟踪已退出
今天在打码出现了vs无法调试,我在网上查了很久没有发现一个方法。 vs点击启动时,出现了一下提示
林德熙
2018/09/18
4K0
visual Studio 无法调试,提示程序跟踪已退出

相似问题

SceneKit如何在SCNSphere内部添加纹理

15

在OpenTK上绑定多个纹理的正确方法是什么?

13

如何在SCNSphere中使用SKScene作为纹理?

119

OpenGLUT:球体的纹理:正确的方法是什么?

10

用MVC静态方法将路径映射到图像的正确方法是什么?

13
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档