├── .gitignore ├── Makefile ├── README.md ├── threads-i386.s └── threads-x86_64.s /.gitignore: -------------------------------------------------------------------------------- 1 | threads.x86_64 2 | threads.i386 3 | *.o 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NASM ?= nasm 2 | NASMFLAGS = -Fdwarf -g 3 | 4 | all : threads.x86_64 threads.i386 5 | 6 | threads.x86_64 : threads-x86_64.o 7 | $(LD) -melf_x86_64 $(LDFLAGS) -o $@ $^ $(LDLIBS) 8 | 9 | threads.i386 : threads-i386.o 10 | $(LD) -melf_i386 $(LDFLAGS) -o $@ $^ $(LDLIBS) 11 | 12 | threads-x86_64.o : threads-x86_64.s 13 | $(NASM) -felf64 $(NASMFLAGS) -o $@ $^ 14 | 15 | threads-i386.o : threads-i386.s 16 | $(NASM) -felf32 $(NASMFLAGS) -o $@ $^ 17 | 18 | clean : 19 | $(RM) *.o threads.x86_64 threads.i386 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pure assembly, library-free Linux threading demo 2 | 3 | A demonstration of library-free, Pthreads-free threading in Linux with 4 | pure x86_64 assembly. Thread stacks are allocated with the `SYS_mmap` 5 | syscall and new threads are spawned with `SYS_clone` syscall. 6 | Synchronization is achieved with the x86 `lock` instruction prefix. 7 | 8 | * [Raw Linux Threads via System Calls](http://nullprogram.com/blog/2015/05/15/) 9 | -------------------------------------------------------------------------------- /threads-i386.s: -------------------------------------------------------------------------------- 1 | ;conventional registers used for entering kernel mode. 2 | ; arch/ABI instruction syscall # retval Notes 3 | ; ─────────────────────────────────────────────────────────────────── 4 | ; 5 | ; i386 int $0x80 eax eax 6 | ; x86_64 syscall rax rax 7 | 8 | ;conventional register mapping for syscall arguments 9 | ; arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 10 | ; ────────────────────────────────────────────────────────── 11 | ; i386 ebx ecx edx esi edi ebp - 12 | ; x86_64 rdi rsi rdx r10 r8 r9 - 13 | 14 | 15 | ;; Pure assembly, library-free Linux threading demo 16 | bits 32 17 | global _start 18 | 19 | ;; sys/syscall.h 20 | %define SYS_write 4 21 | %define SYS_mmap2 192 22 | %define SYS_clone 120 23 | %define SYS_exit 1 24 | 25 | ;; unistd.h 26 | %define STDIN 0 27 | %define STDOUT 1 28 | %define STDERR 2 29 | 30 | ;; sched.h 31 | %define CLONE_VM 0x00000100 32 | %define CLONE_FS 0x00000200 33 | %define CLONE_FILES 0x00000400 34 | %define CLONE_SIGHAND 0x00000800 35 | %define CLONE_PARENT 0x00008000 36 | %define CLONE_THREAD 0x00010000 37 | %define CLONE_IO 0x80000000 38 | 39 | ;; sys/mman.h 40 | %define MAP_GROWSDOWN 0x0100 41 | %define MAP_ANONYMOUS 0x0020 42 | %define MAP_PRIVATE 0x0002 43 | %define PROT_READ 0x1 44 | %define PROT_WRITE 0x2 45 | %define PROT_EXEC 0x4 46 | 47 | %define THREAD_FLAGS \ 48 | CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO 49 | 50 | %define STACK_SIZE (4096 * 1024) 51 | 52 | %define MAX_LINES 1000000 ; number of output lines before exiting 53 | 54 | section .data 55 | count: dq MAX_LINES 56 | 57 | section .text 58 | _start: 59 | ; Spawn a few threads 60 | mov ebx, threadfn 61 | call thread_create 62 | mov ebx, threadfn 63 | call thread_create 64 | 65 | .loop: call check_count 66 | mov ebx, .hello 67 | call puts 68 | mov ebx, 0 69 | jmp .loop 70 | 71 | .hello: db `Hello from \e[93;1mmain\e[0m!\n\0` 72 | 73 | ;; void threadfn(void) 74 | threadfn: 75 | call check_count 76 | mov ebx, .hello 77 | call puts 78 | jmp threadfn 79 | .hello: db `Hello from \e[91;1mthread\e[0m!\n\0` 80 | 81 | ;; void check_count(void) -- may not return 82 | check_count: 83 | mov eax, -1 84 | lock xadd [count], eax 85 | jl .exit 86 | ret 87 | .exit mov ebx, 0 88 | mov eax, SYS_exit 89 | int 0x80 90 | 91 | ;; void puts(char *) 92 | puts: 93 | mov ecx, ebx 94 | mov edx, -1 95 | .count: inc edx 96 | cmp byte [ecx + edx], 0 97 | jne .count 98 | mov ebx, STDOUT 99 | mov eax, SYS_write 100 | int 0x80 101 | ret 102 | 103 | ;; long thread_create(void (*)(void)) 104 | thread_create: 105 | push ebx 106 | call stack_create 107 | lea ecx, [eax + STACK_SIZE - 8] 108 | pop dword [ecx] 109 | mov ebx, THREAD_FLAGS 110 | mov eax, SYS_clone 111 | int 0x80 112 | ret 113 | 114 | ;; void *stack_create(void) 115 | stack_create: 116 | mov ebx, 0 117 | mov ecx, STACK_SIZE 118 | mov edx, PROT_WRITE | PROT_READ 119 | mov esi, MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN 120 | mov eax, SYS_mmap2 121 | int 0x80 122 | ret 123 | -------------------------------------------------------------------------------- /threads-x86_64.s: -------------------------------------------------------------------------------- 1 | ;; Pure assembly, library-free Linux threading demo 2 | bits 64 3 | global _start 4 | 5 | ;; sys/syscall.h 6 | %define SYS_write 1 7 | %define SYS_mmap 9 8 | %define SYS_clone 56 9 | %define SYS_exit 60 10 | 11 | ;; unistd.h 12 | %define STDIN 0 13 | %define STDOUT 1 14 | %define STDERR 2 15 | 16 | ;; sched.h 17 | %define CLONE_VM 0x00000100 18 | %define CLONE_FS 0x00000200 19 | %define CLONE_FILES 0x00000400 20 | %define CLONE_SIGHAND 0x00000800 21 | %define CLONE_PARENT 0x00008000 22 | %define CLONE_THREAD 0x00010000 23 | %define CLONE_IO 0x80000000 24 | 25 | ;; sys/mman.h 26 | %define MAP_GROWSDOWN 0x0100 27 | %define MAP_ANONYMOUS 0x0020 28 | %define MAP_PRIVATE 0x0002 29 | %define PROT_READ 0x1 30 | %define PROT_WRITE 0x2 31 | %define PROT_EXEC 0x4 32 | 33 | %define THREAD_FLAGS \ 34 | CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_PARENT|CLONE_THREAD|CLONE_IO 35 | 36 | %define STACK_SIZE (4096 * 1024) 37 | 38 | %define MAX_LINES 1000000 ; number of output lines before exiting 39 | 40 | section .data 41 | count: dq MAX_LINES 42 | 43 | section .text 44 | _start: 45 | ; Spawn a few threads 46 | mov rdi, threadfn 47 | call thread_create 48 | mov rdi, threadfn 49 | call thread_create 50 | 51 | .loop: call check_count 52 | mov rdi, .hello 53 | call puts 54 | mov rdi, 0 55 | jmp .loop 56 | 57 | .hello: db `Hello from \e[93;1mmain\e[0m!\n\0` 58 | 59 | ;; void threadfn(void) 60 | threadfn: 61 | call check_count 62 | mov rdi, .hello 63 | call puts 64 | jmp threadfn 65 | .hello: db `Hello from \e[91;1mthread\e[0m!\n\0` 66 | 67 | ;; void check_count(void) -- may not return 68 | check_count: 69 | mov rax, -1 70 | lock xadd [count], rax 71 | jl .exit 72 | ret 73 | .exit mov rdi, 0 74 | mov rax, SYS_exit 75 | syscall 76 | 77 | ;; void puts(char *) 78 | puts: 79 | mov rsi, rdi 80 | mov rdx, -1 81 | .count: inc rdx 82 | cmp byte [rsi + rdx], 0 83 | jne .count 84 | mov rdi, STDOUT 85 | mov rax, SYS_write 86 | syscall 87 | ret 88 | 89 | ;; long thread_create(void (*)(void)) 90 | thread_create: 91 | push rdi 92 | call stack_create 93 | lea rsi, [rax + STACK_SIZE - 8] 94 | pop qword [rsi] 95 | mov rdi, THREAD_FLAGS 96 | mov rax, SYS_clone 97 | syscall 98 | ret 99 | 100 | ;; void *stack_create(void) 101 | stack_create: 102 | mov rdi, 0 103 | mov rsi, STACK_SIZE 104 | mov rdx, PROT_WRITE | PROT_READ 105 | mov r10, MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN 106 | mov r8, -1 107 | mov r9, 0 108 | mov rax, SYS_mmap 109 | syscall 110 | ret 111 | --------------------------------------------------------------------------------