Page 1 of 2

JMP vs. RETF [Solved] + Problem with Bootloader [SOLVED!]

Posted: Mon Apr 13, 2009 6:40 am
by kay10
Hi,
I don't know whether I should use JMP or RETF in the following code when jumping from bootloader to kernel to execute it.

Code: Select all

[...]

CALL READ_FAT
CALL READ_ROOT
MOV SI, KernelName
CALL READ_ROOT_FILE
MOV WORD [FreeMem], 0
MOV BX, 0x1000
MOV ES, BX
XOR BX, BX
CALL READ_FILE                     ; Reads data to ES:BX
XOR BX, BX
CALL EXEC_FILE

[...]

EXEC_FILE:
PUSH ES                            ; ES = 0x1000
PUSH BX                            ; BX = 0x0000
RETF                               ; <- how can I replace this part with JMP?
And if I should use JMP, how do I write it correctly with NASM syntax?
My second problem is, the code above works on bochs, QEMU and VirtualBox but not on a real pc, I don't know how to solve it.
I think there might be a mistake in the READ_FILE part. I'll post it if you need it.
Can you help me?

EDIT: The description of the bootloader problem starts at post 8.

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 7:08 am
by XanClic
Many people use RETF (at least many tutorials use it), but I don't, I use JMP.

My implementation is

Code: Select all

jmp   0x1000:0x0000
If this should not work, use

Code: Select all

jmp far 0x1000:0x0000
P. S.: I have never had any problems with JMP.

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 7:49 am
by Love4Boobies
I see absolutely no reason for using RETs since the kernel isn't supposed to return. If you use GCC, you could even skip all the stack mumbo-jumbo at the start-up (check the GCC manual for the appropriate flag) and if you do an infinite loop (for safety) before the function returns, GCC will detect that and not even generate a RET.

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 8:15 am
by kay10
I think you misunderstood me, the RETF should execute the kernel, not a normal program.

It is a part of my bootloader.
I add some comments to the code.

Code: Select all

MOV BX, 0x1000                       
MOV ES, BX                         ; ES = 0x1000
XOR BX, BX                         ; BX = 0x0000
CALL READ_FILE                     ; load kernel into memory
XOR BX, BX                         ; BX = 0x0000

EXEC_FILE:
PUSH ES                            ; ES = 0x1000 (kernel segment)
PUSH BX                            ; BX = 0x0000 (kernel offset)
RETF                               ; execute kernel
I hope it's understandable now.

EDIT: JMP 0x1000:0x0000 works for me, but I'm looking for something like this:

Code: Select all

JMP ES:BX                          ; execute program/kernel/whatever
so I can change the segment/offset and don't "hardcode" it to 0x1000:0x0000

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 8:32 am
by Love4Boobies
Sorry, I semi-read what you wrote on my mobile phone and thought I got you right :) Still, I see no reason for using RETs in this case either. Not that it would matter but it's also a bit slower to use the stack. One of the reasons is cause you'd do a few extra memory accesses.

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 8:48 am
by kay10
Yeah, the extra memory accesses are the reason why I'm searching an alternative
(And, of course, why my code doesn't work on a real pc too, but I think that's another problem... :wink: )

I tried some combinations with JMP, but none worked for me

Code: Select all

JMP ES:BX
JMP [ES:BX]
JMP [ES]:[BX]
JMP FAR ES:BX
JMP FAR [ES:BX]
JMP FAR [ES]:[BX]
Isn't there any working combination? :(

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 8:54 am
by earlz
EA cd JMP ptr16:16
FF /5 JMP m16:16
those are the only two far jmp types in the x86 architecture. You can either use an immediate segment and offset, or you can use a pointer to a segment and offset block(of 4 bytes in realmode)

So simply put, you should stick to the retf method unless you want to do somethign like

Code: Select all

mov [pointer+2],es
mov [pointer],bx
jmp far [pointer]
And it is more size-efficient to use the retf method. It is probably more speed-efficient as well....

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 9:19 am
by kay10
Ok, thanks for the recommendation.
My first problem is solved now, I don't want to create another thread for the second.
As I said in my first post, my code only works perfectly in Bochs, QEMU and VirtualBox but not on my real pc.

