/*  This symbol is used at runtime to figure out the virtual address that the */
/*  enclave is loaded at. */
.section absolute
.global IMAGE_BASE
IMAGE_BASE:

.section ".note.x86_64-fortanix-unknown-sgx", "", @note
    .align 4
    .long 1f - 0f              /* name length (not including padding) */
    .long 3f - 2f              /* desc length (not including padding) */
    .long 1                    /* type = NT_VERSION */
0:  .asciz "toolchain-version" /* name */
1:  .align 4
2:  .long 1                    /* desc - toolchain version number, 32-bit LE */
3:  .align 4

.section .rodata
/*  The XSAVE area needs to be a large chunk of readable memory, but since we are */
/*  going to restore everything to its initial state (XSTATE_BV=0), only certain */
/*  parts need to have a defined value. In particular: */
/*  */
/*    * MXCSR in the legacy area. This register is always restored if RFBM[1] or */
/*      RFBM[2] is set, regardless of the value of XSTATE_BV */
/*    * XSAVE header */
.align 64
.Lxsave_clear:
.org .+24
.Lxsave_mxcsr:
    .short 0x1f80

/*  We can store a bunch of data in the gap between MXCSR and the XSAVE header */

/*  The following symbols point at read-only data that will be filled in by the */
/*  post-linker. */

/*  When using this macro, don't forget to adjust the linker version script! */
.macro globvar name:req size:req
    .global \name
    .protected \name
    .align \size
    .size \name , \size
    \name :
        .org .+\size
.endm
    /*  The base address (relative to enclave start) of the heap area */
    globvar HEAP_BASE 8
    /*  The heap size in bytes */
    globvar HEAP_SIZE 8
    /*  Value of the RELA entry in the dynamic table */
    globvar RELA 8
    /*  Value of the RELACOUNT entry in the dynamic table */
    globvar RELACOUNT 8
    /*  The enclave size in bytes */
    globvar ENCLAVE_SIZE 8
    /*  The base address (relative to enclave start) of the enclave configuration area */
    globvar CFGDATA_BASE 8
    /*  Non-zero if debugging is enabled, zero otherwise */
    globvar DEBUG 1
    /*  The base address (relative to enclave start) of the enclave text section */
    globvar TEXT_BASE 8
    /*  The size in bytes of enclacve text section */
    globvar TEXT_SIZE 8
    /*  The base address (relative to enclave start) of the enclave .eh_frame_hdr section */
    globvar EH_FRM_HDR_OFFSET 8
    /*  The size in bytes of enclave .eh_frame_hdr section */
    globvar EH_FRM_HDR_LEN 8
    /*  The base address (relative to enclave start) of the enclave .eh_frame section */
    globvar EH_FRM_OFFSET 8
    /*  The size in bytes of enclacve .eh_frame section */
    globvar EH_FRM_LEN 8

.org .Lxsave_clear+512
.Lxsave_header:
    .int 0, 0 /*  XSTATE_BV */
    .int 0, 0 /*  XCOMP_BV */
    .org .+48 /*  reserved bits */

.data
.Laborted:
    .byte 0

/*  TCS local storage section */
.equ tcsls_tos,                 0x00 /*  initialized by loader to *offset* from image base to TOS */
.equ tcsls_flags,               0x08 /*  initialized by loader */
.equ tcsls_flag_secondary,      0    /*  initialized by loader; 0 = standard TCS, 1 = secondary TCS */
.equ tcsls_flag_init_once,      1    /*  initialized by loader to 0 */
/*  14 unused bits */
.equ tcsls_user_fcw,            0x0a
.equ tcsls_user_mxcsr,          0x0c
.equ tcsls_last_rsp,            0x10 /*  initialized by loader to 0 */
.equ tcsls_panic_last_rsp,      0x18 /*  initialized by loader to 0 */
.equ tcsls_debug_panic_buf_ptr, 0x20 /*  initialized by loader to 0 */
.equ tcsls_user_rsp,            0x28
.equ tcsls_user_retip,          0x30
.equ tcsls_user_rbp,            0x38
.equ tcsls_user_r12,            0x40
.equ tcsls_user_r13,            0x48
.equ tcsls_user_r14,            0x50
.equ tcsls_user_r15,            0x58
.equ tcsls_tls_ptr,             0x60
.equ tcsls_tcs_addr,            0x68

.macro load_tcsls_flag_secondary_bool reg:req comments:vararg
    .ifne tcsls_flag_secondary /* to convert to a bool, must be the first bit */
    .abort
    .endif
        mov $(1<<tcsls_flag_secondary),%e\reg
        and %gs:tcsls_flags,%\reg
.endm

/* We place the ELF entry point in a separate section so it can be removed by
   elf2sgxs */
