Skip to content

Commit

Permalink
Add utility functions and create simple threads
Browse files Browse the repository at this point in the history
  • Loading branch information
ljanyst committed Jan 29, 2016
0 parents commit b1ef686
Show file tree
Hide file tree
Showing 9 changed files with 1,599 additions and 0 deletions.
20 changes: 20 additions & 0 deletions CMakeLists.txt
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 2.4)
project (thread-bites)

enable_language(ASM)
include_directories(".")

set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g")

add_library(
tb SHARED
tb-utils.c
tb-threads.c
tb-clone.S)

add_executable(test-00 test-00.c)
target_link_libraries(test-00 tb)

add_executable(test-01 test-01.c)
target_link_libraries(test-01 tb)
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions README.md
@@ -0,0 +1,17 @@
Thread Bites
============

Thread Bites is a simple and, admittedly, a pretty useless threading library.
Its raison d'être is to be as simple as possible in order to illustrate how
one can implement a threading library that somewhat resembles pthreads and plays
well with x86_64 Linux. Thread Bites does not, and likely will not, support the
compiler generated thread-local storage, `__thread int number;`, and other
linker trickery, such as
`__attribute__ ((section ("__libc_thread_freeres_fn")))`. This fact, in
conjunction with glibc expecting a bunch of globals to be in TLS, makes calling
anything non-trivial from glibc a rather risky business.

The goal of this project is to, over time, implement all/most of pthreads
functionality.

See http://jany.st/post/2016-01-30-thread-bites-1.html for more details.
106 changes: 106 additions & 0 deletions tb-clone.S
@@ -0,0 +1,106 @@
//------------------------------------------------------------------------------
// Copyright (c) 2016 by Lukasz Janyst <lukasz@jany.st>
//------------------------------------------------------------------------------
// This file is part of thread-bites.
//
// thread-bites is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// thread-bites 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with thread-bites. If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include <asm/unistd_64.h> // for syscall numbers

//------------------------------------------------------------------------------
// All this has been derived from glibc's sysdeps/unix/sysv/linux/x86_64/clone.S
// the Linux kernel sources, and a bunch of websites.
//
// We need to make a function callable from C that calls the clone syscall and
// and starts the user function on the new task. We do this in assembly
// to properly set up the call frame information (CFI) for debugging,
// backtraces, exceptions and such.
//
// The C call has the following format:
//
// int tbclone(int (*fn)(void *arg), void *arg, int flags, void *child_stack
// pid_t *parent_tidptr, pid_t *child_tidptr, void *tls),
//
// This results with the registers having the following values:
//
// rdi: fn
// rsi: arg
// rdx: flags
// rcx: child_stack
// r8: parent TID pointer
// r9: child TID pointer
// %rsp+8: TLS pointer
//
// The syscall has the following interface:
//
// SYSCALL_DEFINE5(clone,
// unsigned long, clone_flags,
// unsigned long, newsp,
// int __user *, parent_tidptr,
// int __user *, child_tidptr,
// unsigned long, tls)
//
// So we need the registers to be:
//
// rax: __NR_clone
// rdi: flags
// rsi: child_stack
// rdx: parent TID pointer
// r10: child TID pointer
// r8: TLS pointer
//
//------------------------------------------------------------------------------

.text

.global tbclone // sets the symbol as externally linkable
.type tbclone,@function // declare this symbol as a function in ELF
.align 16 // place the next instruction at an address
// divisible by 16 in the resulting ELF binary
tbclone:
.cfi_startproc // cfi_* is the stuff for exception frames
subq $16, %rcx // decrement the new stack pointer by 16 because
// we will pop stuff from it later
movq %rsi, 8(%rcx) // store the function argument in the child's stack
movq %rdi, 0(%rcx) // store the function pointer in the child's stack

