diff --git a/config-x86-common.mak b/config-x86-common.mak
index c546ee3..3a77a93 100644
--- a/config-x86-common.mak
+++ b/config-x86-common.mak
@@ -13,6 +13,7 @@ cflatobjs += lib/x86/fwcfg.o
 cflatobjs += lib/x86/apic.o
 cflatobjs += lib/x86/atomic.o
 cflatobjs += lib/x86/desc.o
+cflatobjs += lib/x86/isr.o
 
 $(libcflat): LDFLAGS += -nostdlib
 $(libcflat): CFLAGS += -ffreestanding -I lib
diff --git a/lib/x86/isr.c b/lib/x86/isr.c
new file mode 100644
index 0000000..0bacff9
--- /dev/null
+++ b/lib/x86/isr.c
@@ -0,0 +1,99 @@
+#include "libcflat.h"
+#include "isr.h"
+#include "vm.h"
+#include "desc.h"
+
+#ifdef __x86_64__
+#  define R "r"
+#else
+#  define R "e"
+#endif
+
+extern char isr_entry_point[];
+
+asm (
+    "isr_entry_point: \n"
+#ifdef __x86_64__
+    "push %r15 \n\t"
+    "push %r14 \n\t"
+    "push %r13 \n\t"
+    "push %r12 \n\t"
+    "push %r11 \n\t"
+    "push %r10 \n\t"
+    "push %r9  \n\t"
+    "push %r8  \n\t"
+#endif
+    "push %"R "di \n\t"
+    "push %"R "si \n\t"
+    "push %"R "bp \n\t"
+    "push %"R "sp \n\t"
+    "push %"R "bx \n\t"
+    "push %"R "dx \n\t"
+    "push %"R "cx \n\t"
+    "push %"R "ax \n\t"
+#ifdef __x86_64__
+    "mov %rsp, %rdi \n\t"
+    "callq *8*16(%rsp) \n\t"
+#else
+    "push %esp \n\t"
+    "calll *4+4*8(%esp) \n\t"
+    "add $4, %esp \n\t"
+#endif
+    "pop %"R "ax \n\t"
+    "pop %"R "cx \n\t"
+    "pop %"R "dx \n\t"
+    "pop %"R "bx \n\t"
+    "pop %"R "bp \n\t"
+    "pop %"R "bp \n\t"
+    "pop %"R "si \n\t"
+    "pop %"R "di \n\t"
+#ifdef __x86_64__
+    "pop %r8  \n\t"
+    "pop %r9  \n\t"
+    "pop %r10 \n\t"
+    "pop %r11 \n\t"
+    "pop %r12 \n\t"
+    "pop %r13 \n\t"
+    "pop %r14 \n\t"
+    "pop %r15 \n\t"
+#endif
+    ".globl isr_iret_ip\n\t"
+#ifdef __x86_64__
+    "add $8, %rsp \n\t"
+    "isr_iret_ip: \n\t"
+    "iretq \n\t"
+#else
+    "add $4, %esp \n\t"
+    "isr_iret_ip: \n\t"
+    "iretl \n\t"
+#endif
+    );
+
+void handle_irq(unsigned vec, void (*func)(isr_regs_t *regs))
+{
+    u8 *thunk = vmalloc(50);
+
+    set_idt_entry(vec, thunk, 0);
+
+#ifdef __x86_64__
+    /* sub $8, %rsp */
+    *thunk++ = 0x48; *thunk++ = 0x83; *thunk++ = 0xec; *thunk++ = 0x08;
+    /* mov $func_low, %(rsp) */
+    *thunk++ = 0xc7; *thunk++ = 0x04; *thunk++ = 0x24;
+    *(u32 *)thunk = (ulong)func; thunk += 4;
+    /* mov $func_high, %(rsp+4) */
+    *thunk++ = 0xc7; *thunk++ = 0x44; *thunk++ = 0x24; *thunk++ = 0x04;
+    *(u32 *)thunk = (ulong)func >> 32; thunk += 4;
+    /* jmp isr_entry_point */
+    *thunk ++ = 0xe9;
+    *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4);
+#else
+    /* push $func */
+    *thunk++ = 0x68;
+    *(u32 *)thunk = (ulong)func; thunk += 4;
+    /* jmp isr_entry_point */
+    *thunk++ = 0xe9;
+    *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4);
+#endif
+}
+
diff --git a/lib/x86/isr.h b/lib/x86/isr.h
new file mode 100644
index 0000000..b07a32a
--- /dev/null
+++ b/lib/x86/isr.h
@@ -0,0 +1,14 @@
+#ifndef __ISR_TEST__
+#define __ISR_TEST__
+
+typedef struct {
+    ulong regs[sizeof(ulong)*2];
+    ulong func;
+    ulong rip;
+    ulong cs;
+    ulong rflags;
+} isr_regs_t;
+
+void handle_irq(unsigned vec, void (*func)(isr_regs_t *regs));
+
+#endif
diff --git a/x86/apic.c b/x86/apic.c
index 3dd2485..1366185 100644
--- a/x86/apic.c
+++ b/x86/apic.c
@@ -3,77 +3,7 @@
 #include "vm.h"
 #include "smp.h"
 #include "desc.h"
