裸机版的hello world
	
 
hello world程序绝对经典的让人落泪,这是很多人的第一个程序。这个程序在Brian Kernighan和Dennis M. Ritchie合著的《The C Programme Language》中使用而广泛流行。该程序也体现了两位作者心向世界的博大情怀。
本人编程也是从hello world程序开始的,但是我很多人写的hello world程序都需要库和操作系统的支持才能运行。今天我想来用C语言重新实现一个裸机版hello world程序,即不需要操作系统和库的支持,顺便纪念一下hello world程序和C语言。
首先看看实现裸机版的hello world程序所需要的工具:
- 
		LINUX操作系统 
- 
		编译器:GCC、LD、nasm 
- 
		文件编辑器 
- 
		Make 
- 
		GRUB引导器(安装LINUX时已经自带了) 
下面我们从上向下完成hello world程序,首先来写好main函数,如下:
点击(此处)折叠或打开
- 
															void main()
 
- 
															{
- 
															    printf("hello world!");
- 
															    return;
- }
												
是不是很熟悉,这样的程序,我想很多人闭着眼一通盲码,都可以正确无误
好了,上面的代码依然是调用了printf函数输出“hello world!”字符串的,由于这裸机版的程序,所以不能调用库中的printf函数,而是要自己亲自实现该函数。下面就去实现一个最简单的printf函数。如下:
点击(此处)折叠或打开
- 
																void printf(char* fmt,...)
 
- 
																{
- 
																    _strwrite(fmt);
- 
																    return;
- }
													
确实够简单了,没有像通常的printf函数处理多个参数,也没有对参数进行格式化处理,而是调用了_strwrite函数,下面接着实现_strwrite函数,如下:
点击(此处)折叠或打开
- 
																	void _strwrite(char* string)
 
- 
																	{
- 
																	    char* p_strdst=(char*)(0xb8000);
- 
																	    while(*string)
- 
																	    {
- 
																	        *p_strdst=*string++;
- 
																	        p_strdst+=2;
- 
																	    }
- 
																	    return;
- }
														
_strwrite函数才是输出字符串的核心函数,它把字符串的每个字符,依次写入以0xb8000为开始地址的内存空间,这个内存空间默认映射是显卡的显存,并且我们知道计算机启动时显卡默认工作在字符模式下。对应于屏幕是每行80个字符,一共有25行。
														         可是有了这些代码就可以了吗,当然不行,因为是裸机,所以在调用C函数之前,还要初始化栈和CPU的一些寄存器,更为关键的是我们的程序要被GRUB引导加载,而这些动作用C语言又无法实现,这时我们的大汇编语言就该上场了,发挥它神奇的作用了,下面来用汇编语言写一段代码,如下:
点击(此处)折叠或打开
- 
																		MBT_HDR_FLAGS    EQU 0x00010003
 
- 
																		MBT_HDR_MAGIC    EQU 0x1BADB002
- 
																		MBT_HDR2_MAGIC    EQU 0xe85250d6
- 
																		global _start
- 
																		extern main
- 
																		[section .start.text]
- 
																		[bits 32]
- 
																		_start:
- 
																		    jmp _entry
- 
																		ALIGN 8
- 
																		mbt_hdr:
- 
																		    dd MBT_HDR_MAGIC
- 
																		    dd MBT_HDR_FLAGS
- 
																		    dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
- 
																		    dd mbt_hdr
- 
																		    dd _start
- 
																		    dd 0
- 
																		    dd 0
- 
																		    dd _entry
- 
																		
- 
																		;以上是GRUB所需要的头
- 
																		ALIGN 8
- 
																		mbt2_hdr:
- 
																		    DD    MBT_HDR2_MAGIC
- 
																		    DD    0
- 
																		    DD    mbt2_hdr_end - mbt2_hdr
- 
																		    DD    -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
- 
																		    DW    2, 0
- 
																		    DD    24
- 
																		    DD    mbt2_hdr
- 
																		    DD    _start
- 
																		    DD    0
- 
																		    DD    0
- 
																		    DW    3, 0
- 
																		    DD    12
- 
																		    DD    _entry
- 
																		    DD      0
- 
																		    DW    0, 0
- 
																		    DD    8
- 
																		mbt2_hdr_end:
- 
																		;以上是GRUB2所需要的头
- 
																		;包含两个头是为了同时兼容GRUB、GRUB2
- 
																		
- 
																		ALIGN 8
- 
																		
- 
																		_entry:
- 
																		    ;关中断
- 
																		    cli
- 
																		    ;关不可屏蔽中断
- 
																		    in al, 0x70
- 
																		    or al, 0x80
- 
																		    out 0x70,al
- 
																		    ;重新加载GDT
