views:

265

answers:

2

Hello. I've been working through the tutorials on this webpage which progressively creates a bootloader that displays Hello World.

The 2nd tutorial (where we attempt to get an "A" to be output) works perfectly, and yet the 1st tutorial doesn't work for me at all! (The BIOS completely ignores the floppy disk and boots straight into Windows). This is less of an issue, although any explanations would be appreciated.

The real problem is that I can't get the 3rd tutorial to work. Instead on outputting "Hello World", I get an unusual character (and blinking cursor) in the bottom-left corner of the screen. It looks a bit like a smiley face inside a rounded rectangle. Does anyone know how to get Hello World to display as it should?

+1  A: 

I think the problem is likely to be related to the origin specified.

[ORG 0x7C00]    ;Origin, tell the assembler that where the code will

Based on the conversation we've been having it appears that the address isn't as predicted in some way. It might simply that DS the data segment register is not what you expect. You might actually be able to get the original listing from the web page to work by adding a push and pop of ds before the call to display the string like this,

 push cs
 pop ds

If not the following code works.

 [ORG 0x000]    ; switched to 0 since we are going to try to correct it ourself

 call nextinstruction
 nextinstruction:    ; get the return address of the call into dx
 pop dx              ; which is essentially the start of the code + 3 (3 bytes for the call instruction)
 MOV SI, HelloString ;Store string pointer to SI
 add si, dx          ; add IP from start of program
 sub si, 3           ; subtract the 3 the call instruction probably took
 push cs
 pop ds              ; make ds the same as cs.  
 CALL PrintString   ;Call print string procedure
 JMP $      ;Infinite loop, hang it here.

This code figures out the offset at runtime of the code being run and also makes sure DS is point to the same segment. Unless otherwise noted instructions involving SI generally also use DS as their code segment to reference the memory.

DS is a segment register and you might want to read something like the Art of Assembly to learn more.

Earlz is also doing the same sort of thing, just making sure the registers are correct so that the memory address is referenced correctly. It's just he knows more about the boot sector specifics than me.

Colin Newell
Like I said, tutorial 2 works fine, proving that the BIOS is set up correctly for booting from a floppy. It's only when it's told to do NOTHING BUT HANG (tutorial 1) that the BIOS ignores it.I have to be honest I'm new to this whole bootloading thing - this tutorial is all that I've done. I assumed I *was* loading via the boot sector - what makes you think I'm not? Also, as the tutorial expains, the program is compiled using NASM into a raw binary file (not a COM file).But I'll give your suggestion a try anyway.
Newbie
True, I misinterpretted what you said. In that case I suspect the origin is still the cause of the problem, but figuring out the actual cause is likely to require printing out the register IP to figure out the actual origin. I'll see if I can come up with something to help.
Colin Newell
OK, I've tried your sugestion. I still don't get "Hello World", but I don't get that unusual face character anymore either. I simply get a blinking cursor towards the top-left (as opposed to the bottom corner, where the unusual character has previously been).
Newbie
Thanks for your help. What's this "register IP" and how can I obtain it?
Newbie
IP is the instruction pointer. You can kind of reference it in the same sort of way that you reference AL and SI but it's not quite that simple. With IP for example there are some extra restrictions on how you can copy it about. Then you need to convert it to something useful to you. I'll try to knock something up for you to demonstrate.
Colin Newell
I've just tested your additional code. The only effect it has had is to shift the unusual character (and blinking cursor) most of the way across the screen. It is still at the bottom of the screen.
Newbie
I've just added the push cs, pop ds to see if that will fix it.
Colin Newell
Thanks for all your help so far. Earlz answer seems to work anyway, but I'll give your ammendment a try.
Newbie
Colin, I'm pleased to say your code works on my physical BIOS also! One minor difference I've spotted is that, whilst Ctrl+Alt+Del restarts the system in both cases, with yours I have to take care to ensure that Ctrl+Alt are pressed (and held) BEFORE Del. Anyway, now that we know it works, would you mind explaining your lines in a little more detail please?
Newbie
I've added a bit more explanation to the answer and tweaked it since we know what works.
Colin Newell
Thanks Colin, I'll make sure to keep all commented code to hand as I learn more Assembly.
Newbie
+6  A: 

