Jump to page: 1 2
Thread overview
asm+D build bootloader
Oct 27, 2015
guodemone
Oct 27, 2015
Kagamin
Oct 27, 2015
guodemone
Oct 28, 2015
lobo
Hello,Can you give me your files[kickstart32.s kmain.d linker32.ld makefile]?
Oct 28, 2015
guodemone
My email: 704975494@qq.com
Oct 28, 2015
guodemone
My email
Oct 28, 2015
guodemone
Can you give me your files[kickstart32.s kmain.d linker32.ld makefile]?
Oct 28, 2015
guodemone
Oct 29, 2015
lobo
very very thank you
Oct 29, 2015
guodemone
Oct 29, 2015
lobo
Oct 29, 2015
Meta
我是程序员
Oct 30, 2015
guodemone
October 27, 2015
sorry,My english is poot.

file asm.h

/*
是bootasm.S汇编文件所需要的头文件,主要是一些与X86保护模式的段访问方式相关的宏定义
*/

#ifndef __BOOT_ASM_H__
#define __BOOT_ASM_H__

/* Assembler macros to create x86 segments */

/* Normal segment */
#define SEG_NULLASM												\
	.word 0, 0;													\
	.byte 0, 0, 0, 0

#define SEG_ASM(type,base,lim)									\
	.word (((lim) >> 12) & 0xffff), ((base) & 0xffff);			\
	.byte (((base) >> 16) & 0xff), (0x90 | (type)),				\
		(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)


/* Application segment type bits */
#define STA_X		0x8		// 可执行
#define STA_E		0x4		// 向下扩展段(非可执行段)
#define STA_C		0x4		// 一致性代码段(只执行)
#define STA_W		0x2		// 段可写(非可执行段)
#define STA_R		0x2		// 段可读 (可执行段)
#define STA_A		0x1		// 可访问

#endif /* !__BOOT_ASM_H__ */

**********************************************************
file bootasm.S

# 定义并实现了bootloader最先执行的函数start,此函数进行了一定的初始化,完成了
# 从实模式到保护模式的转换,并调用bootmain.c中的bootmain函数

#include <asm.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

# gdt 全局描述符表内的数组索引
.set PROT_MODE_CSEG,		0x8						# kernel code segment selector
.set PROT_MODE_DSEG,		0x10					# kernel data segment selector
.set CR0_PE_ON,				0x1						# protected mode enable flag

.globl start
start:
.code16												# Assemble for 16-bit mode
	cli												# 禁用中断
	cld												# 字符串操作设定为递增 si++ di++ ,cld的作用是将direct flag标志位清零

	# Set up the important data segment registers (DS, ES, SS).
	xorw %ax, %ax									# Segment number zero
	movw %ax, %ds									# -> Data Segment
	movw %ax, %es									# -> Extra Segment
	movw %ax, %ss									# -> Stack Segment

	# A20地址线控制打开工作
	# Enable A20:
	# 为了向后兼容早期的PC机,让物理地址线20接低电平
	# 如果A20是关闭的,16bit的寻址范围2^20是1M,如果是打开的,那么就是2^21次方,
	# 但是寻址还是FFFFh:FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes
seta20.1:
	inb $0x64, %al									# Wait for not busy
	testb $0x2, %al
	jnz seta20.1      #测试 bit 1 是不是为0,如果不是跳回去继续执行

	# 对于键盘的8042控制芯片 0x64是命令端口 0xd1 代表写命令
	movb $0xd1, %al									# 0xd1 -> port 0x64
	outb %al, $0x64