- 
																		    lgdt [GDT_PTR]
- 
																		    jmp dword 0x8 :_32bits_mode
- 
																		
- 
																		_32bits_mode:
- 
																		    ;下面初始化C语言可能会用到的寄存器
- 
																		    mov ax, 0x10
- 
																		    mov ds, ax
- 
																		    mov ss, ax
- 
																		    mov es, ax
- 
																		    mov fs, ax
- 
																		    mov gs, ax
- 
																		    xor eax,eax
- 
																		    xor ebx,ebx
- 
																		    xor ecx,ecx
- 
																		    xor edx,edx
- 
																		    xor edi,edi
- 
																		    xor esi,esi
- 
																		    xor ebp,ebp
- 
																		    xor esp,esp
- 
																		    ;初始化栈,C语言需要栈才能工作
- 
																		    mov esp,0x9000
- 
																		    ;调用C语言函数main
- 
																		    call main
- 
																		    ;让CPU停止执行指令
- 
																		halt_step:
- 
																		    halt
- 
																		    jmp halt_step
- 
																		
- 
																		
- 
																		GDT_START:
- 
																		knull_dsc: dq 0
- 
																		kcode_dsc: dq 0x00cf9e000000ffff
- 
																		kdata_dsc: dq 0x00cf92000000ffff
- 
																		k16cd_dsc: dq 0x00009e000000ffff
- 
																		k16da_dsc: dq 0x000092000000ffff
- 
																		GDT_END:
- 
																		
- 
																		GDT_PTR:
- 
																		GDTLEN    dw GDT_END-GDT_START-1
- GDTBASE dd GDT_START
															
															
这段代码不必多说,上面的注释已经写的很好了,汇编程序代码也写好了,最后的工作就是编译链接程序了,编译还好说,但是链接就不能用通常链接应用程序的方法了,因为这时裸机程序,所以我们得写个链接脚本来控制链接过程,如下:
点击(此处)折叠或打开
- ENTRY(_start)
- OUTPUT_ARCH(i386)
- SECTIONS
- {
- . = 0x200000;
- __begin_start_text = .;
- .start.text : ALIGN(4) { *(.start.text) }
- __end_start_text = .;
- __begin_text = .;
- .text : ALIGN(4) { *(.text) }
- __end_text = .;
- __begin_data = .;
- .data : ALIGN(4) { *(.data) }
- __end_data = .;
- __begin_rodata = .;
- .rodata : ALIGN(4) { *(.rodata) *(.rodata.*) }
- __end_rodata = .;
- __begin_kstrtab = .;
- .kstrtab : ALIGN(4) { *(.kstrtab) }
- __end_kstrtab = .;
- __begin_bss = .;
- .bss : ALIGN(4) { *(.bss) }
- __end_bss = .;
- }
																
上面的链接脚本最关键的是告诉LD链接器,我们的程序从0x200000的内存地址开始运行。最后还要写个makefile控制编译、链接过程。如下:
点击(此处)折叠或打开
- MAKEFLAGS = -sR
- MKDIR = mkdir
- RMDIR = rmdir
- CP = cp
- CD = cd
- DD = dd
- RM = rm
- ASM = nasm
- CC = gcc
- LD = ld
- ASMBFLAGS = -f elf
- CFLAGS = -c -Os -std=c99 -m32 -Wall -Wshadow -W -Wconversion -Wno-sign-conversion -fno-stack-protector -fomit-frame-pointer -fno-builtin -fno-common -ffreestanding -Wno-unused-parameter -Wunused-variable
- LDFLAGS = -s -static -T hello.lds -n --oformat binary
- PMHELLO_OBJS :=
- PMHELLO_OBJS += entry.o helkrlmain.o vgastr.o
- PMHELLO_BIN = pmhello.bin
- .PHONY : build clean all link
- all: clean build link
- clean:
- $(RM) -f *.o *.bin
- build: $(PMHELLO_OBJS)
- link: $(PMHELLO_BIN)
- $(PMHELLO_BIN): $(PMHELLO_OBJS)
- $(LD) $(LDFLAGS) -o $@ $(PMHELLO_OBJS)
- %.o : %.asm
- $(ASM) $(ASMBFLAGS) -o $@ $<
- %.o : %.c
- $(CC) $(CFLAGS) -o $@ $<
																	
安装测试,在linux系统下则非常方便,因为linux系统已经安装好了GRUB2,默认情况下,只要把pmhello.bin文件复制到linux系统的/boot/目录下,同时修改/boot/grub/目录下的grub.cfg文件。如下图所示:
																	  
 
																
重启计算机就可以看到PMHELLO启动选项了……
该项目代码地址是:https://code.csdn.net/lmnos/pmhelloworld
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号