Hi, in the seventh and last part of my x86 assembler series, I want to talk about interrupts.
In the last part we had a closer look at functions and function calls. But to use the full power of x86 assembler we also need to understand interrupts.
But before we can work with interrupts, we must first understand what an interrupt is and what happens during an interrupt.
An interrupt is a possibility, with which events as for example keyboard inputs, network packets etc... can be tapped, in order to process them in the further course of the data processing. In addition an interrupt is more efficient than the polling, because not in a continuous loop everything must be queried, and unnecessary computing time is needed, where the processor can do first nothing else.
When an interrupt occurs, the process currently being processed by the CPU is interrupted internally in the operating system. All states of the process are saved and the interrupt service routine (ISR) is executed according to its interrupt number. After the interrupt has been processed, all saved states of the interrupted process are restored and the process continues to run at the point where it was interrupted. The interrupting program does not notice that it was interrupted.
There are 3 types of interrupts:
-
Hardware interrupt Such interrupts come from the hardware itself, e.g. on a keyboard when a key is pressed.
-
Software interrupt: Such interrupts are created by the software, e.g. in the Linux kernel with the instruction "int".
-
Exceptions: Are generated by the CPU when e.g. a page fault or a devide by zero occurs.
Here I would like to deal however only with the software interrupts.
In the x86 assembler world, we have also for the interrupts also commands, these look e.g. like this:
INTO(INT 4), INT 3, INT n
I want to introduce a few of them to you.
-
INTO: This interrupt has the opcode
CE
, and calls interrupt service routine for the overflow flag, if the OF flag = 1. -
INT 0: Here an interrupt is only triggered if a division by 0 occurs.
-
INT 3: This instruction is quite special because it is used by debuggers when breakpoints are involved. This instruction has the opcode
CC
and can be used everywhere without overwriting other instructions. -
INT 0x0E: This interrupt is triggered when we have a page fault. A page fault is when memory tries to access a page, i.e. a 4KB block of memory that does not exist or is protected.
-
INT 0x80: Is for Linux, a very important interrupt command, because here programs have the possibility to execute syscalls or system calls. Here one gives for the call the necessary parameters like Syscallnummer, Filedescriptoren, program names, arguments etc... with.
But it is important that every system handles its interrupt numbers differently. Here an overview which interrupts, generally valid and which can be defined system differently.
Interruptnumber | Purpose |
---|---|
0x00 | Division by 0 |
0x01 | Reserved |
0x02 | NMI (Non-Maskable-Interrupt) |
0x03 | Breakpoint (INT3) |
0x04 | Overflow (INTO) |
0x05 | Bounds range exceeded (BOUND) |
0x06 | Invalid opcode |
0x07 | Device not available |
0x08 | Double Fault |
0x09 | Coprocessor segment overrun |
0x0A | Invalid TSS |
0x0B | Segment not present |
0x0C | Stack-segment fault |
0x0D | General protection fault |
0x0E | Page fault |
0x0F | Reserviert |
0x10 | x87 FPU Error |
0x11 | Alignment check |
0x12 | Machine check |
0x13 | SIMD Floating-Point Exception |
0x14 - 0x1F | Reserved |
0x20 - 0xFF | User-definable |
Here we see that on an x86 CPU we have an interrupt vector, i.e. the table above, with 255 different interrupts. Some are generally defined and some can be defined by the developer like the interrupt number .
Ok let's make a small practical example of an interrupt. We make an interrupt 0x80
on a Linux system and see what happens.
section .data
msg db 'hello world', 0x0a
section .text
global _start
_start:
mov al, 4; Syscallnummer write
mov ebx, 1; Filedescriptor (0 = stdin, 1 = stdout, 2 = stderr)
mov ecx, msg; Address of the string
mov edx, 12; Length of the string including 0x0a
int 0x80; Trigger Syscall
mov al, 1; Syscallnumber exit
mov bl, 0; Exitcode
int 0x80; Trigger Syscall
If we look at the code, we see that first we create a string "hello world", with a new-line. What we do after that is that we write in the EAX register, our syscall number, so we determine which syscall should be executed.
The EBX register gets the number of our filedescriptor. With the filedescriptor under Linux, we have 3 types. One is the filedescriptor stdin, which has the number 0. This is meant to read in e.g. input in a shell. The filedescriptor with the number 1, is the normal output stdout, which shows us e.g. data in the shell. For error output there is the filedescriptor stderr, which has the number 2. Here all error messages are forwarded.
We assign the address of the created string "hello world" to the ECX register. Finally, we have to specify how long the string or the byte string is. To execute our syscall now, we only have to execute the int
instruction with the operands 0x80
. Now an interrupt is triggered, which gives us "hello world" in the shell. But so that now no segmentationfault comes as before, we can also make a program exit.
Here we give again in the EAX register our syscall number. Exit has the syscall number 1 and as exit code, we give the 0. Again we execute this with int 0x80
and the program is ended cleanly.
The termination of the program is to be considered as in C/C++ with return 0. Internally the return value is executed with this instruction.
If we just run the program now, we can see how it behaves.
I have again linked the syscall table for a 32bit Linux here. https://syscalls.kernelgrok.com/
I hope you enjoyed the topic interrupts and see you in the next post 😄