-
-typedef struct {
-    ulong regs[sizeof(ulong)*2];
-    ulong func;
-    ulong rip;
-    ulong cs;
-    ulong rflags;
-} isr_regs_t;
-
-#ifdef __x86_64__
-#  define R "r"
-#else
-#  define R "e"
-#endif
-
-extern char isr_entry_point[];
-
-asm (
-    "isr_entry_point: \n"
-#ifdef __x86_64__
-    "push %r15 \n\t"
-    "push %r14 \n\t"
-    "push %r13 \n\t"
-    "push %r12 \n\t"
-    "push %r11 \n\t"
-    "push %r10 \n\t"
-    "push %r9  \n\t"
-    "push %r8  \n\t"
-#endif
-    "push %"R "di \n\t"
-    "push %"R "si \n\t"
-    "push %"R "bp \n\t"
-    "push %"R "sp \n\t"
-    "push %"R "bx \n\t"
-    "push %"R "dx \n\t"
-    "push %"R "cx \n\t"
-    "push %"R "ax \n\t"
-#ifdef __x86_64__
-    "mov %rsp, %rdi \n\t"
-    "callq *8*16(%rsp) \n\t"
-#else
-    "push %esp \n\t"
-    "calll *4+4*8(%esp) \n\t"
-    "add $4, %esp \n\t"
-#endif
-    "pop %"R "ax \n\t"
-    "pop %"R "cx \n\t"
-    "pop %"R "dx \n\t"
-    "pop %"R "bx \n\t"
-    "pop %"R "bp \n\t"
-    "pop %"R "bp \n\t"
-    "pop %"R "si \n\t"
-    "pop %"R "di \n\t"
-#ifdef __x86_64__
-    "pop %r8  \n\t"
-    "pop %r9  \n\t"
-    "pop %r10 \n\t"
-    "pop %r11 \n\t"
-    "pop %r12 \n\t"
-    "pop %r13 \n\t"
-    "pop %r14 \n\t"
-    "pop %r15 \n\t"
-#endif
-#ifdef __x86_64__
-    "add $8, %rsp \n\t"
-    "iretq \n\t"
-#else
-    "add $4, %esp \n\t"
-    "iretl \n\t"
-#endif
-    );
+#include "isr.h"
 
 static int g_fail;
 static int g_tests;
@@ -106,34 +36,6 @@ void test_enable_x2apic(void)
     }
 }
 
-static void handle_irq(unsigned vec, void (*func)(isr_regs_t *regs))
-{
-    u8 *thunk = vmalloc(50);
-
-    set_idt_entry(vec, thunk, 0);
-
-#ifdef __x86_64__
-    /* sub $8, %rsp */
-    *thunk++ = 0x48; *thunk++ = 0x83; *thunk++ = 0xec; *thunk++ = 0x08;
-    /* mov $func_low, %(rsp) */
-    *thunk++ = 0xc7; *thunk++ = 0x04; *thunk++ = 0x24;
-    *(u32 *)thunk = (ulong)func; thunk += 4;
-    /* mov $func_high, %(rsp+4) */
-    *thunk++ = 0xc7; *thunk++ = 0x44; *thunk++ = 0x24; *thunk++ = 0x04;
-    *(u32 *)thunk = (ulong)func >> 32; thunk += 4;
-    /* jmp isr_entry_point */
-    *thunk ++ = 0xe9;
-    *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4);
-#else
-    /* push $func */
-    *thunk++ = 0x68;
-    *(u32 *)thunk = (ulong)func;
-    /* jmp isr_entry_point */
-    *thunk ++ = 0xe9;
-    *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4);
-#endif
-}
-
 static void eoi(void)
 {
     apic_write(APIC_EOI, 0);