After loading the FAT, the Root Directory and searching for the kernel.bin, the cursor keeps blinking and it happens nothing anymore.
(It should now execute the kernel, but it doesn't)

I think the problem is somewhere around "loading kernel into memory" and "executing kernel".
Maybe I should post my whole code?

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 9:34 am
by Love4Boobies
Try zeroing memory. That's the most often cause for such problems.

Re: JMP or RETF?

Posted: Mon Apr 13, 2009 12:57 pm
by kay10
I don't know if I did it right or not, but I zeroed 1024 Byte from 0x1000:0x0000,
copied the kernel at this place and it still don't work.

I post the code of my bootloader here (if you want it, I'll try to translate all the comments into english):

Code: Select all


[BITS 16]
[ORG 0x0000]					; NICHT 0x7C00 -> Wird später selbst in Register eingetragen

; FAT12 Daten
JMP START
OSName			db 'OS      '
BytesPerSec		dw 512
SecPerClus		db 1
RsvdSecCnt		dw 1
NumFATs			db 2
RootEntCnt		dw 224
TotSec			dw 2880
MediaType		db 0xF0
FATSize			dw 9
SecPerTrack		dw 18
NumHeads		dw 2
HiddenSec		dd 0
TotSec32		dd 0
DrvNum			db 0x00
Reserved		db 0
BootSig			db 0x29
VolumeID		dd 00000000h
VolumeLabel		db 'NO NAME    '
FileSysType		db 'FAT12   '
;********************;


; Variablen
LoadMsg db "L",13,10,0
RebootMsg db "P",13,10,0
;-----
BootDrive db 0				; Speicherort des Bootlaufwerks
DataName db "KERNEL  BIN"	; Name des Kernels

DataSektor dw 0
FreeMem dw 512
AbsoluteSector db 0
AbsoluteHead db 0
AbsoluteTrack db 0
;********************;


START:
MOV AX, 0x07C0				; Vorbereiten für den Sprung zu "MAIN"
MOV ES, AX					; ES -> Neues Extrasegment
MOV DS, AX					; DS -> Neues Datensegment
JMP WORD 0x07C0:MAIN		; Springe zum 0x07C0:MAIN-Code -> Durch BIOS dorthin kopiert

MAIN:
CLI							; Interrupte verbieten
MOV AX, 0x9000				; Vorbereiten für Verschiebung des Stacks
MOV SS, AX					; Verschiebe den Stack nach 0x9000
XOR SP, SP					; Stackpointer auf Null setzen -> Keine Variablen auf dem Stack
STI							; Interrupte erlauben

MOV [BootDrive], DL			; Laufwerk speichern, von dem gebootet worden ist

MOV SI, LoadMsg
CALL PRINT

CALL  ZeroMemory
CALL READ_FAT
MOV SI, LoadMsg
CALL PRINT
CALL READ_ROOT
MOV SI, LoadMsg
CALL PRINT
MOV SI, DataName
CALL READ_ROOT_FILE
MOV WORD [FreeMem], 0
MOV BX, 0x1000
MOV ES, BX					; ES -> Segment für die Speicherung des Kernels
XOR BX, BX					; BX -> Offset für die Speicherung des Kernels (BX = 0)
CALL READ_FILE				; Kernel wird an die Adresse 1000:0000 geladen (ES:BX) -> entspricht absoluter Adresse 0x10000
XOR BX, BX
CALL EXEC_FILE


;************************************************;
; Ausgabe von Text
; DS:SI -> Mit "0" endender String
;************************************************;
PRINT:
LODSB
OR AL, AL					; Letztes Zeichen?
JZ SHORT .1					; Wenn ja, stoppe die Zeichenausgabe
MOV AH, 0x0E
MOV BX, 0x0007
INT 0x10
JMP SHORT PRINT
.1:
RETN


;************************************************;
; Speicher mit 0 überschreiben
;************************************************;
ZeroMemory:
PUSH ES
PUSH DI
MOV CX, 1024
MOV AX, 0x1000
MOV ES, AX
XOR AX, AX
MOV DI, AX
REP STOSB
POP DI
POP ES
RET

;************************************************;
; CX -> Anzahl der Sektoren die gelesen werden sollen
; AX -> Anfangssektor(1. Durchgang, alle anderen Durchgänge: Sektor der gelesen wird)
; ES:BX -> Speicher zu dem geschrieben wird
;************************************************;
READ_SECTORS:
PUSH DI
MOV DI, 5					; Error Zähler (Maximal 5 Errors beim Lesen erlaubt)
.1:
PUSH AX
PUSH CX
PUSH DX
CALL LBAToCHS
MOV AX, 0x0201
XOR CX, CX
MOV CH, BYTE [AbsoluteTrack]
; SHR CX, 2
ADD CL, BYTE [AbsoluteSector]
MOV DH, BYTE [AbsoluteHead]
MOV DL, BYTE [BootDrive]
INT 0x13					; Lesen nach ES:BX
JNC SHORT .2				; Überprüfen ob beim Lesen ein Fehler aufgetreten ist
XOR AX, AX					; Wenn ja, Wiederholung einleiten...
INT 0x13					; Diskettenlaufwerk zurücksetzen
DEC DI						; Errorzähler verringern (Wenn 0 -> Lesevorgang gescheitert)
POP DX
POP CX						; CX wiederherstellen (Der Sektor muss nochmals gelesen werden)
POP AX						; AX wiederherstellen (Der Sektor muss nochmals gelesen werden)
JNZ SHORT .1				; Wiederholung durchführen
POP DI
JMP Failure
.2:							; Kein Fehler beim Lesen des Sektors
POP DX
POP CX
POP AX
ADD BX, WORD [BytesPerSec]
INC AX						; Nächster Sektor
LOOP .1						; Zum Einlesen springen
POP DI
RET

READ_FAT:
MOV AX, [RsvdSecCnt]		; AX -> Startsektor(-Cluster) der FAT Tabelle
MOV CX, WORD [FATSize]		; CX -> Größe der FAT Tabelle in Sektoren(Clustern)
MOV BX, WORD [FreeMem]		; BX -> Adresse von freiem Speicher
CALL READ_SECTORS			; Sektoren einlesen
MOV WORD [FreeMem], BX		; Adresse von freiem Speicher in "FreeMem" schreiben
RET


;************************************************;
; Laden des Root Verzeichnisses in den Speicher
;
;************************************************;
READ_ROOT:
XOR CX, CX
MOV AX, 32
MUL WORD [RootEntCnt]
DIV WORD [BytesPerSec]
XCHG AX, CX					; CX -> Größe des Root Ordners in Sektoren(Clustern)
MOV AL, [NumFATs]
MUL WORD [FATSize]
ADD AX, [RsvdSecCnt]		; AX -> Startsektor(-Cluster) des Root Ordners
MOV BX, WORD [FreeMem]		; BX -> Adresse von freiem Speicher
MOV WORD [DataSektor], AX
ADD WORD [DataSektor], CX	; [DataSektor] -> 1. Datensektor
CALL READ_SECTORS
MOV WORD [FreeMem], BX		; Adresse von freiem Speicher in "FreeMem" schreiben
RET


;************************************************;
; Suchen einer Datei im Root Verzeichnis
; DS:SI -> Name der Datei die gesucht werden soll
;
; Rückgabe:
; AX -> Eintrag aus der FAT Tabelle, Zeiger auf ersten Cluster der Datei
;************************************************;
READ_ROOT_FILE:
XOR CX, CX					; CX auf 0 setzen
MOV AX, 32					; 32 in AX schreiben (Größe eines Eintrages im Root Verzeichnis in Byte)
MUL WORD [RootEntCnt]		; Mit der Anzahl an maximalen Einträgen multiplizieren
XCHG CX, AX					; Werte von AX und CX austauschen
MOV AX, WORD [FreeMem]
SUB AX, CX
MOV CX, WORD [RootEntCnt]	; CX -> Anzahl der maximalen Einträge im Root Verzeichnis (Vorbereitung auf Schleife)
MOV DI, AX					; DI -> Anfang des Root Verzeichnisses im Speicher
.1:
PUSH CX
MOV CX, 0x000B
MOV SI, DataName
PUSH DI
CLD							; Direction Flag auf 0 setzen
REPE CMPSB					; Vergleiche Dateinamen
POP DI
JE SHORT .2					; Wenn Dateinamen gleich, springe zu .2
POP CX
ADD DI, 0x0020
LOOP .1
XOR AX, AX
RET
.2:
INC SP
INC SP
MOV AX, WORD [DI+26]		; DI+26 -> Zeiger auf den ersten Cluster der Datei
RET


;************************************************;
; Lesen einer Datei in den Speicher
; AX -> Zeiger auf den ersten FAT Cluster der Datei
; ES:BX -> Speicher, zu dem die Datei geschrieben werden soll
; DS -> Segment in dem der Bootloader anfängt
;
; Rückgabe:
; ES:BX -> Zeiger auf den Beginn der Datei im Speicher
;************************************************;
READ_FILE:
PUSH AX
;PUSH AX
CALL ClusterToLBA			; AX -> Zeiger auf die Datei im Datenbereich
XOR CX, CX					; CX auf 0 setzen
MOV CL, BYTE [SecPerClus]	; CX -> Anzahl der Sektoren die gelesen werden sollen
MOV BX, WORD [FreeMem]		; BX -> Freier Speicher
CALL READ_SECTORS
MOV WORD [FreeMem], BX

POP AX
XOR DX, DX					; DX auf 0 setzen (Vorbereitung für Rechnung)
MOV BX, 0x0003				; 3 in BX schreiben
MUL BX						; AX mit BX multiplizieren
DEC BX						; BX um 1 verringern (BX = 2)
DIV BX						; DX -> AX % 2 (-> Erkennung ob AX gerade oder ungerade)

MOV BX, WORD [BytesPerSec]
ADD BX, AX
MOV AX, WORD [DS:BX]

CMP DX, 1					; Erkennung ob DX 0 oder 1 enthält (-> AX gerade oder ungerade)
JZ .2						; Wenn gerade, springe zu .2

.1:
AND AX, 0000111111111111b	; Lösche die Bits 9-12 (bzw. 8-11, wenn die Zählung bei 0 anfängt)
JMP SHORT .3

.2:
SHR AX, 4					; verschiebe alle Bits in AX um 4 nach rechts und fülle die Lücke mit 0 (z.B. 0101110110010101 -> 0000010111011001)

.3:
CMP AX, 0x0FF0
JB READ_FILE
RET

;************************************************;
; Ausführen einer Datei im Speicher ES:BX
; ES -> Speichersegment
; BX -> Offset
;************************************************;
EXEC_FILE:
PUSH ES
PUSH BX
RETF						; Springe "zurück" zu ES:BX


;************************************************;
; Convert Cluster to LBA
; LBA = (cluster - 2) * sectors per cluster
; AX -> Logical Adress to convert
;************************************************;
ClusterToLBA:
DEC AX
DEC AX
MOV CL, BYTE [SecPerClus]
MUL CL
ADD AX, WORD [DataSektor]
RET

;************************************************;
; Konvertiere LBA Adresse zu CHS
; AX -> LBA Addresse zum konvertieren
;
; absolute sector = (logical sector / sectors per track) + 1
; absolute head   = (logical sector / sectors per track) MOD number of heads
; absolute track  = logical sector / (sectors per track * number of heads)
;
;************************************************;
LBAToCHS:
PUSH BX
PUSH CX
PUSH DX

XOR BX, BX
XOR DX, DX
XCHG AX, BX
MOV AX, WORD [NumHeads]
MUL WORD [SecPerTrack]
XCHG AX, BX						; BX -> [NumHeads] * [SecPerTrack]
DIV BX
MOV BYTE [AbsoluteTrack], AL
MOV AX, DX
XOR DX, DX
DIV WORD [SecPerTrack]
MOV BYTE [AbsoluteHead], AL
INC DL
MOV BYTE [AbsoluteSector], DL

POP DX
POP CX
POP BX
RET

Failure:
MOV SI, RebootMsg
CALL PRINT
XOR AH, AH
INT 0x16
INT 0x19

times 512-($-$$)-2 db 0		; Größe des Bootloaders auf 512 Byte erhöhen durch Auffüllen des freien Plaztes mit Nullen
dw 0AA55h
Can you see any mistakes in there or things, which could be done better?

EDIT: Cleaned up the code a little bit

Re: JMP vs. RETF [Solved] + Problem with Bootloader [Unsolved]

Posted: Mon Apr 13, 2009 4:27 pm
by Love4Boobies
I didn't get the chance to take a close look at it yet but I'll be sure sure to edit my post when I do. Note that:
  • You don't really need to clear the interrupt flag when setting up the stack. The MOV instruction setting SS and the one after it (whatever it is) are executed atomically by the CPU. Just be sure the next one is "MOV SP,..."
  • The BIOS Boot Specification states that the boot sector is loaded at 0000:7C00h, not 07C00:0000h (although they do refer to the same physical address). You can just set the origin to 7C00h (using the ORG directive) and delete all the 07C0h:LABEL jumps/calls. They are pretty error-prone.
  • When zeroing memory, be sure you know if DF in EFLAGS is set or not. You need it cleared, so put a CLD instruction somewhere.

Re: JMP vs. RETF [Solved] + Problem with Bootloader [Unsolved]

Posted: Mon Apr 13, 2009 7:36 pm
by earlz
Love4Boobies wrote: [*]The BIOS Boot Specification states that the boot sector is loaded at 0000:7C00h, not 07C00:0000h (although they do refer to the same physical address). You can just set the origin to 7C00h (using the ORG directive) and delete all the 07C0h:LABEL jumps/calls. They are pretty error-prone.
7C00:0000=0x7C000, where 0:7C00=0x7C00.. there is a difference, and is very likely the cause of your problems.

Re: JMP vs. RETF [Solved] + Problem with Bootloader [Unsolved]

Posted: Tue Apr 14, 2009 2:56 am
by Love4Boobies
Sorry, I made a typo there. Wrote 07C00h instead of 07C0h. If you look at his code, it's 07C0h. And that resolves to the same address.

Re: JMP vs. RETF [Solved] + Problem with Bootloader [Unsolved]

Posted: Tue Apr 14, 2009 5:23 am
by kay10
Love4Boobies wrote:If you look at his code, it's 07C0h. And that resolves to the same address.
You're right, I did some of your recommendations and got some questions:
Love4Boobies wrote:
  • You don't really need to clear the interrupt flag when setting up the stack. The MOV instruction setting SS and the one after it (whatever it is) are executed atomically by the CPU. Just be sure the next one is "MOV SP,..."
Many tutorials told me to clear interrupt flag before setting up the stack, should I just ignore them now? :?
Love4Boobies wrote:
  • The BIOS Boot Specification states that the boot sector is loaded at 0000:7C00h, not 0x07C0:0x0000 (although they do refer to the same physical address). You can just set the origin to 7C00h (using the ORG directive) and delete all the 07C0h:LABEL jumps/calls. They are pretty error-prone.
It looks like I have to rewrite a lot of my code because it works nowhere, when I change the ORG directive to 0x7C00.
I deleted the whole "START" label, now he jumps directly to the "MAIN" label. I did some debugging with Bochs and saw the new problem starts in READ_FAT when READ_SECTORS is called, it chooses the right CHS adress but I looks like it "int 0x13" doesn't load it correctly, because the CF was set, so it loads again and again...

I would prefer my old code, cause it works in Bochs, and some other emulators/virtual machines :mrgreen:
I hope my old code isn't the reason why it doesn't work on a real pc :?

Re: JMP vs. RETF [Solved] + Problem with Bootloader [Unsolved]

Posted: Tue Apr 14, 2009 5:38 am
by Love4Boobies
kay10 wrote:
Love4Boobies wrote:If you look at his code, it's 07C0h. And that resolves to the same address.
You're right, I did some of your recommendations and got some questions:
Love4Boobies wrote:
  • You don't really need to clear the interrupt flag when setting up the stack. The MOV instruction setting SS and the one after it (whatever it is) are executed atomically by the CPU. Just be sure the next one is "MOV SP,..."
Many tutorials told me to clear interrupt flag before setting up the stack, should I just ignore them now? :?
Well, it's just a little paragraph in one of the Intel manuals. Whoever wrote those tutorials is probably not aware of this feature. It's frankly not very important either, it just saves you 2 bytes. Most tutorials are based on other tutorials before them so you'd expect most of them to have CLI & STI there.
Love4Boobies wrote:
  • The BIOS Boot Specification states that the boot sector is loaded at 0000:7C00h, not 0x07C0:0x0000 (although they do refer to the same physical address). You can just set the origin to 7C00h (using the ORG directive) and delete all the 07C0h:LABEL jumps/calls. They are pretty error-prone.
It looks like I have to rewrite a lot of my code because it works nowhere, when I change the ORG directive to 0x7C00.
I deleted the whole "START" label, now he jumps directly to the "MAIN" label. I did some debugging with Bochs and saw the new problem starts in READ_FAT when READ_SECTORS is called, it chooses the right CHS adress but I looks like it "int 0x13" doesn't load it correctly, because the CF was set, so it loads again and again...

I would prefer my old code, cause it works in Bochs, and some other emulators/virtual machines :mrgreen:
I hope my old code isn't the reason why it doesn't work on a real pc :?
Where's the infinite loop in your old code?