seta20.2:
	inb $0x64, %al									# Wait for not busy
	testb $0x2, %al
	jnz seta20.2

	# 设置写命令后 给0x60端口 发送命令数据0xdf就是打开A20地址线,0xdd就是关闭
	movb $0xdf, %al									# 0xdf -> port 0x60
	outb %al, $0x60

	# 转入保护模式,这里需要指定一个临时的GDT,来翻译逻辑地址。
	# 这里使用的GDT通过gdtdesc段定义,它翻译得到的物理地址和虚拟地址相同,
	# 所以转换过程中内存映射不会改变
	lgdt gdtdesc									# 启动保护模式前建立好的段描述符合段描述符表
	
	# 打开保护模式标志位,相当于按下了保护模式的开关。
	# cr0寄存器的第0位就是这个开关,通过CR0_PE_ON或cr0寄存器,将第0位置1
	movl %cr0, %eax
	orl $CR0_PE_ON, %eax
	movl %eax, %cr0

	# 由于上面的代码已经打开了保护模式了,所以这里要使用逻辑地址,
	# 而不是之前实模式的地址了。这里用到了PROT_MODE_CSEG,
	# 他的值是0x8。根据段选择子的格式定义,0x8就翻译成:
  #      INDEX         TI     CPL
  #      0000 0000 0000 1          00      0
    # INDEX代表GDT中的索引,TI代表使用GDTR中的GDT, CPL代表处于特权级。
	# PROT_MODE_CSEG选择子选择了GDT中的第1个段描述符。
	# 这里使用的gdt就是变量gdt,下面可以看到gdt的第1个段描述符的基地址是0x0000,
	# 所以经过映射后和转换前的内存映射的物理地址一样。0000:7C00=0x00007C00 0000:protcseg 都是相对于物理内存0000基址的
	ljmp $PROT_MODE_CSEG, $protcseg

.code32												# Assemble for 32-bit mode
protcseg:
	# 重新初始化各个段寄存器。也就是采用平坦式内存方式,
	# 代码段同其它段都采用一个内存空间
	movw $PROT_MODE_DSEG, %ax						# 自定义数据段选择子,因为段选择子是16位的
	movw %ax, %ds									# -> DS: Data Segment
	movw %ax, %es									# -> ES: Extra Segment
	movw %ax, %fs									# -> FS
	movw %ax, %gs									# -> GS
	movw %ax, %ss									# -> SS: Stack Segment

	# 栈顶设定在start处,也就是地址0x7c00处,        # 低地址 0x0000    ^  此地址为栈基址 0000
	# call函数将返回地址入栈,将控制权交给bootmain   #                 /|\
	movl $0x0, %ebp                                  #
   |
	movl $start, %esp                                #
   |
	call bootmain                                    # 地址   0x7C00
 |  栈顶指针

	# bootmain 如果返回,就会在此处死循环,但是目前的bootmain的函数不会返回了,因为他内部就会死循环
spin:
	jmp spin

# 注意以下数据结构
# Bootstrap GDT
.p2align 2											# 调整为4字节对齐
# 3个段描述符,每个段描述符占8字节,共24字节
# gdtdesc指出了全局描述符表(可以看成是段描述符组成的一个数组)的起始位置在gdt符号处,
# 而gdt符号处放置了三个段描述符的信息

# 第一个是NULL段描述符,没有意义,表示全局描述符表的开始
# 紧接着是代码段描述符(位于全局描述符表的0x8处的位置),具有可读(STA_R)和可执行(STA_X)的属性,
#   并且段起始地址为0,段大小为4GB;
# 接下来是数据段描述符(位于全局描述符表的0x10处的位置),具有可读(STA_R)和可写(STA_W)的属性,
#   并且段起始地址为0,段大小为4GB。
gdt:
	SEG_NULLASM										# null seg NULL段
	SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)			# code seg for bootloader and kernel CODE段
	SEG_ASM(STA_W, 0x0, 0xffffffff)					# data seg for bootloader and kernel DATA段

gdtdesc:
	.word 0x17										# sizeof(gdt) - 1 类似于数组 数组是0开始的,所以数组长度就要减1
	.long gdt										# address gdt

*****************************************************************************
file bootmain.c

