Bootloader hangs

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
vile

Bootloader hangs

Post by vile »

Hello, I am vile and I am new to this forum, and OS dev in general.

Now here's my problem...
I recently wrote a small bootsector that boots up, and asks for a password, and if the key pressed is incorrect, it asks for it again. If it is correct, it says "Welcome to PsychOS v0.2a.":

Code: Select all


[bits 16]
[org 0]      ; start at offset 0


   jmp 0x7c0:start

   leetMsg db 10,13,10,13,"Welcome to PsychOS v0.2a.",10,13,0
   pWord db 10,13,"password: ",0
   diskError db 10,13,"Error: cannot read from disk",10,13,0

start:
   mov ax, cs
   mov ds, ax
   mov es, ax
   mov ah, 0
   mov al, 0x10
   int 0x10
   jmp pass

msg:
   xor si, si
   mov si, leetMsg
   call printMsg
   call read

printMsg:
   
   mov ah, 0x0e
   mov bh, 0x00
   mov bl, 0x09
   lodsb
   cmp al, 0
   je nichts
   int 0x10
   
   jmp printMsg

input:
   mov ah, 0x00
   int 0x16
   cmp al, 'a'
   je msg
   jmp pass

pass:
   mov si, pWord
   call printMsg
   call input

nichts:
   ret
   
times 510-($-$$) db 0
dw 0xAA55
But here's the problem. After I accomplished that, I wrote a little fake test kernel, and edited the original code to this to jump to the kernel:

Code: Select all

[bits 16]
[org 0]      ; start at offset 0


   jmp 0x7c0:start

   leetMsg db 10,13,10,13,"Welcome to PsychOS v0.2a.",10,13,0
   pWord db 10,13,"password: ",0
   diskError db 10,13,"Error: cannot read from disk",10,13,0

start:
   mov ax, cs
   mov ds, ax
   mov es, ax
   mov ah, 0
   mov al, 0x10
   int 0x10
   jmp pass

msg:
   xor si, si
   mov si, leetMsg
   call printMsg
   call read

printMsg:
   
   mov ah, 0x0e
   mov bh, 0x00
   mov bl, 0x09
   lodsb
   cmp al, 0
   je nichts
   int 0x10
   
   jmp printMsg

input:
   mov ah, 0x00
   int 0x16
   cmp al, 'a'
   je msg
   jmp pass

pass:
   mov si, pWord
   call printMsg
   call input

reset:
   mov cx, 3

   .reset_loop:
      mov ax, 0x0
      mov dl, 0x0
      int 0x13
      jc .no_reset
      jmp read

   .no_reset:
      dec cx
      cmp cx, 0
      je .reset_err
      jmp .reset_loop

   .reset_err:
      mov si, diskError
      call printMsg
      jmp $
   
read:
   
   dec cx
   mov ax, 0x1000
   mov es, ax
   mov bx, 0x00

   mov ah, 0x02
   mov al, 0x05
   mov ch, 0x00
   mov cl, 0x02
   mov dh, 0x00
   mov dl, 0x00
   int 0x13
   jc reset
   
   jmp 1000h:0000
   
nichts:
   ret
   
times 510-($-$$) db 0
dw 0xAA55
Now when I assemble and test it, the system just hangs. It doesn't even ask for the password! Here is the so-called "kernel":

Code: Select all

[bits 16]
[org 0x1000]
   
   kernelBooting db 10,13,"PsychOS kernel starting...",10,13,0
   kernelBooted db 10,13,"PsychOS kernel successfully started!",10,13,

start:   
   mov ax, cx
   mov ds, ax
   mov es, ax

   mov ah, 0x00
   mov al, 0x10
   int 0x10
   jmp go

go:
   mov si, kernelBooting
   call kernelMsg

go2:
   mov si, kernelBooted
   call kernelMsg

kernelMsg:
   
   mov ah, 0x0e
   mov bh, 0x00
   mov bl, 0x0a
   lodsb
   cmp al, 0
   je halt
   int 0x10

   jmp kernelMsg

halt:
   ret   
Thanks in advance for the help.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Bootloader hangs

Post by Brendan »

Hi,
vile wrote:Thanks in advance for the help.
Don't be too hasty! :)

I'm not going to tell you exactly what's wrong (and I hope no-one here does). The reason for this is that it's often best to learn how to carve a turkey before attempting brain surgery, or learn how to light a barbeque before designing a rocket engine.

Instead of telling you what's wrong, I offer you 2 priceless gifts.

The first gift is the knowledge that every bit code code invoked via. a "call" instruction should return via. a "ret" instruction sooner or later.

The second gift is the opportunity to rewrite the entire boot loader such that it does not contain the instruction "jmp" anywhere, except for the first "jmp 0x07C0:start" instruction and the final "jmp 1000h:0000". Conditional branching is acceptable (ie. "je .somewhere").

