Open wjk13720041 opened 6 years ago
;boot.asm
;本文件主要是将主引导和内核读取到内存中
[BITS 16]
org 0x7c00
jmp startDash
nop
;FAT12信息
db "lovelive" ;生产厂商名
dw 512 ;每扇区字节数
db 1 ;每簇扇区数
dw 1 ;保留扇区数
db 2 ;FAT表份数
dw 224 ;根目录可容纳目录项数
dw 2880 ;总扇区数(16位)
db 0xf0 ;介质描述符
dw 9 ;每FAT使用的扇区数大小
dw 18 ;每个磁道的扇区数
dw 2 ;磁头数
dd 0 ;隐藏扇区数
dd 0 ;总扇区数(32位)
db 0 ;13号中断的驱动器号
db 0 ;保留
db 0x29 ;扩展引导标记
dd 0 ;卷序列号
db "boot ";卷标
db "FAT12 " ;文件系统类型
echo16: ;输出字符串 push ax push bx echoChar: lodsb ;输入字符给al or al,al ;自己或自己,不改变al值但是清理了符号位 jz bre ;到0了结束 mov ah,0x0e mov bx,0x0f int 0x10 jmp echoChar bre: pop bx pop ax ret
startDash:
mov ax,0
mov ss,ax
mov sp,0x7c00
mov ds,ax
mov es,ax
;mov al,0x03
;int 0x10
firstRead: mov ax,0x07e0 ;7C00后面的位置,没占用 mov es,ax ;缓冲器基地址
mov ch,0 ;CH柱面
mov cl,2 ;CL扇区,现在这些代码位置在第一扇区,512B,load应该从第二扇区开始读取
mov dh,0 ;DH磁头号
Read: mov si,0 ;用来记录读取出错的次数,每次成功读取一个簇会重置
ReadMain: mov ah,0x02 ;int13的02号中断 读盘 mov al,1 ;一次读的扇区数 mov bx,0 ;缓冲区偏移量 mov dl,0 ;DL驱动号 int 0x13 jnc next ;没有出错就跳 add si,1 ;出错记录 cmp si,5 ;错误超过五次 jae fail ;结束程序运行顺便报错 mov ah,0x00 ;否则调int13的00号中断 复位 mov dl,0x00 ;复位内存地址 int 0x13 jmp ReadMain
next:
push si
mov si,msg_reading
call echo16
pop si
mov ax,es
cmp ax,0x07e0
je readCore
add ax,0x20
jmp readNext
readCore: mov ax,0x1000
readNext: mov es,ax add cl,1 ;开始搬运下一个扇区 cmp cl,18 jbe Read ;成功读完一个扇区,重置错误数并开始读取下一个扇区
mov cl,1 ;如果超过18个扇区,重置扇区号
add dh,1 ;然后开始读下一个面(也就是磁头号++)
cmp dh,1
jbe Read ;如果磁头号在1以内(0,1两个磁头)就跳回和刚才一样读取这一面的内容
mov dh,0 ;成功读取一面后重置磁头号
add ch,1 ;开始换柱面读
cmp ch,10 ;读10个柱
jbe Read
jmp success
success: ;突然想加function(e){console.log(e)} mov si,msg_readE call echo16
jmp 0x7e00
fail: mov si,msg_err call echo16 jmp $
msg_reading: db 46,0 msg_readE: db 13,10,"read end!",13,10,0 msg_err: db "error!",13,10,0
times 510-($-$$) db 0
db 0x55,0xaa
;loader.asm
;本文件主要加载GDT表IDT表,保存bios的信息,移动内核,切换保护模式和跳转到内核
;0x7c00 boot ;0x7e00 loader ;0x8200 bios信息 ;0x10000 内核
%include "/home/lovelive/desktop/os/os/boot/pm.asm" ;引入oranges的常量表 [BITS 16] LEDS equ 0x8200 VMODE equ 0x8202 SCRNX equ 0x8204 SCRNY equ 0x8206 VRAM equ 0x8208
jmp start
;gdt表 gdt: ;基地址 大小限制 这段的属性 gdt_null: Descriptor 0, 0, 0 ;空记录(基) gdt_code: Descriptor 0, 0xfffff-1, DA_C+DA_32+DA_LIMIT_4K ;代码段 gdt_data: Descriptor 0, 0xfffff-1, DA_DRW+DA_32+DA_LIMIT_4K ;数据段
gdtLenght equ $-gdt gdtLimit dw gdtLenght-1 gdtAddress dd gdt
;gdt选择子 selgdt_code equ gdt_code-gdt_null selgdt_data equ gdt_data-gdt_null
start: ;切显示模式 ;支持VBE -> VBS是2.0以上 -> 支持指定的显示模式 -> 用1024x768 8bit模式显示 ;不支持就滚回320x200 ;先做个判断,判断支持不支持vbe https://blog.csdn.net/happinux/article/details/5815914 mov ax,0x9000 mov es,ax mov di,0 mov ax,0x4f00 int 0x10 cmp ax,0x004f jne scr320x200 ;不支持,用320x200显示
mov ax,[es:di+4]
cmp ax,0x0200 ;判断vbe是不是2.0以上
jb scr320x200
mov cx,0x105 ;显示模式,1024x768 的 8bit模式(这里做判断,下面才是显示)
mov ax,0x4f01
int 0x10
cmp ax,0x004f ;判断支持不支持
jne scr320x200
cmp byte [es:di+0x19],8 ;支持不支持8bit的颜色数
jne scr320x200
cmp byte [es:di+0x1b],4 ;调色板方法能不能用
jne scr320x200
mov ax,[es:di] ;模式属性
and ax,0x0080 ;判断第7位是不是1
jz scr320x200
scr1024x768: mov bx,0x4105 mov ax,0x4f02 int 0x10 mov byte [VMODE],8 mov ax,[es:di+0x12] mov [SCRNX],ax mov ax,[es:di+0x14] mov [SCRNY],ax mov eax,[es:di+0x28] mov [VRAM],eax
jmp getKey
scr320x200: mov al,0x13 mov ah,0x00 int 0x10 mov byte [VMODE],8 mov word [SCRNX],320 mov word [SCRNY],200 mov dword [VRAM],0x000a0000
getKey:
mov ah,0x02
int 0x16
mov [LEDS],al ;把键盘led信息写入内存
writeGDT: ;写gdt表
lgdt [gdtLimit]
;开A20地址线
cli
in al,0x92
or al,0x02
out 0x92,al
;切保护模式
mov eax,cr0
or eax,1 ;控制寄存器0的最后一位是0则是实模式,1则是保护模式
mov cr0,eax
;jmp $
jmp dword selgdt_code:0x10000
times 512-($-$$) db 0
显示模式切换我是参考的《30天》,所以和大佬你现在的有些不同,大佬开始好像也是看的《30天》,然后loader我是对齐到了第二个扇区,这样我就直接把第二个扇区加到指定位置上,比较方便我操作
还有大佬,代码段中限长为0xfffff-1好像不够管理4G空间吧。。。
或者大佬,这么说吧。。。我之前在实模式下将内核加载到了0x10000开头的位置,我认为保护模式下GDT的起点是0x0000,那么我是不是将基指向gdt的data段然后跳转到0x10000就行,如果是那么ELF我需要如何执行?是直接将其装载到0X10000他就会被系统识别并通过找入口直接跳转到入口开始执行么?
Hello,我已经好几年没有继续研究 kernel 了,而且现在也没有从事相关的工作, 有些问题凭印象作答,难免有纰漏,太过细节的地方可能也无法深入研究, 因此这些回答仅供参考。
好的大佬
我现在理解的内核加载是:bootloader把软盘里的内容加载到内存,然后跳转到ELF文件找到入口执行内核程序,但是不太清楚这里elf是自行进入脚本ld文件设置的入口还是需要我们给定入口位置
你的理解大致没错,在 OS67 里,boot/bootsect.asm 会在实模式下把内核加载到 0x8000,开启保护模式并进入 32 位模式后之后跳转到 Selec_Code32_R0:0x8000
时,其控制流实际上是到了 kern/loader.asm 中,(loader.asm 会将内核整体从 0x8000 移到 0x100000,之所以这么做应该是由于 MBR 的历史原因)。
loader.asm 在这里是内核的一部分,和内核本体的 c 代码被编译成一个 binary,因此符号也是共享的,入口位置通过 [extern osmain]
伪指令即可获取。
大佬。。。[extern osmain] 不是引用一个外部定义了的函数么。。
Orz 好吧,看来我的理解有偏差,你要 ELF 入口点难道不是想找到内核代码开始的地方么?在没有 crt0 的情况下(其实我不知道有没有……),入口点不就是 main 函数么地址么?
对 OS67 来说并不存在什么 ELF 入口点,script/link.ld#L8 把内核的 .text 段安排在文件的最开始,生成一个 elf 格式的 kernel,然后 Makefile#L54 会把 elf 给 strip 成 binary,所以其实加载到内核的只是一个普通的 binary,它的 0x0 处就是 kernstart。
然后我现在lgdt [gdtLimit] 后打算跳到内核执行 jmp dword selgdt_code:0x10000 但是貌似bochs又跳回了boot未启动的部分,也就是说重新加载了一遍程序,我想问一下大佬这里跳转部分我应该怎么做。bochs的GDT表好像和设置的不太一样
你用 bochs 看过 GDT 表了么?建议用 bochsdbg 在 JMP 前加个 INT 3 断下来单步跟跟看。
:joy: 你贴的代码感觉我现在已经看不懂了,你列举的四个可能的问题,都可以通过 bochsdbg 来解决,当然由于 GDT 设置错误的问题可能调起来不是很直观。
还有大佬,代码段中限长为0xfffff-1好像不够管理4G空间吧。。。
记得 DA_LIMIT_4K 也就是 Gr 位被置位的段的粒度是 4K 吧。
我之前在实模式下将内核加载到了0x10000开头的位置,我认为保护模式下GDT的起点是0x0000,那么我是不是将基指向gdt的data段然后跳转到0x10000就行,如果是那么ELF我需要如何执行?是直接将其装载到0X10000他就会被系统识别并通过找入口直接跳转到入口开始执行么?
当然不「会被系统识别」,你就是系统,并没有一个第三者来帮你识别 elf 文件,除非你用更先进的 bootloader(比如基于 UEFI 的)。
如我刚才所说,OS67 的内核并不是一个 elf 文件,而是一个普通的 binary,因此与你现在的实现有出入,从汇编跳转到 C 代码这一段可能缺少参考价值。
对于 elf 来说,你直接跳转到 0x10000 也就是 elf 的 0x0 处当然是不行的,于渊的 Orange's 的做法是在汇编里解析 ELF 的 header,你可以参考看看,当年我看得很累于是采用了现在的做法……
那如果我采用打成一个elf或者其他文件然后加入镜像,并且不使用elf的格式,只相当于把内核打包成一个整体的文件,然后通过打包之后的地址进行访问不知道能不能成功,访问地址可以通过hexdump找到
应该可以,只要你保证总是能找到「入口点」,是什么格式都没所谓其实。
注意在跑 C 代码前先把其他的各种段基址设置好 kern/loader.asm#L21。
我现在是学校有个编程活动,我想参加,之前抄了一遍30天的代码,但是那位大佬的编译器什么什么的全是他自带了。。。感觉抄一遍代码交了学到的东西不多,参加活动的诚意也不足,然后看了os67和neuos,都是类unix,没有图形化,然后我现在卡住了。。一个打包的问题,因为现在的格式是FAT12,需要按照文件系统格式保存,我现在是在centos下创建img然后挂载之后往里面存东西来打img的,然后现在又卡在了c编译成内核,我想的是c编译成bin然后c里面加个头来引用其他所有的内容
谢谢大佬指点,我再去试试
我先试着保留boot.asm和loader.asm然后对齐两个文件到7c00和0x7e00然后将核心文件转成bin然后移动到0x10000处然后通过loader跳转到10000处试试
之前抄了一遍30天的代码,但是那位大佬的编译器什么什么的全是他自带了。。。感觉抄一遍代码交了学到的东西不多
看 30 天只能看到有趣的东西,重要的细节通通被作者藏起来了,这也是我放弃抄 SilverRainZ/OSASK 的原因。
我对这本书的看法:https://www.cnblogs.com/lastavengers/p/4115612.html
然后看了os67和neuos
neuos 是 @VOID001 那个么 :joy: 么?世界真小。
图形化
等你跨过保护模式和 C 语言的坎,开始做驱动,内存和进程部分的工作时,你会发现图形化实在算不得内核的一部分,尽管它也应该很有趣。
我现在是在centos下创建img然后挂载之后往里面存东西来打img的
这是科学的做法。
然后现在又卡在了c编译成内核,我想的是c编译成bin然后c里面加个头来引用其他所有的内容
可以参考下 loader.asm 是怎么做的,如果你不介意内核不是 elf 的话。
0.主要是第一次写,后面肯定会更加深入去了解 1.是那位大佬的。。。。b站貌似就那位大佬在写教程了。。。不过。。鸽了一年了。。如果大佬认识他麻烦帮忙催更。。 2.对的,不过现在主要是因为想做一个小型的,可以编辑简易文本的刷在U盘上面能直接跑起来带了一个简易文件系统的操作系统,所以就先把驱动和内核写一起了23333 3.但是貌似权限对他的影响特别大 4.好的大佬
现在确实把指令加载到内存中了,我不再使用FAT12文件系统了
那应该就是GDT的问题,我现在加载到指定位置了但是没有跳转,我打算去掉保护模式进行一次实模式的跳转来验证正确性
成功
看到被 LA at 了,我就出来了 > < @wjk13720041 除了自己写 bootloader 推荐另一个模式,使用 multiboot 标准构建你的镜像,这样你可以通过 grub 运行你的 os, 并且 grub 会帮你做很多初始化的事情 (其实我是想说之前一直工作+最近也很忙于是就咕咕咕neuos视频教程了,不过还是可以交流探讨的 :D)
看到被 LA at 了,我就出来了 > < @wjk13720041 除了自己写 bootloader 推荐另一个模式,使用 multiboot 标准构建你的镜像,这样你可以通过 grub 运行你的 os, 并且 grub 会帮你做很多初始化的事情 (其实我是想说之前一直工作+最近也很忙于是就咕咕咕neuos视频教程了,不过还是可以交流探讨的 :D)
大师球
看到被 LA at 了,我就出来了 > < @wjk13720041 除了自己写 bootloader 推荐另一个模式,使用 multiboot 标准构建你的镜像,这样你可以通过 grub 运行你的 os, 并且 grub 会帮你做很多初始化的事情 (其实我是想说之前一直工作+最近也很忙于是就咕咕咕neuos视频教程了,不过还是可以交流探讨的 :D) 这样其实也可以,但是我现在其实bootloader已经写好了,只是GDT加载部分有点问题,我直接用16进制写结果还是没办法跳过去,我现在如果可以跳到段的0x10000位置那么下面的我就有思路了,我可以达成二进制文件来运行,C的话我想如果引用把所有文件串联起来应该是可以的,就像os67这位大佬的软盘生成asm一样
还有大佬你们。。。貌似都不喜欢用QQ和微信。。。
大佬们。。睡了么。。没睡要不咱来讨论个问题? @SilverRainZ @VOID001 是这样,我不使用lgdt来设置gdtr就能正常加载GDT表。。。
我把代码整理一下贴在ubuntu pause吧,,,
这个是loader.asm 也就是主要切保护并跳到保护模式下的code段的代码 https://paste.ubuntu.com/p/pY9TSxW2fw/
其中我将lgdt注释掉了,现在这样的gdt最后加载了的话gdt表好像全是0
莫名其妙把几天的问题解决了
建议你用 Bochs 断点调试,看 gdtr 的地址是多少,并且用 bochs 打印出那个地址的 data 确认是否有误,GDT Table 和 GDT Descriptor 的 format 可以看 Intel 手册得知
建议你用 Bochs 断点调试,看 gdtr 的地址是多少,并且用 bochs 打印出那个地址的 data 确认是否有误,GDT Table 和 GDT Descriptor 的 format 可以看 Intel 手册得知
大佬,原因我查出来了,我看了一下gdtr的地址,是0x00a2,突然想到可能是因为加载到了内存的原因,因为他是在编译的时候确定的,所以我在开头加上了org 0x7e00这样他的地址就变成了0x7ea2,就可以正常读取到GDTR了
大佬,又遇到了新问题。。。 每次运行到下面这条指令--向0x000a0000写入的时候就会被重置(应该是被重置,跳到0xfffffff0,然后寄存器全清空)
感觉是数据段的属性没设置对,触发某硬件中断然后 reset 了?
感觉是数据段的属性没设置对,触发某硬件中断然后 reset 了?
大佬,这里的数据段是指elf数据段还是GDT表数据段呢。。
这个是gdt表部分
我试着向0x20000处写入但是还是reset了
gdt表也正常
这里的数据段是指elf数据段还是GDT表数据段呢。。
GDT,你 ds 的值对得上么?
大佬,又遇到了新问题。。。 每次运行到下面这条指令--向0x000a0000写入的时候就会被重置(应该是被重置,跳到0xfffffff0,然后寄存器全清空)
看异常向量是多少,查手册看对应的错误是什么问题
这里的数据段是指elf数据段还是GDT表数据段呢。。
GDT,你 ds 的值对得上么?
ds是0x0000,能对上
大佬,又遇到了新问题。。。 每次运行到下面这条指令--向0x000a0000写入的时候就会被重置(应该是被重置,跳到0xfffffff0,然后寄存器全清空)
看异常向量是多少,查手册看对应的错误是什么问题
大佬劳烦问一下。。异常向量要怎么看。。。
ds是0x0000,能对上
0x0000 咋就对得上呢…… 保护模式下段寄存器不是段选择子的值么。
大佬我懂了,我ds应该设置gdt表中的段号,谢谢两位大佬的帮助 @SilverRainZ @VOID001
ds是0x0000,能对上
0x0000 咋就对得上呢…… 保护模式下段寄存器不是段选择子的值么。
我刚才发现了。。。。好丢脸啊、、、
大佬们。。。我又带着问题来请教了。。。这次问题比较好玩。。。 首先。。先看下面的图,我现在在操作系统引入文字绘制部分,我检验过font和i的值都是正确的
我箭头指向的位置之前是ascii_font[font][i],作用是从ascii二进制字库取对应的二进制值,但是如果我font和i的位置只要有一个位置是填的变量名,就无法进行渲染
让我将两个空填了两个常数。。。奇迹发生了。。。。渲染正常了。。。??????
然后我不信邪。。又把其中一个空填为变量。。。???又不能正常显示了。。
现在我觉得可能出现的情况都测试了,有几个猜想 1.是不是gcc有什么优化,讲一部分代码删去了 2.是不是链接的时候出了什么问题
想问一下大佬们发生过这种情况么
1.我现在理解的内核加载是:bootloader把软盘里的内容加载到内存,然后跳转到ELF文件找到入口执行内核程序,但是不太清楚这里elf是自行进入脚本ld文件设置的入口还是需要我们给定入口位置
2.我现在是把bootloader分为boot和loader来写,boot主要把程序加载到内存,loader主要改显示模式,加载GDT表,开A20和切换保护模式等,我现在内存中的结构大概如此: ;0x7c00 boot ;0x7e00 loader ;0x8200 bios信息 ;0x10000 内核
而GDT表现在是这样: ;gdt表 gdt: ;基地址 大小限制 这段的属性 gdt_null: Descriptor 0, 0, 0 ;空记录(基) gdt_code: Descriptor 0, 0xfffff-1, DA_C+DA_32+DA_LIMIT_4K ;代码段 gdt_data: Descriptor 0, 0xfffff-1, DA_DRW+DA_32+DA_LIMIT_4K ;数据段
gdtLenght equ $-gdt gdtLimit dw gdtLenght-1 gdtAddress dd gdt
;gdt选择子 selgdt_code equ gdt_code-gdt_null selgdt_data equ gdt_data-gdt_null
然后我现在lgdt [gdtLimit] 后打算跳到内核执行 jmp dword selgdt_code:0x10000
但是貌似bochs又跳回了boot未启动的部分,也就是说重新加载了一遍程序,我想问一下大佬这里跳转部分我应该怎么做。bochs的GDT表好像和设置的不太一样,我现在总结出可能出现的问题:
1.我GDT设置有问题,基地址跳错位置了 2.我GDT表没设置上去 3.我跳转有问题 4.我从软盘加载到内存时候有问题
而其中内核加载到的内存地址我是boot中通过先将loader加载到0x7e00后将剩余部分全部加载到0x1000:0开始的位置
我下面会把boot和loader放上来,希望大佬给指点指点