diff --git a/src/arch/x86_64/interrupt/syscall.rs b/src/arch/x86_64/interrupt/syscall.rs
index 9ddd3c79aef35b745b7113c531d4208467c676b8..9f5ed74b253032f23342867e5e5bb6126220c6f1 100644
--- a/src/arch/x86_64/interrupt/syscall.rs
+++ b/src/arch/x86_64/interrupt/syscall.rs
@@ -67,12 +67,7 @@ pub unsafe extern fn syscall_instruction() {
           : "intel", "volatile");
 
     // Push scratch registers
-    scratch_push!();
-    preserved_push!();
-    asm!("push fs
-         mov r11, 0x18
-         mov fs, r11"
-         : : : : "intel", "volatile");
+    interrupt_push!();
 
     // Get reference to stack variables
     let rsp: usize;
@@ -87,10 +82,8 @@ pub unsafe extern fn syscall_instruction() {
     pti::unmap();
 
     // Interrupt return
-    asm!("pop fs" : : : : "intel", "volatile");
-    preserved_pop!();
-    scratch_pop!();
-    asm!("iretq" : : : : "intel", "volatile");
+    interrupt_pop!();
+    iret!()
 }
 
 #[naked]
@@ -106,12 +99,7 @@ pub unsafe extern fn syscall() {
     }
 
     // Push scratch registers
-    scratch_push!();
-    preserved_push!();
-    asm!("push fs
-         mov r11, 0x18
-         mov fs, r11"
-         : : : : "intel", "volatile");
+    interrupt_push!();
 
     // Get reference to stack variables
     let rsp: usize;
@@ -126,10 +114,8 @@ pub unsafe extern fn syscall() {
     pti::unmap();
 
     // Interrupt return
-    asm!("pop fs" : : : : "intel", "volatile");
-    preserved_pop!();
-    scratch_pop!();
-    asm!("iretq" : : : : "intel", "volatile");
+    interrupt_pop!();
+    iret!();
 }
 
 #[naked]
diff --git a/src/arch/x86_64/macros.rs b/src/arch/x86_64/macros.rs
index 84021f6b6c755f403f476b23d2f91d8a13166584..22fe5a3cf649ade69d1cb04180acdb1d5738cd5d 100644
--- a/src/arch/x86_64/macros.rs
+++ b/src/arch/x86_64/macros.rs
@@ -1,5 +1,6 @@
 use core::mem;
 use syscall::data::IntRegisters;
+use super::gdt;
 
 /// Print to console
 #[macro_export]
@@ -19,6 +20,7 @@ macro_rules! println {
 }
 
 #[allow(dead_code)]
