Page 1 of 1

Begginer's problem with OS

Posted: Sat Jul 21, 2007 12:24 pm
by sznurek
Hello!

I am completely newbie to os dev. I've read many articles, tutorials etc. about asm, c and os theory. So, I've started to make my own OS :)
Now I have bootloader thats loads kernel, then GDT, enter PMODE and jumping to kernel. But:

First: I run mu os with QEMU. But If I enter to kernel I have page fault(In host OS).
Second: I don't have idea how to link asm and c. Where is something like linker manual? I cannot find it.

Here is code:
Bootloader:

Code: Select all

org 7C00h;
BITS 16
start:
    ; Wacky stuff...
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ax, 0x2000
    mov ss, ax
    mov sp, 0x200
    sti
    ; loading kernel
    mov ah, 2
    mov al, 10
    mov ch, 0
    mov cl ,2
    xor dh, dh
    xor dl, dl
    mov bx, 8000h
    mov es, bx
    xor bx, bx

    int 13h
    ;GDT stuff
    cli
    ;pusha
    lgdt [gdt_toc]
    ;popa
    ; GDT stuff end

    ;GO PMODE!
    mov eax, cr0
    or eax, 1
    mov cr0, eax
    
    mov ax, 0x10
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    jmp 0x08:apm



BITS 32
gdt:
    ; null descriptor
    dd 0
    dd 0
    ;gdt code:
    dw 0FFFFh       ; lilit low word
    dw 0            ; base low word
    db 0            ; base middle
    db 10011010b    ; access ; 
    db 11001111b    ;granularity ;
    db 0            ; base high word
    ;gdt data:
    dw 0FFFFh       ; lilit low word
    dw 0            ; base low word
    db 0            ; base middle
    db 10010010b    ; access ; 
    db 11001111b    ;granularity ;
    db 0
gdt_end:

gdt_toc:
    dw gdt_end - gdt - 1
    dd gdt

apm:
    mov ax, 0x10
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov esp, 0x90000
    hang:
	jmp 0x08:0x80000

times 510 - ($ - start) db 0
db 55h
db 0AAh
Kernel:

Code: Select all

[BITS 32]
start:
    jmp start
And how I build and run:

Code: Select all

nasm boot.asm -f bin -o boot.bin
nasm kernel.asm -f bin -o kernel.bin
cat boot.bin kernel.bin > image.img
qemu -fda image.img
Ufff... end.
Thanks for reading my post. Cheers

Ps. Sorry for my English :) I learning it only 2 years ;)

Posted: Sat Jul 21, 2007 5:37 pm
by B.E
Just had a quick look over the code, it looks like you haven't disabled interrupts, and you don't have a IDT.

Re: Begginer's problem with OS

Posted: Sat Jul 21, 2007 11:55 pm
by Brendan
Hi,
sznurek wrote:First: I run mu os with QEMU. But If I enter to kernel I have page fault(In host OS).
Qemu crashes, or your OS crashes?

The guest OS should be able to crash in many spectacular and confusing ways without effecting Qemu or the host OS - if Qemu causes a page fault (regardless of what your code does), then Qemu is buggy.

I couldn't find much wrong with your code. You should check if there was an error after trying to read sectors from the disk (and retry 3 times if there was an error).

You also don't need to load protected mode data segments twice, LGDT can be done with interrupts enabled, it's good to do a JMP immediately after enabling protected mode (to flush the CPUs pipeline on older CPUs), the GDT should be aligned on a 4 byte (or higher) boundary for performance reasons, and I'd put all the code in one place with the data at the end. For e.g.:

Code: Select all

    ** Other stuff here **

    ;GDT stuff
    lgdt [gdt_toc]
    ; GDT stuff end

    ;GO PMODE!
    mov eax, cr0
    cli
    or eax, 1
    mov cr0, eax
    jmp 0x08:apm

BITS 32
apm:
    mov ax, 0x10
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov esp, 0x90000
    hang:
	jmp 0x08:0x80000

    align 4
gdt:
    ; null descriptor
    dd 0
    dd 0

    ** Other stuff here **
None of this would cause your code to crash (unless there was an error loading sectors from disk, or the sectors loaded correctly but contained the wrong data) .


Cheers,

Brendan

Posted: Sun Jul 22, 2007 3:27 am
by sznurek
Hello again!

Thanks for replies :) Here is the qemu error:

Code: Select all

qemu: fatal: Trying to execute code outside RAM or ROM at 0x000a0000

