==== About ====
Unicornel is a multi-process, multi-architecture emulator server
with concurrency and system call support. All processes of any architecture
share the underlying kernel, and can interact with each other via
system calls and in particular a lightweight shared memory interface.

==== Starting processes ====
In order to start a new process, you must first send a unicornelf header,
which naturally bears no resemblance whatsoever to the actual ELF standard:
struct unicornelf {
    uc_arch arch; //Desired unicorn-supported ISA
    uc_mode mode; //Desired unicorn-supported mode
    struct {
        unsigned long va; //Virtual address to map
        unsigned long length; //Length of memory to map
    } maps[4]; //Up to 4 mappings supported
    unsigned short code_length; //The length of the code to follow the unicornelf header
    unsigned char num_maps; //The number of mappings initialized in the maps array
};
Following the unicornelf header should be <code length> bytes of assembled machine code
in the specified instruction set architecture.

NOTE: Any feedback about the unicornelf format should be submitted to:
https://docs.google.com/forms/d/e/1FAIpQLSck2N2w5J84iu7CKYlGkEmwn1Xsjtl5Jmlm_4t2DfC8vwNLOw/viewform?usp=sharing&resourcekey=0--aU-tRVYI9eI9UCRMuEMfQ

There MUST be at least one mapping specified - the first mapping ALWAYS stores the
uploaded machine code. Any unused maps array elements can be set to whatever values you want, and unicornelf will dutifully ignore them.

After receiving the unicornelf and <code length> bytes of machine code, the "process" will
be automatically started on a new POSIX thread. The lowest available pid is assigned to the process. Bear in 
mind that this pid is utterly unrelated to the actual Linux tid of the 
thread.

==== Process Limitations ====
There can only be up to 8 processes at a time.

==== Process lifetime ====
Processes execute until one of the following conditions:
 - The exit syscall is called by the process
 - The process executes the last instruction in the uploaded assembly code
 - The process encounters some exception condition
 - The client connection to the Unicornel is terminated (all processes
   unceremoniously terminate)

==== System Call conventions ====
The system call interface is invoked whenever an interrupt is generated by the uploaded
and executing machine code. System call arguments are passed in on all architectures via
registers. The system call number is always arg0 (e.g. rax on x86).
The remaining 3 arguments are used to pass whatever data is needed to the syscall.

The register to arguments mappings for all architectures is defined by the call_regs array. Each element index 
of the inner per-architecture array element corresponds to the given argument index:
static unsigned int call_regs[UC_ARCH_MAX][4] = {
    {0,0,0,0}, //NONE
    {UC_ARM_REG_R0,UC_ARM_REG_R1,UC_ARM_REG_R2,UC_ARM_REG_R3}, //UC_ARCH_ARM
    {UC_ARM64_REG_X0,UC_ARM64_REG_X1,UC_ARM64_REG_X2,UC_ARM64_REG_X3}, //UC_ARCH_ARM64
    {UC_MIPS_REG_A0,UC_MIPS_REG_A1,UC_MIPS_REG_A2,UC_MIPS_REG_A3}, //UC_ARCH_MIPS
    {UC_X86_REG_RAX,UC_X86_REG_RBX,UC_X86_REG_RCX,UC_X86_REG_RDX}, //UC_ARCH_X86
    {UC_PPC_REG_0,UC_PPC_REG_1,UC_PPC_REG_2,UC_PPC_REG_3}, //UC_ARCH_PPC
    {UC_SPARC_REG_O0,UC_SPARC_REG_O1,UC_SPARC_REG_O2,UC_SPARC_REG_O3}, //UC_ARCH_SPARC
    {UC_M68K_REG_D0,UC_M68K_REG_D1,UC_M68K_REG_D2,UC_M68K_REG_D3}, //UC_ARCH_M68K
    {UC_RISCV_REG_A0,UC_RISCV_REG_A1,UC_RISCV_REG_A2,UC_RISCV_REG_A3}, //UC_ARCH_RISCV
    {UC_S390X_REG_R0,UC_S390X_REG_R1,UC_S390X_REG_R2,UC_S390X_REG_R3}, //UC_ARCH_S390X
    {UC_TRICORE_REG_D0,UC_TRICORE_REG_D1,UC_TRICORE_REG_D2,UC_TRICORE_REG_D3}, //UC_ARCH_TRICORE
};

E.g. for an X86 process to call unicornel_write, you would set rax to 1 (the unicornel write syscall number), 
rbx to the location of the buffer to write, and rcx to the number of bytes to write.