/*
定义并实现了bootmain函数实现了通过屏幕、串口和并口显示字符串
*/

//#include <types.h>
//#include <x86.h>

#define COM1			0x3F8
#define CRTPORT			0x3D4
#define LPTPORT			0x378
#define COM_TX			0			// Out: Transmit buffer (DLAB=0)
#define COM_LSR			5			// In:  Line Status Register
#define COM_LSR_TXRDY	20			// Transmit buffer avail

static uint16_t *crt = (uint16_t *) 0xB8000;		// CGA memory

/* stupid I/O delay routine necessitated by historical PC design flaws */
static void
delay(void) {
	inb(0x84);
	inb(0x84);
	inb(0x84);
	inb(0x84);
}

/*
考虑到简单性,在proj1中没有对并口设备进行初始化,通过并口进行输出的过程也很简单:
第一步:执行inb指令读取并口的I/O地址(LPTPORT + 1)的值,如果发现发现读出的值代表并口忙,
则空转一小会再读;
如果发现发现读出的值代表并口空闲,则执行outb指令把字符写到并口的I/O地址(LPTPORT ),
这样就完成了一个字符的并口输出。
*/
/* lpt_putc - copy console output to parallel port */
static void
lpt_putc(int c) {
	int i;
	for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) {
		delay();
	}
	outb(LPTPORT + 0, c);
	outb(LPTPORT + 2, 0x08 | 0x04 | 0x01);
	outb(LPTPORT + 2, 0x08);
}

/*
通过CGA显示控制器进行输出的过程也很简单:首先通过in/out指令获取当前光标位置;
然后根据得到的位置计算出显存的地址,直接通过访存指令写内存来完成字符的输出;
最后通过in/out指令更新当前光标位置。
*/
/* cga_putc - print character to console */
static void
cga_putc(int c) {
	int pos;

	// cursor position: col + 80*row.
	outb(CRTPORT, 14);
	pos = inb(CRTPORT + 1) << 8;
	outb(CRTPORT, 15);
	pos |= inb(CRTPORT + 1);

	if (c == '\n') {
		pos += 80 - pos % 80;
	}
	else {
		crt[pos ++] = (c & 0xff) | 0x0700;
	}

	outb(CRTPORT, 14);
	outb(CRTPORT + 1, pos >> 8);
	outb(CRTPORT, 15);
	outb(CRTPORT + 1, pos);
}

/*
通过串口进行输出的过程也很简单:第一步:执行inb指令读取串口的I/O地址(COM1 + COM_LSR)的值,
如果发现发现读出的值代表串口忙,则空转一小会(0x84是什么地址???);
如果发现发现读出的值代表串口空闲,则执行outb指令把字符写到串口的I/O地址(COM1 + COM_TX),
这样就完成了一个字符的串口输出。
*/
/* serial_putc - copy console output to serial port */
static void
serial_putc(int c) {
    int i;
	for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) {
		delay();
	}
	outb(COM1 + COM_TX, c);
}

/* 显示字符的函数接口*/
/* 一个cons_putc函数接口,完成字符的输出*/
/* cons_putc - print a single character to console*/
static void
cons_putc(int c) {
	lpt_putc(c);
	cga_putc(c);
	serial_putc(c);
}

/* 提供了一个cons_puts函数接口:完成字符串的输出*/
/* cons_puts - print a string to console */
static void
cons_puts(const char *str) {
	int i;
	for (i = 0; *str != '\0'; i ++) {
		cons_putc(*str ++);
	}
}

/* bootmain - the entry of bootloader */
void
bootmain(void) {
	cons_puts("This is a bootloader: Hello world!!");

	/* do nothing */
	while (1);
}

***************************************************
These codes(asm.h bootasm.S bootmain.c) trans to (asm.h bootasm.S bootmain.d).

ldc -c asm.h bootasm.S bootmain.d

ld bootasm.o bootmain.o of outbin.o