EAX=600a00af EBX=00000000 ECX=53f00101 EDX=00000000
ESI=0000009d EDI=0000ffde EBP=00000000 ESP=0008fffc
EIP=0009ffba EFL=00000002 [-------] CPL=0 II=0 A20=1 HLT=0
ES =0010 00000000 ffffffff 00cf9300
CS =0008 00000000 ffffffff 00cf9a00
SS =0010 00000000 ffffffff 00cf9300
DS =0010 00000000 ffffffff 00cf9300
FS =0010 00000000 ffffffff 00cf9300
GS =0010 00000000 ffffffff 00cf9300
LDT=0000 00000000 0000ffff 00008000
TR =0000 00000000 0000ffff 00008000
GDT=     00007c39 00000017
IDT=     00000000 0000ffff
CR0=60000011 CR2=00000000 CR3=00000000 CR4=00000000
CCS=600a00af CCD=600a00af CCO=ADDB
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
zsh: abort (core dumped)  qemu -fda image.img
And i have new questions:
What does exactly linking does? I don't understand it. Where I can read about this?
Next: What is

Code: Select all

; ASM CODE
section .text
;ASM CODE
section .bss
etc. 
Do i must include that directives into my bootloader and kernel? What is they doing?

An the last one:
What does do Align 4 in your code Brendan?

But, if you don't wanna reply for that many question, just give me a link/book/tutorial where i can read about this.

EDIT:
B.E:
Does cli does not disable interputs (right after int 13h)?

Do I must have IDT for that simple kernel?

Cheers, Sznurek

Posted: Sun Jul 22, 2007 4:20 am
by Brendan
Hi,

I'd guess that the "jmp start" in your kernel didn't work, and then CPU executed all the zeros (all the ADD instructions) from your kernel up until the BIOS's data in the EBDA (just below 0x000A0000). When the CPU starts trying to execute this data it statrs doing erratic things (as the data wasn't meant to be instructions) and finally the CPU tries to execute data from video display memory (and Qemu puts it out of it's misery).

To test if I'm right, change your kernel to:

Code: Select all

    org 0x00080000
    [BITS 32]

    dd 0xFEEDFACE

start:
    jmp start
Then, in your bootloader do something like:

Code: Select all

    BITS 32
apm:
    mov ax, 0x10
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov esp, 0x90000

    mov eax,[0x00080000]    ;eax = magic number
    cmp eax,0xFEEDFACE    ;Is the magic number correct?
    jne $                                 ; no, lock up
    jmp 0x08:0x80004           ; yes, jump to the kernel (the byte after the magic number)
That way if the kernel wasn't loaded properly it'll lock up instead of trying to execute the (missing) kernel, and you can stop the emulator to see where it is.

sznurek wrote:And i have new questions:
What does exactly linking does? I don't understand it. Where I can read about this?
Any basic programming course covers linking. The idea is that you've got several files of source code and compile them into several object files, then link those object files together into a single object file (and convert that final object file into an executable file).
sznurek wrote:Next: What is

Code: Select all

; ASM CODE
section .text
;ASM CODE
section .bss
etc. 
Do i must include that directives into my bootloader and kernel? What is they doing?
Most languages sort the code into different sections, so that your data isn't all mixed in with your code, etc. For assembly you do this yourself, by telling the assembler which section to use.
sznurek wrote:An the last one:
What does do Align 4 in your code Brendan?
It aligns the output of the assembler to a 4 byte boundary.
sznurek wrote:EDIT:
B.E:
Does cli does not disable interputs (right after int 13h)?
I don't think B.E. saw your CLI in there.... ;)
sznurek wrote:Do I must have IDT for that simple kernel?
For a protected mode kernel, you need an IDT to be able to handle IRQs (and other stuff, like exceptions). For a kernel that does nothing, you don't need one (as long as IRQs are disabled).
sznurek wrote:But, if you don't wanna reply for that many question, just give me a link/book/tutorial where i can read about this.
If you have enough experience with general programming to consider writing an OS, then read all the information for the tools you're using (e.g. NASM's manual) and make sure you understand everything, then read Intel's System Programming Guide and make sure you understand enough, then read just about everything in the Wiki, then read other informations (use Google) about operating system design.

Don't forget that 80x86 is one of the most complicated architectures, and that operating systems are one of the most complicated projects you could consider. For something like Vista it took thousands of programmers (who knew what they were doing beforehand) 5 years to modify an existing OS (rather than actually writing a whole OS from scratch)...

Designing and building your own space ship (that works) is probably easier than writing a good operating system for 80x86. How hard could it be? Glue some containers of fuel onto some sort of cabin, then add some air purifiers, find a helmet, light the fuse and hold on!

Of course maybe I don't know enough about space ship design to realise how hard it really is. Maybe I should start with something simpler, like a submarine? ;)


Cheers,

Brendan

Posted: Sun Jul 22, 2007 4:39 am
by sznurek
Brendan: 1000 x thanks for your post! :D

Now my code works!

Yes, I have some experience in programing. I've downloaded Intel Manuals and I'm reading it now. It's very, very useful. And again, thanks for your reply.