Please accept these gifts, and profit from them in some way...

If the rewritten (jumpless) code still has problems, post it here and I will offer all the assistance I possibly can.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
vile

Re:Bootloader hangs

Post by vile »

Thank you very much. Being an inexperienced assembly programmer, I never knew that the call instruction requires a ret. I'll remember that from now on. My one question, though, is where to place the rets? Is it always after a call, or...?
AR

Re:Bootloader hangs

Post by AR »

Ret is the return instruction, CALL implies that you are running a function that performs some work then returns to the caller. The CALL instruction pushes the return value on the stack (and you haven't set up a stack which will most likely be a problem).

Code: Select all

start:
     CALL DoSomeWork
     CALL DoSomeMoreWork
     jmp $

DoSomeWork:
    ; Do something
    ret

DoSomeMoreWork:
    ; Do something else
    ret
vile

Re:Bootloader hangs

Post by vile »

May I ask why I shouldn't be using the jmp instruction?
Kemp

Re:Bootloader hangs

Post by Kemp »

That's an old coding convention that applies as much today as it used to, too many jumps can make a program utterly unreadable. Take a look at BIOS code *shudders*
hgb

Re:Bootloader hangs

Post by hgb »

Code: Select all

  jmp go

go:
normally you will use only this for "wait" a little, tought if Im not wrong you can use instead

Code: Select all

jmp $+2
This symbol $ mark the end of the anterior instruction in the code or the start of the line... like you whant to see it... for example, you can know "where you are" in the code... like this:

Code: Select all

%macro OFFSETHERE 1
%%__here__
%assign _here_3455342 %%__here__ -$$
%error  [_here_3455342] %1
%endmacro
Use it like this:

Code: Select all

OFFSETHERE This is the _offset_here_

My recommendation, tought in asm you dont have direct access to structured programming, use the paradigm like a guide :). Or in other words follow the sugestions here... try to use less jumps where posible, when there is a functional part by itself and is repeating some times here and there, move the code to a function for be called and the function should ret... (learn how to do this and the standar conventions about pass arguments, "clean" of stack.. and such things...)
vile

Re:Bootloader hangs

Post by vile »

Ok, here's my code without the unconditional jmp's, except the starting one:

Code: Select all

[bits 16]
[org 0]

   jmp 0x7c0:start

   welcomeMsg db 10,13,10,13,"Welcome to PsychOS v0.2a",10,13,0
   pWord db 10,13,"password: ",0

start:
   mov ax, cx
   mov ds, ax
   mov es, ax
   
   mov ah, 0x00
   mov al, 0x10
   int 0x10

   call pass

welcome_Msg:
   
   mov si, welcomeMsg
   call printMsg

printMsg:
   
   mov ah, 0x0e
   mov bh, 0x00
   mov bl, 0x09

   lodsb
   cmp al, 0
   je hang
   int 0x10
   
   call printMsg


input:
   mov ah, 0x00
   int 0x16
   cmp al, 'a'
   je welcome_Msg
   call pass

pass:
   mov si, pWord
   call printMsg
   call input

hang:
   ret
I wrote it almost exactly the same, and yet it doesn't work? I have no idea why =/.