movq $__NR_clone, %rax // clone syscall number to rax
movq %rdx, %rdi // flags to rdi
movq %rcx, %rsi // child_stack to rsi
movq %r8, %rdx // parent_tid to rdx
movq %r9, %r10 // child_tid to r10
movq 8(%rsp), %r8 // TLS pointer to r8
.cfi_endproc // end FDE now to get proper unwind info in the
// child
syscall // call sys_clone
testq %rax, %rax // compare %rax && %rax to zero
jz .Lstart_thread // 0 == we're in the child
ret // we're in the parent with either an error or
// the child's TID

.Lstart_thread: // ELF local label
.cfi_startproc
.cfi_undefined rip // previous value of the instruction pointer cannot
// be restored anymore
xorq %rbp, %rbp // clear the frame pointer

popq %rax // pop the function pointer
popq %rdi // pop the argument
call *%rax // call the function

movq %rax, %rdi // whatever the function returned goes to rdi
movq $__NR_exit, %rax // exit syscall number to %rax
syscall // call sys_exit
.cfi_endproc
114 changes: 114 additions & 0 deletions tb-threads.c
@@ -0,0 +1,114 @@
//------------------------------------------------------------------------------
// Copyright (c) 2016 by Lukasz Janyst <lukasz@jany.st>
//------------------------------------------------------------------------------
// This file is part of thread-bites.
//
// thread-bites is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// thread-bites 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with thread-bites. If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include "tb.h"

#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <linux/sched.h>
#include <linux/mman.h>
#include <asm-generic/mman-common.h>
#include <asm-generic/param.h>

//------------------------------------------------------------------------------
// Init the attrs to the defaults
//------------------------------------------------------------------------------
void tbthread_attr_init(tbthread_attr_t *attr)
{
attr->stack_size = 8192 * 1024;
}

//------------------------------------------------------------------------------
// Thread function wrapper
//------------------------------------------------------------------------------
static int start_thread(void *arg)
{
tbthread_t th = (tbthread_t)arg;
uint32_t stack_size = th->stack_size;
void *stack = th->stack;
th->fn(th->arg);
free(th);

//----------------------------------------------------------------------------
// Free the stack and exit. We do it this way because we remove the stack from
// underneath our feet and cannot allow the C code to write on it anymore.
//----------------------------------------------------------------------------
register long a1 asm("rdi") = (long)stack;
register long a2 asm("rsi") = stack_size;
asm volatile(
"syscall\n\t"
"movq $60, %%rax\n\t" // 60 = __NR_exit
"movq $0, %%rdi\n\t"
"syscall"
:
: "a" (__NR_munmap), "r" (a1), "r" (a2)
: "memory", "cc", "r11", "cx");
return 0;
}

//------------------------------------------------------------------------------
// Spawn a thread
//------------------------------------------------------------------------------
int tbthread_create(
tbthread_t *thread,
const tbthread_attr_t *attr,
void *(*f)(void *),
void *arg)
{
//----------------------------------------------------------------------------
// Allocate the stack with a guard page at the end so that we could protect
// from overflows (by receiving a SIGSEGV)
//----------------------------------------------------------------------------
void *stack = tbmmap(NULL, attr->stack_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
long status = (long)stack;
if(status < 0)
return status;

status = SYSCALL3(__NR_mprotect, stack, EXEC_PAGESIZE, PROT_NONE);
if(status < 0) {
tbmunmap(stack, attr->stack_size);
return status;
}

//----------------------------------------------------------------------------
// Pack everything up
//----------------------------------------------------------------------------
*thread = malloc(sizeof(struct tbthread));
memset(*thread, 0, sizeof(struct tbthread));
(*thread)->stack = stack;
(*thread)->stack_size = attr->stack_size;
(*thread)->fn = f;
(*thread)->arg = arg;

//----------------------------------------------------------------------------
// Spawn the thread
//----------------------------------------------------------------------------
int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_SIGHAND;
flags |= CLONE_THREAD;
int tid = tbclone(start_thread, *thread, flags, stack+attr->stack_size);
if(tid < 0) {
tbmunmap(stack, attr->stack_size);
free(*thread);
return tid;
}

return 0;
}

0 comments on commit b1ef686

Please sign in to comment.