Uff... Os developing is very hard... But I don't give up :wink:

Cheers, Sznurek.

Posted: Sun Jul 22, 2007 6:02 am
by Kevin McGuire
And i have new questions: What does exactly linking does? I don't understand it. Where I can read about this?
Let make a pretend machine language:

A Pretend Instruction Language

ADD 0x0(byte) <reg>(nibble) <reg>(nibble)
SUB 0x1(byte) <reg>(nibble) <reg>(nibble)
DIV 0x2(byte) <reg>(nibble) <reg>(nibble)
MUL 0x3(byte) <reg>(nibble) <reg>(nibble)
SHL 0x4(byte) <reg>(nibble) <reg>(nibble)
SHR 0x5(byte) <reg>(nibble) <reg>(nibble)
MOV 0x6(byte) <reg>(nibble) <reg>(nibble)
CALL 0x7(byte) <reg>(byte)
RET 0x8(byte)

On each instruction if the reg operand is zero then it is a memory address. This allows each operation to perform a operation for:
(using the ADD instruction for a examples)
ADD register, register

Bytes: 0x0,0x23

The first is 0x0 which is a add instruction. The next is the two nibbles 0x2 and 0x3, a nibble is a half byte or four bits. Together they look like just one single byte. You remember is a nibble is not zero then it is a register. We can address fifteen registers when leaving zero out for a memory address. This means ADD register 2 with register 3 and store the result in register 2.

ADD register, memory
Bytes: 0x0,0x20,0x11223344

This says ADD (0x0) register 2 (0x20) with memory 0 (0x20) and store the result in register 2. The problem is what memory address, right? Well. We append the memory addresses in order at the end of the instruction. Since this is a mock up of a thirty-two bit machine we will use four bytes (thirty-two bits) for each memory address. Look at the bytes again:

00 20 11 22 33 44

The 00 is ADD, 20 is register and memory, 11 22 33 44 is the memory address.

ADD memory, register
Bytes: 0x0,0x02,0x11223344

Notice the 0x02 is reversed from 0x20 which shows we want to ADD a memory location (0x02) with a register (0x02) and store the result in the memory location (0x02). The memory location is once again appended at the end as 11 22 33 44 (0x11223344).

ADD memory, memory
Bytes: 0x0,0x00,0x11223344,0x88772233

You can see that we have 0x0 for the ADD instruction, and 0x00 for the operands. This means we need two memory addresses:

0x00 = 0x11223344
0x00 = 0x88772233

This effect applies to all of our instructions that we can use to make a program.

A Program Using Our Pretend Instruction Language
06 10 00 00 00 30 = MOV register1, memory(0x00000030) = move 4 bytes of data from memory location 0x30 into register1.
00 10 00 00 00 34 = ADD register1, memory(0x00000034) = add register1 with 4 bytes of data as memory location 0x34.
07 00 00 FF 00 00 = CALL a function that has been compiled in machine instruction at memory address 0x00FF0000.

Here we load a value from memory into a register, add it with another memory location, and finally call another function. Lets look at this other function.

04 10 00 00 00 38 = shift the register left by the number of bits stored at memory location 0x38.
08 = return (to where the CALL instruction was made)

Here is what these two functions might have looked like in C.

File: other.c

Code: Select all

unsigned long gvar3 = SOMETHING;
unsigned long b(unsigned long v)
{
	return v << gvar3;		// SHIFT LEFT.. and RET.
}
File: main.c

Code: Select all

unsigned long gvar1 = SOMETHING;
unsigned long gvar2 = SOMETHING;
void a()
{
	b(gvar1 + gvar2);			// MOV..ADD... and CALL.
	return;				// RET (which was not shown above)
}
These function are compiled and placed in a object file such as: main.o and other.o. The instructions might look like this:
06 10 00 00 00 00 = MOV register1, memory(0x00000030) = move 4 bytes of data from memory location 0x30 into register1.
00 10 00 00 00 00 = ADD register1, memory(0x00000034) = add register1 with 4 bytes of data as memory location 0x34.
07 00 00 00 00 00 = CALL a function that has been compiled in machine instruction at memory address 0x00FF0000.

If you notice the memory locations for the instructions are all zeros.. why? Well. It can be a complicated answer, but the simplest one I can give at this moment is because it allows the linker to decide where to place the function and global variables because:
  • You might be linking multiple programs from some of the same sources file. This keeps you from having to recompile each source file.
  • It allows you to reorder where the instructions and data are placed into memory instead of doing so inside the code which makes it messy.
  • It allows each source file to be compiled separately; making the potential for only compiling the files changed in a large project with hundreds of source files. What has already been compiled can stay in object form..
But, I still have not answered how and hopefully I have not got too deep and confusing for you.. but anyway I will try...