How to build?
October 27, 2015
You chose quite advanced topic. Maybe you want to build common skills in system programming first?
October 27, 2015
Asm + D with the ability to write on behalf of Clang bootloader, and prove that he can completely replace Clang.

This is my wish.
October 28, 2015
On Tuesday, 27 October 2015 at 12:13:13 UTC, guodemone wrote:
> sorry,My english is poot.
>
> file asm.h
>
> [...]

Can ldc work with C header files? I don't think it can but I could be wrong.

Here's how I build my 32-bit bootloader and link with my kernel main (you will have to replace names etc.):

---
nasm -felf -o kickstart32.o kickstart32.s (I don't have an asm.h)
gdc -m32 -gdwarf-2 -nostdlib -fPIC -c -o kernel32.main.o kmain.d
ld -nodefaultlibs -melf_i386 -z max-page-size=0x1000 -T linker32.ld -o kernel32.bin kickstart32.o kernel32.main.o
---

For this build setup you will need a linker script. Here's mine in case you don't have one. 'kickstart' is the entry point in my kickstart.s. Replace names and offsets as required for your code.


---
/* Use -melf_i386 or -melf64_x86-64
 * to specify the architecture
 * ld -nodefaultlibs -melf_i386 -z max-page-size=0x1000 -T <linker_script> -o <output_binary>
*/
ENTRY (kickstart)

SECTIONS{
    . = 0x00100000;

    .text :{
        code = .; _code = .; __code = .;
        *(.text)
        *(.rodata)
    }

    .rodata ALIGN (0x1000) : {
        *(.rodata)
    }

    .data ALIGN (0x1000) : {
        data = .; _data = .; __data = .;
        *(.data)
        start_ctors = .; *(.ctors)   end_ctors = .;
        start_dtors = .; *(.dtors)   end_dtors = .;
    }

    .bss : {
        sbss = .;
        bss = .; _bss = .; __bss = .;
        *(COMMON)
        *(.bss)
        ebss = .;
    }
    end = .; _end = .; __end = .;
}
---

I got a lot of info from these sites:

https://www.cs.cmu.edu/~410-s07/p4/p4-boot.pdf
https://en.wikibooks.org/wiki/X86_Assembly/Bootloaders

http://wiki.osdev.org/Bare_bones

(NOTE: wiki.osdev.org has a lot of incorrect information, but it was useful as a starting point when I got stuck moving to a 64-bit kernel)


bye,
lobo
October 28, 2015
My english is poor.

My code to build is wrong.so need make some improvements.
I would like to refer to your 32-bit code, make some improvements.
October 28, 2015
thank you.


October 28, 2015
704975494@qq.com
very very thank you.
October 28, 2015
My english is poor.

My code to build is wrong.so need make some improvements.
I would like to refer to your 32-bit code, make some improvements.

My Email:  704975494@qq.com
very very thank you.
October 29, 2015
On Wednesday, 28 October 2015 at 11:01:14 UTC, guodemone wrote:
> My english is poor.
>
> My code to build is wrong.so need make some improvements.
> I would like to refer to your 32-bit code, make some improvements.
>
> My Email:  704975494@qq.com
> very very thank you.


I've uploaded a dummy kernel with bootloader to Github. Hopefully it will help you.

https://github.com/swamplobo/lyrebirdos


A few points:

* The Makefile uses DMD but if you prefer you can use the gdc_Makefile to switch compilers easily.

* This example uses GRUB, which you can replace this if you wish. I did once I got things working (I've now gone back to GRUB because it has some nice features).

* I use qemu, VirtualBox and real hardware to test. I recommend testing in a VM before trying real hardware.

* The name Lyrebird OS is just the name I chose, rename to whatever you like.

* Keep at it because it's very rewarding watching your own kernel grow.

bye,
lobo
October 29, 2015
衷心的谢谢你,(very very........very thank you in english)
« First   ‹ Prev
1 2