+#[derive(Default)]
 #[repr(packed)]
 pub struct ScratchRegisters {
     pub r11: usize,
@@ -77,6 +79,7 @@ macro_rules! scratch_pop {
 }
 
 #[allow(dead_code)]
+#[derive(Default)]
 #[repr(packed)]
 pub struct PreservedRegisters {
     pub r15: usize,
@@ -124,9 +127,13 @@ macro_rules! preserved_pop {
 
 macro_rules! fs_push {
     () => (asm!(
-        "push fs
+        "
+        push fs
+
+        // Load kernel tls
         mov rax, 0x18
-        mov fs, ax"
+        mov fs, ax // can't load value directly into `fs`
+        "
         : : : : "intel", "volatile"
     ));
 }
@@ -139,6 +146,7 @@ macro_rules! fs_pop {
 }
 
 #[allow(dead_code)]
+#[derive(Default)]
 #[repr(packed)]
 pub struct IretRegisters {
     pub rip: usize,
@@ -198,6 +206,7 @@ macro_rules! interrupt {
 }
 
 #[allow(dead_code)]
+#[derive(Default)]
 #[repr(packed)]
 pub struct InterruptStack {
     pub fs: usize,
@@ -207,6 +216,24 @@ pub struct InterruptStack {
 }
 
 impl InterruptStack {
+    pub fn new_usermode(ip: usize, sp: usize, arg: usize) -> Self {
+        // See which registers are set in start.rs, function `usermode`
+        Self {
+            fs: gdt::GDT_USER_TLS << 3 | 3,
+            preserved: PreservedRegisters::default(),
+            scratch: ScratchRegisters {
+                rdi: arg,
+                ..ScratchRegisters::default()
+            },
+            iret: IretRegisters {
+                rip: ip,
+                cs: gdt::GDT_USER_CODE << 3 | 3,
+                rflags: 1 << 9,
+                rsp: sp,
+                ss: gdt::GDT_USER_DATA << 3 | 3,
+            },
+        }
+    }
     pub fn dump(&self) {
         self.iret.dump();
         self.scratch.dump();
@@ -298,9 +325,24 @@ impl InterruptStack {
     }
 }
 
+macro_rules! interrupt_push {
+    () => {
+        scratch_push!();
+        preserved_push!();
+        fs_push!();
+    };
+}
+macro_rules! interrupt_pop {
+    () => {
+        fs_pop!();
+        preserved_pop!();
+        scratch_pop!();
+    };
+}
+
 #[macro_export]
 macro_rules! interrupt_stack {
-    ($name:ident, $stack: ident, $func:block) => {
+    ($name:ident, $stack:ident, $func:block) => {
         #[naked]
         pub unsafe extern fn $name () {
             #[inline(never)]
@@ -309,9 +351,7 @@ macro_rules! interrupt_stack {
             }
 
             // Push scratch registers
-            scratch_push!();
-            preserved_push!();
-            fs_push!();
+            interrupt_push!();
 
             // Get reference to stack variables
             let rsp: usize;
@@ -327,15 +367,14 @@ macro_rules! interrupt_stack {
             $crate::arch::x86_64::pti::unmap();
 
             // Pop scratch registers and return
-            fs_pop!();
-            preserved_pop!();
-            scratch_pop!();
+            interrupt_pop!();
             iret!();
         }
     };
 }
 
 #[allow(dead_code)]
+#[derive(Default)]
 #[repr(packed)]
 pub struct InterruptErrorStack {
     pub fs: usize,
@@ -366,9 +405,7 @@ macro_rules! interrupt_error {
             }
 
             // Push scratch registers
-            scratch_push!();
-            preserved_push!();
-            fs_push!();
+            interrupt_push!();
 
             // Get reference to stack variables
             let rsp: usize;
@@ -384,10 +421,8 @@ macro_rules! interrupt_error {
             $crate::arch::x86_64::pti::unmap();
 
             // Pop scratch registers, error code, and return
-            fs_pop!();
-            preserved_pop!();
-            scratch_pop!();
-            asm!("add rsp, 8" : : : : "intel", "volatile");
+            interrupt_pop!();
+            asm!("add rsp, 8" : : : : "intel", "volatile"); // pop error code
             iret!();
         }
     };
diff --git a/src/arch/x86_64/start.rs b/src/arch/x86_64/start.rs
index ab7fc39629f4a7b2f69a4a2d92adcb227a4104b7..b2bc4e5fc1efc093626ed521fe285b5239b7117a 100644
--- a/src/arch/x86_64/start.rs
+++ b/src/arch/x86_64/start.rs
@@ -3,7 +3,7 @@
 /// It must create the IDT with the correct entries, those entries are
 /// defined in other files inside of the `arch` module
 
-use core::slice;
+use core::{slice, mem, ptr};
 use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
 
 use crate::allocator;
@@ -17,6 +17,7 @@ use crate::gdt;
 use crate::idt;
 use crate::interrupt;
 use crate::log;
+use crate::macros::InterruptStack;
 use crate::memory;
 use crate::paging;
 
@@ -283,3 +284,31 @@ pub unsafe fn usermode(ip: usize, sp: usize, arg: usize) -> ! {
          : "intel", "volatile");
     unreachable!();
 }
+
+#[naked]
+pub unsafe fn usermode_interrupt_stack(stack: InterruptStack) -> ! {
+    // Push fake stack to the actual stack
+    let rsp: usize;
+    asm!("sub rsp, $1" : "={rsp}"(rsp) : "r"(mem::size_of::<InterruptStack>()) : : "intel", "volatile");
+    ptr::write(rsp as *mut InterruptStack, stack);
+
+    // Unmap kernel
+    pti::unmap();
+
+    // Set up floating point and TLS
+    asm!("mov ds, r14d
+         mov es, r14d
+         mov fs, r15d
+         mov gs, r14d
+         fninit"
+         : // No output because it never returns
+         :   "{r14}"(gdt::GDT_USER_DATA << 3 | 3), // Data segment
+             "{r15}"(gdt::GDT_USER_TLS << 3 | 3) // TLS segment
+         : "ds", "es", "fs", "gs"
+         : "intel", "volatile");
+
+    // Go to usermode
+    interrupt_pop!();
+    iret!();
+    unreachable!();
+}
diff --git a/src/syscall/process.rs b/src/syscall/process.rs
index 18de7f27ada9cb30b08e8151e55ce65f0fc38022..e27576fd3e248ec542b0b40df67034f97ec23686 100644
--- a/src/syscall/process.rs
+++ b/src/syscall/process.rs
@@ -6,6 +6,7 @@ use core::{intrinsics, mem};
 use core::ops::DerefMut;
 use spin::Mutex;
 
+use crate::arch::macros::InterruptStack;
 use crate::context::file::FileDescriptor;
 use crate::context::{ContextId, WaitpidKey};
 use crate::context;
@@ -20,14 +21,14 @@ use crate::paging::temporary_page::TemporaryPage;
 use crate::paging::{ActivePageTable, InactivePageTable, Page, VirtualAddress, PAGE_SIZE};
 use crate::{ptrace, syscall};
 use crate::scheme::FileHandle;
-use crate::start::usermode;
+use crate::start::{usermode, usermode_interrupt_stack};
 use crate::syscall::data::{SigAction, Stat};
 use crate::syscall::error::*;
-use crate::syscall::flag::{CloneFlags, CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,
-                           CLONE_STACK, MapFlags, PROT_EXEC, PROT_READ, PROT_WRITE, PTRACE_EVENT_CLONE,
-                           PTRACE_STOP_EXIT, SigActionFlags, SIG_DFL, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK,
-                           SIGCONT, SIGTERM, WaitFlags, WCONTINUED, WNOHANG, WUNTRACED, wifcontinued,
-                           wifstopped};
+use crate::syscall::flag::{CloneFlags, CLONE_FILES, CLONE_FS, CLONE_SIGHAND,
+                           CLONE_STACK, CLONE_VFORK, CLONE_VM, MapFlags, PtraceFlags, PROT_EXEC,
+                           PROT_READ, PROT_WRITE, PTRACE_EVENT_CLONE, PTRACE_STOP_EXIT, SigActionFlags,
+                           SIG_BLOCK, SIG_DFL, SIG_SETMASK, SIG_UNBLOCK, SIGCONT, SIGTERM, WaitFlags,
+                           WCONTINUED, WNOHANG,WUNTRACED, wifcontinued, wifstopped};
 use crate::syscall::ptrace_event;
 use crate::syscall::validate::{validate_slice, validate_slice_mut};
 
@@ -899,8 +900,22 @@ fn fexec_noreturn(
         }
     }
 
-    // Go to usermode
-    unsafe { usermode(entry, sp, 0); }
+    // Create dummy stack for ptrace to read from
+    let mut regs = InterruptStack::new_usermode(entry, sp, 0);
+
+    // ptrace breakpoint
+    let was_traced = {
+        let _guard = ptrace::set_process_regs(&mut regs);
+        ptrace::breakpoint_callback(PtraceFlags::PTRACE_STOP_EXEC, None).is_some()
+    };
+
+    if !was_traced {
+        // Go to usermode, fast route
+        unsafe { usermode(entry, sp, 0) }
+    } else {
+        // Go to usermode, take ptrace-modified stack into account
+        unsafe { usermode_interrupt_stack(regs) }
+    }
 }
 
 pub fn fexec_kernel(fd: FileHandle, args: Box<[Box<[u8]>]>, vars: Box<[Box<[u8]>]>, name_override_opt: Option<Box<[u8]>>) -> Result<usize> {
diff --git a/syscall b/syscall
index 9ecdc11d73677477b37567a47af1633478093cbb..c23d36e8923b8087039c26fcde3c359b5e0accc7 160000
--- a/syscall
+++ b/syscall
@@ -1 +1 @@
-Subproject commit 9ecdc11d73677477b37567a47af1633478093cbb
+Subproject commit c23d36e8923b8087039c26fcde3c359b5e0accc7