Skip to main content

ASM x86 – Interrupts

· 5 min read
Strider

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.

InterruptnumberPurpose
0x00Division by 0
0x01Reserved
0x02NMI (Non-Maskable-Interrupt)
0x03Breakpoint (INT3)
0x04Overflow (INTO)
0x05Bounds range exceeded (BOUND)
0x06Invalid opcode
0x07Device not available
0x08Double Fault
0x09Coprocessor segment overrun
0x0AInvalid TSS
0x0BSegment not present
0x0CStack-segment fault
0x0DGeneral protection fault
0x0EPage fault
0x0FReserviert
0x10x87 FPU Error
0x11Alignment check
0x12Machine check
0x13SIMD Floating-Point Exception
0x14 - 0x1FReserved
0x20 - 0xFFUser-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 128100x8016128_{10} \Leftrightarrow 0x80_{16}.

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.

asciicast

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 😄