Whenever we have a page fault, we need to see if the fault was caused by %gs 
being pinned to 0 in OpenBSD. For this we parse the command assuming %gs was 0
(fault was at "zero-page"), find if it is really %gs command and then rewrite
it without %gs. Sometimes another thread will already rewrite gs to fs/jmp and
we just need a retry.

Following approaches are implemented:
(1 In place - this is NtCurrentTeb() call. It always compiles into 
	 mov %gs:0x30, %r for this to work, since we have ref to teb in our 
	 teb_tls_offset, we can just replace few values in the command itself
	 for example
 65 48 8b 04 25 30 00 00 00          mov %gs:0x30, %rax
 64 48 8b 04 25 xx xx xx xx          mov %fs:teb_tls_offset, %rax 
                        --- where teb_tls_offset is negative
                        
(2 Generic Stub
  for this to work, the instruction has to be 5 bytes or longer. If it is, 
  and we find a spot to park a stub in rel32 we can replace it with a jmp rel32
  command to a stub that rewrites the command without %gs.
  This can be done with ANY command that is 5 bytes or longer. If command is 
  less than 5 bytes, we don't have enough space to replace it. I will have to 
  be handled by case 3, but commands like that are rare. This replacement is 
  stable but we have to specifically handle case when thread is being stopped
  inside of the stub.
  
(3 SIGSEGV on every trap over that instruction
  Same as 2 but there was not enough space to replace it with a JMP REL 
  (5 bytes). We pay signal round trip every time but at least it works.
  The stub is basically the same but we are replacing a "replaced instruction" 
  with a hash table that points that for this SIGSEGV on this instruction we 
  jump to that Stub.
  
Stub Generation:
The form of the stub for 64-bit addressing is
 mov %S,%fs:slot+8           save the scratch register S
 mov %fs:slot, %S            S = TEB
 lea 0(%base, %S, 1), %S     S = TEB + base , if base register
 <the instruction>           [S + index*scale + disp32] instead of %gs:...
 mov %fs:slot+8, %S          restore S
 jmp *0(%rip)                back to next instruction
 .quad site + len
 
Slot is our offset (negative) below OpenBSD's threadblock.
Slot contains TEB at *slot, and scratch register at *slot+8
32-bit addressing uses a second scratch register at *slot+16.
%S scratch register is any register that is not used in the instruction.
There are up to 3 registers that can be used in the instruction, so
we always have a one of 7 (not you %rsp) registers free.

Most of the code is built on 64 bit specifications and list of instructions 
that Open Source (Copyleft and free) compilers generate for Windows for accesses
to TEB block. The code is written manually based on instruction decoding 
diagrams generated by LLM. List of instructions was provided by LLM and checked 
manually with specifications. Prefix 0x67 (32bit addressing mode) is supported
except for moffs instructions. Other unsupported prefixes were not detected
to be generated alongside %gs by reviewed compilers.

Rest of the code is built to support that generation and keep it as compact and
specialized just towards this one instruction and stub generation.

Another notable method is gs_leave_stub that allows us to leave stub on SIGSEGV
and SIGUSR1, so outside it never looks like we are in a stub. This helps with 
debug, signal handling and general sanity checks.

Index: dlls/ntdll/unix/gs_openbsd.c
--- dlls/ntdll/unix/gs_openbsd.c.orig
+++ dlls/ntdll/unix/gs_openbsd.c
@@ -0,0 +1,974 @@
+/*
+ * OpenBSD patcher for %gs calls
+ *
+ * Copyright (C) 2026 Andrii Yukhymchak
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#if 0
+#pragma makedep unix
+#endif
+
+#include "config.h"
+
+#if defined(__OpenBSD__)
+
+#include <dlfcn.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ntstatus.h"
+#include "windef.h"
+#include "winternl.h"
+#include "unix_private.h"
+#include "gs_openbsd.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(seh);
+
+long teb_tls_offset;
+
+/***********************************************************************
+ *           gs_init_process
+ * Initializes and checks __wine_teb_tls_offset for a thread
+ */
+void gs_init_process(void)
+{
+    long (*get_offset)(void) = dlsym( RTLD_DEFAULT, "__wine_teb_tls_offset" );
+
+    if (!get_offset)
+    {
+        ERR( "__wine_teb_tls_offset not found. Old loader?\n");
+        exit(1);
+    }
+    teb_tls_offset = get_offset();
+
+    if((int)teb_tls_offset != teb_tls_offset || (int)(teb_tls_offset + 8) != teb_tls_offset + 8)
+    {
+        ERR( "TEB TLS offset %lx is out of range\n", teb_tls_offset);
+        exit(1);
+    }
+
+    if((int)(teb_tls_offset + 16) != teb_tls_offset + 16 || (int)(teb_tls_offset + 24) != teb_tls_offset + 24)
+    {
+        ERR( "TEB Extended Slots offset %lx is out of range\n", teb_tls_offset);
+        exit(1);
+    }
+}
+
+/***********************************************************************
+ *           gs_set_thread_teb
+ */
+void gs_set_thread_teb( TEB *teb )
+{
+    __asm__ volatile( "movq %0, %%fs:(%1)" :: "r" (teb), "r" (teb_tls_offset) : "memory" );
+}
+
+
+/* Instruction decoding helpers.
+ *
+ * There is a way to attach an instruction decoder library, but since all that we need is just if there is %gs prefix
+ * understand which register we want to pick, and the length of command, we can limit ourselves to just few hundred lines of decoder
+ * instead of thousands and thousands of lines for a full decompiler
+ */
+
+/* Intel register numbers for ModRM and SIB (8-15 regs need a REX bit) */
+enum { GS_RAX, GS_RCX, GS_RDX, GS_RBX, GS_RSP, GS_RBP, GS_RSI, GS_RDI };
+
+/* instructions accepted in front of %gs accessor */
+#define PREFIX_OPSIZE       0x66    /* operand size override: 16-bit operands */
+#define PREFIX_LOCK         0xf0    /* lock prefix */
+
+/* GS and FS - what we search for and what we replace with */
+#define PREFIX_GS           0x65
+#define PREFIX_FS           0x64
+
+/* 32 bit addr mode prefix */
+#define PREFIX_32ADDR       0x67
+
+/* REX prefix and its bits: 0100WRXB */
+#define REX_BASE            0x40    /* REX marker bit */
+#define REX_W               0x08    /* 64-bit operand size */
+#define REX_R               0x04    /* ModRM.reg += 8 */
+#define REX_X               0x02    /* SIB.index += 8 */
+#define REX_B               0x01    /* ModRM.rm or SIB.base += 8 */
+
+/* ModRM byte: ddrrrmmm - mod-reg-rm */
+#define MODRM(mod,reg,rm)   (((mod) << 6) | (((reg) & 7) << 3) | ((rm) & 7)) /* build ModRM from segments */
+#define MODRM_RM_SIB        4       /* if ModRM.rm = 100 then SIB byte follows */
+#define MODRM_RM_DISP       5       /* if ModRM.rm = 101 AND mod = 00 then %rip-relative */
+
+/* SIB byte: ssiiibbb - scale-index-base */
+#define SIB(scale,index,base) (((scale) << 6) | (((index) & 7) << 3) | ((base) & 7)) /* build SIB from segments */
+#define SIB_NO_INDEX        4       /* if SIB.index = 100 without REX.X - no index */
+#define SIB_NO_BASE         5       /* if SIB.base  = 101 with mod = 00 then no base and disp32 follows */
+#define SIB_ABSOLUTE        SIB ( 0, SIB_NO_INDEX, SIB_NO_BASE ) /* just disp32 */
+
+/* Opcode defines, so instead of magic constants we have some understanding what's being added */
+#define OPCODE_ESCAPE       0x0f    /* first byte of two-byte opcode kept as 0x100 | second byte */
+#define MAX_INSN_LEN        15      /* longest possible x86-64 instruction */
+#define IMM_Z               0xff
+
+/* decoded %gs instruction and its information */
+struct gs_insn
+{
+    BYTE                    bytes[MAX_INSN_LEN];        /* full instruction as it is */
+    unsigned int            len;                        /* length of the instruction */
+    BYTE                    seg;                        /* segment prefix - GS (to be rewritten) or FS (already rewrote it) */
+    unsigned int            seg_pos;                    /* gs/fs segment location */
+    BYTE                    rex;                        /* REX prefix, 0 if no rex prefix */
+    unsigned int            rex_pos;                    /* where REX prefix is located, or where opcode is if no rex */
+    unsigned int            opcode;                     /* opcode 0x100 | xx for 0f xx */
+    unsigned int            opcode_pos;                 /* location of the first opcode byte */
+    LONG64                  disp;                       /* displacement, or moffs address */
+    unsigned int            disp_pos;                   /* position of the displacement */
+    unsigned int            disp_len;                   /* size of the displacement - 0, 1, 4, or 8 (moffs) */
+    unsigned int            imm_pos;                    /* position of the immediate */
+    unsigned int            imm_len;                    /* size of the immediate - 0, 1, 2, 4 */
+    unsigned int            size;                       /* bytes of memory accessed */
+    unsigned int            ext;                        /* ModRM.reg field (opcode extension for group opcodes) */
+    int                     reg;                        /* register operand or -1 if none */
+    int                     base;                       /* base register or -1 if none */
+    int                     index;                      /* index register or -1 if none */
+    unsigned int            scale;                      /* index is shifted left by this */
+    BOOL                    high_byte;                  /* reg 4-7 are %ah %ch %dh %bh */
+    BOOL                    moffs;                      /* a0-a3 form: 64 bit absolute addr without ModRM */
+    BOOL                    read_only;                  /* does not affect memory */
+    BOOL                    addr32bit;                  /* 32 bit addressing mode - needs second scratch */
+};
+
+/***********************************************************************
+ *           get_gs_reg
+ * converts register index into pointer in the signal context
+ */
+static ULONG64 *get_gs_reg( ucontext_t *ucontext, unsigned int reg )
+{
+    switch (reg)
+    {
+    case 0: return (ULONG64 *)&ucontext->sc_rax;
+    case 1: return (ULONG64 *)&ucontext->sc_rcx;
+    case 2: return (ULONG64 *)&ucontext->sc_rdx;
+    case 3: return (ULONG64 *)&ucontext->sc_rbx;
+    case 4: return (ULONG64 *)&ucontext->sc_rsp;
+    case 5: return (ULONG64 *)&ucontext->sc_rbp;
+    case 6: return (ULONG64 *)&ucontext->sc_rsi;
+    case 7: return (ULONG64 *)&ucontext->sc_rdi;
+    case 8: return (ULONG64 *)&ucontext->sc_r8;
+    case 9: return (ULONG64 *)&ucontext->sc_r9;
+    case 10: return (ULONG64 *)&ucontext->sc_r10;
+    case 11: return (ULONG64 *)&ucontext->sc_r11;
+    case 12: return (ULONG64 *)&ucontext->sc_r12;
+    case 13: return (ULONG64 *)&ucontext->sc_r13;
+    case 14: return (ULONG64 *)&ucontext->sc_r14;
+    case 15: return (ULONG64 *)&ucontext->sc_r15;
+    default: ERR( "register with index %d does not exist\n", reg );
+        return NULL;
+    }
+}
+
+/***********************************************************************
+ *           get_gs_opcode_info
+ * builds opcode info. insn->opcode, insn->ext and insn->rex expected to be already set
+ * This method fills out what those opcodes imply
+ */
+static BOOL get_gs_opcode_info( struct gs_insn *insn, BOOL *reg_operand, BOOL *byte_op,
+                               unsigned int *mem_size, unsigned int *imm )
+{
+    unsigned int op = insn->opcode;
+    unsigned int ext = insn->ext;
+
+    /* most common states */
+    *reg_operand = TRUE;    /* ModRM.reg is a register, not an opcode */
+    *byte_op     = FALSE;   /* 8-bit operands */
+    *mem_size    = 0;       /* memory operand size if not the operand size */
+    *imm         = 0;
+    insn->read_only = FALSE;
+
+    /* add, or, adc, sbb, and, sub, xor, cmp with a register: 00ooodw
+     * ooo - the operation, and 111 is cmp
+     * d - 1 if register is the destination
+     * w - 0 for bytes
+     */
+    if (op < 0x40 && (op & 7) < 4)
+    {
+        *byte_op = !(op & 1);
+        insn->read_only = (op & 2) || (op >> 3) == 7;
+        return TRUE;
+    }
+
+    /* other cases */
+    switch (op)
+    {
+        case 0x63: /* movsxd r/m32, r64 */
+            *mem_size = 4;
+            insn->read_only = TRUE;
+            return TRUE;
+        /* group 1 ([add, or, adc, sbb, and, sub, xor, cmp] via ext) */
+        case 0x80: /* r/m8, imm8 */
+            *reg_operand = FALSE;
+            *byte_op = TRUE;
+            *imm = 1;
+            insn->read_only = (ext == 7); /* cmp */
+            return TRUE;
+        case 0x81: /* r/m, imm16/32 */
+            *reg_operand = FALSE;
+            *imm = IMM_Z;
+            insn->read_only = (ext == 7); /* cmp */
+            return TRUE;
+        case 0x83: /* r/m, imm8 (sign-extended) */
+            *reg_operand = FALSE;
+            *imm = 1;
+            insn->read_only = (ext == 7); /* cmp */
+            return TRUE;
+        /* end of group 1 */
+        case 0x84: /* test r8, r/m8 */
+            *byte_op = TRUE;
+            insn->read_only = TRUE;
+            return TRUE;
+        case 0x85: /* test r, r/m */
+            insn->read_only = TRUE;
+            return TRUE;
+        case 0x88: /* mov r8, r/m8 */
+            *byte_op = TRUE;
+            return TRUE;
+        case 0x89: /* mov r, r/m */
+            return TRUE;
+        case 0x8a: /* mov r/m8, r8 */
+            *byte_op = TRUE;
+            insn->read_only = TRUE;
+            return TRUE;
+        case 0x8b: /* mov r/m, r */
+            insn->read_only = TRUE;
+            return TRUE;
+        /* ext picks the instruction, we only support those we can stub */
+        case 0xc6: /* mov imm8, r/m8  , only ext = 0 is defined behavior */
+            if (ext) return FALSE;
+            *reg_operand = FALSE;
+            *byte_op = TRUE;
+            *imm = 1;
+            return TRUE;
+        case 0xc7: /* mov imm16/32, r/m , only ext = 0 is defined behavior */
+            if (ext) return FALSE;
+            *reg_operand = FALSE;
+            *imm = IMM_Z;
+            return TRUE;
+        case 0xf6: /* group 3 r/m8: only test imm8 is allowed to be stubbed - ext = 0 */
+            if (ext) return FALSE;
+            *reg_operand = FALSE;
+            *byte_op = TRUE;
+            *imm = 1;
+            insn->read_only = TRUE;
+            return TRUE;
+        case 0xf7: /* group 3 r/m: only test imm16/32 is allowed to be stubbed - ext = 0 */
+            if (ext) return FALSE;
+            *reg_operand = FALSE;
+            *imm = IMM_Z;
+            insn->read_only = TRUE;
+            return TRUE;
+        case 0xfe: /* group 4 r/m8: exts - 0 (inc) and 1 (dec), rest is undefined */
+            if (ext > 1) return FALSE;
+            *reg_operand = FALSE;
+            *byte_op = TRUE;
+            return TRUE;
+        case 0xff: /* group 5 r/m: exts - 0 (inc) and 1 (dec), rest is call/jmp/push - dangerous to stub */
+            if (ext > 1) return FALSE;
+            *reg_operand = FALSE;
+            return TRUE;
+        /* end of ext cases */
+        case 0x1b6: /* movzx r/m8, r */
+        case 0x1be: /* movsx r/m8, r */
+            *mem_size = 1;
+            insn->read_only = TRUE;
+            return TRUE;
+        case 0x1b7: /* movzx r/m16, r */
+        case 0x1bf: /* movsx r/m16, r */
+            *mem_size = 2;
+            insn->read_only = TRUE;
+            return TRUE;
+        /* cmovcc group */
+        case 0x140: case 0x141: case 0x142: case 0x143:
+        case 0x144: case 0x145: case 0x146: case 0x147:
+        case 0x148: case 0x149: case 0x14a: case 0x14b:
+        case 0x14c: case 0x14d: case 0x14e: case 0x14f:
+            insn->read_only = TRUE;
+            return TRUE;
+        default:
+            return FALSE;
+    }
+}
+
+/***********************************************************************
+ *           decode_gs_insn
+ * fully decodes an instruction with %gs or %fs memory operand:
+ *
+ * should be of form
+ * [prefixes] [REX] opcode ModRM [SIB] [disp8/disp32] [imm]
+ * [prefixes] [REX] a0-a3 moffs64
+ *
+ * bytes[0..avail) are the bytes readable at the instruction
+ */
+
+static BOOL decode_gs_insn( struct gs_insn *insn, const BYTE *bytes, unsigned int avail )
+{
+    unsigned int i, modrm, mod, rm, sib, rex_w, rex_r, rex_x, rex_b;
+    /* some common defaults */
+    unsigned int mem_size = 0;
+    unsigned int imm = 0;
+    BOOL opsize = FALSE;
+    BOOL byte_op = FALSE;
+    BOOL reg_operand = TRUE;
+    BOOL addr32bit = FALSE;
+
+    /* store instruction bytes */
+    memset( insn, 0, sizeof(*insn));
+    avail = min( avail, MAX_INSN_LEN );
+    memcpy( insn->bytes, bytes, avail );
+
+    /* prefixes in any order */
+    for (i = 0; ; i++)
+    {
+        if (i >= avail) return FALSE; /* all prefixes? bad pointer/command */
+        if (bytes[i] == PREFIX_GS || bytes[i] == PREFIX_FS)
+        {
+            if (insn->seg) return FALSE; /* two segment prefixes */
+            insn->seg = bytes[i];
+            insn->seg_pos = i;
+        }
+        else if (bytes[i] == PREFIX_32ADDR) addr32bit = TRUE;
+        else if (bytes[i] == PREFIX_OPSIZE) opsize = TRUE;
+        else if (bytes[i] != PREFIX_LOCK) break; /* found end of prefixes */
+    }
+    if (!insn->seg) return FALSE; /* not an FS/GS command */
+    insn->rex_pos = i;
+
+    /* REX must come right before opcode bytes */
+    if ((bytes[i] & 0xf0) == REX_BASE) insn->rex = bytes[i++];
+    rex_w = insn->rex & REX_W ? 8 : 0;
+    rex_r = insn->rex & REX_R ? 8 : 0;
+    rex_x = insn->rex & REX_X ? 8 : 0;
+    rex_b = insn->rex & REX_B ? 8 : 0;
+
+    /* check if we escaped allowed size. Bad pointer/command? */
+    if (i >= avail) return FALSE;
+
+    insn->opcode_pos = i;
+    insn->opcode = bytes[i++];
+
+    /* 2byte opcodes */
+    if (insn->opcode == OPCODE_ESCAPE)
+    {
+        /* check if we escaped allowed size. Bad pointer/command? */
+        if (i >= avail) return FALSE;
+
+        insn->opcode = 0x100 | bytes[i++];
+    }
+
+    /* [prefixes] [REX] a0-a3 moffs64 cases
+     * mov moffs, %al
+     * mov moffs, %eax
+     * mov %al, moffs
+     * mov %eax moffs
+     */
+    if(insn->opcode >= 0xa0 && insn->opcode <= 0xa3)
+    {
+        /* check if we escaped allowed size. Bad pointer/command? */
+        if (i + 8 > avail) return FALSE;
+        if (addr32bit) return FALSE; /* nothing I know generates 32bit mode with moffs and they are generally complex to stub */
+        insn->moffs = TRUE;
+        insn->reg = GS_RAX;
+        insn->base = insn->index = -1;
+        insn->disp_pos = i;
+        insn->disp_len = 8;
+        memcpy( &insn->disp, bytes + i, 8 ); /* copy moffs */
+        i += 8;
+        byte_op = !(insn->opcode & 1);
+        insn->read_only = (insn->opcode <= 0xa1);
+    }
+    /* other cases */
+    else
+    {
+        /* check if we escaped allowed size. Bad pointer/command? */
+        if (i >= avail) return FALSE;
+        modrm = bytes[i++];
+        mod = modrm >> 6;
+        insn->ext = (modrm >> 3) & 7;
+        rm = modrm & 7;
+        if (mod == 3) return FALSE; /* a register, not a memory. Should never fault on %gs */
+
+        /* get/check remaining gs info */
+        if (!get_gs_opcode_info( insn, &reg_operand, &byte_op, &mem_size, &imm )) return FALSE;
+
+        insn->reg = reg_operand ? (int)(insn->ext | rex_r) : -1;
+
+        if (rm == MODRM_RM_SIB)
+        {
+            if (i + 1 > avail) return FALSE;
+            sib = bytes[i++];
+            insn->scale = sib >> 6;
+            insn->index = ((sib >> 3) & 7) | rex_x;
+            if (insn->index == SIB_NO_INDEX) insn->index = -1; /* 100 means no index reg, 1100 is %r12 */
+            insn->base = (sib & 7) | rex_b;
+            if ((sib & 7) == SIB_NO_BASE && mod == 0) /* no base reg even with REX_B, it is disp32 instead */
+            {
+                insn->base = -1;
+                mod = 2;
+            }
+            /* don't you just love all those special cases? Thanks x86-64 */
+        }
+        else if (rm == MODRM_RM_DISP && mod == 0) return FALSE; /* %rip-relateive, not a TEB */
+        else
+        {
+            insn->base = rm | rex_b;
+            insn->index = -1;
+        }
+
+        insn->disp_pos = i;
+        if (mod == 1) /* disp8, sign extended */
+        {
+            if (i + 1 > avail) return FALSE;
+            insn->disp = (signed char) bytes[i];
+            insn->disp_len = 1;
+        }
+        else if (mod == 2) /* disp32, sign extneded */
+        {
+            int disp;
+            if (i + 4 > avail) return FALSE;
+            memcpy( &disp, bytes + i, 4 );
+            insn->disp = disp;
+            insn->disp_len = 4;
+        }
+
+        i += insn->disp_len;
+    }
+
+    /* the immediate - size depends on the operand size */
+    if (imm == IMM_Z) imm = (opsize && !rex_w) ? 2 : 4;
+    insn->imm_pos = i;
+    insn->imm_len = imm;
+    i += imm;
+
+    /* final boundary check */
+    if (i > avail) return FALSE;
+    insn->len = i;
+
+    /* bytes accessed - 8-bit or a fixed size or the operand size. Keep REX.W in mind */
+    if (byte_op) insn->size = 1;
+    else if (mem_size) insn->size = mem_size;
+    else insn->size = rex_w ? rex_w : opsize ? 2 : 4;
+
+    /* without any rex prefix, 4-7 byte registers are %ah %ch %dh %bh */
+    insn->high_byte = byte_op && !insn->rex && insn->reg >= 4 && insn->reg <= 7;
+
+    /* set 0x67 32bit addr mode flag */
+    insn->addr32bit = addr32bit;
+
+    /* success */
+    return TRUE;
+}
+
+/***********************************************************************
+ *                      %GS FAULT HANDLING BLOCK
+ *
+ * Whenever we have a page fault, we need to see if the fault was caused by %gs being pinned to 0 in OpenBSD
+ * For this we parse the command assuming %gs was 0 (fault was at "zero-page"), find if it is really %gs command
+ * and then rewrite it without %gs. Sometimes another thread will already rewrite gs to fs/jmp and we just need a retry.
+ * We have 4 approaches
+ * 1. In place - this is NtCurrentTeb() call. It always compiles into mov %gs:0x30, %r
+ *      for this to work, since we have ref to teb in our teb_tls_offset, we can just replace few values in the command itself
+ *      for example
+ *      65 48 8b 04 25 30 00 00 00          mov %gs:0x30, %rax
+ *      64 48 8b 04 25 xx xx xx xx          mov %fs:teb_tls_offset, %rax --- where teb_tls_offset is negative
+ *
+ *      this replacement is very stable and does not require us to create any weird stubs.
+ *      SOURCE OF THE DECOMP: wine's own compiled code for NtCurrentTeb() in winnt.h for defined(__x86_64__) && defined(__GNUC__)
+ *          other cases will be handled in approach 3
+ *
+ * 2. Generic Stub (if approach 3 failed - this is easier to cover second even though it is handled 3rd)
+ *      for this to work, the instruction has to be 5 bytes or longer. If it is, and we find a spot to park a stub in rel32
+ *      we can replace it with a jmp rel32 command to a stub that rewrites the command without %gs.
+ *      This can be done with ANY command that is 5 bytes or longer. If command is less than 5 bytes, we don't have enough space
+ *      to replace it. I will have to be handled by case 4 (with notable exception in case 3), but commands like that are rare.
+ *      This replacement is stable but we have to specifically handle case when thread is being stopped inside of the stub
+ *      more on that in gs_leave_stub
+ *
+ *      SOURCE OF THE DECOMP: no need to decompile anything, it's a stable replacement, regardless of the command
+ *
+ * 3. !!STILL NOT IMPLEMENTED!! - code to implement is about 500-700 extra lines of code just for MIMALLOC/BUN:
+ *    Pair Stub - investigations into faults with apps that use mimalloc (MIT license, include/mimalloc/prim.h) shows that
+ *      mimalloc inlines TlsGetValue() in form of a four-byte mov %gs(%R'),%D which has no room for a jmp rel32.
+ *      Thankfully it also puts address arithmetic lea in front of it.
+ *      x86 cannot be decoded backwards, so even if we did find it we need to check that the register in the mov is also %R'
+ *      and inex register in lea itself should also be different from %R' because otherwise we would lost the index in the stub.
+ *
+ *      SOURCE OF THE DECOMP: mimalloc - MIT licensed
+ *
+ * 4. Everything else - we will pay a full SIGSEGV trip here every time. We want so that most cases are handled before this one
+ *
+ *
+ * Stubs do not use the stack; scratch registers are saved in TLS's second and, if needed, third qwords.
+ * This means %rsp is the same inside of a stub as at the site. This also means that we can safely leave stub either to before jump
+ * or after jump, depending on where exactly we stopped.
+ */
+
+/* Code building constants */
+#define JMP_REL32           0xe9    /* used to replace the command and then jump back from the stub */
+#define JMP_REL32_LEN       5       /* length of JMP_REL32 - e9 and 32 bits of rel jump offset */
+#define JMP_RIP             0xff    /* jmp *0(%rip) */
+#define INT3                0xcc    /* we are using int3 to pad our codestubs and source of our jumps to trap any stray jumps */
+#define LEA                 0x8d    /* lea */
+#define MOV_STORE           0x89    /* mov mem, reg */
+#define MOV_LOAD            0x8b    /* mov reg, mem */
+#define FS_MOV_LEN          9       /* length of emit_fs_mov()'s instructions */
+#define GS_LEA_MAX          8       /* longest lea that we recognize for gs pair */
+#define GS_PAIR_MAX         (GS_LEA_MAX + MAX_INSN_LEN)
+
+/* what stub replaced */
+enum gs_stub_kind
+{
+    GS_STUB_PLAIN, /* replaced a %gs instruction on its own */
+    GS_STUB_PAIR   /* replaced a lea + %gs instruction that uses that register */
+};
+
+/* stub details and its code
+ * code goes first so it is in one cache
+ */
+struct gs_stub
+{
+    BYTE  code[CODE_STUB_SLOT_SIZE - 40]; /* longest instruction set is 81 bytes */
+    BYTE *site;                     /* address of the %gs instruction */
+    BYTE  code_len;                 /* length of our code block */
+    BYTE  insn[MAX_INSN_LEN];       /* original bytes of the command */
+    BYTE  insn_len;                 /* length of the original command */
+    BYTE  scratch1;                 /* which register we picked as scratch1 (0-7 w/o %rsp) */
+    BYTE  scratch2;                 /* which register we picked as scratch2 (0-7 w/o %rsp) */
+    BYTE  op_start;                 /* offset in code[] of the rewritten instruction */
+    BYTE  op_end;                   /* offset in code just after it - there we will restore the scratch reg */
+    BYTE  kind;                     /* of type enum gs_stub_kind */
+    BYTE  lea_len;                  /* for stub pair - length of the lea */
+    BYTE  lea[GS_LEA_MAX];          /* original bytes of the lea command */
+    BYTE  two_scratch;              /* uses two scratch registers - needed for gs_leave_stub */
+};
+
+C_ASSERT( sizeof(struct gs_stub) == CODE_STUB_SLOT_SIZE );
+C_ASSERT( offsetof(struct gs_stub, code) == 0 );
+
+/* Code generators for the stub */
+
+/***********************************************************************
+ *           emit_fs_mov
+ * generates
+ * mov %reg,%fs:disp
+ * or
+ * mov %fs:disp, %reg
+ * bytecode: 64 48 89/8b ModRM(00,reg,100) 25 disp32
+ */
+static BYTE *emit_fs_mov( BYTE *p, BYTE opcode, unsigned int reg, int disp )
+{
+    *p++ = PREFIX_FS;
+    *p++ = REX_BASE | REX_W;
+    *p++ = opcode;
+    *p++ = MODRM( 0, reg, MODRM_RM_SIB );
+    *p++ = SIB_ABSOLUTE;
+    memcpy( p, &disp, 4);
+    return p + 4;
+}
+
+/***********************************************************************
+ *           build_gs_stub
+ *
+ * Builds the stub for a %gs instruction at a site. This handles non-lea pair cases
+ *
+ * Stub for a %gs instruction looks like this
+ * code[0]                  mov %S,%fs:slot+8           save the scratch register S
+ * code[9]                  mov %fs:slot, %S            S = TEB
+ * code[18]                 lea 0(%base, %S, 1), %S     S = TEB + base , if base register, otherwise no lea
+ * code[op_start]           <the instruction>           [S + index*scale + disp32] instead of %gs:...
+ * code[op_end]             mov %fs:slot+8, %S          restore S
+ *                          jmp *0(%rip)                back to next instruction
+ *                          .quad site + len
+ *
+ * Stub for 0x67 - 32bit addr mode instruction looks like this:
+ * code[0]                  mov %S,%fs:slot+8           save the scratch register S
+ * code[9]                  mov %T,%fs:slot+16          save the scratch register T
+ * code[18]                 mov %fs:slot, %S            S = TEB
+ * code[27]                 lea ..., %ST                T = 32 bit addr
+ * code[op_start]           <the instruction>           [S + T] *
+ * code[op_end]             mov %fs:slot+8 , %S         restore S
+ * code[op_end + 9]         mov %fs:slot+16, %T         restore T
+ * code[op_end + 18]        jmp *0(%rip)                back to next instruction
+ *                          .quad site + len
+ *
+ * none of the added instructions change the flags.
+ */
+static void build_gs_stub( struct gs_stub *stub, BYTE *site, const struct gs_insn *insn )
+{
+    unsigned int i, s1, s2;
+    int slot = teb_tls_offset;
+    int scratch_slot1 = slot + 8;
+    int scratch_slot2 = slot + 16;
+    int disp = insn->disp;
+    ULONG_PTR ret_addr = (ULONG_PTR)site + insn->len;
+    int reg_full = insn->reg;
+    BYTE *p = stub->code;
+
+    /* Find an unused register. At most 3 are used, so out of 7 we will always find something */
+    if (insn->high_byte) reg_full -= 4;
+    for (s1 = GS_RAX; s1 <= GS_RDI; s1++)
+        if (s1 != GS_RSP && (int)s1 != reg_full && (int)s1 != insn->base && (int)s1 != insn->index) break;
+
+    /* Nothing bad in always looking for a second register even if we don't need it */
+    for (s2 = GS_RAX; s2 <= GS_RDI; s2++)
+        if (s2 != GS_RSP && (int)s2 != reg_full && (int)s2 != insn->base && (int)s2 != insn->index && s2 != s1) break;
+
+    memset( stub, INT3, sizeof(*stub) ); /* trap stray calls/jumps */
+    stub->site = site;
+    memcpy( stub->insn, insn->bytes, insn->len );
+    stub->insn_len = insn->len;
+    stub->scratch1 = s1;
+    stub->scratch2 = s2;
+    stub->kind = GS_STUB_PLAIN;
+    stub->lea_len = 0;
+    stub->two_scratch = (BYTE)insn->addr32bit;
+
+    p = emit_fs_mov( p, MOV_STORE, s1, scratch_slot1 );       /* mov %S, %fs:slot+8 */
+    if (stub->two_scratch) p = emit_fs_mov( p, MOV_STORE, s2, scratch_slot2 );  /* mov %T, %fs:slot+16 */
+    p = emit_fs_mov( p, MOV_LOAD,  s1, slot);                 /* mov %fs:slot, %S */
+
+    if (stub->two_scratch)
+    {
+        /* lea disp32(%base,%index32,scale), %T
+         * No REX.W, REX.B/REX.X only for base/index 8-15
+         */
+        BYTE rex_b = insn->base  >= 8 ? REX_B : 0;
+        BYTE rex_x = insn->index >= 8 ? REX_X : 0;
+
+        *p++ = PREFIX_32ADDR;
+        if(rex_b || rex_x) *p++ = REX_BASE | rex_b | rex_x;
+        *p++ = LEA;
+        *p++ = MODRM( insn->base != -1 ? 2 : 0, s2, MODRM_RM_SIB );
+        *p++ = SIB( insn->scale, insn->index != -1 ? insn->index : SIB_NO_INDEX, insn->base != -1 ? insn->base : SIB_NO_BASE);
+        memcpy( p, &disp, 4 ); /* Wrap displacement into this LEA */
+        p += 4;
+        disp = 0; /* Displacement is wrapped into this LEA */
+    }
+    else if (insn->base != -1)
+    {
+        /* lea 0(%base,%S,1), %S. REX.W, plus REX.B for a base 8-15
+         * ModRM mod 01, rm 100; SIB index S, base, disp8 0
+         */
+        *p++ = REX_BASE | REX_W | (insn->base >= 8 ? REX_B : 0);
+        *p++ = LEA;
+        *p++ = MODRM( 1, s1, MODRM_RM_SIB );
+        *p++ = SIB( 0, s1, insn->base );
+        *p++ = 0;
+    }
+
+    stub->op_start = p - stub->code;
+    /* copy old instruction bytes */
+    /* legacy prefixes, without the seg prefix and 32bit addr mode prefix */
+    for (i = 0; i < insn->rex_pos; i++) if (i != insn->seg_pos && insn->bytes[i] != PREFIX_32ADDR) *p++ = insn->bytes[i];
+    /* REX: W and R stay; X only if there is still an index 8-15; B removed, the base is now S (0-7 w/o %rsp)
+     * Without REX: don't add a new one (S and any index are 0-7) so %ah..%bh stay what they are */
+    if (insn->rex)
+    {
+        if (insn->moffs) *p++ = REX_BASE | (insn->rex & REX_W);
+            /* REX - with addr32 index is s2, which is always 0-7, so clean up REX_X */
+        else *p++ = REX_BASE | (insn->rex & (REX_W | REX_R)) | (insn->index >= 8 && !insn->addr32bit ? REX_X : 0);
+    }
+    if (insn->moffs)
+    {
+        /* modrm forms a0 -> 8a (load 8), a1 -> 8b (load), a2 -> 88 (store 8), a3 -> 89 (store) */
+        static const BYTE modrm_form[4] = { 0x8a, 0x8b, 0x88, 0x89 };
+        *p++ = modrm_form[insn->opcode - 0xa0];
+        *p++ = MODRM( 2, GS_RAX, MODRM_RM_SIB );
+    }
+    else
+    {
+        /* opcode bytes as they are and ModRM with the same reg, mod 10 (disp32), rm 100 (SIB) */
+        *p++ = insn->bytes[insn->opcode_pos];
+        if (insn->opcode >= 0x100) *p++ = insn->bytes[insn->opcode_pos + 1];
+        *p++ = MODRM( 2, insn->ext, MODRM_RM_SIB );
+    }
+    /* two-scratch case - 64 bit addr [s1+s2], s2 is a zero-extended 32 bit offset */
+    if (stub->two_scratch) *p++ = SIB( 0, s2, s1 );
+    /* SIB: base S, and then original index and scale */
+    else if (insn->index != -1) *p++ = SIB( insn->scale, insn->index, s1 );
+    else *p++ = SIB( 0, SIB_NO_INDEX, s1 );
+    /* NOTE: this will write 0 when addr32 case - see above, it's wrapped into LEA above */
+    memcpy( p, &disp, 4);
+    p += 4;
+    /* immediate as is */
+    memcpy( p, insn->bytes + insn->imm_pos, insn->imm_len );
+    p += insn->imm_len;
+    stub->op_end = p - stub->code;
+
+    p = emit_fs_mov( p, MOV_LOAD, s1, scratch_slot1); /* mov %fs:slot+8, %S */
+    if (stub->two_scratch) p = emit_fs_mov( p, MOV_LOAD, s2, scratch_slot2); /* mov %fs:slot+16, %T */
+    /* jmp *0(%rip) */
+    *p++ = JMP_RIP;
+    *p++ = MODRM( 0, 4, MODRM_RM_DISP );
+    memset( p, 0, 4); /* disp32 = 0 */
+    p += 4;
+    memcpy( p, &ret_addr, 8);
+    p += 8;
+    stub->code_len = p - stub->code;
+}
+
+static ULONG_PTR get_gs_insn_offset( const struct gs_insn *insn, ucontext_t *ucontext )
+{
+    ULONG_PTR offset = insn->disp;
+
+    if (insn->base != -1) offset += *get_gs_reg( ucontext, insn->base );
+    if (insn->index != -1) offset += *get_gs_reg( ucontext, insn->index ) << insn->scale;
+    if (insn->addr32bit) offset = (unsigned int)offset; /* truncated, like 32 bit mode would */
+
+    return offset;
+}
+
+static int gs_in_place_offset( const struct gs_insn *insn, ULONG_PTR offset )
+{
+    const ULONG_PTR self = offsetof( TEB, Tib.Self ); /* should be 0x30 */
+
+    /* right command */
+    if (!insn->read_only || insn->base != -1 || insn->index != -1 || insn->addr32bit) return -1;
+
+    if (offset < self || offset + insn->size > self + sizeof(void *)) return -1;
+
+    return offset - self; /* located offset bytes that we need to change */
+}
+
+/* Static redirect for fail-all cases */
+#define GS_REDIRECTS 4096 /* want to keep low, but on a safer side. Without cases 1-3 we would have 10k+ of cases here */
+static struct gs_stub *gs_redirects[GS_REDIRECTS];
+static pthread_mutex_t gs_redirect_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static unsigned int gs_redirect_hash( const BYTE *site )
+{
+    /* Fib hashing and then 4096 entires. Should be fine. We shouldn't have lots of cases here anyways */
+    return ((ULONG_PTR)site * 0x9e3779b97f4a7c15ull) >> (64 - 12);
+}
+
+static BOOL is_same_gs_site( const struct gs_stub *stub, const BYTE *site, const struct gs_insn *insn )
+{
+    return stub->site == site && stub->insn_len == insn->len && !memcmp( stub->insn, insn->bytes, insn->len );
+}
+
+/* returns redirect stub for this site and instruction, if any. Lock free */
+static struct gs_stub *find_gs_redirect( const BYTE *site, const struct gs_insn *insn)
+{
+    unsigned int i;
+    struct gs_stub *stub;
+    unsigned int h = gs_redirect_hash( site );
+
+    for (i = 0; i < GS_REDIRECTS; i++)
+    {
+        if(!(stub = __atomic_load_n( &gs_redirects[(h + i) % GS_REDIRECTS], __ATOMIC_ACQUIRE ))) break;
+        if(stub->site == site) return is_same_gs_site( stub, site, insn ) ? stub : NULL;
+    }
+
+    return NULL;
+}
+
+/* adds a redirect; returns the stub to use. If another thread was faster - returns their stub. NULL if table is full */
+static struct gs_stub *add_gs_redirect( struct gs_stub *new_stub, const struct gs_insn *insn )
+{
+    unsigned int i;
+    struct gs_stub *stub;
+    sigset_t sigset;
+    unsigned int h = gs_redirect_hash( new_stub->site );
+    struct gs_stub *ret = NULL;
+
+    server_enter_uninterrupted_section( &gs_redirect_mutex, &sigset );
+    for (i = 0; i < GS_REDIRECTS; i++)
+    {
+        struct gs_stub **entry = &gs_redirects[(h + i) % GS_REDIRECTS];
+        if ((stub = *entry) && stub->site != new_stub->site) continue;
+        if (stub && is_same_gs_site( stub, new_stub->site, insn )) ret = stub; /* already there */
+        else
+        {
+            __atomic_store_n( entry, new_stub, __ATOMIC_RELEASE ); /* free, or a stale entry */
+            ret = new_stub;
+        }
+        break;
+    }
+    server_leave_uninterrupted_section( &gs_redirect_mutex, &sigset );
+    return ret;
+}
+
+/* checks if something already rewritten the instruction. If yes, we can just re-run it
+ * Don't ask me how many hours I had to spend to find that you actually need this.
+ */
+static BOOL is_gs_rewritten( BYTE *rip, const BYTE *bytes, unsigned int avail,
+                             ucontext_t *ucontext, ULONG_PTR fault_addr )
+{
+    struct gs_insn insn;
+    struct gs_stub *stub;
+    int rel;
+
+    if (avail >= JMP_REL32_LEN && bytes[0] == JMP_REL32)
+    {
+        memcpy( &rel, bytes + 1, 4 );
+        if (!(stub = virtual_code_stub_slot( rip + JMP_REL32_LEN + rel )) || stub->site != rip) return FALSE;
+        return decode_gs_insn( &insn, stub->insn, stub->insn_len) &&
+            get_gs_insn_offset( &insn, ucontext ) == fault_addr;
+    }
+
+    if (!decode_gs_insn( &insn, bytes, avail ) || insn.seg != PREFIX_FS) return FALSE;
+    if (insn.base != -1 || insn.index != -1) return FALSE;
+    if (insn.disp < teb_tls_offset || insn.disp >= teb_tls_offset + (LONG64)sizeof(void *)) return FALSE;
+    return offsetof( TEB, Tib.Self ) + (insn.disp - teb_tls_offset) == fault_addr;
+}
+
+
+
+/***********************************************************************
+ *           gs_handle_fault
+ * Page fault handler
+ */
+BOOL gs_handle_fault( ucontext_t *ucontext, ULONG_PTR fault_addr )
+{
+    BYTE bytes[MAX_INSN_LEN], new_bytes[MAX_INSN_LEN];
+    struct gs_stub stub, *slot, *used;
+    struct gs_insn insn;
+    unsigned int i, avail, n;
+    ULONG_PTR offset;
+    BOOL patched;
+    LONG64 disp;
+    LONG_PTR rel;
+    int self_pos;
+    BYTE *rip = (BYTE *)ucontext->sc_rip;
+
+    /* since %gs pins value to 0, TEB access faults at 0+TEB offset */
+    if (fault_addr >= sizeof(TEB)) return FALSE;
+
+    if (!(avail = virtual_uninterrupted_read_memory( rip, bytes, sizeof(bytes) ))) return FALSE;
+    if (is_gs_rewritten( rip, bytes, avail, ucontext, fault_addr )) return TRUE;
+
+    if (!decode_gs_insn( &insn, bytes, avail ) || insn.seg != PREFIX_GS) return FALSE;
+
+    offset = get_gs_insn_offset( &insn, ucontext );
+    if (offset != fault_addr) return FALSE; /* the fault isn't this instruction's %gs access */
+    if (offset + insn.size > sizeof(TEB)) return FALSE; /* not a TEB fault */
+
+    /* seen before site that can't be patched -> just jump to stub*/
+    if ((slot = find_gs_redirect( rip, &insn ))) goto redirect;
+
+    /* 1. In place update %gs->%fs, and then instead of TEB offset (0x30) -> TLS slot */
+    if ((self_pos = gs_in_place_offset( &insn, offset )) != -1)
+    {
+        /* just change the command */
+        memcpy( new_bytes, insn.bytes, insn.len ); /* first copy it as is */
+        new_bytes[insn.seg_pos] = PREFIX_FS; /* replace %gs with %fs */
+        disp = teb_tls_offset + self_pos; /* fits a disp32 and same in moffs64 */
+        memcpy( new_bytes + insn.disp_pos, &disp, insn.disp_len );
+        /* then patch it in one go */
+        switch (virtual_patch_code( rip, insn.bytes, new_bytes, insn.len ))
+        {
+            case PATCH_DONE:
+                /* no stub created for this so we can treat DONE same as if someone else changed the instruction */
+            case PATCH_CHANGED:
+                return TRUE;
+            case PATCH_FAILED:
+                break;
+        }
+    }
+
+    /* generate a stub */
+    build_gs_stub( &stub, rip, &insn );
+    if (!(slot = virtual_alloc_code_stub( rip, &stub, sizeof(stub) )))
+    {
+        WARN( "%p: no memory for a %%gs stub\n", rip);
+        return FALSE;
+    }
+
+    rel = slot->code - (rip + JMP_REL32_LEN); /* the allocator has to keep it within rel32 reach */
+    if (insn.len >= JMP_REL32_LEN && rel == (int)rel)
+    {
+        new_bytes[0] = JMP_REL32;
+        memcpy( new_bytes + 1, &rel, 4 );
+        memset( new_bytes + JMP_REL32_LEN, INT3, insn.len - JMP_REL32_LEN ); /* stamp everything else with INT3 */
+        switch (virtual_patch_code( rip, insn.bytes, new_bytes, insn.len ))
+        {
+            case PATCH_DONE:
+                return TRUE;
+            case PATCH_CHANGED:
+                virtual_free_code_stub( slot ); /* someone already did it, free old slot */
+                return TRUE;
+            case PATCH_FAILED:
+                break;
+        }
+    }
+
+    /* case 4 - unpatchable redirect */
+    if (!(used = add_gs_redirect( slot, &insn )))
+    {
+        WARN( "%p: too many %%gs sites that can't be patched\n", rip);
+        virtual_free_code_stub( slot );
+        return FALSE;
+    }
+    if (used != slot) virtual_free_code_stub( slot );
+    slot = used;
+redirect:
+    ucontext->sc_rip = (ULONG_PTR)slot->code;
+    return TRUE;
+}
+
+/***********************************************************************
+ *           gs_leave_stub
+ *
+ * If thread was stopped in a stub, we need to move it outside, so that nothing else sees a stub address.
+ *
+ * By position, we can split it into 4 cases
+ * [0, 9)           nothing done yet                    -> return to site
+ * [9, op_start]    S saved, instruction not run        -> return to site       restore S from TLS scratch
+ * op_end           instruction done, S not restored    -> site+len             restore S from TLS scratch
+ * (op_end+9, end)  we were actually almost done        -> site+len
+ *
+ * For 0x67 32 bit add mode, we have extra cases where second scratch register needs restoration
+ */
+BOOL gs_leave_stub( ucontext_t *ucontext )
+{
+    struct gs_stub *stub;
+    unsigned int pos;
+    ULONG64 saved;
+    BYTE *rip = (BYTE *)ucontext->sc_rip;
+
+    if (!(stub = virtual_code_stub_slot( rip ))) return FALSE; /* not in a stub slot */
+    if (rip < stub->code || rip >= stub->code + stub->code_len) return FALSE; /* somehow in a stub but not in stub code? */
+
+    pos = rip - stub->code;
+
+    if (pos >= FS_MOV_LEN && pos < stub->op_end + FS_MOV_LEN)
+    {
+        /* sig handler runs on the same thread, so we just grab it from TLS scratch */
+        __asm__( "movq %%fs:(%1),%0" : "=r" (saved) : "r" (teb_tls_offset + 8) );
+        *get_gs_reg( ucontext, stub->scratch1 ) = saved;
+    }
+
+    if (stub->two_scratch && pos >= FS_MOV_LEN * 2 && pos < stub->op_end + FS_MOV_LEN * 2)
+    {
+        /* sig handler runs on the same thread, so we just grab it from TLS scratch */
+        __asm__( "movq %%fs:(%1),%0" : "=r" (saved) : "r" (teb_tls_offset + 16) );
+        *get_gs_reg( ucontext, stub->scratch2 ) = saved;
+    }
+
+    ucontext->sc_rip = (ULONG_PTR)stub->site + (pos <= stub->op_start ? 0 : stub->insn_len);
+
+    return TRUE;
+}
+
+#endif /* defined(__OpenBSD__)*/
