I am fairly new to assembly, but I'm trying to dive into the world of low level computing. I'm trying to learn how to write assembly code that would run as bootloader code; so independent of any other OS like Linux or Windows. After reading this page and a few other lists of x86 instruction sets, I came up with some assembly code that is supposed to print 10 A's on the screen and then 1 B.
BITS 16
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
mov cl, 10 ; Use this register as our loop counter
mov ah, 0Eh ; This register holds our BIOS instruction
.repeat:
mov al, 41h ; Put ASCII 'A' into this register
int 10h ; Execute our BIOS print instruction
cmp cl, 0 ; Find out if we've reached the end of our loop
dec cl ; Decrement our loop counter
jnz .repeat ; Jump back to the beginning of our loop
jmp .done ; Finish the program when our loop is done
.done:
mov al, 42h ; Put ASCII 'B' into this register
int 10h ; Execute BIOS print instruction
ret
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55
So the output should look like this:
AAAAAAAAAAB
I assembled the code using the nasm assembler running on the Windows 10 Ubuntu Bash program. After it produced the .bin file, I opened it using a hex editor. I used the same hex editor to copy the contents of that .bin file into the first 512 bytes of a flash drive. Once I had written my program to the flash drive, I disconnected it and plugged it into a computer with an Intel Core i3-7100. On bootup, I selected my USB flash drive as the boot device, only to get the following output:
A
After changing various things in the program, I finally got frustrated and tried the program on another computer. The other computer was a laptop with an i5-2520m. I followed the same process as I mentioned before. Sure enough, it gave me the expected output:
AAAAAAAAAAB
I immediately tried it on my original computer with the i3, but it still didn't work.
So my question is: Why does my program work with one x86 processor but not the other? They both support the x86 instruction set. What gives?
Solution:
Ok, I've been able to track down the real solution with some help. If you read Michael Petch's answer below, you'll find a solution that will fix my problem, and another problem of a BIOS looking for a BPB.
Here was the problem with my code: I was writing the program to the first bytes of my flash drive. Those bytes were loaded into memory, but some BIOS interrupts were using those bytes for itself. So my program was being overwritten by the BIOS. To prevent this, you can add a BPB description as shown below. If your BIOS works the same way mine does, it will simply overwrite the BPB in memory, but not your program. Alternatively, you can add the following code to the top of your program:
jmp start
resb 0x50
start:
;enter code here
This code (courtesy of Ross Ridge) will push your program to memory location 0x50 (offset from 0x7c00) to prevent it from being overwritten by the BIOS during execution.
Also keep in mind that whenever you call any subroutine, the values of the registers you were using could be overwritten. Make sure you either use push
, pop
or save your values to memory before calling a subroutine. Look at Martin Rosenau's answer below to read more about that.
Thank you to all who replied to my question. I now have a better understanding of how this low-level stuff works.
ret
instead of usingcli
/hlt
may possibly have prevented your BIOS from actually getting all the characters displayed. (I think I've read thatint 10h
can return before the characters are actually displayed. I might be totally wrong here, but ending withret
looks obviously wrong. Why would there be a return address on the stack?) – Gauteajmp start
andresb 0x50
after theBITS 16
line and before thestart:
line. – Photomapret
with the endless loop should be done to avoid problems, I doubt it caused the issue you saw. Thepush cx
/pop cx
likely fixes nothing and was a guess by Martin. Although setting the stack is a good idea, the BIOS sets one up that is designed to be big enough for most BIOS call needs. Usually you need to set your own stack when you start writing to memory outside the 512 bytes of the bootloader. – Quartersaw