Welcome back to Intro to Intel Assembly Language. If you haven't read them already, I suggest taking a look at the previous tutorials so you don't get lost.
Today we're going to cover software interrupts and system calls, if I have space. Using our knowledge from previous tutorials, we're going to create a printint function and a readint function that, well, do exactly what they say.
Software Interrupts
A long long time ago, in a PC far far away, there was this thing called a BIOS--Basic I/O System. It had two main functions:
1) Load the operating system*
2) Provide a library of functions for programs to use to perform tasks such as drawing on the screen, reading from the keyboard, read and write from files, and so on.
* Actually the BIOS would load a bootstrap loader provided by the operating system in a fixed location on the disk. Because the loader could only be 460 bytes long at most, cramming the entire operating system in there was impractical. So the loader was jammed in there, and it found the operating system on the disk and loaded that.
The BIOS was slow because it was designed to work with just about any hardware, but it was convenient for programmers to use. Eventually faster and more secure device drivers were developed, and calls were made to the operating system, not the BIOS. The operating system then used the device drivers to talk to the hardware. For simplicity we're going to use the BIOS because it's the same across all operating systems. After you're comfortable with that, then I'll get into using system calls (the modern way). There'll be a section on Linux and Windows, so no one really gets left out. (Mac people, go with the Linux part as Macs are based on Unix anyway.)
I must warn you, though, that I cannot guarantee these programs will work on Linux without DOSEMU. The reason is that Linux doesn't let you execute pure binary code, but Windows does provided that it's 64KB or less, subject to a few restrictions on port I/O and other potentially dangerous functions. Linux under DOSEMU works just like Windows in this respect. You cannot use interrupts in modern 32- or 64-bit programs, as the operating system will block them and crash your program for security reasons. Besides, device drivers are faster and provide better control than the BIOS. Really the only time you'd need the BIOS is if you're writing a bootstrap loader or something that cannot run on top of an operating system.
Interrupts
The BIOS library is divided into a bunch of sections called interrupts. Each interrupt handled a different aspect of the computer--keyboard I/O, screen drawing functions, file and disk I/O, and so on. Each interrupt then had a whole bunch of functions, each given a number. They used AH (and if the interrupt had a lot of functions, AX) to determine which function to call. Arguments to the interrupts were passed in registers instead of on the stack to speed things up a bit. Let's look at a simple interrupt call:
INTERRUPT 21H, SUBFUNCTION 3DH: Open File
Arguments
AH - 3Dh
AL - Access and sharing modes (0:read, 1:write, 2:read/write, etc.)
DS:DX - Null-terminated string containing path to file. Must be in 8.3 format.
Return Values
Carry flag clear on sucess, set on error
AX - file handle if CF=0, error code if CF=1
Unless stated, you can safely assume that all registers not listed in the "return values" section are untouched. This is because interrupt calls automatically save all registers (except the stack pointer) before they jump to the interrupt function.
You can get information on every function of every interrupt here.
We're going to concern ourselves with two functions of interrupt 21H: 09H--print string to console, and 0AH--read string from keyboard. If we look at our handy reference, we see:
INT 21H, SUBFUNCTION 09H: Write string to standard output
Arguments
AH - 09H
DS:DX - String terminated by '$'
Return Values
AL is trashed.
----------------
INT 21H, SUBFUNCTION 0AH: Buffered Input
Arguments
AH - 0AH
DS:DX - Buffer
The first byte of the buffer indicates the buffer size. This means that a maximum of 255 bytes may be read.
Upon returning, second byte of the buffer contains the number of characters actually read from STDIN, including the carriage return.
In short, if you want to read X bytes, your buffer needs to be X + 2 bytes long.
Return Values
Buffer filled with user input, second byte contains number of bytes read.
Let's get to it! To create our readint and writeint functions, we first need functions to convert a string to an integer, and an integer to a string. Because I'm lazy and these have little to do with what I'm trying to teach, I'm just going to assume that we have two functions called int2string and string2int with the following prototypes:
Let's try making writeint since I think it's easier:Code:/*returns length of string written*/ uint8_t int2string(uint32_t integer, char *buffer); /*returns int value of string representation*/ uint32_t string2int(const char *str);
Not too bad, now is it? readint takes a little more work:Code:_writeint: push ebp mov ebp, esp ;create 14-byte buffer for our ;integer, since the maximum number ;is 4294967295 (13 chars) plus our ;terminating '$' sub esp, 0eh ;get our 32-bit integer to print mov eax, [ebp + 8] push esp ;buffer address push eax ;integer to convert call _int2string ;AL now contains the number of bytes ;written to our buffer. however, the ;upper 24 bits of EAX contain garbage. ;we need to clear them, so we'll use ;AND with a mask to zero them out. and eax, 000000ffh ;terminate our string with '$' mov [esp + eax], '$' ;print our string to STDOUT. because ;our buffer is in the stack segment, ;we need to make sure that DS=SS. ; ;save DS and then set DS = SS push ds push ss pop ds ;DS:EDX must point to our buffer mov edx, esp mov ah,09h int 21h ;restore DS to original value pop ds ;clean up stack and return add esp, 0eh pop ebp ret
I hope you've enjoyed this introduction to BIOS routines. Next time I'll teach you a few more useful interrupts that you might use for coding a bootloader, and then get on to system calls.Code:_readint: push ebp mov ebp, esp ;create buffer for STDIN input sub esp, 101h ;set buffer length for BIOS routine mov [esp], 0ffh ;same as before push ds push ss pop ds mov edx, esp ;call read routine mov ah, 0ah int 21h ;replace terminating CR with null mov al, [esp + 1] and eax, 000000ffh mov [esp + eax + 1], 00h ;convert to int, skip over buffer length and bytes read counts lea eax, [esp+2] push eax call _string2int ;clean up and return pop ds add esp, 101h pop ebp ret
Next In This Series
Intro to Intel Assembly Language: Part 8
Last edited by dargueta; 11-18-2010 at 07:23 PM.
sudo rm -rf /
Very nice! BIOS calls can also work around some OS imposed restrictions, I would imagine. +rep
No. There is a BIOS interrupt function that allows you to redirect BIOS interrupts to your own code. Operating systems use this to patch in code that performs security checks first. If the requested interrupt is safe to execute, or the calling program has the correct privileges, then the operating system calls the actual BIOS code and returns. Otherwise, the OS blocks the interrupt and takes appropriate action (usually causing an access violation and crashing the program).BIOS calls can also work around some OS imposed restrictions, I would imagine.
EDIT: Slight clarification: Most modern OS's switch into protected mode on startup. Not even the OS can access interrupts in this mode. However, if the operating system finds it has to run a program in 16-bit compatibility mode, it switches to VM86 mode (Virtual Monitored 8086) where the program thinks it's running in 16-bit real mode. Whenever a software interrupt is called, the processor automatically switches over to protected mode and the OS handles the interrupt. It can then switch back to VM86 mode and continue deceiving the program.
A little more info on the different modes of the x86 processor:
Real Mode (The old 8086 - all x86 processors boot in this mode.)
VM86 Mode (Faked real mode)
"Unreal" Mode (Basically real mode tweaked to have more memory)
Protected Mode (The mode most operating systems use to protect programs from overwriting each other)
System Management Mode (Special firmware executes stuff like emergency shutdowns when the CPU gets too hot, etc.)
Long mode (An AMD thing that I don't understand.)
Last edited by dargueta; 03-19-2010 at 01:26 AM. Reason: Clarification
sudo rm -rf /
What I'm thinking of is things like CD-ROM copying that tries to "interpret" the data rather than just copy it. Of course, I could be completely wrong.
You could do the same with read() from /dev/cdrom0 on Linux or ReadFile on \\.\D: (whatever the CDROM drive is) on Windows. It'd probably be faster than using the BIOS, plus you're not stuck using 16-bit pointers and buffer sizes.
Last edited by dargueta; 12-28-2009 at 11:08 AM. Reason: Grammar
sudo rm -rf /
Great job on all 7 tutorials - great explanation, great examples and fantastic way of teaching. I hope to see more. +rep!
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks