MacOS环境-手写操作系统-48-让内核从错误中恢复

简介: MacOS环境-手写操作系统-48-让内核从错误中恢复

让内核从错误中恢复

1.简介

微软早期的DOS系统 存在一个严重的问题是


如果应用程序运行出现问题 它会导致整个系统完全奔溃掉


我们当前的系统内核也存在这一的问题


例如打开api_call.asm,其内容如下

[SECTION .s32]
BITS 32
call main
retf
api_putchar:
  mov edx, 1
  mov al, [esp + 4]
  int 02Dh
  ret
%include "app.asm"


call main 时CPU控制权会提交给应用程序


执行应用程序的代码 应用程序执行完毕后


返回到call main语句的下一条指令继续执行


call main 的下一条语句是retf


它的作用是从堆栈上得到内核代码的全局描述符


把该描述符在描述符表中的下标赋值给寄存器cs


同时把内核要执行语句的地址从堆栈上取得 并赋值给寄存器ip


这样CPU的控制器会重新交还给内核


假设我们对上面代码做一个修改如下


[SECTION .s32]
BITS 32
call main
pop  eax ;故意让返回地址出错造成异常保护中断
retf
....


我们在执行retf语句时 把存储在堆栈上的内核要执行的语句地址弹出


这样要返回的内核地址就会遭遇破坏


当执行retf语句后 ip指针会执行内存的任意一个无法确定的地方


于是CPU在接下来的执行中就会遇到错误


上面代码修改后运行起来 就会出现报错


点击确定之后 系统就被关闭了


出现这种现象的原因


由于ip指针指向了无效地址 致使CPU执行了无效指令进而导致系统的整体奔溃


接下来我们看看如何处理让系统从这种严重错误中恢复回来


这样就可以防止应用程序执行恶意代码而对系统造成伤害


当CPU在执行指令出现错误时


例如遇到了无效指令 那么CPU会出发0Dh号中断


让该中断来处理当前所出现的异常局面


如果该中断无法处理 那么CPU就会停止运行


于是就出现了上面的情况


为了让系统能从错误中恢复 我们需要实现0Dh号中断


在中断中 我们直接结束掉当前正在运行的应用程序 直接把CPU的控制全交还给内核


2.代码

kernel.asm中注册0Dh号中断


代码修改如下

LABEL_IDT:
%rep  13
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep
.0Dh:
    Gate SelectorCode32, exceptionHandler,0, DA_386IGate
%rep  18
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep


上面代码在中断描述符表中注册了一个0Dh号中断


当中断发送时 CPU会调用exceptionHandler函数


看看该函数的实现 同样也是在kernel.asm中

_exceptionHandler:
exceptionHandler equ _exceptionHandler - $$
    cli
    ;把内存段切换到内核
    mov  ax, SelectorVram
    mov  ds, ax
    mov  es, ax 
    mov  ax, SelectorStack ;切换到内核堆栈段
    mov  ss, ax
    
    mov  ecx, [0xfe4];获取内核堆栈指针
    add  ecx, -8
    mov  [ecx+4], ss ;保存中断时的堆栈段
    mov  [ecx], esp  ;保存中断时堆栈指针

    mov  esp, ecx ;切换内核指针

    call intHandlerForException
    
.kill:  ;通过把CPU交给内核的方式直接杀掉应用程序
     mov  esp, [0xfe4]
     sti
     popad
     ret

中断运行时


先把内核专有内存段的描述符赋值给寄存器ds,es


这样代码运行时可以直接读写内核的数据 同时把内核的堆栈描述符赋值给寄存器ss


这样代码运行时使用的堆栈就是内核的专有堆栈


然后把错误发生时的应用程序使用的堆栈段描述符和堆栈指针存入内核堆栈


接着调用函数intHandlerForException进行错误处理


该函数实现在内核的C语言部分 也就是在write_vga_desktop.c中


void intHandlerForException(int *esp) {
    g_Console.cur_x = 8;
    cons_putstr("INT 0D ");
    g_Console.cur_x = 8;
    g_Console.cur_y += 16;
    cons_putstr("General Protected Exception");         
    g_Console.cur_y += 16;
    g_Console.cur_x = 8;
    return 1;
}


它的做法很简单 只是在控制台上打印出两行字符串


分别是"INT OD" 和 “General Protected Exception”


然后就返回了


回到中断实现部分 也就是.kill对应的代码


在内核把控制权交给应用程序时 会把它当时的堆栈指针存储到内存[0xfe4]处


内核时通过start_app把控制权交给应用程序的 我们再看看start_app的代码


start_app:  ;void start_app(int eip, int cs,int esp, int ds)
    cli
    pushad
    ...
    mov  [0xfe4], esp
    ...


start_app在执行时 通过指令把当时所有通用寄存器的数据存储到堆栈上


同时把esp指针的值 也就是当时的内核堆栈指针存储到内存0xfe4这个地方


