I am trying to write a simply bootloader in assembler.
The bootloader copies sector 2 from a floppy to address 0x5000 (segment 0x500, offset 0x0), jumps to the segment and prints a message.
However, when I change the segment address to 0x1000, the message does not get printed anymore. I suspect the org 0x10000 instruction has a problem, which might be related to segmentation. I tried org 0x1000:0 too, but the message won't be printed.
Here is my bootloader code, which gets written to the first sector of the floppy:
[BITS 16]
org 0x7C00
start:
mov ah, 0x02 ; Read sectors from drive
mov al, 1 ; Read 1 sector
mov ch, 0 ; Cylinder 0
mov cl, 2 ; Sector 2
mov dh, 0 ; Head 0
mov bx, sect2dest;
mov es, bx
mov bx, 0x0
int 0x13
jmp sect2dest:0;
data:
sect2dest equ 0x500
The magic identifier in the end is written by a custom linking script, so don't worry about that.
Here is my sector two, which should print a message:
[BITS 16]
org 0x5000
sect2:
mov ah, 0x13
mov al, 1
mov bl, 0x17
mov cx, msg_len
mov dh, 0
mov dl, 0
mov bh, 0
mov bp, 0
mov es, bp
mov bp, msg
int 0x10
jmp $
msg db 13,10,"Hello, World!"
msg_len equ $ - msg
As mentioned above, when I try writing sector 2 to any address larger than 0xFFFF, the message doesn't get printed.
Consider that bp is 16 bit, so if you use an ORG of 10000h any offset won't fit in it.
I was expecting the assembler to raise a warning but a quick test shown otherwise.
Remember also that generally is best to avoid challenging the BIOS, thought I don't know how it is actually handled, I would avoid to print a string that strides two segments.
Since you are putting zero in es, make sure that the ORG is at most 10000h-[msg_len], so that the whole string is reachable within es.
Related
For context I'm using NASM on a 64 bit Debian distro.
I'm still learning Assembly as part of writing my own programming language but I recently ran into a problem that I'm not sure how to handle. The following is a snippet of code that my compiler spits out:
section .text
global _start
section .var_1 write
char_1 db 'Q', 0
section .var_2 write
string_1 db 'Asdf', 0
section .var_3 write
char_2 db 'W', 0
section .text
_start:
push 4 ; String length onto stack
push string_1
;; Push a raw char onto the stack
mov bl, [char_1]
push bx
pop ax
pop rbx
pop rcx
mov byte [rbx+rcx], al
If I then print out the value of string_1, I see AsdfWQ. As I understand it, this is because of the mov command I am using to append combined with the fact that I have some data declared after the string's termination character. I've been trying to search around on Google with no luck about how to resolve this problem (partially because I don't know exactly what to search for). Conceptually I would think I could move the address of everything after string_1 by the offset of the length of what I'm appending but this seems highly inefficient if I had something like 40 different pieces of data after that. So what I'm trying to sort out is, how do I manage dynamic data that could increase or decrease in size in assembly?
Edit
Courtesy of fuz pointing out that dynamic memory allocation via the brk calls works, I've revised the program a little but am still experience come issues:
section .var_1 write
hello_string db '', 0
section .var_2 write
again_string db 'Again!', 0
section .text
_start:
;; Get current break address
mov rdi, 0
mov rax, 12
syscall
;; Attempt to allocate 8 bytes for string
mov rdi, rax
add rdi, 8
mov rax, 12
syscall
;; Set the memory address to some label
mov qword [hello_string], rax
;; Try declaring a string
mov byte [hello_string], 'H'
mov byte [hello_string+1], 'e'
mov byte [hello_string+2], 'l'
mov byte [hello_string+3], 'l'
mov byte [hello_string+4], 'o'
mov byte [hello_string+5], ','
mov byte [hello_string+6], ' '
mov byte [hello_string+7], 0
;; Print the string
mov rsi, hello_string
mov rax, 1
mov rdx, 8
mov rdi, 1
syscall
;; Print the other string
mov rsi, again_string
mov rax, 1
mov rdx, 5
mov rdi, 1
syscall
This results in Hello, ello, which means that I'm still overwriting data associated with the again_string label? But I was under the impression that using brk to allocate would do so after the data had been initialized?
I've created a string and turned it into an array. Looping through each index and moving to the al register so it can print out to the vga. The problem is, it prints the size of the string with no problem, but the characters in gibberish. Can you please help me figure out what the problem is in the code. It will be highly appreciated.
org 0
bits 16
section .text
global _start
_start:
mov si, msg
loop:
inc si
mov ah, 0x0e
mov al, [si]
or al, al
jz end
mov bh, 0x00
int 0x10
jmp loop
end:
jmp .done
.done:
jmp $
msg db 'Hello, world!',0xa
len equ $ - msg
TIMES 510 - ($ - $$) db 0
DW 0xAA55
bootloader code
ORG 0x7c00
BITS 16
boot:
mov ah, 0x02
mov al, 0x01
mov ch, 0x00
mov cl, 0x02
mov dh, 0x00
mov dl, 0x00
mov bx, 0x1000
mov es, bx
int 0x13
jmp 0x1000:0x00
times 510 - ($ - $$) db 0
dw 0xAA55
The bootloader
Before tackling the kernel code, let's look at the bootloader that brings the kernel in memory.
You have written a very minimalistic version of a bootloader, one that omits much of the usual stuff like setting up segment registers, but thanks to its reduced nature that's not really a problem.
What could be a problem is that you wrote mov dl, 0x00, hardcoding a zero to select the first floppy as your bootdisk. No problem if this is indeed the case, but it would be much better to just use whatever value the BIOS preloaded the DL register with. That's the ID for the disk that holds your bootloader and kernel.
What is a problem is that you load the kernel to the segmented address 0x1000:0x1000 and then later jump to the segmented address 0x1000:0x0000 which is 4096 bytes short of the kernel. You got lucky that the kernel code did run in the end, thanks to the memory between these two addresses most probably being filled with zero-bytes that (two by two) translate into the instruction add [bx+si], al. Because you omitted setting up the DS segment register, we don't know what unlucky byte got overwritten so many times. Let's hope it was not an important byte...
mov bx, 0x1000
mov es, bx
xor bx, bx <== You forgot to write this instruction!
int 0x13
jmp 0x1000:0x0000
What is a problem is that you ignore the possibility of encountering troubles when loading a sector from the disk. At the very least you should inspect the carry flag that the BIOS.ReadSector function 02h reports and if the flag is set you could abort cleanly. A more sophisticated approach would also retry a limited number of times, say 3 times.
ORG 0x7C00
BITS 16
; IN (dl)
mov dh, 0x00 ; DL is bootdrive
mov cx, 0x0002
mov bx, 0x1000
mov es, bx
xor bx, bx
mov ax, 0x0201 ; BIOS.ReadSector
int 0x13 ; -> AH CF
jc ERR
jmp 0x1000:0x0000
ERR:
cli
hlt
jmp ERR
times 510 - ($ - $$) db 0
dw 0xAA55
The kernel
After the jmp 0x1000:0x0000 instruction has brought you to the first instruction of your kernel, the CS code segment register holds the value 0x1000. None of the other segment registers did change, and since you did not setup any of them in the bootloader, we still don't know what any of them contain. However in order to retrieve the bytes from the message at msg with the mov al, [si] instruction, we need a correct value for the DS data segment register. In accordance with the ORG 0 directive, the correct value is the one we already have in CS. Just two 1-byte instructions are needed: push cs pop ds.
There's more to be said about the kernel code:
The printing loop uses a pre-increment on the pointer in the SI register. Because of this the first character of the string will not get displayed. You could compensate for this via mov si, msg - 1.
The printing loop processes a zero-terminating string. You don't need to prepare that len equate. What you do need is an explicit zero byte that terminates the string. You should not rely on that large number of zero bytes thattimes produced. In some future version of the code there might be no zero byte at all!
You (think you) have included a newline (0xa) in the string. For the BIOS.Teletype function 0Eh, this is merely a linefeed that moves down on the screen. To obtain a newline, you need to include both carriage return (13) and linefeed (10).
There's no reason for your kernel code to have the bootsector signature bytes at offset 510. Depending on how you get this code to the disk, it might be necessary to pad the code up to (a multiple of) 512, so keep times 512 - ($ - $$) db 0.
The kernel:
ORG 0
BITS 16
section .text
global _start
_start:
push cs
pop ds
mov si, msg
mov bx, 0x0007 ; DisplayPage=0, GraphicsColor=7 (White)
jmp BeginLoop
PrintLoop:
mov ah, 0x0E ; BIOS.Teletype
int 0x10
BeginLoop:
mov al, [si]
inc si
test al, al
jnz PrintLoop
cli
hlt
jmp $-2
msg db 'Hello, world!', 13, 10, 0
TIMES 512 - ($ - $$) db 0
Whenever the user inputs s, the expected value in the rax register that the buffer is moved to would be 73, but instead it is a73. Why is this? I need these two values to be equal in order to perform the jumps I need for the user input menu.
On any user input, the information in the register is always preceded by an a, while the register that I use to check for the value is not. This makes it impossible to compare them for a jump.
Any suggestions?
section .data
prompt: db 'Enter a command: '
section .bss
buffer: resb 100; "reserve" 32 bytes
section .text ; code
global _start
_start:
mov rax, 4 ; write
mov rbx, 1 ; stdout
mov rcx, prompt ; where characters start
mov rdx, 0x10 ; 16 characters
int 0x80
mov rax, 3 ; read
mov rbx, 0 ; from stdin
mov rcx, buffer ; start of storage
mov rdx, 0x10; no more than 64 (?) chars
int 0x80
mov rax, [buffer]
mov rbx, "s"
cmp rax, rbx
je _s
; return to Linux
mov rax, 1
mov rbx, 0
int 0x80
_s:
add r8, [buffer]
; dump buffer that was read
mov rdx, rax ; # chars read
mov rax, 4 ; write
mov rbx, 1 ; to stdout
mov rcx, buffer; starting point
int 0x80
jmp _start
If the user types s, followed by <enter>, the memory starting at the address of buffer will contain bytes ['s', '\n', '\0', '\0', ...] (where the newline byte '\n' is from pressing <enter> and the null bytes '\0' are from the .bss section being initialized to 0). As integers, represented in hex, the corresponding values in memory are [0x73, 0x0A, 0x00, 0x00, ...].
The mov rax, [buffer] instruction will copy 8 bytes of memory starting at the address of buffer to the rax register. The byte ordering is little endian on x86, so the 8 bytes will be loaded from memory in reversed order, resulting in rax having 0x0000000000000A73.
Workarounds
This workaround is based on Peter Cordes's comment below. The idea is to compare 1) the first byte starting at the address of buffer with 2) the byte 's'. This would replace the three lines in your question that 1) move [buffer] to rax, 2) move 's' to rbx, and 3) cmp rax, rbx.
cmp byte [buffer], 's'
je _s
This would check that the first character entered is 's', even if followed by other characters. If your intent is to check that only a single character 's' is entered (optionally followed by '\n' in the case that <enter> was pressed to end the input, as opposed to <ctrl-d>), a more thorough approach could utilize the value returned by the read system call, which indicates how many bytes were read.
Without checking how many characters are read, you might want to clear the buffer on each iteration. As is, a user could enter 's' on one iteration, followed by <ctrl-d> on the next iteration, and the buffer would still start with an 's'.
Band-aid Workarounds
(I had originally proposed the following two ideas as workarounds, but they have their own problems that Peter Cordes's identifies in the comments below)
To work around the issue, one option could be to add the newline to your target for comparison.
mov rax, [buffer]
mov rbx, `s\n` ; the second operand was formerly "s"
cmp rax, rbx
je _s
Alternatively, specifying that the read system call only consume 1 byte could be another approach to address the issue.
mov rax, 3 ; read
mov rbx, 0 ; from stdin
mov rcx, buffer ; start of storage
mov rdx, 0x01 ; the second operand was formerly 0x10
int 0x80
I'm trying to create a bootable floppy drive for my boot.bin and kernel.bin files. My boot.asm code is:
bits 16
section .text
start:
JMP MAIN
WAIT_FOR_KEY:
MOV AH, 00H
INT 16H
RET
CLEAR:
MOV AH, 0H ;CHANGING THE VIDEO MODE TO CLEAR THE SCREEN
MOV AL, 03H ;VIDEO MODE COD
INT 10H
RET
LOG_TO_HTS:
PUSH BX
PUSH AX
MOV BX, AX
MOV DX, 0
DIV WORD[ALL_SECTORS]
ADD DL, 01H
MOV CL, DL
MOV AX, BX
MOV DX, 0
DIV WORD [ALL_SECTORS]
MOV DX, 0
DIV WORD[FACES]
MOV DH, DL
MOV CH, AL
POP AX
POP BX
MOV DL, BYTE [BOOT_DEVICE]
RET
ECHO: ;-=-=-=PRINTING FUNCTION =-=-=-;
LODSB ;MOV ONE CHAR FROM SI TO AL AND DELETE IT FROM SI
CMP AL, 0 ;CHECK IF THE VALUE IN AL=0, IF ITS ZERO THEN WE
JE DONE ;WE ARE DONE PRINTING, AND JUMP BACK TO THE MAIN FUNCTION
CMP AL, 59
JE NEWLINE
MOV AH, 0EH ;THE FUNCTION CODE FOR PRINTING (TELETYPE PRINTING)
INT 10H ;BIOS CALL
JMP ECHO ;IF WE ARRIVED TO THIS LINE THATS MEAN WE ARE NOT DONE WITH
;PRINTING THE WHOLE STREING SO JUMP BACK AND COMPLETE THE PROCESS
DONE: ;LABEL CONTAINS ONE INSTRUCTION TO JUMP BACK TO THE LINE
RET ;WHERE WE CALLED THE ECHO FUNCTION
NEWLINE:
PUSHA
MOV AH, 0EH
MOV AL, 13
INT 10H
MOV AL, 10
INT 10H
POPA
RET
RESET_DRIVE: ;RESET FLOPPY DRIVE FUNCTION
MOV AX, 0 ;THE FUNCTION CODE TO RESET DRIVE
MOV DL, BYTE [BOOT_DEVICE] ;DRIVE ID TO RESET
INT 13H
RET
MAIN: ;THE MAIN FUNCTION LABEL
;TO BEGIN LOADING EXTERNAL FILE LIKE KERNEL SOME THINGS MUST BE DONE LIKE :
;1- SETTING UP THE STACK
;2- SETTING UP THE SEGMENTS
;3- SPECIFY THE BOOT DRIVE
;SETTING UP THE STACK
CLI ;CLEAR INTERRUPTS TO SET THE SEGMENT
XOR AX, AX ;SET THE BOTTOM OF THE STACK
MOV SS, AX
MOV SP, 0XFFFF ;SET THE TOP OF THE STACK
STI ;RESTORE THE INTERRUPTS
;SETTING UP THE SEGMENTS
MOV AX, 07C0H
MOV DS, AX
MOV ES, AX
;AFTER SETTING UP SEGMENTS AND STACK, YOU ARE FREE TO CODE WHAT EVER YOU WANT
;THE CPU WILL RECOGNISE YOUR CODE AS A BOOTLOADER
CALL CLEAR
;MOV SI, WELCOME_MSG
;CALL ECHO
MOV [BOOT_DEVICE], DL ;SAVE THE DRIVE ID TO USE IT LATER
;WE WILL USE IN 13H TO LOAD ROOT DIR. THAT WE NEED TO LOAD THE FIRST SECTOR OF THE KERNEL
;THEN WE WILL LOAD FAT TO LOAD THE WHOLE KERNEL SINCE THE FILE LOAD PROCESS CONSISTS OF 3 STEPS
;1- LOAD THE FISRT SECTOR OF THE FILE
;2- LOAD THE WHOLE FILE AND COPY IT INTO RAM
;3- EXCUTE THE FILE
;THE ROOT DIR. AND FAT ARE LOCATED IN SOMEWHERE ON THE FLOPPY DRIVE AS FOLLOWS
;1- SETTING UP AND LOADING THE ROOT DIR.
MOV AH, 02H ;THE FUNCTION CODE FOR LOADING ROOT DIR.
MOV AL, 14 ;THE SIZE OF THE ROOT DIR. THE ROOT DIR. = 14 AND WE WANT TO LOAD IT ALL SO AL=14
MOV BX, TEMP ;THE TEMP STORAGE FOR THE DATA WILL BE READED OR LOADED
MOV CH, 0 ;TRACK WE WANT TO LOAD, ITS TRACK 0 BEACUSE THE ROOT DIR. LOCATED THERE (CYLINDER)
MOV CL, 2 ;SECTOR WE WANT TO LOAD, ITS SECTOR 2 BEACUSE THE ROOT DIR. LOCATED THERE
MOV DH, 1 ;HEAD WE WANT TO LOAD, ITS HEAD 1 BEACUSE THE ROOT DIR. LOCATED THERE
PUSHA ;TO BE ABLE TO RETRY TO LOAD ROOT DIR. IF INT 13H FAILED
LOAD_RD:
INT 13H
JNC LOAD_RD_DONE ;IF WE ARRIVE HERE, THATS MEAN THAT THE ROOT DIR. LOADED SUCCESSFULLY
CALL RESET_DRIVE ;RESET FLOPPY FUNCTION CALL
JMP LOAD_RD ;RETRY IF INT 13H FAILED
LOAD_RD_DONE:
POPA
;NOW WE WILL SEARCH IN THE ROOT DIR. TABLE IF IT CONTAINS THE DESIRED FILE NAME WHICH IS KERNEL.BIN
;WE WILL USE CMPSB INSTRUCTION THAT COMPARES THE VALUE OF DS:SI AND ES:DI
;ES AND DS ARE ALREADY SET
;THE SEARCH WILL BE LIMITED TO SEARCH AMONG 11 BYTES BECAUSE THE FILE NAME IS 11 BYTES
MOV DI, TEMP ;THE RESULT WE GOT (THE ENTRY)
MOV CX, 224 ;THE MAX. POSSIBLE FILE NUMBER THAT WE COULD HAVE IS 224 SO WE LIMIT THE SEARCH TO IT (THE VALUE THAT WILL BE DECREASED WHILE LOOPING)
FIND_KERNEL:
PUSH CX ;PUSH CX AND DI BECAUSE THEY HELD IMPORTANT DATA THAT WE DONT WANT TO LOSE
POP DX
MOV SI, FILE_NAME ;THE VALUE THAT WE WILL COMPARE THE RESULT OF ROOT DIR. TO FIND
MOV CX, 11 ;TO REPEATE THE SEARCH PROCESS 11 TIMES(THE FILE NAME LENGTH)
REP CMPSB ;COPMARES SI WITH DI
JE FOUND_KERNEL ;IF WO GO TO THE NEXT LINE, THAT MEANS THAT THE KERNEL IS NOT FOUNDED YET
;AND SINCE DI IS POINTS TO THE FIRST ENTRY, WE NEED TO MOVE TO THE NEXT ONE BY ADDING 32
ADD AX, 32
MOV DI, TEMP
ADD DI, AX
PUSH DX
POP CX
LOOP FIND_KERNEL
;IF WE ARE HERE, THEN THE KERNEL IS NOT EXISTS
;MOV SI, FAILED_TO_LOAD_KERNEL
;CALL ECHO
CLI
INT 18H ;CRASH AND HALT THE PROCESSOR
FOUND_KERNEL: ;IF WE FOUND THE KERNEL WE WILL BE HERE
;NOW WE HAVE THE ENTRY POINT FOR THE KERNEL SAVED IN DL REGISTER
;WE GOT THE KERNEL FROM THE FIRST 11 BYTES SO WE NEED TO ADD 15 TO GET TO THE 26 BYTES
;THE FIRST SECTOR IS LOCATED AT 26 AND 27 SO WE MUST ADD 15 TO LOAD THE KERNEL IN THE FIRST SECTOR
MOV AX, WORD [DI+15] ;WORD ADDED TO COPY BOTH 26 AND 27 BYTES TO AX, THE WORD IS 2 BYTES
MOV [ENTERY_POINT], AX
;2- SETTING UP AND LOADING THE FAT
MOV AX, 1
MOV BX, TEMP ;THE TEMP STORAGE FOR THE DATA WILL BE READED OR LOADED
CALL LOG_TO_HTS
MOV AH, 2 ;THE FUNCTION CODE FOR LOADING ROOT DIR.
MOV AL, 9 ;THE SIZE OF THE ROOT DIR. THE ROOT DIR. = 14 AND WE WANT TO LOAD IT ALL SO AL=14
PUSHA ;TO BE ABLE TO RETRY TO LOAD ROOT DIR. IF INT 13H FAILED
LOAD_FAT:
INT 13H
JNC LOAD_FAT_DONE ;IF WE ARRIVE HERE, THATS MEAN THAT THE FAT LOADED SUCCESSFULLY
CALL RESET_DRIVE ;RESET FLOPPY FUNCTION CALL
JMP LOAD_FAT ;RETRY IF INT 13H FAILED
LOAD_FAT_DONE: ;IF LOADING FAT DONE SUCCESSFULLY, WE WILL BE HERE
MOV AH, 2
MOV AL, 1
PUSH AX
LOAD_SECTOR: ;ROOT DIR. GIVES US A DATA SECTOR AND WE WANT A LOGICAL SECTOR, WE WILL DO IT HERE
MOV AX, WORD [ENTERY_POINT]
ADD AX, 31 ;31 ADDED TO DONE THE CONVERTION
CALL LOG_TO_HTS ;COVERTS THE LOGICAL SECTORS INTO HEAD TRACK SECTORS
MOV AX, 2000H
MOV ES, AX
MOV BX, WORD[OFFSET_]
POP AX
PUSH AX
INT 13H
JNC GET_NEXT
CALL RESET_DRIVE
JMP LOAD_SECTOR
GET_NEXT:
MOV AX, [ENTERY_POINT]
MOV DX, 0
MOV BX, 6
MUL BX
MOV BX, 4
DIV BX
MOV SI, TEMP
ADD SI, AX
MOV AX, WORD[SI]
OR DX, DX
JZ EVEN
ODD:
SHR AX, 4
JMP SHORT COMP_SECTOR
EVEN:
AND AX, 0FFFH
COMP_SECTOR:
MOV WORD [ENTERY_POINT], AX
CMP AX, 0FF8H
JAE END
ADD WORD[OFFSET_], 512
JMP LOAD_SECTOR
END:
;MOV SI, KERNEL_LOADED_MSG
CALL ECHO
CALL WAIT_FOR_KEY
POP AX
MOV DL, BYTE[BOOT_DEVICE]
JMP 2000H:0000H
OFFSET_ DW 0
ALL_SECTORS DW 18
FACES DW 2
ENTERY_POINT DW 0
;FAILED_TO_LOAD_KERNEL DB "Error, Cannot Load The Kernel. Booting Process Aborted;",0
BOOT_DEVICE DB 0
FILE_NAME DB "KERNEL BIN"
;WELCOME_MSG DB "Litesoft bootloader Copyright (C) 2012-2016 By Mohamed El Sayed;",0
;KERNEL_LOADED_MSG DB "Kernel Loaded, Press Any Key To Continue...",0
TIMES 510 - ($-$$) DB 0 ;LOOK FOR EVERY SINGLE EMPTY BIT AND FILL IT WITH ZERO
;TILL THE BOOTLOADER SIZE BE 512 BYTE
DW 0xAA55 ;THE BOOT SIGNETUARE, ALWAYS THE SAME
TEMP: ;HERE IN BYTE 513 WE HAVE SOME MEMORY THAT WILL NOT BE EXECUTED
;AND WE CAN USE IT AS A TEMP MEMORY
The method i planed to use is creating a floppy image using dd and write my bootloader to the first sector then mount it and copy my kernel to it.
The commands i used
1- mkdosfs -C floppy.flp 1440 , This command creates a floppy image
2- dd status=noxfer conv=notrunc if=boot.bin of=floppy.flp ,This command burns the bootloader to the first sector
Those commands working very will and the floppy image with my bootloader at the first sector created successfully
The problem comes when i try to mount the floppy image to copy the kernel to it using the following commands
rm -rf tmp-loop
mkdir tmp-loop && mount -o loop -t vfat floppy.flp tmp-loop && cp kernel.bin tmp-loop/
Every time i try to mount the floppy image i get this error
mount: wrong fs type, bad option, bad superblock on /dev/loop0,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so.
What is the solution for this error? I'm using Ubuntu 16.04.
You lack a proper BIOS Parameter Block(BPB) at the beginning of your bootloader. This structure contains information used/updated by some BIOSes; can be used by the bootloader to read a FAT file system on unpartitioned media like a floppy; must be present for software like mount to determine what type of media a virtual floppy image represents. In the case of FAT12 on unpartitioned media (like a floppy) file system information is present in the Master Boot Record's BPB. A common layout is the DOS 3.4 EBPB and it could look something like:
start:
JMP MAIN
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 3.4 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
Based on the DOS 3.4 EBPB above and your boot.asm code, this BPB represents one similar to a 1.44MB floppy disk boot sector produced with mkdosfs -C floppy.flp 1440:
bits 16
section .text
start:
JMP MAIN
TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB.
; Dos 3.4 EBPB 1.44MB floppy
OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses
bytesPerSector: dw 512
sectPerCluster: db 1
reservedSectors: dw 1
numFAT: db 2
numRootDirEntries: dw 224
numSectors: dw 2880
mediaType: db 0xf0
numFATsectors: dw 9
sectorsPerTrack: dw 18
numHeads: dw 2
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0x29
volumeID: dd 0x2d7e5a1a
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
WAIT_FOR_KEY:
MOV AH, 00H
INT 16H
RET
CLEAR:
MOV AH, 0H ;CHANGING THE VIDEO MODE TO CLEAR THE SCREEN
MOV AL, 03H ;VIDEO MODE COD
INT 10H
RET
LOG_TO_HTS:
PUSH BX
PUSH AX
MOV BX, AX
MOV DX, 0
DIV WORD[ALL_SECTORS]
ADD DL, 01H
MOV CL, DL
MOV AX, BX
MOV DX, 0
DIV WORD [ALL_SECTORS]
MOV DX, 0
DIV WORD[FACES]
MOV DH, DL
MOV CH, AL
POP AX
POP BX
MOV DL, BYTE [BOOT_DEVICE]
RET
ECHO: ;-=-=-=PRINTING FUNCTION =-=-=-;
LODSB ;MOV ONE CHAR FROM SI TO AL AND DELETE IT FROM SI
CMP AL, 0 ;CHECK IF THE VALUE IN AL=0, IF ITS ZERO THEN WE
JE DONE ;WE ARE DONE PRINTING, AND JUMP BACK TO THE MAIN FUNCTION
CMP AL, 59
JE NEWLINE
MOV AH, 0EH ;THE FUNCTION CODE FOR PRINTING (TELETYPE PRINTING)
INT 10H ;BIOS CALL
JMP ECHO ;IF WE ARRIVED TO THIS LINE THATS MEAN WE ARE NOT DONE WITH
;PRINTING THE WHOLE STREING SO JUMP BACK AND COMPLETE THE PROCESS
DONE: ;LABEL CONTAINS ONE INSTRUCTION TO JUMP BACK TO THE LINE
RET ;WHERE WE CALLED THE ECHO FUNCTION
NEWLINE:
PUSHA
MOV AH, 0EH
MOV AL, 13
INT 10H
MOV AL, 10
INT 10H
POPA
RET
RESET_DRIVE: ;RESET FLOPPY DRIVE FUNCTION
MOV AX, 0 ;THE FUNCTION CODE TO RESET DRIVE
MOV DL, BYTE [BOOT_DEVICE] ;DRIVE ID TO RESET
INT 13H
RET
MAIN: ;THE MAIN FUNCTION LABEL
;TO BEGIN LOADING EXTERNAL FILE LIKE KERNEL SOME THINGS MUST BE DONE LIKE :
;1- SETTING UP THE STACK
;2- SETTING UP THE SEGMENTS
;3- SPECIFY THE BOOT DRIVE
;SETTING UP THE STACK
CLI ;CLEAR INTERRUPTS TO SET THE SEGMENT
XOR AX, AX ;SET THE BOTTOM OF THE STACK
MOV SS, AX
MOV SP, 0XFFFF ;SET THE TOP OF THE STACK
STI ;RESTORE THE INTERRUPTS
;SETTING UP THE SEGMENTS
MOV AX, 07C0H
MOV DS, AX
MOV ES, AX
;AFTER SETTING UP SEGMENTS AND STACK, YOU ARE FREE TO CODE WHAT EVER YOU WANT
;THE CPU WILL RECOGNISE YOUR CODE AS A BOOTLOADER
CALL CLEAR
;MOV SI, WELCOME_MSG
;CALL ECHO
MOV [BOOT_DEVICE], DL ;SAVE THE DRIVE ID TO USE IT LATER
;WE WILL USE IN 13H TO LOAD ROOT DIR. THAT WE NEED TO LOAD THE FIRST SECTOR OF THE KERNEL
;THEN WE WILL LOAD FAT TO LOAD THE WHOLE KERNEL SINCE THE FILE LOAD PROCESS CONSISTS OF 3 STEPS
;1- LOAD THE FISRT SECTOR OF THE FILE
;2- LOAD THE WHOLE FILE AND COPY IT INTO RAM
;3- EXCUTE THE FILE
;THE ROOT DIR. AND FAT ARE LOCATED IN SOMEWHERE ON THE FLOPPY DRIVE AS FOLLOWS
;1- SETTING UP AND LOADING THE ROOT DIR.
MOV AH, 02H ;THE FUNCTION CODE FOR LOADING ROOT DIR.
MOV AL, 14 ;THE SIZE OF THE ROOT DIR. THE ROOT DIR. = 14 AND WE WANT TO LOAD IT ALL SO AL=14
MOV BX, TEMP ;THE TEMP STORAGE FOR THE DATA WILL BE READED OR LOADED
MOV CH, 0 ;TRACK WE WANT TO LOAD, ITS TRACK 0 BEACUSE THE ROOT DIR. LOCATED THERE (CYLINDER)
MOV CL, 2 ;SECTOR WE WANT TO LOAD, ITS SECTOR 2 BEACUSE THE ROOT DIR. LOCATED THERE
MOV DH, 1 ;HEAD WE WANT TO LOAD, ITS HEAD 1 BEACUSE THE ROOT DIR. LOCATED THERE
PUSHA ;TO BE ABLE TO RETRY TO LOAD ROOT DIR. IF INT 13H FAILED
LOAD_RD:
INT 13H
JNC LOAD_RD_DONE ;IF WE ARRIVE HERE, THATS MEAN THAT THE ROOT DIR. LOADED SUCCESSFULLY
CALL RESET_DRIVE ;RESET FLOPPY FUNCTION CALL
JMP LOAD_RD ;RETRY IF INT 13H FAILED
LOAD_RD_DONE:
POPA
;NOW WE WILL SEARCH IN THE ROOT DIR. TABLE IF IT CONTAINS THE DESIRED FILE NAME WHICH IS KERNEL.BIN
;WE WILL USE CMPSB INSTRUCTION THAT COMPARES THE VALUE OF DS:SI AND ES:DI
;ES AND DS ARE ALREADY SET
;THE SEARCH WILL BE LIMITED TO SEARCH AMONG 11 BYTES BECAUSE THE FILE NAME IS 11 BYTES
MOV DI, TEMP ;THE RESULT WE GOT (THE ENTRY)
MOV CX, 224 ;THE MAX. POSSIBLE FILE NUMBER THAT WE COULD HAVE IS 224 SO WE LIMIT THE SEARCH TO IT (THE VALUE THAT WILL BE DECREASED WHILE LOOPING)
FIND_KERNEL:
PUSH CX ;PUSH CX AND DI BECAUSE THEY HELD IMPORTANT DATA THAT WE DONT WANT TO LOSE
POP DX
MOV SI, FILE_NAME ;THE VALUE THAT WE WILL COMPARE THE RESULT OF ROOT DIR. TO FIND
MOV CX, 11 ;TO REPEATE THE SEARCH PROCESS 11 TIMES(THE FILE NAME LENGTH)
REP CMPSB ;COPMARES SI WITH DI
JE FOUND_KERNEL ;IF WO GO TO THE NEXT LINE, THAT MEANS THAT THE KERNEL IS NOT FOUNDED YET
;AND SINCE DI IS POINTS TO THE FIRST ENTRY, WE NEED TO MOVE TO THE NEXT ONE BY ADDING 32
ADD AX, 32
MOV DI, TEMP
ADD DI, AX
PUSH DX
POP CX
LOOP FIND_KERNEL
;IF WE ARE HERE, THEN THE KERNEL IS NOT EXISTS
;MOV SI, FAILED_TO_LOAD_KERNEL
;CALL ECHO
CLI
INT 18H ;CRASH AND HALT THE PROCESSOR
FOUND_KERNEL: ;IF WE FOUND THE KERNEL WE WILL BE HERE
;NOW WE HAVE THE ENTRY POINT FOR THE KERNEL SAVED IN DL REGISTER
;WE GOT THE KERNEL FROM THE FIRST 11 BYTES SO WE NEED TO ADD 15 TO GET TO THE 26 BYTES
;THE FIRST SECTOR IS LOCATED AT 26 AND 27 SO WE MUST ADD 15 TO LOAD THE KERNEL IN THE FIRST SECTOR
MOV AX, WORD [DI+15] ;WORD ADDED TO COPY BOTH 26 AND 27 BYTES TO AX, THE WORD IS 2 BYTES
MOV [ENTERY_POINT], AX
;2- SETTING UP AND LOADING THE FAT
MOV AX, 1
MOV BX, TEMP ;THE TEMP STORAGE FOR THE DATA WILL BE READED OR LOADED
CALL LOG_TO_HTS
MOV AH, 2 ;THE FUNCTION CODE FOR LOADING ROOT DIR.
MOV AL, 9 ;THE SIZE OF THE ROOT DIR. THE ROOT DIR. = 14 AND WE WANT TO LOAD IT ALL SO AL=14
PUSHA ;TO BE ABLE TO RETRY TO LOAD ROOT DIR. IF INT 13H FAILED
LOAD_FAT:
INT 13H
JNC LOAD_FAT_DONE ;IF WE ARRIVE HERE, THATS MEAN THAT THE FAT LOADED SUCCESSFULLY
CALL RESET_DRIVE ;RESET FLOPPY FUNCTION CALL
JMP LOAD_FAT ;RETRY IF INT 13H FAILED
LOAD_FAT_DONE: ;IF LOADING FAT DONE SUCCESSFULLY, WE WILL BE HERE
MOV AH, 2
MOV AL, 1
PUSH AX
LOAD_SECTOR: ;ROOT DIR. GIVES US A DATA SECTOR AND WE WANT A LOGICAL SECTOR, WE WILL DO IT HERE
MOV AX, WORD [ENTERY_POINT]
ADD AX, 31 ;31 ADDED TO DONE THE CONVERTION
CALL LOG_TO_HTS ;COVERTS THE LOGICAL SECTORS INTO HEAD TRACK SECTORS
MOV AX, 2000H
MOV ES, AX
MOV BX, WORD[OFFSET_]
POP AX
PUSH AX
INT 13H
JNC GET_NEXT
CALL RESET_DRIVE
JMP LOAD_SECTOR
GET_NEXT:
MOV AX, [ENTERY_POINT]
MOV DX, 0
MOV BX, 6
MUL BX
MOV BX, 4
DIV BX
MOV SI, TEMP
ADD SI, AX
MOV AX, WORD[SI]
OR DX, DX
JZ EVEN
ODD:
SHR AX, 4
JMP SHORT COMP_SECTOR
EVEN:
AND AX, 0FFFH
COMP_SECTOR:
MOV WORD [ENTERY_POINT], AX
CMP AX, 0FF8H
JAE END
ADD WORD[OFFSET_], 512
JMP LOAD_SECTOR
END:
;MOV SI, KERNEL_LOADED_MSG
CALL ECHO
CALL WAIT_FOR_KEY
POP AX
MOV DL, BYTE[BOOT_DEVICE]
JMP 2000H:0000H
OFFSET_ DW 0
ALL_SECTORS DW 18
FACES DW 2
ENTERY_POINT DW 0
;FAILED_TO_LOAD_KERNEL DB "Error, Cannot Load The Kernel. Booting Process Aborted;",0
BOOT_DEVICE DB 0
FILE_NAME DB "KERNEL BIN"
;WELCOME_MSG DB "Litesoft bootloader Copyright (C) 2012-2016 By Mohamed El Sayed;",0
;KERNEL_LOADED_MSG DB "Kernel Loaded, Press Any Key To Continue...",0
TIMES 510 - ($-$$) DB 0 ;LOOK FOR EVERY SINGLE EMPTY BIT AND FILL IT WITH ZERO
;TILL THE BOOTLOADER SIZE BE 512 BYTE
DW 0xAA55 ;THE BOOT SIGNETUARE, ALWAYS THE SAME
TEMP: ;HERE IN BYTE 513 WE HAVE SOME MEMORY THAT WILL NOT BE EXECUTED
;AND WE CAN USE IT AS A TEMP MEMORY
If you generate boot.bin from this code and place it in a floppy image and use the command to display file type information:
file floppy.flp
It should produce something like this if it worked:
floppy.flp: , code offset 0x99+3, OEM-ID "mkfs.fat", root entries 224, sectors 2880 (volumes <=32 MB) , sectors/FAT 9, sectors/track 18, serial number 0x2d7e5a1a, unlabeled, FAT (12 bit), followed by FAT
If you get similar output it should be mountable with the mount command.
As you may have noticed the data within an EBPB/BPB can be used to navigate over a FAT12 file system.
I have writen this little experiement bootstrap that has a getline and print_string "functions". The boot stuff is taken from MikeOS tutorial but the rest I have writen myself. I compile this with NASM and run it in QEMU.
So the actual question: I've declared this variable curInpLn on line 6. What ever the user types is saved on that variable and then after enter is hit it is displayed to the user with some additional messages. What I'd like to do is to clear the contents of curInpLn each time the getline function is called but for some reason I can't manage to do that. I'm quite the beginner with Assmebly at the moment.
You can compile the code to bin format and then create a floppy image of it with: "dd status=noxfer conv=notrunc if=FILENAME.bin of=FILENAME.flp" and run it in qemu with: "qemu -fda FILENAME.flp"
BITS 16
jmp start
welcomeSTR: db 'Welcome!',0
promptSTR: db 'Please prompt something: ',0
responseSTR: db 'You prompted: ',0
curInpLn: times 80 db 0 ;this is a variable to hold the input 'command'
curCharCnt: dw 0
curLnNum: dw 1
start:
mov ax, 07C0h ; Set up 4K stack space after this bootloader
add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
mov ss, ax
mov sp, 4096
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
call clear_screen
lea bx, [welcomeSTR] ; Put string position into SI
call print_string
call new_line
.waitCMD:
lea bx, [promptSTR]
call print_string
call getLine ; Call our string-printing routine
jmp .waitCMD
getLine:
cld
mov cx, 80 ;number of loops for loopne
mov di, 0 ;offset to bx
lea bx, [curInpLn] ;the address of our string
.gtlLoop:
mov ah, 00h ;This is an bios interrupt to
int 16h ;wait for a keypress and save it to al
cmp al, 08h ;see if backspace was pressed
je .gtlRemChar ;if so, jump
mov [bx+di], al ;effective address of our curInpLn string
inc di ;is saved in bx, di is an offset where we will
;insert our char in al
cmp al, 0Dh ;see if character typed is car-return (enter)
je .gtlDone ;if so, jump
mov ah, 0Eh ;bios interrupt to show the char in al
int 10h
.gtlCont:
loopne .gtlLoop ;loopne loops until cx is zero
jmp .gtlDone
.gtlRemChar:
;mov [bx][di-1], 0 ;this needs to be solved. NASM gives error on this.
dec di
jmp .gtlCont
.gtlDone:
call new_line
lea bx, [responseSTR]
call print_string
mov [curCharCnt], di ;save the amount of chars entered to a var
lea bx, [curInpLn]
call print_string
call new_line
ret
print_string: ; Routine: output string in SI to screen
mov si, bx
mov ah, 0Eh ; int 10h 'print char' function
.repeat:
lodsb ; Get character from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp .repeat
.done:
ret
new_line:
mov ax, [curLnNum]
inc ax
mov [curLnNum], ax
mov ah, 02h
mov dl, 0
mov dh, [curLnNum]
int 10h
ret
clear_screen:
push ax
mov ax, 3
int 10h
pop ax
ret
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55 ; The standard PC boot signature
I haven't written code in Assembly for 20 years (!), but it looks like you need to use the 'stosw' instruction (or 'stosb'). STOSB loads the value held in AL to the byte pointed to by ES:DI, whereas STOSSW loads the value held in AX to the word pointed to by ES:DI. The instruction automatically advances the pointer. As your variable curInpLn is 80 bytes long, you can clear it with 40 iterations of STOSW. Something like
xor ax, ax ; ax = 0
mov es, ds ; point es to our data segment
mov di, offset curInpLn ; point di at the variable
mov cx, 40 ; how many repetitions
rep stosw ; zap the variable
This method is probably the quickest method of clearing the variable as it doesn't require the CPU to retrieve any instructions from the pre-fetch queue. In fact, it allows the pre-fetch queue to fill up, thus allowing any following instructions to execute as quickly as possible.