You say "boot straight into windows" so I assume you are using a physical PC. Future note to make: Always use an emulator for development! It's just easier. I like Bochs for OSDeving cause it has nice debugging features. Now, onto the possible solution.

There are a lot of buggy BIOSes that break the informal specifications of the IBM PC for the 0x7C00 load address.

This can give a lot of problems with memory addresses and such whenever you are assembling. So make the beginning look like this:

[BITS 16] ;tell the assembler that its a 16 bit code
[ORG 0x7C00] ;this tells the assembler where the code will be loaded at when it runs on your machine. It uses this to compute the absolute addresses of labels and such.

jmp word 0:flush ;#FAR jump so that you set CS to 0. (the first argument is what segment to jump to. The argument(after the `:`) is what offset to jump to)
;# Without the far jmp, CS could be `0x7C0` or something similar, which will means that where the assembler thinks the code is loaded and where your computer loaded the code is different. Which in turn messes up the absolute addresses of labels.
flush: ;#We go to here, but we do it ABSOLUTE. So with this, we can reset the segment and offset of where our code is loaded.
mov BP,0 ;#use BP as a temp register
mov DS,BP ;#can not assign segment registers a literal number. You have to assign to a register first.
mov ES,BP ;#do the same here too
;#without setting DS and ES, they could have been loaded with the old 0x7C0, which would mess up absolute address calculations for data. 

See, some load at 0x07C0:0000 and most load(and its considered proper to) at 0x0000:7C00. It is the same flat address, but the different segment settings can really screw up absolute memory addresses. So let's remove the "magic" of the assembler and see what it looks like (note I don't guarantee addresses to be completely correct with this. I don't know the size of all opcodes)

jmp word 0:0x7C04 ;# 0x7C04 is the address of the `flush` label 
...

So, we jump to an absolute address.

Now then. What happens when we don't do this?

take this program for example:

mov ax,[mydata]
hlt

mydata: dw 500 ;#just some data

This disassembles to something like

mov ax,[0x7C06] 

Oh, well it uses absolute addressing, so how could that go wrong? Well, what if DS is actually 0x7C0 ? then instead of getting the assembler expected 0:0x7C06 it will get 0x7C0:0x7C06 which are not the same flat address.

I hope this helps you to understand. It's really a complicated topic though and takes a while of low level programming to fully understand.

Earlz
Hi, thanks for the reply. Unfortunately your code is not syntaxly correct in NASM. It tells me that there is a mismatch in the operand size (??) on the line beginning "jmp FAR 0x0000...". Still, thanks for thanks emulator tip.
Newbie
@Newbie yea my NASM syntax is a bit rusty try `jmp word 0:begin`
Earlz
Just installed Bochs and booted the original tutorial code into it. The code form each tutorial now works! Question is, is it my phyiscal BIOS that is non-standard, or is it the code? (Surely all x86 compatible BIOSes must comply with the same standards???) I'll try your ammendment on my PCs physical BIOS anyway.
Newbie
@Newbie Ah, if it doesn't work on your PC then yes, the physical BIOS is most likely slightly-buggy. If you do very much OS deving you'll soon learn that most manufacturers are satisfied if it works with Windows and don't care if it breaks the informal standard to save a few pennies per computer. (and yes, the IBM PC "standard" is informal. There is no committee that determines it and it isn't included in any ISO or ANSI)
Earlz
Oh I see. Anyway, I've tested your code out again, this time using "jmp word 0:begin", and it works in Bochs. It ALSO WORKS on my PHYSICAL BIOS!!! :D Thanks for your help! (Would you mind explaining each of your lines in a little more detail, please?)
Newbie
@Newb I tried to explain it a bit more in detail. You'll understand it eventually, but it is a complex topic because it goes below the assembler level and into the machine code level
Earlz
Thanks Earlz :)
Newbie
I've read it and I can't claim to understand it all, but I'll certainly keep it to hand as I learn more Assembly. Just for clarity, the comments on the line "jmp word 0..." refers to a FAR jump and then two numbers. I only see WORD jump and one number (0). Could you check and/or rephrase this comment please?
Newbie
`jmp word X:Y` is the NASM syntax for a far jump. ah but yea, the second is really a label. *fixes
Earlz
@Newb ok it's fixed now and I also added `#` to the comments so that the colors look right in SO.
Earlz
That's great Earlz, thanks for all your help today.
Newbie