zoukankan      html  css  js  c++  java
  • 裸机版的hello world


             hello world程序绝对经典的让人落泪,这是很多人的第一个程序。这个程序在Brian KernighanDennis M. Ritchie合著的《The C Programme Language》中使用而广泛流行。该程序也体现了两位作者心向世界的博大情怀。

             本人编程也是从hello world程序开始的,但是我很多人写的hello world程序都需要库和操作系统的支持才能运行。今天我想来用C语言重新实现一个裸机版hello world程序,即不需要操作系统和库的支持,顺便纪念一下hello world程序和C语言。

             首先看看实现裸机版的hello world程序所需要的工具:

    1. LINUX操作系统

    2. 编译器:GCCLDnasm

    3. 文件编辑器

    4. Make

    5. GRUB引导器(安装LINUX时已经自带了)

    下面我们从上向下完成hello world程序,首先来写好main函数,如下:

    点击(此处)折叠或打开

    1. void main()
    2. {
    3.     printf("hello world!");
    4.     return;
    5. }


    是不是很熟悉,这样的程序,我想很多人闭着眼一通盲码,都可以正确无误

    好了,上面的代码依然是调用了printf函数输出“hello world!”字符串的,由于这裸机版的程序,所以不能调用库中的printf函数,而是要自己亲自实现该函数。下面就去实现一个最简单的printf函数。如下:

    点击(此处)折叠或打开

    1. void printf(char* fmt,...)
    2. {
    3.     _strwrite(fmt);
    4.     return;
    5. }


    确实够简单了,没有像通常的printf函数处理多个参数,也没有对参数进行格式化处理,而是调用了_strwrite函数,下面接着实现_strwrite函数,如下:

    点击(此处)折叠或打开

    1. void _strwrite(char* string)
    2. {
    3.     char* p_strdst=(char*)(0xb8000);
    4.     while(*string)
    5.     {
    6.         *p_strdst=*string++;
    7.         p_strdst+=2;
    8.     }
    9.     return;
    10. }


    _strwrite函数才是输出字符串的核心函数,它把字符串的每个字符,依次写入以0xb8000为开始地址的内存空间,这个内存空间默认映射是显卡的显存,并且我们知道计算机启动时显卡默认工作在字符模式下。对应于屏幕是每行80个字符,一共有25行。

             可是有了这些代码就可以了吗,当然不行,因为是裸机,所以在调用C函数之前,还要初始化栈和CPU的一些寄存器,更为关键的是我们的程序要被GRUB引导加载,而这些动作用C语言又无法实现,这时我们的大汇编语言就该上场了,发挥它神奇的作用了,下面来用汇编语言写一段代码,如下:

    点击(此处)折叠或打开

    1. MBT_HDR_FLAGS    EQU 0x00010003
    2. MBT_HDR_MAGIC    EQU 0x1BADB002
    3. MBT_HDR2_MAGIC    EQU 0xe85250d6
    4. global _start
    5. extern main
    6. [section .start.text]
    7. [bits 32]
    8. _start:
    9.     jmp _entry
    10. ALIGN 8
    11. mbt_hdr:
    12.     dd MBT_HDR_MAGIC
    13.     dd MBT_HDR_FLAGS
    14.     dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
    15.     dd mbt_hdr
    16.     dd _start
    17.     dd 0
    18.     dd 0
    19.     dd _entry

    20. ;以上是GRUB所需要的头
    21. ALIGN 8
    22. mbt2_hdr:
    23.     DD    MBT_HDR2_MAGIC
    24.     DD    0
    25.     DD    mbt2_hdr_end - mbt2_hdr
    26.     DD    -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
    27.     DW    2, 0
    28.     DD    24
    29.     DD    mbt2_hdr
    30.     DD    _start
    31.     DD    0
    32.     DD    0
    33.     DW    3, 0
    34.     DD    12
    35.     DD    _entry
    36.     DD 0
    37.     DW    0, 0
    38.     DD    8
    39. mbt2_hdr_end:
    40. ;以上是GRUB2所需要的头
    41. ;包含两个头是为了同时兼容GRUB、GRUB2

    42. ALIGN 8

    43. _entry:
    44.     ;关中断
    45.     cli
    46.     ;关不可屏蔽中断
    47.     in al, 0x70
    48.     or al, 0x80
    49.     out 0x70,al
    50.     ;重新加载GDT
    51.     lgdt [GDT_PTR]
    52.     jmp dword 0x8 :_32bits_mode

    53. _32bits_mode:
    54.     ;下面初始化C语言可能会用到的寄存器
    55.     mov ax, 0x10
    56.     mov ds, ax
    57.     mov ss, ax
    58.     mov es, ax
    59.     mov fs, ax
    60.     mov gs, ax
    61.     xor eax,eax
    62.     xor ebx,ebx
    63.     xor ecx,ecx
    64.     xor edx,edx
    65.     xor edi,edi
    66.     xor esi,esi
    67.     xor ebp,ebp
    68.     xor esp,esp
    69.     ;初始化栈,C语言需要栈才能工作
    70.     mov esp,0x9000
    71.     ;调用C语言函数main
    72.     call main
    73.     ;让CPU停止执行指令
    74. halt_step:
    75.     halt
    76.     jmp halt_step


    77. GDT_START:
    78. knull_dsc: dq 0
    79. kcode_dsc: dq 0x00cf9e000000ffff
    80. kdata_dsc: dq 0x00cf92000000ffff
    81. k16cd_dsc: dq 0x00009e000000ffff
    82. k16da_dsc: dq 0x000092000000ffff
    83. GDT_END:

    84. GDT_PTR:
    85. GDTLEN    dw GDT_END-GDT_START-1
    86. GDTBASE    dd GDT_START



    这段代码不必多说,上面的注释已经写的很好了,汇编程序代码也写好了,最后的工作就是编译链接程序了,编译还好说,但是链接就不能用通常链接应用程序的方法了,因为这时裸机程序,所以我们得写个链接脚本来控制链接过程,如下:

    点击(此处)折叠或打开

    1. ENTRY(_start)
    2. OUTPUT_ARCH(i386)
    3. SECTIONS
    4. {
    5. . = 0x200000;
    6. __begin_start_text = .;
    7. .start.text : ALIGN(4) { *(.start.text) }
    8. __end_start_text = .;
    9. __begin_text = .;
    10. .text : ALIGN(4) { *(.text) }
    11. __end_text = .;
    12. __begin_data = .;
    13. .data : ALIGN(4) { *(.data) }
    14. __end_data = .;
    15. __begin_rodata = .;
    16. .rodata : ALIGN(4) { *(.rodata) *(.rodata.*) }
    17. __end_rodata = .;
    18. __begin_kstrtab = .;
    19. .kstrtab : ALIGN(4) { *(.kstrtab) }
    20. __end_kstrtab = .;
    21. __begin_bss = .;
    22. .bss : ALIGN(4) { *(.bss) }
    23. __end_bss = .;
    24. }


    上面的链接脚本最关键的是告诉LD链接器,我们的程序从0x200000的内存地址开始运行。最后还要写个makefile控制编译、链接过程。如下:

    点击(此处)折叠或打开

    1. MAKEFLAGS = -sR
    2. MKDIR = mkdir
    3. RMDIR = rmdir
    4. CP = cp
    5. CD = cd
    6. DD = dd
    7. RM = rm
    8. ASM = nasm
    9. CC = gcc
    10. LD = ld
    11. ASMBFLAGS = -f elf
    12. 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
    13. LDFLAGS = -s -static -T hello.lds -n --oformat binary
    14. PMHELLO_OBJS :=
    15. PMHELLO_OBJS += entry.o helkrlmain.o vgastr.o
    16. PMHELLO_BIN = pmhello.bin
    17. .PHONY : build clean all link
    18. all: clean build link
    19. clean:
    20. $(RM) -f *.o *.bin
    21. build: $(PMHELLO_OBJS)
    22. link: $(PMHELLO_BIN)
    23. $(PMHELLO_BIN): $(PMHELLO_OBJS)
    24. $(LD) $(LDFLAGS) -o $@ $(PMHELLO_OBJS)
    25. %.o : %.asm
    26. $(ASM) $(ASMBFLAGS) -o $@ $<
    27. %.o : %.c
    28. $(CC) $(CFLAGS) -o $@ $<


    安装测试,在linux系统下则非常方便,因为linux系统已经安装好了GRUB2,默认情况下,只要把pmhello.bin文件复制到linux系统的/boot/目录下,同时修改/boot/grub/目录下的grub.cfg文件。如下图所示:

     

    重启计算机就可以看到PMHELLO启动选项了……

    该项目代码地址是:https://code.csdn.net/lmnos/pmhelloworld

    <script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
    阅读(63) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~
    评论热议
  • 相关阅读:
    TCP之Nagle算法与TCP_NODELAY
    CSPS模拟 87
    CSPS模拟 86
    CSPS模拟 85
    CSPS模拟 84
    CSPS模拟 83
    CSPS模拟 82
    CSPS模拟 81
    CSPS模拟 80
    CSPS模拟 79
  • 原文地址:https://www.cnblogs.com/ztguang/p/12649254.html
Copyright © 2011-2022 走看看