0%

OS写作0

qemu 的小知识,x86_64 是 64位,i386 是 32位,但是 x86 是 32,64是64……所以为什么要叫86!哦,i386,我知道了。所以x86_64 指的是基于86的64位!

i386是80386,第一个支持32位的处理器,8086酱才是16位哒

其实 x86是这一系列的统称,8086这孩子是第一个,现在都说32了……因为16的花季太短了吧?

开始征程吧哈哈哈哈!

先是看着 birmingham 的教程做,也有这个

GitHub - cfenollosa/os-tutorial: How to create an OS from scratch

不过先看第一个好了

使用 NASM 的汇编语言!因为一半都是要写汇编所以加油吧

https://zhuanlan.zhihu.com/p/109264787

汇编语法

mov ah , 0 x0e ; int 10/ ah = 0 eh -> scrolling teletype BIOS routine

这个解释可以看 INT 10H - 维基百科,自由的百科全书

显示模式,可以直接输出

总结一下看不懂的,times 是连续赋值多少个,db 是 define byte. $是当前地址,$$ 是开头地址,所以差值是当前 offset,510 - offset 等于距离 510 还有多少位,都填满 0. boot sector 有 514,最后 4byte 是 magic number

nasm bootsect.asm -f bin -o bootsect.bin

hourse’s 下次一定记得经常存盘

初步程序

里面有 reg,一个16bits 一共2word,可以单独定义

print hello 程序

该如何读取?

第三第四个可以读出来,[a],a 存储的东西作为地址去寻找,因为这个是开始于 0x7c00,所以在 the_secret label 里定义的X需要加上0x7c00偏移

org 指令 ORG是Origin的缩写:起始地址,源。在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址。如果不用ORG规定则汇编得到的目标程序将从0000H开始。例如:
             ORG 2000H

这次的 org 就是 0x7c00

def string

my_string :
db ’ Booting OS ’

因为括号起来,所以里面的每个字母转换成 ASCII,虽然是 byte 但是往后 def

加上 null terminate

my_string :
db ’ Booting OS ’ ,0

使用 stack

因为 CPU 自带的 reg 经常不够用,所以我们可以使用内存了 bp(base) 和 sp (top)两个特殊 reg 用来记录地址,底部和顶端。因为这个是 16 bits,所以一次 pop 16 bits

base 会远离敏感程序区域,因此不用担心 stack 太大 overwrite 重要的代码。是向下发展的!sp 比 bp 地址更低(所以不会往上才碰不到),每次 push 会降低 sp 地址

push 和 pop 都是 16 bits,当 push,A的时候前8 bits 填充为0

跳转语句?

跟 cmp 一起使用

然后写一个jmp 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mov bx, 30

cmp bx, 4
jle less4 ; jump if less equal 4
cmp bx, 40
jl less40 ; jump if less than 40
jmp nothing ; jump nothing happen

less4:
mov al, 'A'
less40:
mov al, 'B'
nothing:
mov al, 'C'
; def label

mov ah, 0x0e
int 0x10

jmp $

;padding with magic num
times 510-($-$$) db 0
dw 0xaa55

之前写错了,未定义的时候是0,打印出来是 U?这样的话它会继续执行下面的label,所以再加入一个 jmp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
mov bx, 0

cmp bx, 4
jle less4 ; jump if less equal 4
cmp bx, 40
jl less40 ; jump if less than 40
jmp nothing ; jump nothing happen

less4:
mov al, 'A'
jmp end
less40:
mov al, 'B'
jmp end
nothing:
mov al, 'C'
; def label

end:

mov ah, 0x0e
int 0x10

jmp $

;padding with magic num
times 510-($-$$) db 0
dw 0xaa55
定义 function
1
2
3
4
5
6
7
8
mov al, 'H' ; store H in al so func can show it
call func ; call 相当于jump,但是把下一行地址压入stack
...
func:
mov ah, 0x0e
int 0x10
ret ;return pop stack 地址,jump回去
;感觉其实没有那么简单不过先这样好了

int 定义是常量,常量(constant)是程序中使用的一个确定数值,在汇编阶段就可以确定,直接编码于指令代码中,不是保存在存储器中可变的变量,因为是编码在指令中的量,和指令一起存储了,所以不用单独开辟主存空间,所以也就没法动态改变它了,这也正是高级语言常量无法修改的原因。

但是这样会把 reg 数据一起洗掉!怎么办呢

加入 pusha 和 popa,将数据扔到stack再return。虽然我很好奇那么它怎么知道哪个是哪个,但是避免太超纲先不看

修改版本

1
2
3
4
5
6
7
8
9
10
mov al, 'H' ; store H in al so func can show it
call func ; call 相当于jump,但是把下一行地址压入stack
...
func:
pusha ;把 reg 内容压入 stack
mov ah, 0x0e
int 0x10
popa ; 把它们弄回来
ret ;return pop stack 地址,jump回去
;感觉其实没有那么简单不过先这样好了

不过这样改动之后是不是 al 也没有了?试试看

不会的说!然后就 print 了两个h!

卧槽,什么情况

哦,是因为正常向下执行的时候再次执行了 func 所以 print 了两次,普通执行的话并不会改变什么,就算提前 pusha 也能打印出来……嗯……所以

这就是为啥 func 内部改动不会改变数值!因为可能 call func 的时候要改变 reg 去 perform job,所以pusha不会把东西倒出去,数值还是一样的,但是 popa 就会一切还原

所以你才用指针直接修改!!!!!!!!

我!的!天!

我终于看到了这个未解之谜的真相了!!!!!!!!!!!!

include external file

想要 reuse your file?可以直接引用,那么请结合以上自己写作一个打印两个 string 的程序吧~

label 到底是什么?

int 0x10 不是! 那是16进制!第17终止符,ah 0x0e 是这个模式下面支持的func,详情查看 wiki

INT 10H - Wikipedia

int 10 是啥?int 13?

CR and LF are control characters, respectively coded 0x0D (13 decimal) and 0x0A (10 decimal)

这俩都是早期 tty 留下的产物,CR 是移动到行首但不换行,LF 是往下竖直移动一格,但不水平移动

CR = carriage return,ascii 码 13

LF = line feed,ascii 码 10

现在的话win使用两个移动,就是 \r\n,0x0D 0x0A,unix 只用 LF 了,所以要换行直接在最后加上10,13

附带的 print string程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; using for loop to print char, until it reach 0
print_string:
pusha
mov ah, 0x0e ;show mode
mov al, [bx] ; print first char
start:
int 0x10 ; print current char
add bx, 1 ; add one to bx move to next char
mov al, [bx] ;load char
cmp al, 0
je end
jmp start

end:
popa
ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[org 0x7c00] ; start at this point

mov bx, HELLO_MSG ; move hello to b reg
call print_string
mov bx, BYE_MSG
call print_string

jmp $ ;hang the code

%include "print_string.asm" ; simply replaced by the file content

;Data
;10 13 \r\n change line
HELLO_MSG:
db 'Hello, world',10,13, 0
BYE_MSG:
db 'Goodbye',10,13, 0

;padding with 0
times 510-($-$$) db 0 ; $ for current, $$ for starting point
dw 0xaa55