CPU runs with a privilege mode that determines what kind of operations it can perform:
A userspace program (running in user mode) can issue a syscall (e.g. read()
)
to enter kernel mode so that it can perform a privileged operation (e.g. issue
I/O request).
Syscalls act as predefined entry-points into the kernel. They allow userspace programs to “trap” into the kernel to perform a privileged operation and then “return-from-trap” back to user mode.
User-invoked syscalls aren’t the only way for the kernel to gain control of the system. There are many instances where the kernel must gain control regardless of what the user program is doing.
Interrupts are a mechanism that stops the CPU from executing the current stream of instructions and makes it jump to a predetermined piece of OS kernel code to perform certain tasks.
There are three classes of interrupts:
int <code>
instruction: raise software interrupt
int 0x80
(see syscall dispatch section below)int3
(see breakpoint section below)The psuedocode below demonstrates how the CPU handles interrupts while executing instructions:
while (1) {
if (interrupt or exception) {
n = interrupt/exception type
call interrupt handler n
}
fetch next instruction
if (instruction == int n)
call interrupt handler n
else
run instruction
}
Note: do not confuse interrupts with userspace signals!
SIGFPE
SIGSEGV
SIGTRAP
The diagram below illustrates how system calls are invoked in x86-32:
User program invokes read()
:
0x80
(system call)
__NR_read
into %eax
register0x80
in Interrupt Descriptor Table (IDT)0x80
’s handler: system_call()
system_call()
looks up __NR_read
in sys_call_table
__NR_read
’s handler: sys_read()
read()
’s real work in kernel’s sys_read()
int 0x80
was how system calls were invoked in x86-32. x86-64 has a dedicated
syscall
instruction so it no longer relies on the software interrupt mechanism
to invoke system calls.
Debuggers allow users to stop execution at arbitrary points in the debugged
program by defining “breakpoints”. One way to implement breakpoints in x86 is by
using the int3
instruction as follows:
addr
of the code we want to set a breakpoint at, the
debugger will replace the byte at addr
with the int3
instruction and save
the byte so it can be restored later.
int <code>
instruction is encoded using two
bytes because the code is specified as a separate operand. int3
is a
single-byte instruction which allows it to be patched in at arbitrary
points in the program code.int3
instruction, a software
interrupt is raised that causes the CPU to jump to the debug exception handler.
The OS kernel implements this handler by stopping the debugged process and
sending it a SIGTRAP
. The debugger intercepts the SIGTRAP
and regains
control, allowing the user to inspect the debugged process’s state.These breakpoints are known as “software breakpoints” because they are implemented by the debugger using software instructions. Many CPUs offer special debug registers that can be used to implement “hardware breakpoints” (i.e., watchpoints). These are much more powerful than software breakpoints – it allows debuggers to specify memory addresses and what kinds of operations should cause the breakpoint to be triggered (e.g, read/write/execute).
Last updated: 2024-03-18