因此.kill 的指令 mov esp [0xfe4]实际上就是对上面指令mov [0xfe4], esp 的逆操作


同时popad也是对上面指令pushad的逆操作


我们再看看start_app是如何被调用的 在write_vga_desktop.c中

void cmd_hlt() {
    ....
    start_app(0, 11*8,64*1024, 12*8);
    char *pApp = (char*)(q + 0x100);
    showString(shtctl, sht_back, 0, 179, COL8_FFFFFF, pApp);
    memman_free_4k(memman, buffer.pBuffer, buffer.length);
    memman_free_4k(memman, q, 64 * 1024);
}


start_app是在cmd_hlt中被调用的


我们知道C语言在调用子函数时


会把调用子函数后要执行的下一条指令的地址压入堆栈


因此当上面代码在调用start_app函数时


下一条指令 也就是char pApp=(char)这条语句的地址会被压入到堆栈上


当.kill处的代码执行完语句popad后


此时堆栈上存储的恰好就是语句char pApp=(char)的地址


于是当我们执行指令ret的时候 该语句的地址会赋值给ip寄存器


这样CPU就会直接从char pApp=(char)这条语句开始执行


这就类似与应用程序正常执行完毕后 CPU控制权正常返还给内核的情况是一样的


这样 当应用程序执行出现严重错误时 CPU触发0Dh号异常处理中断


在该中断中 代码会把CPU控制权直接交还给内核


就相当于把出现异常的应用程序毁尸灭迹


内核就好像什么事都没发生过一样 继续按照老样子运行


3.运行

目录
相关文章
|
18天前
|
存储 物联网 调度
操作系统的心脏:内核深度解析
在数字世界的构建中,操作系统扮演着基石的角色,而其核心—内核,则是这一复杂系统的灵魂。本文将深入探讨操作系统内核的工作原理,揭示它是如何管理硬件资源、运行程序以及提供系统服务的。通过理解内核的结构和功能,我们可以更好地把握计算机系统的运作机制,进而优化和创新我们的技术实践。
|
27天前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
9天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
32 9
|
12天前
|
存储 人工智能 安全
操作系统的心脏——内核深度解析
【10月更文挑战第29天】 本文深入探讨了操作系统的核心组件——内核,包括其定义、功能、架构以及在现代计算中的重要性。通过对比不同操作系统内核的设计哲学和技术实现,揭示了内核如何影响系统性能、稳定性和安全性。此外,文章还讨论了未来内核技术的潜在发展方向,为读者提供了一个全面了解内核工作原理的平台。
|
9天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
27 2
|
9天前
|
存储 消息中间件 算法
深入探索操作系统的心脏——内核机制解析
本文旨在揭示操作系统核心——内核的工作原理,通过剖析其关键组件与机制,为读者提供一个清晰的内核结构图景。不同于常规摘要的概述性内容,本文摘要将直接聚焦于内核的核心概念、主要功能以及其在系统管理中扮演的角色,旨在激发读者对操作系统深层次运作原理的兴趣与理解。
|
10天前
|
算法 调度 C语言
探索操作系统的心脏:内核与用户空间的交互
【10月更文挑战第36天】本文将深入探讨操作系统的核心组件—内核,以及它如何与用户空间进行交互。我们将通过浅显易懂的语言和生动的例子来揭示这一复杂主题的面纱。文章不仅涉及理论知识,还会展示具体的代码示例,帮助读者更好地理解内核机制。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供新的视角和深入的理解。
|
13天前
|
缓存 网络协议 Linux
Linux操作系统内核
Linux操作系统内核 1、进程管理: 进程调度 进程创建与销毁 进程间通信 2、内存管理: 内存分配与回收 虚拟内存管理 缓存管理 3、驱动管理: 设备驱动程序接口 硬件抽象层 中断处理 4、文件和网络管理: 文件系统管理 网络协议栈 网络安全及防火墙管理
35 4
|
15天前
|
存储 程序员 Linux
深入理解操作系统:从用户到内核的旅程
【10月更文挑战第31天】本文将带领读者踏上一场从应用层到内核层的探索之旅,揭示操作系统如何协调硬件资源、管理进程和提供系统服务。我们将通过具体代码示例,展示如何在Linux环境下编写简单的程序来与操作系统交互,并解释背后的原理。文章旨在为非专业读者提供一个易于理解的操作系统概念框架,同时为有志于深入了解计算机科学核心的读者打下坚实基础。
|
23天前
|
存储 Linux 调度
探索操作系统的心脏:内核设计与实现
【10月更文挑战第23天】在这篇文章中,我们将深入探讨现代操作系统的核心—内核。通过浅显易懂的语言和实例,本文旨在揭示操作系统内核的设计哲学、关键组件以及它们如何协同工作以支持复杂的系统功能。无论你是初学者还是有一定基础的开发者,本篇文章将为你打开一扇了解操作系统深层工作原理的大门。
38 8

热门文章

最新文章