Assembly programming can be intimida
Found at: gopher.blog.benjojo.co.uk:70/interactive-x86-bootloader-tutorial
x86 assembly doesn't have to be scary (interactive)
===
Assembly programming can be intimidating for people who have
never looked into it any deeper than a glance, but giving that
it underpins how the computers we use work it can be helpful
having context in regards to what is actually being run by the
CPU.
IBM PC
Photo by: [Drew Sadik]
x86 PCs are impressive in that they are fully backwards
compatible with the original IBM PC. All the way down to timing
circuits and boot process. This is both great in that almost all
PCs should be able to run software from 1983, but this
backwards compatibility also has a very high cost. The x86 ISA is
inframous in it's edge cases and complexity.
To boot a modern x86 PC, you need to step through the
history of x86, this is handled by the bootloader in 99% of
cases:
flow of a modern system booting
For the case of our demo's we are going to focus on the
first mode. 16 bit "real mode" is the mode that DOS and other
vintage operating systems run on. This mode offers no system
security that we are used to on our modern systems. When a program
is run it has full access to the system and can alter the
operating system if it wants to. This allowed a creative virus scene
to spring up for DOS (though these viruses were more annoying
than actually malicious)
This mode also has a very simple model for interacting
with hardware. Because PCs had a wide range of hardware they
outsourced a lot of the code to interact with hardware with software
interrupts (often handled by the BIOS)
This makes the initial stages of OS development easy, since
you only need to know a handful of software interrupts to get
most of of the I/O work done.
With that, we can start on our first demo:
box = document.getElementById("nasm_1")
box.value = '[BITS 16]
bit code\n[ORG 0x7C00];Origin, tell the assembler that where the
code will\n ;be in memory after it is been
loaded\n\nmov ah, 0x0A ; Set the call type for the BIOS\nmov al, 66
; Letter to display\nmov cx, 1 ; Times to print it\nmov
bh, 0 ; Page number\n\nint 0x10 ; Call the BIOS
to print letter\n\nhlt ; Don\'t do anything
else.\n\nTIMES 510 - ($ - $$) db 0
0xAA55
triggerbutton = document.getElementById("compile_1");
aaa = function() {
console.log('prerun');
nasmbox =
document.getElementById("nasm_1");
console.log(nasminst.FS_createDataFile)
window.nasminst.FS_createDataFile("/",
"lol.asm", nasmbox.value, true, true);
console.log("file made")
window.shouldRunNow = true
};
isAppleSafari = function() {
var is_safari =
navigator.userAgent.toLowerCase().indexOf('safari/') > -1;
var is_apple = navigator.userAgent.toLowerCase().indexOf('apple')
> -1;
var is_chrome =
navigator.userAgent.toLowerCase().indexOf('chrome/') > -1;
return is_safari && !is_chrome && is_apple
}
triggerbutton.onclick = function(){
if(isAppleSafari()) {
alert("Sorry, Safari is the only browser that doesnt work
with this, I don't have the time to figure out why :(")
return
}
if(window.emulator) {
window.emulator.stop()
window.emulator.screen_adapter.timer=function(){return}
window.emulator.v86.do_tick = function(){return}
window.emulator.v86.do_run = function(){return}
window.emulator.v86.cpu.run_hardware_timers =
function(){return}
window.emulator.v86.register_tick = function(){return}
window.emulator.speaker_adapter.dac.audio_context.suspend()
}
var note = document.getElementById("screen_container_1");
note.innerHTML='';
window.shouldRunNow=false;
window.nasminst = nasm({
'arguments':["-f", "bin", "/lol.asm", "-o", "/lol.bin"],
//
print: (function() {
var element =
document.getElementById('output_1');
if (element) element.value = ''; // clear
browser cache
return function(text) {
if (arguments.length > 1) text =
Array.prototype.slice.call(arguments).join(' ');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight;
// focus on bottom
}
};
})(),
printErr: (function() {
var element =
document.getElementById('output_1');
if (element) element.value = ''; // clear
browser cache
return function(text) {
if (arguments.length > 1) text =
Array.prototype.slice.call(arguments).join(' ');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight;
// focus on bottom
}
};
})(),
noInitialRun: true
})
nasminst.preRun.push(aaa)
nasminst["run"]()
console.log("Built??")
var element = document.getElementById('output_1');
element.value += "Assembly done!" + "\n";
window.compiledOutput = nasminst.FS.readFile("/lol.bin");
window.NAR = new Uint8Array(2880 * 512);
for (let i = 0; i < window.compiledOutput.length; i++) {
window.NAR[i] = window.compiledOutput[i];
}
window.emulator = new V86Starter({
memory_size: 1*1024*1024,
vga_memory_size: 1*1024*1024,
disable_mouse: true,
screen_container:
document.getElementById("screen_container_1"),
bios: {
url: "/asset/ELYnVTTTtI",
},
vga_bios: {
url: "/asset/x7sn4T45K4",
},
fda: {
buffer: window.NAR.buffer,
size: 2880 * 512,
},
autostart: true,
});
setTimeout(function() {
window.emulator.stop()
window.emulator.screen_adapter.timer=function(){return}
window.emulator.v86.do_tick = function(){return}
window.emulator.v86.do_run = function(){return}
window.emulator.v86.cpu.run_hardware_timers =
function(){return}
window.emulator.v86.register_tick = function(){return}
window.emulator.speaker_adapter.dac.audio_context.suspend()
},5 * 1000);
};
You can play around with the demo above. The assembler
(NASM) and VM is being run in your browser thanks to emscripten
and V86.
So what is happening here? The `mov` instruction moves data
from one place to another. In this case we are purely setting
registers in the CPU and not touching RAM in the code. The `int`
instruction fires off a software interrupt, in our case the 16th one.
After that, we invoke `hlt` to stop the CPU from doing
anything else. Since we are done with what we wanted to do.
But hang on, how did we even get to this stage? What did
the computer do to get our code running just then?
The BIOS is generally the first thing that gets run on
the system, its job is to setup and test that the computer is
working correctly, It also writes out information about the system
so that operating systems later on can easily detect hardware.
Assuming the BIOS knows what device it wants to boot off,
it will load the first sector (512 bytes) from that, into a
standardised position of memory, and then hand over control by jumping
to that part of memory
memory loading
In the demo above, the browser made the 512 byte payload,
padded it into a 1.4MB floppy disk image, and gave it into a
emulator to run.
So what happens when the program you need to run is more
than 512 bytes?
Luckily for us the BIOS has us covered with disk I/O as
well with int 13h calls that allow easy access to the drives
in the system.
box = document.getElementById("nasm_2")
box.value = "[BITS 16]\norg 0x7C00\nstart:\n ;
This section of code is added based on Michael Petch's
bootloader tips\n xor ax,ax ; We want a segment
of 0 for DS for this question\n mov ds,ax \n
mov bx,0x8000 ; Stack segment can be any
usable memory\n mov ss,bx ; Top of the stack
@ 0x80000.\n mov sp,ax ; Set SP=0 so
the bottom of stack will be just below 0x90000\n
cld ; Set the direction flag to be positive
direction\n mov ah, 0x02\n mov al, 1 \n
mov ch, 0 \n mov cl, 2 \n
mov dh, 0 \n mov bx, new \n
mov es, bx \n xor bx, bx\n
int 0x13\n jmp new:0\ndata:\n new
equ 0x0500\ntimes 510-($-$$) db 0 \ndw 0xaa55
\nsect2:\n mov ax, cs\n
mov ds, ax ; Set CS=DS. CS=0x0500, therefore DS=0x500\n
; If variables are added to
this code then this\n ;
will be required to properly reference them\n
; in memory\n mov ax, 0xB800\n
mov es, ax\n mov byte [es:420],
'H'\n mov byte [es:421], 0x48\n mov byte
[es:422], 'E'\n mov byte [es:423], 0x68\n mov
byte [es:424], 'L'\n mov byte [es:425], 0x28\n
mov byte [es:426], 'L'\n mov byte [es:427],
0x38\n mov byte [es:428], 'O'\n mov byte
[es:429], 0x18\n mov byte [es:430], '!'\n mov
byte [es:431], 0x58\n hlt\n"
triggerbutton = document.getElementById("compile_2");
aaa2 = function() {
console.log('prerun');
nasmbox =
document.getElementById("nasm_2");
console.log(nasminst.FS_createDataFile)
window.nasminst.FS_createDataFile("/",
"lol.asm", nasmbox.value, true, true);
console.log("file made")
window.shouldRunNow = true
};
triggerbutton.onclick = function(){
if(isAppleSafari()) {
alert("Sorry, Safari is the only browser that doesnt work
with this, I don't have the time to figure out why :(")
return
}
if(window.emulator2) {
window.emulator2.stop()
window.emulator2.screen_adapter.timer=function(){return}
window.emulator2.v86.do_tick = function(){return}
window.emulator2.v86.do_run = function(){return}
window.emulator2.v86.cpu.run_hardware_timers =
function(){return}
window.emulator2.v86.register_tick = function(){return}
window.emulator2.speaker_adapter.dac.audio_context.suspend()
}
var note = document.getElementById("screen_container_2");
note.innerHTML='';
window.shouldRunNow=false;
window.nasminst = nasm({
'arguments':["-f", "bin", "/lol.asm", "-o", "/lol.bin"],
//
print: (function() {
var element =
document.getElementById('output_2');
if (element) element.value = ''; // clear
browser cache
return function(text) {
if (arguments.length > 1) text =
Array.prototype.slice.call(arguments).join(' ');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight;
// focus on bottom
}
};
})(),
printErr: (function() {
var element =
document.getElementById('output_2');
if (element) element.value = ''; // clear
browser cache
return function(text) {
if (arguments.length > 1) text =
Array.prototype.slice.call(arguments).join(' ');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight;
// focus on bottom
}
};
})(),
noInitialRun: true
})
nasminst.preRun.push(aaa2)
nasminst["run"]()
console.log("Built??")
window.compiledOutput = nasminst.FS.readFile("/lol.bin");
var element = document.getElementById('output_2');
element.value += "Assembly done!" + "\n";
window.NAR = new Uint8Array(2880 * 512);
for (let i = 0; i < window.compiledOutput.length; i++) {
window.NAR[i] = window.compiledOutput[i];
}
window.emulator2 = new V86Starter({
memory_size: 1*1024*1024,
vga_memory_size: 1*1024*1024,
disable_mouse: true,
screen_container:
document.getElementById("screen_container_2"),
bios: {
url: "/asset/ELYnVTTTtI",
},
vga_bios: {
url: "/asset/x7sn4T45K4",
},
fda: {
buffer: window.NAR.buffer,
size: 2880 * 512,
},
autostart: true,
});
setTimeout(function() {
window.emulator2.stop()
window.emulator2.screen_adapter.timer=function(){return}
window.emulator2.v86.do_tick = function(){return}
window.emulator2.v86.do_run = function(){return}
window.emulator2.v86.cpu.run_hardware_timers =
function(){return}
window.emulator2.v86.register_tick = function(){return}
window.emulator2.speaker_adapter.dac.audio_context.suspend()
},5 * 1000);
};
This code loads a segment from the next sector of the
virtual floppy disk, and then jumps to it! The int 13h API is
pretty simple, and gives away some of the low level details of
how storage works (though with the invention of flash memory, a
lot of these values are emulated to retain compatibility)
int13 breakdown
There are a lot of BIOS provided services out there, some
are more official than others, there is a great reference guide
at http://www.ctyme.com/intr/rb-0608.htm (Though I do warn you
about the 90's style porn banner ads at the bottom of these
pages)
Hopefully this was a fun intro, You can move onwards and
even switch to C (or Rust!) and find more information about the
underlying hardware of most of the computers you sit down and use
daily on http://wiki.osdev.org/
If you found this post interesting, you might like the
other things I've done! If not you can always stay up to date
with when I push out a new post by following me on Twitter,
or using my blog's RSS if that is more of your thing.
Until next time!