Exceptions¶
Learning Objectives: Firmware and exceptions during program execution.
Firmware¶
Firmware, known in the PC world as BIOS (Basic Input Output System), connects the computer system's components into a functioning computer. It initializes and boots the computer by starting the command interpreter or operating system and provides low-level interfaces (e.g., protections) for accessing hardware resources for the operating system and applications. In the PC world, various BIOS (or more modernly UEFI) systems include user-visible system setup programs and hardware tests performed during boot.
Generally, to ensure hardware compatibility, firmware provides a standardized integration environment for components from different manufacturers, enabling computer systems to be built with parts from multiple sources. This idea was pivotal (originally developed by IBM) to the success of PC technology in the early days of personal computing.
The services of firmware/BIOS are accessed through two mechanisms:
- By software interrupts from machine code (e.g., trap instructions)
- Example: The BIOS interrupt list for PC personal computers.
- Through system calls from program code, which are used as if they were ready-made subroutine libraries.
- Example: A boot sequence using the BIOS.
Example: Printing a character on the screen using a BIOS call and a software interrupt:
mov ah, 0x0e # Register AH function = 0x0E (Display Character) mov al, 0x56 # Register AL character to print int 0x10 # Interrupt 0x10 (BIOS video service)
Firmware is often stored in read-only memory (ROM) as machine/microcode that the program code calls using jump instructions, much like regular subroutines. In early personal computers, firmware was implemented on a ROM chip integrated into the processor’s circuit board and visible as part of program memory. BIOS updates were done by physically replacing the chip.
Example: The Kernal functions in the Commodore 64’s firmware were implemented using machine code.
BIOS Call Layering¶
System calls often have a higher-level wrapper function that invokes the actual low-level BIOS system call. For instance, in the C programming language, the standard library (unistd.h) provides system calls for programs, which are then used by standard library implementations.
Since these calls are still from a lower/more hardware-adjacent layer, they require the programmer to consider more hardware/operating system-specific details. This kind of layering from the program through the operating system to the firmware is necessary because system calls have broader permissions for system resources (kernel mode) than the calling program (user mode). Firmware calls are therefore "hidden" from the programmer.
For example, in the x86 processor architecture, there are different protection rings. Additionally, some processors have protected instructions that only system calls can execute. Before executing the call, the firmware verifies the permissions.
Exceptions and their management¶
Exceptions¶
In general, an exception is an event that originates outside the program being executed, interrupts the program's control flow (engl. control flow), and requires the processor to handle it by executing an exception handler (engl. exception handler). After the handler is executed, the processor resumes the program from where it was interrupted.
The event may be related to the instruction being executed, such as attempting to divide by zero. External events can also interrupt the program execution, such as a message from an I/O device. Internally, the processor changes its state, for example, by setting status flags or modifying register values, which the operating system/program/firmware can read to determine what exception occurred.
Exception handling is thus a collaboration between hardware and software. The hardware sets the state change, and the software reacts to it. Generally, operating systems/firmware have default handlers for exceptions, but custom handlers can be written selectively for specific cases. Processors also support software-defined exceptions, which programmers can use freely.
In this way, exceptions provide a mechanism for collaboration between applications and the operating system. Applications can request lower-level services, such as I/O, from the operating system via exceptions. The operating system then manages this task in collaboration with the firmware. Similarly, peripherals communicate with applications via exceptions.
In systems supporting multitasking, exceptions are often used to control the concurrent execution of different program tasks. Examples include SensorTag’s RTOS libraries like Task, Pin, UART, etc.
Handling Exceptions¶
Exceptions are identified by their unique (positive, starting from 0) identifier number. This order is determined during the design phase of the processor hardware or during the BIOS initialization phase. The identifier number also indicates the priority order of the exceptions, with the smallest number representing the highest priority. Based on these numbers, an exception table is created, which stores the handler's memory address for each exception. Thus, handling an exception involves an (unconditional) jump to the memory address where the handler code is located. In some architectures (e.g., modern x86), a parameter can be passed to the handler, similar to subroutine calls.
For example, the x86 interrupt descriptor table. In embedded systems, this is called an interrupt vector table.
Often, the exception table itself is located at the very beginning of memory, starting at address
0
. During system startup, the firmware and/or operating system initializes the exception table with default values. Operating systems can usually also create their exceptions towards the end of the exception table. Similarly, applications can modify the vector memory addresses within the limits of their permissions. In embedded systems, for instance, programmers typically have the freedom to modify the interrupt vector values while implementing handlers.Each exception must have a handler , which can either be the operating system’s default handler or a custom handler written by the programmer. As presented earlier in the context of interrupts, a handler functions similarly to a subroutine, but with key differences under the hood:
- During an exception, in addition to general registers, the status register is also saved. Some processors have dedicated registers to store the exception context, such as the address of the instruction that caused the exception.
- If a stack is used, it is the operating system’s stack, not the program’s stack.
- Some processors have dedicated machine instructions for returning from exception handlers, separate from subroutine return instructions. In x86, these are
rti
(return from interrupt) andrts
(return from subroutine). - A handler may terminate program execution, meaning it never returns.
- The return address is not always the next instruction of the program but can also re-execute the current instruction.
- Exception handlers, especially those of the operating system, often have broader, if not full, privileges over system resources compared to user programs.
Types of Exceptions¶
We have already encountered one type of exception, interrupts, in earlier material. However, there are actually other types of exceptions.
Interrupt¶
An interrupt is caused by an asynchronous (time and program execution-independent) signal either internally within the processor or externally from a peripheral device. The processor checks the status of interrupt flags after executing each instruction. An interrupt does not interrupt the execution of a machine instruction already being executed. The setting of the interrupt flag in the status register is typically handled by the microarchitecture and/or firmware. When the interrupt flag is raised in the status register, the corresponding handler is executed. After the handler execution, the program resumes and executes its next instruction, depending on the interrupt type.
Hardware interrupts are referred to by two terms: IRQ (Interrupt Request) and NMI (Non-maskable Interrupt). IRQ is reserved for peripherals, connected to the processor via control circuits. NMI, on the other hand, is reserved for monitoring the internal operation of the central processing unit, such as preventing division by zero or handling memory errors, and thus has higher priority. When a hardware interrupt occurs, the corresponding handler in the firmware is executed similarly.
Example: The x86 family has 16 hardware interrupts (divided into two controllers).
Software interrupts, which are often system calls (see below), may have dedicated instructions in the machine language. For example, the x86 instruction
int
below. In x86, the interrupt call int 21
requests the BIOS service 0x2a
to retrieve the date, calculated by the integrated real-time clock component.mov ah,2ah # Service number in AH register
int 21h # Interrupt executes desired service
System Call¶
Firmware services are partly managed by the operating system, accessed via system calls. Examples of system calls include I/O operations like reading a file or multitasking functionalities such as starting another process/program or terminating the current program. System calls are synchronous since they are invoked at specific points in the program.
System calls may be executed using dedicated machine instructions (trap instructions) or function calls (with parameters passed in registers). Additionally, some processors have protected instructions that only system calls can execute.
Example of a C program (linux/x86) using system calls instead of printf. The first parameter of the write function,
1
, refers to the output stream ( stdout), typically the screen.int main() {
write(1,"hello world\n",13); // printf("hello world\n");
_exit(0); // return 0;
}
Observe that the C standard library (unistd.h) provides system calls for program use. Since they are lower-level functions, they require more details from the programmer.
The equivalent x86 assembly code would look like this. Note the
syscall
instruction for the system call.string:
.ascii "hello world\n" # String in ASCII format in memory
main:
movq $1,%rax # write is call number 1
movq $1, %rdi # Output stream (screen) is 1
movq $string, %rsi # String address
movq $13, %rdx # Length of the string
syscall # Execute write
exit:
movq $60,%rax # exit is call number 60
movq $0, %rdi # Return value 0
syscall
In assembly, all system call parameters are passed in registers, and the stack is not used. Parameters also have a specific order, where the
%rax
register contains the call number, etc. The return value is also provided in the %rax register.Fault¶
A fault is an error condition that the handler may be able to fix. After executing the handler, the current program instruction is re-executed. If the handler cannot resolve the issue, the program execution is terminated with an error.
Example: A memory address reference that is not present in the processor's cache ( page fault). In this case, the handler retrieves the content of the memory address from main memory (or possibly from a lower-level cache) and stores it in the cache.
Abort¶
This exception occurs when an error condition arises in the system that cannot be resolved by the program/firmware/operating system (engl. abort). Examples include kernel crashes, faulty data (parity error) in memory, or hardware/component failure. Program execution halts at this point, and the program does not resume.
Example: The infamous Windows Blue Screen of Death, although similar issues also occur in Linux.
y86 Exceptions¶
In the y86 architecture, there are three internal exceptions:
- Machine language instruction
halt
, which stops program execution - Invalid opcode
INS
- Invalid memory address
ADR
In the sequential y86, program execution halts, and the corresponding status bit is set. Instructions following this do not affect the processor's state.
In the y86 pipelined processor, exception handling is slightly more complex, as shown in the example of exception handling control logic:
- Multiple instructions in different stages can trigger exceptions simultaneously (within the same clock cycle). In such cases, the order in which exceptions are handled must be determined. In y86, the idea is to prioritize the instruction furthest along in the pipeline.
- A conditional jump may result in an exception if the instruction fetched from memory causes an exception and it turns out that the prediction was incorrect. This situation can arise, for example, if the memory address (or even the opcode) is invalid.
main: xorq %rax,%rax jne target irmovq $1,%rax target: movl $13,%rbx # invalid opcode (here, an x86 instruction)
- Pipelined instructions can modify a shared resource to an invalid state (e.g., stack pointer), resulting in an exception. This can occur if an instruction is in the Memory stage while another instruction in the Execute stage updates the status flags. In this case, the later instruction does not check the status flags before execution. For this reason, the
STAT
register should move along with the pipeline registers. - In a pipelined processor, the Write back stage checks the status flags, and if an exception has occurred, the values of the W pipeline register can be saved as the processor state.
However, exception handlers are not implemented in the y86 simulator. In y86, when an exception occurs, program execution halts, and subsequent instructions no longer alter the processor state.
Other bibliography¶
Please refer to the course book Bryant & O'Hallaron, Computer Systems: A Programmer's Perspective, 3rd edition. Chapter 8.
Conclusion¶
It is interesting to note that the fundamental idea behind firmware has remained the same for decades. However, as processor technology has advanced, exception handling in modern processors has become a rather complex matter.
Give feedback on this content
Comments about this material