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_magicmbt_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启动选项了……
该项目代码地址是:
阅读(139513) | 评论(10) | 转发(15) |