Not only are the instructions stored in each object file, but also a symbol table. The symbols are: gvar1, gvar2, gvar3, and b. Also in the object file is a relocation table which associates relocations with symbols. The text section and relocation table might look something like this:

Text Section (section .text)
06 10 00 00 00 00 = MOV register1, memory(0x00000030) = move 4 bytes of data from memory location 0x30 into register1.
00 10 00 00 00 00 = ADD register1, memory(0x00000034) = add register1 with 4 bytes of data as memory location 0x34.
07 00 00 00 00 00 = CALL a function that has been compiled in machine instruction at memory address 0x00FF0000.

Relocation Table (section .reloc)
00 02 'gvar1'
00 08 'gvar2'
00 0E 'b'

Linking main.o and other.o
ld main.o other.o -o myprogram

The linker will look for gvar1 and gvar2 which will be found in the current object file. It will then use the relocation information of 00 02, 00 08, and 00 0E to insert the actual address of where these symbols will live in memory when the program is executed. It is the linker that choose to place gvar1 at 0x30 and gvar2 at 0x34.

One symbol will not be found in the current object file by the linker: b. It will find this symbol in other.o. When found the linker will compute where in memory the symbol b from other.o will be. Of course going from our example above it had decided that it will be at 0xFF0000. So it will know where to write this address at in the .text section..

I dunno. I most likely confused it much more than I should have. If someone has a link to resource that explains this much better it would be nice, since I feel like it could take quite a few pages to do a through explanation of this topic.

Posted: Sun Jul 22, 2007 9:13 am
by sznurek
Kevin McGuire wrote: I dunno. I most likely confused it much more than I should have. If someone has a link to resource that explains this much better it would be nice, since I feel like it could take quite a few pages to do a through explanation of this topic.
Kevin McGuire, it IS very good explanation :)

But if I know more, I have more question ;) Now I understand linkink, but don't understand linker files, for example:

Code: Select all

OUTPUT_FORMAT("binary")
ENTRY("k_main")
SECTIONS {
 .text 0x80000 : {
  code = . ; _code = . ;
  *(.text)
 }
 .data : {
  *(.data)
 }
 .bss : {
  bss = . ; _bss = . ;
  *(.bss)
  *(.COMMON)
 }
 end = . ; _end = . ;
}
I think, this file says, that code (text) section is located in 0x80000, and bss and data too, I'm right? But why i must create that file? Can't I just use:
Kevin McGuire wrote: ld main.o other.o -o myprogram
:?:

Cheers, Sznurek.

Posted: Sun Jul 22, 2007 11:06 am
by Kevin McGuire
You can just use ld with no linker script:
  • The image will have to be loaded to the default image base (On my machine it is 0x08048000) to function correctly.
  • That address must exist in physical memory to be loaded by GRUB.
  • If that address does not exist in physical memory you must setup paging before jumping into (or to) the kernel image.
About the linker script. I know a good bit but there are some things that I have only learned by looking at and tinkering with so I have no formal education about it and my current projects do not require me to learn more about it. I can get the linker script to do anything I need it to do, but I have never read anything about it for these many years. Here is a link I searched up using Google. I am not sure if it will explain, although I have not read it, but it should from my glance over the first page.
http://www.delorie.com/gnu/docs/binutils/ld_6.html

Posted: Sun Jul 22, 2007 4:15 pm
by pcmattman
sznurek wrote:
Kevin McGuire wrote: I dunno. I most likely confused it much more than I should have. If someone has a link to resource that explains this much better it would be nice, since I feel like it could take quite a few pages to do a through explanation of this topic.
Kevin McGuire, it IS very good explanation :)

But if I know more, I have more question ;) Now I understand linkink, but don't understand linker files, for example:

Code: Select all

OUTPUT_FORMAT("binary")
ENTRY("k_main")
SECTIONS {
 .text 0x80000 : {
  code = . ; _code = . ;
  *(.text)
 }
 .data : {
  *(.data)
 }
 .bss : {
  bss = . ; _bss = . ;
  *(.bss)
  *(.COMMON)
 }
 end = . ; _end = . ;
}
I think, this file says, that code (text) section is located in 0x80000, and bss and data too, I'm right? But why i must create that file? Can't I just use:
Kevin McGuire wrote: ld main.o other.o -o myprogram
:?:

Cheers, Sznurek.
.text will be at 0x80000, .data will come after the .text section (with page alignment, so there may be a filler in between which the linker adds automatically), and .bss will come after the .data section.

You use a linker script to tell the linker that you want your code to start at a certain location (in this case, it will be linked to execute at 0x80000). It's a bit like the 'org' keyword in assembly - it'll offset all memory accesses.

Posted: Mon Jul 23, 2007 11:00 am
by sznurek
Again, thanks guys!

I'll be reading, and coding now. But more reading :D

Yours advices are VERY useful.

See Ya,
Sznurek