diff --git a/arch/x64/arch-switch.hh b/arch/x64/arch-switch.hh
index e919d603644c6c37d27b9d890a1036006b997600..5417961be24e0e01999683e7b466917a4b58735e 100644
--- a/arch/x64/arch-switch.hh
+++ b/arch/x64/arch-switch.hh
@@ -14,13 +14,37 @@ void stack_trampoline(sched::thread* t, void (*func)(sched::thread*),
 
 namespace sched {
 
+void set_fsbase_msr(u64 v)
+{
+    processor::wrmsr(msr::IA32_FS_BASE, v);
+}
+
+void set_fsbase_fsgsbase(u64 v)
+{
+    processor::wrfsbase(v);
+}
+
+extern "C"
+void (*resolve_set_fsbase(void))(u64 v)
+{
+    // can't use processor::features, because it is not initialized
+    // early enough.
+    if (processor::cpuid(0).a >= 7 && (processor::cpuid(7).b & 1)) {
+        return set_fsbase_fsgsbase;
+    } else {
+        return set_fsbase_msr;
+    }
+}
+
+void set_fsbase(u64 v) __attribute__((ifunc("resolve_set_fsbase")));
+
 void thread::switch_to()
 {
     thread* old = current();
     // writing to fs_base invalidates memory accesses, so surround with
     // barriers
     barrier();
-    processor::wrmsr(msr::IA32_FS_BASE, reinterpret_cast<u64>(_tcb));
+    set_fsbase(reinterpret_cast<u64>(_tcb));
     barrier();
     _cpu->arch.set_exception_stack(&_arch);
     asm volatile
diff --git a/arch/x64/processor.hh b/arch/x64/processor.hh
index 6c3cd6ea227a78435a4d97a6eb2c7725f66d7cb5..93ece95b9b73339322e6af138225309e7dba0182 100644
--- a/arch/x64/processor.hh
+++ b/arch/x64/processor.hh
@@ -202,6 +202,11 @@ inline void wrmsr(u32 index, u64 data) {
     asm volatile ("wrmsr" : : "c"(index), "a"(lo), "d"(hi));
 }
 
+inline void wrfsbase(u64 data)
+{
+    asm volatile("wrfsbase %0" : : "r"(data));
+}
+
 inline void halt_no_interrupts() {
     asm volatile ("cli; hlt");
 }
diff --git a/core/elf.cc b/core/elf.cc
index ecb479c9d32e58726bcd7c866590705dfc949e30..502484390c85a2a77a00e3a66d7001c37cbe42cb 100644
--- a/core/elf.cc
+++ b/core/elf.cc
@@ -924,6 +924,9 @@ init_table get_init(Elf64_Ehdr* header)
                         // FIXME: assumes TLS segment comes before DYNAMIC segment
                         *static_cast<u64*>(addr) = lookup()->st_value - ret.tls.size;
                         break;
+                    case R_X86_64_IRELATIVE:
+                        *static_cast<void**>(addr) = reinterpret_cast<void *(*)()>(base + addend)();
+                        break;
                     default:
                         abort();
                     }
diff --git a/include/elf.hh b/include/elf.hh
index 43eb6e6adecd00a78b6b92ee26edd0ea59b0ee4d..b1419987927c39d663d0f8428ea2137f20bbd975 100644
--- a/include/elf.hh
+++ b/include/elf.hh
@@ -192,6 +192,7 @@ enum {
     R_X86_64_GOTPC32 = 26, //  word32 GOT + A - P
     R_X86_64_SIZE32 = 32, //  word32 Z + A
     R_X86_64_SIZE64 = 33, //  word64 Z + A
+    R_X86_64_IRELATIVE = 37, //  word64 indirect(B + A)
     };
 
 enum {