每个都是顶级文件的文件是操作系统! 它被编译为相应的.img文件。 打开后,这是我必须按Enter进入Boot菜单的时候: 然后,这里我必须按F12选择USB作为引导设备: 或者,要更改启动顺序并选择USB以获得更高的优先级,因此我不必每次手动选择它,我会在&#34上击中f1;启动中断菜单" 屏幕,然后导航到: 您还需要更改启动顺序以将USB从F12 BIOS菜单中放入USB。 这样你就不必每次都要像疯子一样击中F12。 TODO:加载Stage2不使用大图像链加载器的引导扇区。 为什么? 如果您没有Ubuntu框,这是一个简单的替代方案,对于第一次运行:
apt-get更新&& \ apt-get安装-y git&& \ git clone https://github.com/cirosantilli/x86-bare-metal-examples&& \ CD X86-BALE-METAL-实施例&& \ ./ configure -y&& \制作
为了克服缺乏GUI,我们可以使用QEMU的VNC实现而不是默认的SDL,而不是在主机上可见--NET =主机:
还应该可以在容器内运行GUI,但我没有测试:https://stackoverflow.com/questions/40658095/how-to-open-ubuntu-gui-inside-a-docker-image/ 57636624#57636624
这将在我们的程序执行的第一个指令中留下您,这是我们开始宏的开始。
但请注意,这不是第一个指令QEMU执行:这实际上将是在我们的程序本身之前运行的BIOS设置代码。
然后我强烈推荐使用GDB仪表板查看正在发生的事情。
宏中的NI步骤。但是,您需要在GDB仪表板上打印汇编代码,以查看您的位置
通过类似类似的GDB仪表板设置,在89CBE7BE83F164927CAEBC9334BC42990E499CB1中我看到了一个完美的程序视图,如:
1 / * https://github.com/cirosantilli/x86-bare-metal-examples#bios-hello-world * / 2 3 #include" common.h" 4开始5 mov $ msg,%si 6 mov $ 0x0e,%ah 7环:8 lodsb 9或%al,%al10 jz halt11 int $ 0x1012 jmp loop ────────────────────────────────────────────────── ────────────0x00007C00__start + 0 CLI0x00007C01 __start + 1 ljmp $ 0xc031,$ 0x7c060x00007c08 __start + 8 mov%eax,%ds0x00007c0a __start + 10 mov%eax,%es0x00007c0c __start + 12 MOV%EAX,%FS0X00007C0E __START + 14 MOV%EAX,%GS0X00007C10 __start + 16 MOV%EAX,%EBP0X00007C12 __start + 18 MOV%EAX,%SS0X00007C14 __start + 20 MOV%EBP,%ESP───寄存器───────────────────────────────────────────── ────────────────────────────────────────────────── ──────────────────────────────────────────────────────cs0x00000000eSi 0x00000000 eSi 0x00000000 eSi 0x00000000 ex0x0000000000 ex0000000000 0x00000000 fs 0x00000000 gs 0x00000000─── ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────-_ S:4(没有争论)───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────── ─────────────>>>
调试符号是通过首先链接ELF文件而获得的,然后在它们上使用Objcopy来生成最终图像。然后,我们将elf文件用调试信息传递给gdb:https://stackoverflow.com/questions/32955887/how-to-disassemble-16-bit-x86-boot-sector-code-in-gdb-with-xi -pc-it-get-tr / 32960272#32960272
todo:检测我们是否自动从控制寄存器自动播放16或32位。现在我正在使用2个函数16和32手动切换,但很糟糕。问题是,如果我们有CR0,则无法直接阅读它们:http://stackoverflow.com/a/31340294/895245如果我们的cr0,那么如果cr0&amp很容易。 1在挂钩内。
我们的程序本身不会打印到屏幕本身的任何内容,只是使CPU停止。
此示例是通过字节的printf byte生成的:您无法比此更低更低!
BYTES 510和511:强制性魔法字节0xAA55,BIOS需要考虑我们的磁盘。
如果明显相同,但TODO:它可能会在真正的硬件中浪费更多的能量?
此关键文件确定了我们的程序集的内存布局,需要一些时间才能读取该文件中的评论并熟悉它。
Linux内核还使用链接脚本来设置其图像内存布局,例如:https://github.com/torvalds/linux/blob/v4.2/arch/x86/boot/setup.ld
单级,所以仍然限于512字节的代码+数据! Todo:应该易于解决,使用BIOS磁盘负载,发送拉出请求:-)这是我们还可以调整的最佳示例:http://3zanders.co.uk/2017/10/18/11 -bootloader3.
使用使用GCC的-M不会产生"真实" 16位代码,但与0x66和0x67前缀的相当32位代码:https://wiki.osdev.org/x86-64_instruction_encoding#legacy_prefixes
设置初始状态和链接器脚本比与装配更难和易于错误
因此,对于大多数应用程序,您只需要使用多重手具,而是克服所有这些问题。
00007c17< main&gt ;:7c17:66 55推出%EBP 7C19:66 89 E5 MOV%ESP,%EBP 7C1C:66 83 EC 10 Sub $ 0x10,%ESP 这是因为这些指令由前缀0x66修改,这使得它们表现得像32位。 必须存在_start以避免警告,因为默认链接器脚本期望它 这是一个黑客,它可以更方便的快速和脏测试,但只是不使用它。 为OS / Bootloader提供API,允许您尽可能快,脏IO 每个中断ID组多个具有类似功能的函数,例如, 10h组使用视频相关功能。 http://www.cony.com/intr/int.htm Ralf Brown的中断列表。 每个人都说这是最终的非官方汇编。
将背景颜色更改为整个屏幕的红色并打印一个字符: 然后,我们选择用CX和DX给出的角落(1,1)和(2,2)上的矩形作用: 在红色前景,和整个屏幕在绿色背景中,没有任何初始海博消息。 在视频模式下使像素(1,1)清除红颜色(0CH)的像素13h: 通过键盘从用户获取一个字符,将其递增一个,然后将其打印到屏幕,然后停止: 其中A从第一个块的代码打印,B和第二个块上的代码。 这实际上很重要,以便在进入保护模式时,您可以在那里开始堆栈,因为堆栈向下增长。
在16位模式下,它并不重要,因为大多数现代机器都有所有可寻址的内存,但在32位保护它时,它确实如,因为我们的仿真器通常没有所有4GB。 当然,64位RAM目前比世界上的总RAM大。 int 15返回列表:每次调用它时都会返回一个新的内存区域。 民意调查时间计数器,BIOS在0x046C中保持最新,频率为18.2Hz。 检查初始状态固件通过打印多个寄存器的内容: AX = 00 000 00×= 00 00×= 00 00dx = 80 00 = 00 00 = 00 00 = 00 00 = 00 00 00 = 00 00 00×53 ff 00 f0 DX似乎是唯一有趣的常规寄存器:固件存储当前磁盘号的值,以帮助在那里有15h。 因此它通常包含0x80。 与红色前景和蓝色背景显示在清除屏幕的左上角。
然后,我们可以将0xB800移动到段寄存器并使用段:偏移寻址以访问此内存。
然后,我们可以通过将0xb800:0000视为uint16_t数组来显示字符,其中低8字节是ASCII字符,高8字节是此字符的颜色属性。
X86处理器具有几种模式,对处理器如何工作产生巨大影响。
涵盖英特尔手册3.特别有用的是"图2-3。处理器的操作模式中的转换;图表。
(所有模式)||重置| v + -------------------- + |真实地址(pe = 0)| + -------------------- + ^ || PE | V + ----------------------- + |受保护(PE = 1,VM = 0)| + ----------------------- + ^ ^ | || | VM | | v + -------------- + + ------------------- + | IA-32E | | Virtual-8086(VM = 1)| + ------------- + + -------------------- +
IA-32E过渡是棘手的,但在英特尔手册第3 - 9.8.5&#34上清楚地描述;初始化IA-32E模式&#34 ;:
从受保护模式开始,通过设置CR0.pg = 0禁用分页。使用MOV CR0指令禁用分页(指令必须位于标识页面中)。
通过设置CR4启用物理地址扩展(PAE)。 PAE = 1.当尝试初始化IA-32E模式时,未能启用PAE将导致#gp故障。 使用4级页面地图表(PML4)的物理基地址加载CR3。 通过设置CR0.pg = 1.启用分页。这导致处理器将IA32_EFER.LMA位设置为1.启用分页和以下指令的MOV CR0指令必须位于标识映射的页面中(直到此时间为止 可以实现非身份映射页面的分支)。 英特尔手册第3卷中定义的术语 - 第2章"系统架构概述&#34 ;: 实模式,保护模式,虚拟8086模式和系统管理模式。 这些有时被称为遗留模式。 这进一步表明,真实的,受保护和虚拟模式不是主要的预期操作模式。 可以使用此模式中的32位寄存器与"操作数大小覆盖前缀" 0x66。
20位内存(1MB)而不是通常适合寄存器的16位(256KB)。 例如,解决: FS和GS是通用目的:它们不受任何指示隐含地影响。 所有其他人都将被进一步举例说明。 CS设置为LJMP指令,我们将其使用它来跳过代码中的零空白。 在第一个之后,第二个字节是16个字节,并使用SP = 1访问。 SS会影响使用SP等推送和流行的指令:这些将实际使用16 * SS + SP作为实际地址。 20:A0 63 7C MOV 0x7C63,%AL34:26 A0 63 7C MOV%ES:0x7C63,%AL40:64 A0 63 7C MOV%FS:0x7C63,%AL4C:65 A0 63 7C MOV%GS:0x7C63,%AL58: 36 A0 63 7C MOV%SS:0x7C63,%Al 这使得DS最有效的数据访问,因此是一个良好的默认值。
todo beavory:尝试创建一个从处理程序调用中断的无限循环: 显然存在例外时,iret跳回到抛出异常本身的行,而不是一个之后,这导致循环: 但那么为什么它在0081停止? 如果我们将初始值设置为0x0090,则它只运行一次。 存储每个中断的处理程序的实模式内存表。 基地址设置在中断描述符表寄存器(IDTR)中,可以使用LIDT指令进行修改。 idtr - > + --------------------- + 0 |地址(2字节)| 2 |代码段(2字节)| + --------------------- + + ---------------------- + 4 ----> |地址(2字节)| 6 |代码段(2字节)| + --------------------- + + ---------------------- + 8 ----> |地址(2字节)| A |代码段(2字节)| + --------------------- 设置iDtr的值,因此设置IVT的基地址:
我想我明白LIDT将作为输入存储地址,并且该地址的内存必须包含: 4字节:IVT的基础地址。 在实模式下忽略更高的字节,因为地址不是4字节。 我们必须以不同方式编码指令,因此需要.Code32。 16位模式32位指令是具有特殊前缀的可供选择。 英特尔手册第3 - 9.10"初始化和模式切换示例" 确实包含如何进入受保护模式的官方示例。 代码在PDF内部,删除所有格式化,因此我们已将其复制到此仓库 TODO没有已知的工具实际上可以编译该语法...虽然MASM应该关闭: 首先阅读寻呼教程,特别是:https://cirosantilli.com/x86-paging#segation以了解配置CPU所需的寄存器类型和数据结构操纵的感觉,以及分段与分页方式。
如果在比允许的偏移量大于允许的异常发生的偏移量,则会发生访问,这就像中断一样,并且由先前注册的处理程序处理。
+ ---------- + -------- + ------------------------ + |程序1 |未使用|程序2 | + ----------- + -------- + ------------------------ - + ^ ^ ^ ^ | | | | START1 END1 START2 END2
除了地址转换之外,分段系统还管理了保护环等其他功能。 TODO:64位模式下的那些如何完成?
例如,在Linux 32位中,只有两个段始终使用:一个在内核的环0处,另一个在所有用户进程的特权3处彼此。
在受保护模式下,段寄存器CS,DS,SS,ES,FS和GS包含与实际模式中的简单地址更复杂的数据结构,其中包含单个数字。
类似于实模式,使用常规MOV助记符指令将此数据结构加载到寄存器上。
段选择器的索引字段选择要使用哪个段描述符中的哪一个。
基地址设置为LGDT指令,从内存加载6字节结构:
英特尔手册卷3 - 3.4.2"段选择器"说我们不能使用GDT的第一次输入:
处理器不使用GDT的第一个条目。将指向GDT的此条目的段选择器(即,具有0索引的段选择器和设置为0的段选择器为0)作为“空段选择器”。处理器在段寄存器(除CS或SS寄存器除外)加载为空选择器时,处理器不会生成异常。但是,它确实生成了当捕获空档选择器的段寄存器来访问存储器时生成异常。空档选择器可用于初始化未使用的段寄存器。使用空段选择器加载CS或SS寄存器会导致要生成的常规保护异常(#gp)。
在英特尔手册3 - 3.4.5&#34上清楚地描述;段描述符"特别是图3-8"段描述符"
第一个32个处理程序由处理器保留并具有预定义的含义,如英特尔手册第3卷3-3中所指定的。 "英特尔64和IA-32一般例外"
在Linux内核中,https://github.com/torvalds/linux/blob/v4.2/arch/x86/entry/entry_64.s全部设置:每个idtentry divide_error调用设置一个新的。
DES不仅适用于零分,它们也会发生溢出。举例说明。
暗示寻呼工作,因为我们打印并修改了具有两个不同虚拟地址的相同物理地址。 这是从页面故障处理程序打印,我们通过写入未映射地址来设置触发。 看来,用户程序可以在userlland执行期间修改:http://stackoverflow.com/questions/12716419/can-you-enter-x64-32-bit-long-compatibility-sub-mode-outside -of-kernel模式 这个回购中目前没有示例,因为我懒得制作它们。 观看x86-64 cpu靴子就像看着amoeba慢慢地发展到狗身上。 专用的进出方法称为#34;端口映射IO"以及魔法地址的方法"记忆mapp" 从界面的角度来看,我觉得内存映射更优雅:端口IO只是创建了第二个地址空间。
每当按下或向上按下键时,键盘十六进制scancode会打印到屏幕: 始终立即返回最后一个键盘键盘:然后我们只是轮询更改并仅打印更改。 因此,当我们关闭计算机或删除笔记本电池时,必须使用单独的电池继续进行。 RTC不能高于几秒钟的准确性。 为此,考虑坑,或者是HPET。 TODO我认为这从频道0中的值值下降,因此允许在将来安排一个事件。 该坑可以生成定期中断(或声音!),给定频率到IRQ0,默认情况下,真实模式映射到中断8。 端口43h用于控制除频率的信号属性,在通道端口,对于3个通道。
我们不会直接控制坑的频率,该频率固定在0x1234dd。
相反,我们控制频率除数。这是一种经典类型的离散电子电路:https://en.wikipedia.org/wiki/frequency_divider
魔术频率来自历史原因,根据HTTPS://wiki.osdev.org/programmable_interval_timer重用电视硬件,这反过来可能受到晶体振荡器的一些物理性质的影响。
可以使用int 0x10和ah = 0x00设置模式,并使用ah = 0x0f获得
颜色编码只是一个任意调色板,适合1字节,它不会像R R R G G G B B一样拆分颜色,或者在:https://en.wikipedia.org/wiki/8-bit_color。相关:http://stackoverflow.com/questions/14233437/convert-normal-256-color-to-mode-13h-version-color.
在我们进入保护模式时,不能使用BIOS,但我们可以使用VGA接口来获取我们的程序输出。
所有笔记本电脑我用UEFI测试了BIOS,因此UEFI必须具有倒退兼容性的BIOS仿真模式:https://www.howtogeek.com/56958/htg-explains-how-uefi-will -replace-the-bios/
由英特尔制作,主要是MIT开源,这可能意味着供应商将破解封闭的源版本。
由于它是巨大的,它不可避免地包含错误。 Garret表示,英特尔有时不觉得更新具有错误修正的固件。
UEFI提供与大多数人称之为操作系统的大型API:
http://www.rodsbooks.com/efi-programming/hello.html最佳来源到目前为止:允许我编译Hello World! todo:如何在qemu和真正的硬件上运行它?
没有图像运行给出了UEFI shell,而Linux内核映像启动了很多:http://unix.stackexchange.com//228053/32558,所以我们只需要生成图像。
来自:https://sourceforge.net/projects/edk2/files/ovmf/ovmf -ia32-r15214.zip/download todo:自动构建它从来源建立它,摆脱Blob,强迫将其推离历史。工作构建设置草图:https://github.com/cirosantilli/linux-cheat/blob/b1c3740519eff18a7707de981ee3afea2051ba10/ovmf.sh
开源Hippie自由爱好跨平台固件,试图替换BIOS和UEFI的更好的人类。
GRUB / README.ADOC TODO清理并举例说明该文件中的所有内容。有些主持人需要出去。
自我+1:重新加载自己,几乎立即重新加载grub并落在同一个菜单上
此示例说明了Chainloader GRUB命令,它只是加载引导扇区并运行它:https://www.gnu.org/software/grub/manual/grub/html_node/chainloader.html
这就是您需要引导Systems,如Windows,GRUB不了解:只是指向他们的分区,让他们完成这项工作。
构建后,GRUB-MKRESCUES创建一些文件系统,GRUB / ChainLoader / ISO / Boot / Main.img被放置在其中一个文件系统内。
这说明GRUB非常令人敬畏的能力,了解某些文件系统格式,并从中获取文件,从而允许我们在具有单个文件系统的多个操作系统之间挑选。
它是教育,通过在https://askubuntu.com/questions/69363/mount-single-partition-from-image-of-of-en中展开生成的grub / chainloader / main.img
......