.section .text_no_sgx, "ax"
.Lelf_entry_error_msg:
    .ascii "Error: This file is an SGX enclave which cannot be executed as a standard Linux binary.\nSee the installation guide at https://edp.fortanix.com/docs/installation/guide/ on how to use 'cargo run' or follow the steps at https://edp.fortanix.com/docs/tasks/deployment/ for manual deployment.\n"
.Lelf_entry_error_msg_end:

.global elf_entry
.type elf_entry,function
elf_entry:
/* print error message */
    movq $2,%rdi                      /* write to stderr (fd 2) */
    lea .Lelf_entry_error_msg(%rip),%rsi
    movq $.Lelf_entry_error_msg_end-.Lelf_entry_error_msg,%rdx
.Lelf_entry_call:
    movq $1,%rax                      /* write() syscall        */
    syscall
    test %rax,%rax
    jle .Lelf_exit                    /* exit on error          */
    add %rax,%rsi
    sub %rax,%rdx                     /* all chars written?     */
    jnz .Lelf_entry_call

.Lelf_exit:    
    movq $60,%rax                     /* exit() syscall         */
    movq $1,%rdi                      /* exit code 1            */
    syscall
    ud2                               /* should not be reached  */
/*  end elf_entry */

/* This code needs to be called *after* the enclave stack has been setup. */
/* There are 3 places where this needs to happen, so this is put in a macro. */
.macro entry_sanitize_final
/*  Sanitize rflags received from user */
/*    - DF flag: x86-64 ABI requires DF to be unset at function entry/exit */
/*    - AC flag: AEX on misaligned memory accesses leaks side channel info */
    pushfq
    andq $~0x40400, (%rsp)
    popfq
/*  check for abort */
    bt $0,.Laborted(%rip)
    jc .Lreentry_panic
.endm

.text
.global sgx_entry
.type sgx_entry,function
sgx_entry:
/*  save user registers */
    mov %rcx,%gs:tcsls_user_retip
    mov %rsp,%gs:tcsls_user_rsp
    mov %rbp,%gs:tcsls_user_rbp
    mov %r12,%gs:tcsls_user_r12
    mov %r13,%gs:tcsls_user_r13
    mov %r14,%gs:tcsls_user_r14
    mov %r15,%gs:tcsls_user_r15
    mov %rbx,%gs:tcsls_tcs_addr
    stmxcsr %gs:tcsls_user_mxcsr
    fnstcw %gs:tcsls_user_fcw

/*  check for debug buffer pointer */
    testb  $0xff,DEBUG(%rip)
    jz .Lskip_debug_init
    mov %r10,%gs:tcsls_debug_panic_buf_ptr
.Lskip_debug_init:
/*  reset cpu state */
    mov %rdx, %r10
    mov $-1, %rax
    mov $-1, %rdx
    xrstor .Lxsave_clear(%rip)
    mov %r10, %rdx

/*  check if returning from usercall */
    mov %gs:tcsls_last_rsp,%r11
    test %r11,%r11
    jnz .Lusercall_ret
/*  setup stack */
    mov %gs:tcsls_tos,%rsp /*  initially, RSP is not set to the correct value */
                           /*  here. This is fixed below under "adjust stack". */
/*  check for thread init */
    bts $tcsls_flag_init_once,%gs:tcsls_flags
    jc .Lskip_init
/*  adjust stack */
    lea IMAGE_BASE(%rip),%rax
    add %rax,%rsp
    mov %rsp,%gs:tcsls_tos
    entry_sanitize_final
/*  call tcs_init */
/*  store caller-saved registers in callee-saved registers */
    mov %rdi,%rbx
    mov %rsi,%r12
    mov %rdx,%r13
    mov %r8,%r14
    mov %r9,%r15
    load_tcsls_flag_secondary_bool di /* RDI = tcs_init() argument: secondary: bool */
    call tcs_init
/*  reload caller-saved registers */
    mov %rbx,%rdi
    mov %r12,%rsi
    mov %r13,%rdx
    mov %r14,%r8
    mov %r15,%r9
    jmp .Lafter_init
.Lskip_init:
    entry_sanitize_final
.Lafter_init:
/*  call into main entry point */
    load_tcsls_flag_secondary_bool cx /* RCX = entry() argument: secondary: bool */
    call entry /* RDI, RSI, RDX, R8, R9 passed in from userspace */
    mov %rax,%rsi  /* RSI = return value */
    /* NOP: mov %rdx,%rdx */ /*  RDX = return value */
    xor %rdi,%rdi  /* RDI = normal exit */