==== Supported system calls ====
There are 13 supported system calls:
    Syscall Name      #
    unicornel_exit    0
    unicornel_write   1
    print_integer     2
    create_shared     3
    validate_handle   4
    map_address        5
    unicornel_pause   6
    unicornel_resume  7
    create_trustzone  8
    destroy_trustzone 9
    trustzone_invoke  10
    confirm_password  11
    memprot           12

void unicornel_exit();
  Terminates the calling process
  This function never returns

long unicornel_write(void* buf, size_t count);
  Write up to count bytes from the buffer at buf to the unicornel client (eventually sent over the socket).
  Returns the number of bytes written, or an error code if there was a failure.

void-ish print_integer(long integer);
  Write the argument as an ASCII base-10 integer to the unicornel client.
  Always returns 0.

long create_shared(unsigned long length);
  Creates a new shared memory buffer of the specified length
  Returns a handle to the buffer to be used with validate_handle later, or an error code.

long validate_handle(unsigned long handle, unsigned long length);
  Validates a shared buffer handle and length, and returns an emulator-level pointer to the shared buffer that 
  can be used in map_address later.
  Returns 0 on error.
  NOTE:
  This syscall can only be run from trustzone context

long map_address(void* addr,unsigned long length, void* emulator_address);
  Map an emulator address at the location addr.
  Returns 0 on success, or an error code.
  NOTE:
  This syscall only be run from trustzone context

void-ish unicornel_pause();
  Pause the current process until another process calls unicornel_resume() with the
  appropriate pid
  This system call always succeeds, and always returns 0.

long unicornel_resume(unsigned long pid);
  Resume the process specified by pid.
  Returns 0 on success, or an error code.

long create_trustzone(void* addr, char* trustzone_name);
  Map a trustzone at the virtual address addr. Which trustzone is mapped is determined by the provided name.
  The currently available trustzones are provided in the section titled Trustzones
  NOTE:
  Only one trustzone can be mapped at a time

long destroy_trustzone();
  Unmaps the existing trustzone if one exists.
  Returns 0 on success, -1 if no trustzone was mapped in the process

long trustzone_invoke(...):
  Switches the process into trustzone mode and jumps to the beginning of the trustzone region.
  Trusted system calls are unblocked. Argument registers are trustzone binary dependent but generally resemble
  the typical system call arguments.
  This system call returns the unicorn emulation error code for the trustzone invocation

confirm_password(char* passwd);
  Compares the password provided to the password stored on disk.
  Returns 0 if the passwords matched
  NOTE:
  This syscall only be run from trustzone context

long memprot(void* addr, unsigned long length, unsigned long prot);
  Set the memory protection permissions for a specified address. 
  NOTE:
  This syscall can only be run from trustzone context

==== Trustzones ====
Trustzones are a safe region of compartmentalized code within the process virtual address space that cannot be 
read or written by the process. Trustzones can be entered using the system call trustzone invoke. In
trustzone mode, trusted system calls can be executed, and memory in the trustzone can be read directly.

Trustzone execution contexts have full and direct access to the register and memory state of the parent process,
and any registers that are modified by the trustzone will be visible to the parent process after the trustzone
exits.

Trustzones are designed to provide modularized blackbox functionality for various routines or programs whose
inner workings you want to hide from the caller. In order to provide a simple authentication mechanism for 
trustzones that want to gate certain functionalities, a password validation system call is provided.

When a trustzone wants to return to the caller, it simply calls unicornel_exit.

There are currently three example trustzones you can load:

  Name: create_map_shared_x86_64
  Calling Convention: rax = trustzone_invoke syscall #, rbx = addr, rcx = length
  Return Value: rbx = shared memory handle, or error code from create_shared if a buffer could not be created
  Description: This trustzone will create and map a shared memory buffer of the desired length into the current 
  process at the specified address.

  Name: map_shared_x86_64
  Calling Convention: rax = trustzone_invoke syscall #, rbx = shared memory handle, rcx = addr, rdx = length
  Return Value: rbx = 0xffff on validate_handle failure, or the return value from the map_address syscall
  Description: This trustzone maps a shared memory buffer specified by the handle into the current process at 
  the specified address.

  Name: memprot_x86_64
  Calling Convention: rax = trustzone_invoke syscall #, rbx = addr, rcx = length, rdx = prot, rdi = password
  Return Value: rbx = 0xffff on password authentication failure, or the return value from the memprot syscall
  Description: This trustzone sets the memory protection on a range of memory. Please note that rdi must be a 
  pointer to a string containing the password.