EDIT: Nevermind, I figured out that I was doing "mov ax, cx" instead of "mov ax, cs." Stupid typos :(
AR

Re:Bootloader hangs

Post by AR »

You still don't seem to have got the point, I'll redo the code. You need to work on your structuring you are very lavish with your jmps when the code could have been much more straightforward.

Code: Select all

[bits 16]
[org 0]

  jmp 0x7c0:start

  welcomeMsg db 10,13,10,13,"Welcome to PsychOS v0.2a",10,13,0
  pWord db 10,13,"password: ",0

start:
  mov ax, cs
  mov ds, ax
  mov es, ax

  ; You need a stack for CALL (CALL pushes return addresses, you have nothing to push them on)
  mov ss, ax
  mov sp, 0x3FF
 
  mov ah, 0x00
  mov al, 0x10
  int 0x10

  ; Get the password
  mov si, pWord
  call printMsg
  call input

  ; Password was accepted
  mov si, welcomeMsg
  call printMsg

.halt:
   hlt
   jmp .halt


printMsg:
 
  mov ah, 0x0e
  mov bh, 0x00
  mov bl, 0x09

  lodsb
  cmp al, 0
  je .Done
  int 0x10
 
  jmp printMsg
.Done:
  ret


input:
  mov ah, 0x00
  int 0x16
  cmp al, 'a'
  je .Done
  jmp input
.Done:
  ret
vile

Re:Bootloader hangs

Post by vile »

Oh, okay. Question though:
Why did it work when I was using the call instruction even when I didn't setup a stack? Does it just push it into nowhere?
And does moving 0x3FF into the stack pointer just make it start at offset 0x3FF on the stack?
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Bootloader hangs

Post by Brendan »

Hi,

Thanks AR, that's almost exactly what I wanted to see :). In order to do it without any jumps (except the first and last), you'd need to do things like:

Code: Select all

printMsg:
   cld
   lodsb
   cmp al, 0
   je .Done
.Next:
   mov ah, 0x0e
   mov bh, 0x00
   mov bl, 0x09
   int 0x10
   lodsb
   cmp al, 0
   jne .Next
.Done:
   ret
Vile, I'm sorry if this seemed like a silly excercise, however it's obvious you're not fully understanding the difference between "call" and "jmp", and where they should be used.

The CPU uses a stack for temporary storage, where values can be pushed onto the top of the stack and popped back off later. For the "call" instruction the CPU pushes EIP (the address of the instruction the CPU is about to execute) onto the stack and goes to the called code. When the called code executes a "ret" instruction the CPU pops EIP off of the stack so that it can continue executing immediately after the call instruction.

This allows code like this:

Code: Select all

0x1000:    call do_something
0x1003:    call do_another
0x1006:
In this case, when EIP = 0x1000 the CPU will try to execute the first "call", push the value 0x1003 onto the stack and change EIP to the address of "do_something". When the code at "do_something" is finished it will execute a "ret" instruction to return to the caller, and the CPU will get the value 0x1003 from the top of the stack and change EIP to 0x1003.

The same happens for the next call - 0x1006 is pushed on the stack, EIP is changed to "do_another" and when this code is finished 0x1006 is popped back off the stack.

Now, a "jmp" instruction just changes EIP without pushing anything on the stack. Because of this code that was jumped to can't return.

If you do a "call" without doing a matching "ret" you end up with values being pushed on the stack for no reason, which leads the stack getting messed up (as the stack is normally used for more than just return addresses).

If you jump to some code and that code does do a "ret" instruction, then the CPU will attempt to pop a return address from the stack that was never put there. This can cause the CPU to end up in the middle of no-where, or running code that it's not supposed to.

Now consider what the following code would do (derived from your last post):

Code: Select all

input:
   ;some stuff
   call pass

pass:
   ;some stuff
   call input
If this code is executed, the "call" instructions would continually push return values onto the stack that are never taken off again. Eventually it'd end up trashing 64 Kb of memory before the stack overflowed. The only reason this didn't happen for you is that there was a conditional jump within "input:" that jumped to "welcome_Msg:" - if the user kept pressing the wrong keys...

Now consider this code:

Code: Select all

void input(void) {
    if( get_key() == 'a') goto welcome_Msg
    pass();

void pass(void) {
    printMsg( pWord);
    input();
This is your code converted to C. A compiler won't compile it because both functions have no end braces, '}', which is equivelent to a "ret" instruction. Further, the compiler won't allow "goto welcome_Msg" because it's too unstructured.

That's one of the good things about high level languages - they don't give the programmer the flexibility needed to really mess things up. Assembly language gives programmers the power to do anything, including the power to make huge mistakes without getting error messages :).

Anyway, correct use of "call" and "jmp" also leads to much cleaner code, which is easier to read and maintain. Compare the code posted by AR to your original code. AR's code is cleaner and easier to read, but it's also shorter and faster (despite the fact that AR added some extra code to fix other bugs, like not setting up a stack).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Bootloader hangs

Post by Brendan »

Hi,
vile wrote: Oh, okay. Question though:
Why did it work when I was using the call instruction even when I didn't setup a stack? Does it just push it into nowhere?
And does moving 0x3FF into the stack pointer just make it start at offset 0x3FF on the stack?
It pushes it somewhere (where-ever SS:SP happens to be pointing). The problem is that it's impossible to guess where this might be, or what you might be overwriting when you use the stack.

Moving 0x03FF into the stack pointer makes the address SS:0x03FF the top of the stack. The stack actually grows down, so the first thing pushed on the stack would end up at SS:0x03FD and the second thing on the stack would end up at SS:0x03FB.

If you do "push al" the CPU uses 2 bytes even though it only really needs 1 byte. This is done to make sure the stack remains aligned, as the CPU can load words at even addresses faster than it can load words at odd addresses. WIth this in mind it's much better for performance to use "mov sp,0x03FE" so that this alignment is correct, rather than "mov sp,0x03FF" where the alignment will always be wrong.

If you're sneaky you may have noticed that the CPU decreases SP and then stores the value, so you could actually use "mov sp,0x0400" - the first value would be stored at "SS:0x03FE" and the second at "SS:0x03FC". The CPU won't store anything at "SS:0x0400", unless there's bugs in the code and it pops more than it pushed.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
vile

Re:Bootloader hangs

Post by vile »

I see. I never knew about the even and odd thing.
Post Reply