.Lexit:
/*  clear general purpose register state */
    /*  RAX overwritten by ENCLU */
    /*  RBX set later */
    /*  RCX overwritten by ENCLU */
    /*  RDX contains return value */
    /*  RSP set later */
    /*  RBP set later */
    /*  RDI contains exit mode */
    /*  RSI contains return value */
    xor %r8,%r8
    xor %r9,%r9
    xor %r10,%r10
    xor %r11,%r11
    /*  R12 ~ R15 set by sgx_exit */
.Lsgx_exit:
/*  clear extended register state */
    mov %rdx, %rcx /*  save RDX */
    mov $-1, %rax
    mov %rax, %rdx
    xrstor .Lxsave_clear(%rip)
    mov %rcx, %rdx /*  restore RDX */
/*  clear flags */
    pushq $0
    popfq
/*  restore user registers */
    mov %gs:tcsls_user_r12,%r12
    mov %gs:tcsls_user_r13,%r13
    mov %gs:tcsls_user_r14,%r14
    mov %gs:tcsls_user_r15,%r15
    mov %gs:tcsls_user_retip,%rbx
    mov %gs:tcsls_user_rsp,%rsp
    mov %gs:tcsls_user_rbp,%rbp
    fldcw %gs:tcsls_user_fcw
    ldmxcsr %gs:tcsls_user_mxcsr
/*  exit enclave */
    mov $0x4,%eax /*  EEXIT */
    enclu
/*  end sgx_entry */

.Lreentry_panic:
    orq $8,%rsp
    jmp abort_reentry

/*  This *MUST* be called with 6 parameters, otherwise register information */
/*  might leak! */
.global usercall
usercall:
    test %rcx,%rcx            /* check `abort` function argument */
    jnz .Lusercall_abort      /* abort is set, jump to abort code (unlikely forward conditional) */
    jmp .Lusercall_save_state /* non-aborting usercall */
.Lusercall_abort:
/* set aborted bit */
    movb $1,.Laborted(%rip)
/* save registers in DEBUG mode, so that debugger can reconstruct the stack */
    testb $0xff,DEBUG(%rip)
    jz .Lusercall_noreturn
.Lusercall_save_state:
/*  save callee-saved state */
    push %r15
    push %r14
    push %r13
    push %r12
    push %rbp
    push %rbx
    sub $8, %rsp
    fstcw 4(%rsp)
    stmxcsr (%rsp)
    movq %rsp,%gs:tcsls_last_rsp
.Lusercall_noreturn:
/*  clear general purpose register state */
    /*  RAX overwritten by ENCLU */
    /*  RBX set by sgx_exit */
    /*  RCX overwritten by ENCLU */
    /*  RDX contains parameter */
    /*  RSP set by sgx_exit */
    /*  RBP set by sgx_exit */
    /*  RDI contains parameter */
    /*  RSI contains parameter */
    /*  R8 contains parameter */
    /*  R9 contains parameter */
    xor %r10,%r10
    xor %r11,%r11
    /*  R12 ~ R15 set by sgx_exit */
/*  extended registers/flags cleared by sgx_exit */
/*  exit */
    jmp .Lsgx_exit
.Lusercall_ret:
    movq $0,%gs:tcsls_last_rsp
/*  restore callee-saved state, cf. "save" above */
    mov %r11,%rsp
    ldmxcsr (%rsp)
    fldcw 4(%rsp)
    add $8, %rsp
    entry_sanitize_final
    pop %rbx
    pop %rbp
    pop %r12
    pop %r13
    pop %r14
    pop %r15
/*  return */
    mov %rsi,%rax /*  RAX = return value */
    /* NOP: mov %rdx,%rdx */ /*  RDX = return value */
    pop %r11
    lfence
    jmp *%r11

/*
The following functions need to be defined externally:
```
// Called by entry code on re-entry after exit
extern "C" fn abort_reentry() -> !;

// Called once when a TCS is first entered
extern "C" fn tcs_init(secondary: bool);

// Standard TCS entrypoint
extern "C" fn entry(p1: u64, p2: u64, p3: u64, secondary: bool, p4: u64, p5: u64) -> (u64, u64);
```
*/

.global get_tcs_addr
get_tcs_addr:
    mov %gs:tcsls_tcs_addr,%rax
    pop %r11
    lfence
    jmp *%r11

.global get_tls_ptr
get_tls_ptr:
    mov %gs:tcsls_tls_ptr,%rax
    pop %r11
    lfence
    jmp *%r11

.global set_tls_ptr
set_tls_ptr:
    mov %rdi,%gs:tcsls_tls_ptr
    pop %r11
    lfence
    jmp *%r11

.global take_debug_panic_buf_ptr
take_debug_panic_buf_ptr:
    xor %rax,%rax
    xchg %gs:tcsls_debug_panic_buf_ptr,%rax
    pop %r11
    lfence
    jmp *%r11
