12 Interrupts
Published:
Debugger breakpoint
How is it implemented? Tracer fork()
s, and the child program becomes the tracee, but before that, makes a syscall ptrace()
, so the parent process can control this.
Instruction int3
, OS stops the program, and sends info to the tracer of the program
Processor modes
- User mode (mode bit = 1): can’t do privileged operations
- Kernel mode (mode bit = 0): can performed privileged operations and has full hardware access Allows user program to “trap” into kernel and “return from trap”
Interrupt
Stop the CPU from executing the current instructions and makes it jump to a predetermined piece of OS kernel code
- If a user process does
while(1)
, OS must have ways to interrupt this, - Time interrupt handler sees if a process is running too long. If so, it will put it to sleep and run others.
- Hardware interrupt
- Asynchronous (can arrive at any time)
- Timer interrupt
- Network packet arrival, key press, …
- Exceptions & Faults
- Synchronous: result of executing some instruction
- E. division by 0, segmentation fault, page fault
- Software interrupt
- Synchronous, deliberately raised by software (
int <code>
) in x86 - System call
- Debugger breakpoints (
int3
, single byte)
- Synchronous, deliberately raised by software (
CPU cycle
while (1) {
if (HWint or exception) {
n = int/exception type // exception flag
call interrupt handler n
}
fetch next instrction
if (instruction == int n)
call interrupt handler n
else
run instruction
}
Interrupts are not userspace signals
- Signals: enables communication between processes and OS
- Interrupts: enables communication between hardware and OS
- but some interrupts can generate userspace signals…
- divide by zero exception:
SIGFPE
- illegal memory access:
SIGSEGV
- software breakpoint:
SIGTRAP
- divide by zero exception:
- but some interrupts can generate userspace signals…
Linux System Call
User program invokes read()
:
- libc system call wrapper invokes software interrupt
0x80
(system call)- Places syscall number
__NR_read
into%eax
registermovl __NR_read, %eax int 0x80 # syscall, enter kernel mode ret
- (also puts other parameters in the register)
- Places syscall number
- Trap into kernel mode, look up
0x80
in Interrupt Descriptor Table (IDT) - Jump to interrupt
0x80
’s handler:system_call()
system_call()
looks up__NR_read
insys_call_table
- Unpack registers, call
__NR_read
’s handler:sys_read()
- Perform
read()
’s real work in kernel’ssys_read()
- file entry management, I/O requests, copying data, etc.
Breakpoints
Stop execution at arbitrary points, E. using int3
- Debugger replace the byte at
addr
withint3
and restore the byte later - When
int3
reached, trigger interrupt, OS takes over, notify debugger (SIGTRAP
) - Debugger intercepts
SIGTRAP
and regains control Only works for breakpoints at an instruction - E. can’t stop if a variable has changed
- CPU offers hardware breakpoints (watchpoints)
ldb
demo
We set linux to store address at 400000, but instruction pointer is set to be a huge address: hasn’t entered main()
yet.
- Starts with some library routine that’s going to call
main()
Workflow
ldb.c
personality(ADDR_NO_RANDOMIZE)
:objdump
content will be exactly the actual memory
Tracee will stop each time any signal is delivered, even if signals are ignored
- The tracer will be notified at the next call to
waitpid()
waitpid()
will return a status value, telling why child is stopped (any signals received)- Whenever kernel wants to stop the child (E. at the beginning of
execve()
, kernel tells the debuggerSIGTRAP
) - Child didn’t receive
SIGTRAP
, just telling the debugger that it has the control
- Whenever kernel wants to stop the child (E. at the beginning of
- Debugger can inspect or modify the tracee
- Tracer can proceed, delivering the signal or not
sumpause
- Call
c
,waitpid()
until it’s completed - When
SIGINT
sent to debugger,ldb_read_signal()
reads signals and stops the child - Now can do things to the chil
- If
c
again, the signal will be delivered to the child
Breakpoint
b <insr>
x <instr> # modified to cc
c # break at <instr>
s # put back the instruction, but then put `cc` back
objdump ./sum
ldb ./sum
(ldb) b 40114e # break at the next 40114f
(ldb) c # continue
(ldb@40114f*) t
# Gives a backtrace
# 0x40114f in sum:401136 (starting address of the func)
# called from 0x... in sum_array:...
# called from 0x... in main:...
Unroll the stack and backtraces, stop until hit main
- The return address of the stack is printed
- That’s why the address printed is not the call address It also figures out what function makes the call:
sum:401136
- Need to read the executable file (as
objdump
),mmap()
it - How: ELF