OS1-OS版hello world
OS1-OS版hello world
其实就是让BIOS拉起一个裸机汇编(实模式),对应是保护模式。
ds段寄存器。
写一个汇编程序helloOs.S
# 以下是AT&T的语法, $开头表示立即数(如自定义符号代表的偏移量)%开头表示寄存器 BOOTSEG = 0x7c0 # 0x7c00 .code16 #告诉汇编编译器as把当前汇编翻译成16位的指令,若不指定会生成x86的32位/64位的指令,因为编译器是64位的 .text .global _start _start: jmpl $BOOTSEG, $start2 #长跳转到start2,会使得段寄存器cs=0x7c0,pc寄存器=start2地址,跳转的目的地是0x7c0+start2地址(偏移量) # 最终跳转到标签start2偏移量的汇编代码中 start2: movw $BOOTSEG, %ax # 让ax寄存器=0x7c0 movw %ax, %ds # 进一步初始化其他寄存器 movw %ax, %es movw %ax, %fs movw %ax, %gs movw $msg, %ax #%msg表示msg符号的地址,即把地址放到%ax寄存器里边 movw %ax, %bp movw $0x01301, %ax #用来指定打印 movw $0x0c, %bx # 设置屏幕显示helloworld的颜色,bh=0(背景色),bl=0c(前景色(即字体)红色) # movw $0xcf, %bx # 也可换成这行,c会设置成闪烁红色背景色,f会让字体为白色 movw $12, %cx # cx=字符串长度为立即数12 movb $0, %dl int $0x010 # int中断指令,10h号中断是bios中断,作用就是发起10h号中断,让里边固有函数去cx里读入字符串,让显卡刷新内容 loop: jmp loop # 本主程序自己进入死循环 msg: .ascii "hello world" .org 510 # .org作用是让接下来的510个字节填入0,也就是让汇编编出来的二进制在汇编代码结束后的所有字节填0,目的是为了让编出来的目标文件刚好512字节,刚好一个扇区,因为bios一次会读入一个扇区共512字节到0x7c00的位置。 boot_flag: #在编译出来的二进制文件的最后一个字节设为0xaa55,是bios规定的结束符号。 .word 0xaa55
编译的过程
as helloOs.S -o helloOs.o #as是gcc的汇编编译器,这样会产生一个目标文件helloOs.o ld -m elf_x86_64 -Ttext 0x0 -s --oformat binary -o helloOs.img helloOs.o #ld是链接器 $ ls -l -rwxrwxr-x 1 512 5月 13 18:19 helloOs.img
默认情况下,使用as helloOs.S -o helloOs.o编译出来的 helloOs.o是64位的ELF文件,使用readelf能输出如下内容,所有的段都有。
$ readelf -a helloOs.o ELF 头: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 类别: ELF64 数据: 2 补码,小端序 (little endian) 版本: 1 (current) OS/ABI: UNIX - System V ABI 版本: 0 类型: REL (可重定位文件) 系统架构: Advanced Micro Devices X86-64 版本: 0x1 入口点地址: 0x0 程序头起点: 0 (bytes into file) Start of section headers: 968 (bytes into file) 标志: 0x0 本头的大小: 64 (字节) 程序头大小: 0 (字节) Number of program headers: 0 节头大小: 64 (字节) 节头数量: 8 字符串表索引节头: 7 节头: [号] 名称 类型 地址 偏移量 大小 全体大小 旗标 链接 信息 对齐 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000200 0000000000000000 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 00000360 #重定位段 0000000000000030 0000000000000018 I 5 1 8 [ 3] .data PROGBITS 0000000000000000 00000240 0000000000000000 0000000000000000 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 00000240 0000000000000000 0000000000000000 WA 0 0 1 [ 5] .symtab SYMTAB 0000000000000000 00000240 00000000000000f0 0000000000000018 6 9 8 [ 6] .strtab STRTAB 0000000000000000 00000330 000000000000002a 0000000000000000 0 0 1 [ 7] .shstrtab STRTAB 0000000000000000 00000390 0000000000000031 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific) There are no section groups in this file. 本文件中没有程序头。 There is no dynamic section in this file. 重定位节 '.rela.text' at offset 0x360 contains 2 entries: 偏移量 信息 类型 符号值 符号名称 + 加数 000000000002 00010000000a R_X86_64_32 0000000000000000 .text + 8 000000000014 00010000000c R_X86_64_16 0000000000000000 .text + 27 The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported. Symbol table '.symtab' contains 10 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 SECTION LOCAL DEFAULT 1 2: 0000000000000000 0 SECTION LOCAL DEFAULT 3 3: 0000000000000000 0 SECTION LOCAL DEFAULT 4 4: 00000000000007c0 0 NOTYPE LOCAL DEFAULT ABS BOOTSEG 5: 0000000000000008 0 NOTYPE LOCAL DEFAULT 1 start2 6: 0000000000000027 0 NOTYPE LOCAL DEFAULT 1 msg 7: 0000000000000025 0 NOTYPE LOCAL DEFAULT 1 loop 8: 00000000000001fe 0 NOTYPE LOCAL DEFAULT 1 boot_flag 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 1 _start No version information found in this file.
如果用objdump -d解析,会输出如下内容
$ objdump -d helloOs.o helloOs.o: 文件格式 elf64-x86-64 Disassembly of section .text: 0000000000000000 <_start>: 0: 66 ea data16 (bad) 2: 00 00 add %al,(%rax) 4: 00 00 add %al,(%rax) 6: c0 .byte 0xc0 7: 07 (bad) 0000000000000008 <start2>: #能看出与汇编对应 8: b8 c0 07 8e d8 mov $0xd88e07c0,%eax d: 8e c0 mov %eax,%es f: 8e e0 mov %eax,%fs 11: 8e e8 mov %eax,%gs 13: b8 00 00 89 c5 mov $0xc5890000,%eax 18: b8 01 13 bb 0c mov $0xcbb1301,%eax 1d: 00 b9 0c 00 b2 00 add %bh,0xb2000c(%rcx) 23: cd 10 int $0x10 0000000000000025 <loop>: 25: eb fe jmp 25 <loop> 0000000000000027 <msg>: 27: 68 65 6c 6c 6f pushq $0x6f6c6c65 2c: 20 77 6f and %dh,0x6f(%rdi) 2f: 72 6c jb 9d <msg+0x76> 31: 64 00 00 add %al,%fs:(%rax) ... 00000000000001fe <boot_flag>: 1fe: 55 push %rbp 1ff: aa stos %al,%es:(%rdi)
如果把这个ELF格式的二进制直接让BIOS读入,显然是无法启动的,毕竟当前电脑上什么都没有,不会识别ELF格式的文件,只有一个固定的BIOS从固定的地址0x7c00处读入内容,所以OS需要的是一个真正的裸机代码开始,通过特定的汇编指令让CPU一步步进入到设定好的流程,直到运行起来复杂的操作系统。
所以我们本次需要编译出来的二进制不能是ELF格式的,不能有重定位段、数据段这些内容,这就需要用链接器来完成。
解释ld命令
ld -m elf_x86_64 -Ttext 0x0 -s --oformat binary -o helloOs.img helloOs.o # -m elf_x86_64告诉链接器,输入是x86的64位的elf格式的文件helloOs.o # -Ttext 0x0:-T的是表示代码段,连起来意思是代码段名字为text在二进制文件0x0的位置,即第一个字节开始就是第一条指令 # -s表示把符号去了 # --oformat binary表示让链接器不要翻译bss、重定位等段,仅需翻译代码段即可
最后得到helloOs.img,就是大家所说的镜像文件,再使用objdump发现已不认识该文件了,因为不是elf格式的了
$ objdump -d helloOs.img objdump: helloOs.img: 不可识别的文件格式
我们可用xxd命令看下该文件的二进制情况,可见熟悉的hello world字符串。
$ xxd -a -u -s 0 -l 512 helloOs.img 00000000: 66EA 0800 0000 C007 B8C0 078E D88E C08E f............... # 66EA是跳转指令 00000010: E08E E8B8 2700 89C5 B801 13BB 0C00 B90C ....'........... 00000020: 00B2 00CD 10EB FE68 656C 6C6F 2077 6F72 .......hello wor 00000030: 6C64 0000 0000 0000 0000 0000 0000 0000 ld.............. 00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 000001f0: 0000 0000 0000 0000 0000 0000 0000 55AA ..............U. # 结束符55AA,在510和512的位置,共512字节
dos系统就是在16位实模式下的一个操作系统。
放入仿真器运行:
硬件模拟器(用于模拟硬件平台,启动OS)http://www.oldlinux.org/Linux.old/bochs/Bochs/bochs-2.6.1/Bochs-2.6.1.exe。安装好后,配置boch的配置文件bochsrc.bxrc,helloOS放在跟它同目录下即可,配置floppya:1_44=helloOs.img, status=inserted 。
#cat bochsrc.bxrc以下内容: # configuration file generated by Bochs plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, gameport=1 config_interface: win32config display_library: win32 memory: host=32, guest=32 #指定内存32M romimage: file="D:\Program Files\Bochs-2.6.1/BIOS-bochs-latest" #ROM的模拟文件 vgaromimage: file="D:\Program Files\Bochs-2.6.1/VGABIOS-lgpl-latest" #显卡的模拟文件 boot: floppy #指定从软盘启动,写成boot:a也可,表示从软盘a启动,因为a和b盘都是软盘。c是硬盘,但从硬盘启动更复杂,需要很多硬盘知识,后期再介绍。 floppy_bootsig_check: disabled=0 # no floppya floppya:1_44=helloOs.img, status=inserted #定义软盘和装载的内容,就是写的helloworld版os:helloOs.img # no floppyb ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 ata0-master: type=none ata0-slave: type=none ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 ata1-master: type=none ata1-slave: type=none ata2: enabled=0 ata3: enabled=0 pci: enabled=1, chipset=i440fx vga: extension=vbe, update_freq=5 cpu: count=1, ips=4000000, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 cpuid: vendor_string="GenuineIntel", brand_string=" Intel(R) Pentium(R) 4 CPU " cpuid: stepping=3, model=3, family=6, mmx=1, apic=xapic, sse=sse2, sse4a=0, misaligned_sse=0 cpuid: sep=1, movbe=0, adx=0, aes=0, xsave=0, xsaveopt=0, x86_64=1, 1g_pages=0, pcid=0 cpuid: fsgsbase=0, smep=0, smap=0, mwait=1 print_timestamps: enabled=0 port_e9_hack: enabled=0 private_colormap: enabled=0 clock: sync=none, time0=local, rtc_sync=0 # no cmosimage # no loader log: - logprefix: %t%e%d debug: action=ignore info: action=report error: action=report panic: action=ask keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none mouse: type=ps2, enabled=0, toggle=ctrl+mbutton parport1: enabled=1, file=none parport2: enabled=0 com1: enabled=1, mode=null, dev=none com2: enabled=0 com3: enabled=0 com4: enabled=0
启动boch后,在弹出窗里选Load,选中bochsrc.bxrc
可见启动成功。在安装目录下还有个bochsdbg.exe,可用bochsdbg.exe -q -f bochsrc.bxrc就可启动调试模式,单步调试刚刚的汇编程序了
也可用图形界面启动dbg模式,一样是load配置文件,然后进入如下界面
Next at t=0 (0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b ; ea5be000f0 <bochs:1> n Next at t=1 (0) [0x0000000fe05b] f000:e05b (unk. ctxt): xor ax, ax ; 31c0 <bochs:2> n 00000000002i[WGUI ] dimension update x=720 y=400 fontheight=16 fontwidth=9 bpp=8 Next at t=2 (0) [0x0000000fe05d] f000:e05d (unk. ctxt): out 0x0d, al ; e60d <bochs:3> b 0x7c00 <bochs:4> c 00000000025i[MEM0 ] allocate_block: block=0x0 used 0x1 of 0x20 00000004661i[BIOS ] $Revision: 11545 $ $Date: 2012-11-11 09:11:17 +0100 (So, 11. Nov 2012) $ 00000318067i[KBD ] reset-disable command received 00000320801i[BIOS ] Starting rombios32 00000321235i[BIOS ] Shutdown flag 0 00000321830i[BIOS ] ram_size=0x02000000 00000322251i[BIOS ] ram_end=32MB 00000362768i[BIOS ] Found 1 cpu(s) 00000376955i[BIOS ] bios_table_addr: 0x000fa448 end=0x000fcc00 00000704752i[PCI ] i440FX PMC write to PAM register 59 (TLB Flush) 00001032679i[P2I ] PCI IRQ routing: PIRQA# set to 0x0b 00001032698i[P2I ] PCI IRQ routing: PIRQB# set to 0x09 00001032717i[P2I ] PCI IRQ routing: PIRQC# set to 0x0b 00001032736i[P2I ] PCI IRQ routing: PIRQD# set to 0x09 00001032746i[P2I ] write: ELCR2 = 0x0a 00001033512i[BIOS ] PIIX3/PIIX4 init: elcr=00 0a 00001041228i[BIOS ] PCI: bus=0 devfn=0x00: vendor_id=0x8086 device_id=0x1237 class=0x0600 00001043498i[BIOS ] PCI: bus=0 devfn=0x08: vendor_id=0x8086 device_id=0x7000 class=0x0601 00001045607i[BIOS ] PCI: bus=0 devfn=0x09: vendor_id=0x8086 device_id=0x7010 class=0x0101 00001045836i[PIDE ] new BM-DMA address: 0xc000 00001046453i[BIOS ] region 4: 0x0000c000 00001048455i[BIOS ] PCI: bus=0 devfn=0x0a: vendor_id=0x8086 device_id=0x7020 class=0x0c03 00001048659i[UHCI ] new base address: 0xc020 00001049276i[BIOS ] region 4: 0x0000c020 00001049402i[UHCI ] new irq line = 9 00001051287i[BIOS ] PCI: bus=0 devfn=0x0b: vendor_id=0x8086 device_id=0x7113 class=0x0680 00001051520i[ACPI ] new irq line = 11 00001051532i[ACPI ] new irq line = 9 00001051561i[ACPI ] new PM base address: 0xb000 00001051575i[ACPI ] new SM base address: 0xb100 00001051603i[PCI ] setting SMRAM control register to 0x4a 00001215694i[CPU0 ] Enter to System Management Mode 00001215704i[CPU0 ] RSM: Resuming from System Management Mode 00001379722i[PCI ] setting SMRAM control register to 0x0a 00001394656i[BIOS ] MP table addr=0x000fa520 MPC table addr=0x000fa450 size=0xc8 00001396412i[BIOS ] SMBIOS table addr=0x000fa530 00001396470i[MEM0 ] allocate_block: block=0x1f used 0x2 of 0x20 00001398607i[BIOS ] ACPI tables: RSDP addr=0x000fa650 ACPI DATA addr=0x01ff0000 size=0xf72 00001401804i[BIOS ] Firmware waking vector 0x1ff00cc 00001403602i[PCI ] i440FX PMC write to PAM register 59 (TLB Flush) 00001404330i[BIOS ] bios_table_cur_addr: 0x000fa674 00001531947i[VBIOS] VGABios $Id: vgabios.c,v 1.75 2011/10/15 14:07:21 vruppert Exp $ 00001532018i[BXVGA] VBE known Display Interface b0c0 00001532050i[BXVGA] VBE known Display Interface b0c5 00001534975i[VBIOS] VBE Bios $Id: vbe.c,v 1.64 2011/07/19 18:25:05 vruppert Exp $ 00014040328i[BIOS ] Booting from 0000:7c00 (0) Breakpoint 1, 0x0000000000007c00 in ?? () Next at t=14040383 (0) [0x000000007c00] 0000:7c00 (unk. ctxt): jmp far 07c0:00000008 ; 66ea08000000c007 <bochs:5> n Next at t=14040384 (0) [0x000000007c08] 07c0:0008 (unk. ctxt): mov ax, 0x07c0 ; b8c007 <bochs:6> sreg es:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed cs:0x07c0, dh=0x00009300, dl=0x7c00ffff, valid=1 Data segment, base=0x00007c00, limit=0x0000ffff, Read/Write, Accessed ss:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7 Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed ds:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1 tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1 gdtr:base=0x00000000000fa1b7, limit=0x30 idtr:base=0x0000000000000000, limit=0x3ff <bochs:7> n Next at t=14040385 (0) [0x000000007c0b] 07c0:000b (unk. ctxt): mov ds, ax ; 8ed8 <bochs:8> n Next at t=14040386 (0) [0x000000007c0d] 07c0:000d (unk. ctxt): mov es, ax ; 8ec0 <bochs:9> Next at t=14040387 (0) [0x000000007c0f] 07c0:000f (unk. ctxt): mov fs, ax ; 8ee0 <bochs:10> Next at t=14040388 (0) [0x000000007c11] 07c0:0011 (unk. ctxt): mov gs, ax ; 8ee8 <bochs:11> Next at t=14040389 (0) [0x000000007c13] 07c0:0013 (unk. ctxt): mov ax, 0x0027 ; b82700 <bochs:12> x/20xh 0x7c27 # 这个就是hello world [bochs]: 0x0000000000007c27 <bogus+ 0>: 0x6568 0x6c6c 0x206f 0x6f77 0x6c72 0x0064 0x0000 0x0000
第一个程序用的是AT&T的语法,与intel语法相比,简单区别
AT&T语法:立即数用%号开头,寄存器用%开头,movw %ax, %ds表示%ax(源操作数)的内容复制到%ds(目的操作数)。
intel语法:mov 目的操作数,源操作数
扩展:
汇编的后缀名一般是.s和.S
小写.s:表示改汇编程序只含汇编代码,编译器不会进行预编译
大写.S:表示含预编译代码,希望编译器进行预编译(宏替换)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!