diff options
author | Prefetch | 2023-07-24 16:23:27 +0200 |
---|---|---|
committer | Prefetch | 2023-07-24 16:23:27 +0200 |
commit | a211da8cfe9b0565881537cc81b09ae55c722111 (patch) | |
tree | 90c05adac663125fc4f0604edb01431c2dcbc9af /lib/thread_create.asm | |
parent | 7231a21b00028a52d7938131bfeca4d663d09071 (diff) |
Rename lib/ to src/ (better for Tab-completion)
Diffstat (limited to 'lib/thread_create.asm')
-rw-r--r-- | lib/thread_create.asm | 264 |
1 files changed, 0 insertions, 264 deletions
diff --git a/lib/thread_create.asm b/lib/thread_create.asm deleted file mode 100644 index 9a6fe78..0000000 --- a/lib/thread_create.asm +++ /dev/null @@ -1,264 +0,0 @@ -; Under MIT license, see /LICENSE.txt - - -; Cheat sheet for Linux' x86_64 calling convention: -; -; - free to overwrite (caller should save them): -; rax, rcx, rdx, rsi, rdi, r8-r11, xmm0-xmm15 -; - caller expects be kept (callee should save them): -; rbx, rbp, r12-r15 -; -; - for passing paramters to functions: -; rdi, rsi, rdx, rcx, r8, r9, xmm0-xmm7 -; - for getting return values from functions: -; rax, rdx, xmm0 -; -; - for passing parameters to syscalls: -; rax, rdi, rsi, rdx, r10, r8, r9 -; - for getting return values from syscalls: -; rax, rdx -; - overwritten by syscalls (all others preserved): -; rcx, r11 - - -section .text - - -; Relevant system call IDs -%define SYS_MMAP 9 -%define SYS_MPROTECT 10 -%define SYS_CLONE 56 -%define SYS_EXIT 60 - -; Relevant flags for mmap -%define MAP_SHARED 0x00001 -%define MAP_PRIVATE 0x00002 -%define MAP_ANONYMOUS 0x00020 -;%define MAP_GROWSDOWN 0x00100 ; Insecure, segfaults anyway -%define MAP_LOCKED 0x02000 -%define MAP_POPULATE 0x08000 -%define MAP_STACK 0x20000 - -; Relevant flags for mprotect -%define PROT_READ 0x1 -%define PROT_WRITE 0x2 - -; Relevant flags for clone -%define CLONE_VM 0x00000100 -%define CLONE_FS 0x00000200 -%define CLONE_FILES 0x00000400 -%define CLONE_SIGHAND 0x00000800 -%define CLONE_PARENT 0x00008000 -%define CLONE_THREAD 0x00010000 -%define CLONE_SYSVSEM 0x00040000 -%define CLONE_SETTLS 0x00080000 -%define CLONE_PARENT_SETTID 0x00100000 -%define CLONE_CHILD_CLEARTID 0x00200000 -%define CLONE_CHILD_SETTID 0x01000000 -%define CLONE_IO 0x80000000 - - -%define STACK_SIZE 2097152 ; 2 MiB stack -%define GUARD_PAGE 4096 ; 4 KiB guard page - - -; Create a new thread executing a given function. Arguments: -; rdi: struct{u32,u32}** = where to put the thread handle -; rsi: void* (*)(void*) = function to make the child run -; rdx: void* = single argument for function -; Returns zero on success, or a standard error code. -global linen_thread_create -linen_thread_create: - ; Callee-save registers - push rbx - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Check validity of arguments ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; Return EINVAL if any argument is NULL - mov eax, -22 ; (EINVAL = -22) - test rdi, rdi - jz create_return ; Nowhere to store the thread handle - test rsi, rsi - jz create_return ; No function for the thread to run - - ; Note: we allow rdx to be NULL; in that case the worst that can happen - ; is a segmentation fault in the user's code (not really our problem). - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Allocate a stack and guard page ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; Save these registers: we'll clobber them for the mmap call - mov rbx, rdi - push rdx - push rsi - - ; The mmap system call does many things, in this case allocate memory. - ; See: man 2 mmap - - ; mmap: rdi = addr: address for mapping; 0 lets kernel choose - xor edi, edi - ; mmap: rsi = length: size of buffer to allocate - mov esi, (STACK_SIZE + GUARD_PAGE) - ; mmap: rdx = prot: mprotect-style access permissions - mov edx, (PROT_WRITE | PROT_READ) - ; mmap: r10 = flags: configuration flags for mapping: - ; - MAP_ANONYMOUS: there is no file backing this buffer - ; - MAP_PRIVATE: only this process can see thread's stack - ; - MAP_STACK: no-op; inform kernel that this is a stack - mov r10, (MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK) - ; mmap: r8 = fd: ignored for MAP_ANONYMOUS, recommended -1 - mov r8, -1 - ; mmap: r9 = offset: should be 0 when MAP_ANONYMOUS is used - xor r9, r9 - ; mmap: rax = system call ID - mov eax, SYS_MMAP - ; mmap: rax = mmap(rdi, rsi, rdx, r10, r8, 9) - syscall - - ; Pop these now before we start branching. Those registers - ; won't be used by the next system calls, so they're safe. - pop r8 ; function - pop r9 ; argument - - ; Check result of mmap: negative means failure, - ; otherwise rax is the address of the new mapping. - test rax, rax - js create_return - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Revoke guard page's R/W permissions ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; Keep in mind that stacks grow downward, so the guard page is at - ; the lowest address of the newly-allocated buffer, i.e. at [rax]. - - ; The mprotect system call changes the permissions of a memory region. - ; See: man 2 mprotect - - ; mprotect: rdi = addr: lower address of region to control - mov rdi, rax - ; mprotect: rsi = len: size of region, one page in this case - mov esi, GUARD_PAGE - ; mprotect: rdx = prot: access permissions; zero for none - xor edx, edx - ; mprotect: rax = system call ID - mov eax, SYS_MPROTECT - ; mprotect: rax = mprotect(rdi, rsi, rdx) - syscall - - ; Check result of mprotect: nonzero means failure - test rax, rax - jnz create_return - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Spawn a thread with the new stack ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; The clone system call spawns a new thread, cloned from a parent. - ; Both threads end up running the same code, i.e. it returns "twice", - ; once in the parent (0 if success) and once in the child (the TID). - ; See: man 2 clone - - ; clone: rsi = stack - ; Currently rdi points to the lowest byte of the stack area. - ; Again, stacks grow downward, so we calculate the address of - ; the top qword to use as the child thread's starting point. - lea rsi, [rdi + (STACK_SIZE + GUARD_PAGE - 8)] - - ; clone: rdi = flags: settings for cloned thread - ; These flags make the parent and child share resources: - ; - CLONE_VM: memory address space - ; - CLONE_FS: filesystem information, e.g. working directory - ; - CLONE_FILES: file descriptor table - ; - CLONE_IO: I/O scheduler context - ; - CLONE_SIGHAND: signal handlers - ; - CLONE_PARENT: parent process (implied by CLONE_THREAD?) - ; - CLONE_THREAD: shared PID, distinguish by TID instead (I think?) - ; These flags are relevant for a threading API: - ; - CLONE_CHILD_SETTID: store child's TID at supplied address (in r10) - ; - CLONE_CHILD_CLEARTID: set stored TID to zero when child finishes - ; (this will be used for joining threads) - mov edi, (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_IO \ - | CLONE_SIGHAND | CLONE_PARENT | CLONE_THREAD \ - | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID) - - ; clone: rdx = parent_tid: ignored unless CLONE_PARENT_SETTID is used - - ; clone: r10 = child_tid: address to store new thread's TID - ; We use "bottom" of stack (rsi), i.e. where child will start. - mov r10, rsi - - ; clone: r8 = tls: ignored unless CLONE_SETTLS is used - - ; clone: rax = system call ID - mov eax, SYS_CLONE - ; clone: rax = clone(rdi, rsi, (rdx), r10, (r8)); - syscall - - ; Ideally, both parent and new-born child are executing this code now. - - ; Check result of clone: - test rax, rax - js create_return ; Negative means failure - jnz create_success ; Positive means we're in the parent thread - ; Zero means we're in the child thread - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Initialization in child thread ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ; Best practice is to clear the frame pointer - xor ebp, ebp - - ; Move argument into place and call supplied function - mov rdi, r9 - call r8 - - ; Once done, leave function's return value lying around - push rax - - ; Exit the thread with return value 0 - xor edi, edi - mov rax, SYS_EXIT - syscall ; (never returns) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Clean up in parent thread ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - create_success: - ; We use the highest dword of the child's stack buffer as a futex - ; to detect when it has finished (see CLONE_CHILD_CLEARTID above). - ; That dword's address also acts as a thread handle for our API, - ; so we store it at the address the caller supplied (now in rbx). - mov [rbx], rsi - - ; We place a canary value in the unused dword at the top: - ; checking this value tells us if a thread handle is valid. - mov dword [rsi + 4], 0xDEADBEEF - - ; "Sketch" of child's stack buffer's layout: - ; - ; (bottom of range allocated by mmap) - ; 4 KiB: guard page, unused - ; (bottom of usable buffer) - ; ... - ; ... Child is currently doing work here ... - ; ... - ; qword: return address of function called by child (from r8) - ; dword: futex to detect when child has returned (address: rsi) - ; dword: canary value to know if handle is valid (address: rsi + 4) - ; (top of range allocated by mmap = top of usable buffer) - - ; Return 0 for success - xor eax, eax - - create_return: - ; Restore callee-save registers - pop rbx - - ret - |