From 9177e93f8beb091f54078352efda0233cfbf42b1 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sat, 18 Feb 2017 02:30:00 +0100 Subject: [PATCH 01/12] Add basic `thread` It supports `spawn` and `join` so far. Only works on x86_64 so far. CC #13. --- examples/thread.rs | 9 + src/lib.rs | 2 + src/libc.rs | 271 ++++++++++++++++++++++- src/linux/aarch64.rs | 2 + src/linux/arm.rs | 2 + src/linux/mips.rs | 2 + src/linux/mips64.rs | 2 + src/linux/mod.rs | 64 ++++++ src/linux/powerpc.rs | 2 + src/linux/powerpc64.rs | 2 + src/linux/sparc64.rs | 2 + src/linux/types.rs | 6 +- src/linux/x86.rs | 2 + src/linux/x86_64.rs | 2 + src/sys/linux/mod.rs | 7 +- src/sys/linux/os.rs | 4 + src/sys/linux/stack_overflow.rs | 7 + src/sys/linux/thread.rs | 377 ++++++++++++++++++++++++++++++++ src/sys_common/mod.rs | 4 + src/sys_common/thread.rs | 22 ++ src/sys_common/util.rs | 3 + src/thread/mod.rs | 259 ++++++++++++++++++++++ 22 files changed, 1043 insertions(+), 10 deletions(-) create mode 100644 examples/thread.rs create mode 100644 src/sys/linux/stack_overflow.rs create mode 100644 src/sys/linux/thread.rs create mode 100644 src/sys_common/thread.rs create mode 100644 src/sys_common/util.rs create mode 100644 src/thread/mod.rs diff --git a/examples/thread.rs b/examples/thread.rs new file mode 100644 index 000000000..789d0625f --- /dev/null +++ b/examples/thread.rs @@ -0,0 +1,9 @@ +use std::io::{self, Write}; +use std::thread; +use std::time::Duration; + +fn main() { + thread::spawn(|| { + io::stdout().write_all(b"Hello, world!\n").unwrap(); + }).join(); +} diff --git a/src/lib.rs b/src/lib.rs index 957aa58f4..e97d9e07f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ #![feature(dropck_parametricity)] #![feature(generic_param_attrs)] #![feature(exact_size_is_empty)] +#![feature(fnbox)] #![feature(fused)] #![feature(heap_api)] #![feature(int_error_internals)] @@ -178,6 +179,7 @@ pub mod os; pub mod path; // Rust 1.16.0 (incomplete) pub mod process; +pub mod thread; // Rust 1.16.0 pub mod time; diff --git a/src/libc.rs b/src/libc.rs index 5ebc2949a..977c56308 100644 --- a/src/libc.rs +++ b/src/libc.rs @@ -2,16 +2,21 @@ use cmp; use linux; +use mem; +use ptr; pub use ctypes::*; pub use linux::errno::*; -pub use linux::{gid_t, in_addr, in6_addr, ip_mreq, ipv6_mreq, pid_t}; -pub use linux::{sa_family_t, sockaddr, sockaddr_in, sockaddr_in6}; +pub use linux::{gid_t, in_addr, in6_addr, ip_mreq, ipv6_mreq, off_t, off64_t}; +pub use linux::{pid_t, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6}; pub use linux::{sockaddr_storage, sockaddr_un, socklen_t, stat64, suseconds_t}; pub use linux::{time_t, timespec, timeval, uid_t}; pub use linux::{AF_INET, AF_INET6, AF_UNIX}; +pub use linux::{CLONE_CHILD_CLEARTID, CLONE_FILES, CLONE_FS}; +pub use linux::{CLONE_PARENT_SETTID, CLONE_SETTLS, CLONE_SIGHAND}; +pub use linux::{CLONE_SYSVSEM, CLONE_THREAD, CLONE_VM}; pub use linux::{DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK}; pub use linux::{F_DUPFD_CLOEXEC, F_DUPFD, F_GETFL, F_SETFL}; pub use linux::{FIOCLEX, FIONBIO}; @@ -20,10 +25,12 @@ pub use linux::{IP_MULTICAST_TTL, IP_TTL}; pub use linux::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP}; pub use linux::{IPV6_MULTICAST_LOOP, IPV6_V6ONLY}; pub use linux::{IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP}; +pub use linux::{MAP_ANONYMOUS, MAP_PRIVATE}; pub use linux::{MSG_NOSIGNAL}; pub use linux::{O_ACCMODE, O_APPEND, O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL}; pub use linux::{O_LARGEFILE, O_NONBLOCK, O_PATH, O_RDONLY, O_RDWR, O_TRUNC}; pub use linux::{O_WRONLY}; +pub use linux::{PROT_READ, PROT_WRITE}; pub use linux::{S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFDIR, S_IFCHR}; pub use linux::{S_IFIFO}; pub use linux::{SHUT_RD, SHUT_RDWR, SHUT_WR}; @@ -36,11 +43,11 @@ pub use linux::{TCP_NODELAY}; pub use linux::{accept, accept4, bind, close, connect, fdatasync, fstat64}; pub use linux::{fsync, ftruncate64, getpeername, getsockname, getsockopt}; -pub use linux::{ioctl, link, listen, lstat64, pread64, pwrite64, read}; -pub use linux::{recvfrom, rename, rmdir, send, sendto, setsockopt, socket}; -pub use linux::{socketpair, shutdown, symlink, unlink, write}; +pub use linux::{ioctl, link, listen, lstat64, mmap, nanosleep, prctl, pread64}; +pub use linux::{pwrite64, read, recvfrom, rename, rmdir, sched_yield, send}; +pub use linux::{sendto, setsockopt, socket, socketpair, shutdown, symlink}; +pub use linux::{unlink, write}; -pub type off64_t = i64; pub type mode_t = u32; // Rust 1.15.0 @@ -48,6 +55,12 @@ pub type mode_t = u32; #[cfg(issue = "22")] pub const EAI_SYSTEM: c_int = -11; +// Rust 1.15.0 +// src/liblibc/src/unix/notbsd/linux/musl/mod.rs +pub const PTHREAD_STACK_MIN: size_t = 2048; + +pub const MAP_ANON: c_int = MAP_ANONYMOUS; + pub unsafe fn strlen(cs: *const c_char) -> size_t { let mut cs = cs; let mut count = 0; @@ -105,6 +118,252 @@ pub unsafe fn lseek64(fd: c_int, offset: off64_t, whence: c_uint) -> off64_t { } } +#[derive(Clone, Copy)] +pub struct pthread_attr_t { + stack_size: usize, +} + +struct thread { + thread_id: pid_t, +} + +#[derive(Clone, Copy)] +pub struct pthread_t { + thread: *mut thread, +} + +pub unsafe fn pthread_attr_init(attr: *mut pthread_attr_t) -> c_int { + *attr = pthread_attr_t { + stack_size: 0, + }; + 0 +} + +pub unsafe fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> c_int { + pthread_attr_init(attr) +} + +pub unsafe fn pthread_attr_setstacksize(attr: *mut pthread_attr_t, + stacksize: size_t) + -> c_int +{ + (*attr).stack_size = stacksize; + 0 +} + +/* +pub unsafe fn pthread_attr_getstack(attr: *const pthread_attr_t, + stackaddr: *mut *mut c_void, + stacksize: *mut size_t) + -> c_int +{ + *stackaddr = ptr::null_mut(); + *stacksize = (*attr).stack_size; + 0 +} + +pub unsafe fn pthread_attr_getguardsize(attr: *const pthread_attr_t, + guardsize: *mut size_t) + -> c_int +{ + *guardsize = 0; + 0 +} + +pub unsafe fn pthread_getattr_np(pthread: pthread_t, + attr: *mut pthread_attr_t) + -> c_int +{ + pthread_attr_init(attr) +} +*/ + +pub unsafe fn pthread_create(pthread: *mut pthread_t, + attr: *const pthread_attr_t, + start_routine: extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void) + -> c_int +{ + let _ = attr; + let flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND + | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS + | CLONE_CHILD_CLEARTID | CLONE_PARENT_SETTID; + + let align = 16; + let mask = align - 1; + let stack_size = ((*attr).stack_size + mask) & !mask; + + let map = mmap(ptr::null_mut(), + stack_size + mem::size_of::(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0); + + // musl: src/internal/__syscall_ret.c + if map as usize > -4096isize as usize { + return -(map as c_int); + } + + let stack = map.offset(stack_size as isize); + let thread = stack as *mut thread; + + let child_tid = syscall_clone(start_routine, + stack, + flags, + arg, + &mut (*thread).thread_id, + thread as *mut c_void, + &mut (*thread).thread_id); + if child_tid < 0 { + return -child_tid; + } + *pthread = pthread_t { + thread: thread, + }; + 0 +} + +extern { + #[link_name = "__steed_clone"] + fn syscall_clone(fn_: extern "C" fn(*mut c_void) -> *mut c_void, + child_stack: *mut c_void, + flags: c_ulong, + arg: *mut c_void, + ptid: *mut pid_t, + newtls: *mut c_void, + ctid: *mut pid_t) -> pid_t; +} + +#[cfg(target_arch = "x86_64")] +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in %rax, syscall arguments in %rdi, %rsi, %rdx, + // %r10, %r8. The arguments are + // (flags: c_ulong, // %rdi + // child_stack: *mut c_void, // %rsi + // ptid: *mut c_int, // %rdx + // ctid: *mut c_int // %r10 + // newtls: c_ulong) // %r8 + // + // The registers %rcx and %r11 are clobbered, %rax gets the return value. + // + // The System V AMD64 API passes arguments in %rdi, %rsi, %rdx, %rcx, %r8, + // %r9, 8(%rsp). The arguments are + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // %rdi + // child_stack: *mut c_void, // %rsi + // flags: c_ulong, // %rdx + // arg: *mut c_void, // %rcx + // ptid: *mut pid_t, // %r8 + // newtls: *mut c_void, // %r9 + // ctid: *mut pid_t) // 8(%rsp) + // + // Both ABIs return the function result in %rax. + // + // This means we need the following moves: + // %rdx -> %rdi // flags + // %rsi -> %rsi // child_stack + // %r8 -> %rdx // ptid + // 8(%rsp) -> %r10 // ctid + // %r9 -> %r8 // newtls + // + // And to save `fn_`, we need + // %rdi -> %r9 // fn_ + // + // There's a cycle in there (%rdx -> %rdi -> %r9 -> %r8 -> %rdx), we break + // it at %rdi -> %r9 and move it to the scratch register %r11 instead. + + asm!(" + and $$-16,%rsi # Align the child stack to 16 bytes. + + # Push `arg` onto the child stack. + sub $$8,%rsi + mov %rcx,(%rsi) + + # Temporarily store `fn_` + mov %rdi,%r11 + + mov $$56,%rax # CLONE + mov %rdx,%rdi # flags + # child_stack + mov %r8,%rdx # ptid + mov 8(%rsp),%r10 # ctid + mov %r9,%r8 # newtls + + mov %r11,%r9 # fn_ (not for the syscall) + + syscall + + # CLONE returns 0 in the child thread, return if we're the parent. + test %rax,%rax + jnz __steed_clone_end + + # Mark the lowest stack frame? + xor %rbp,%rbp + + pop %rdi # arg + call *%r9 # fn_ + + mov %rax,%rdi # status + mov $$60,%rax # EXIT + syscall + + # Unreachable. + hlt + + __steed_clone_end: + "); +} + +/* +#[inline(always)] +#[cfg(target_arch = "x86_64")] +unsafe fn thread_self() -> *mut thread { + let result; + asm!("mov %fs:0,$0":"=r"(result)); + result +} + +#[inline(always)] +pub unsafe fn pthread_self() -> pthread_t { + pthread_t { + thread: thread_self(), + } +} + +pub unsafe fn pthread_detach(thread: pthread_t) -> c_int { + unimplemented!(); +} +*/ + +pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) + -> c_int +{ + assert!(retval.is_null()); + let thread = pthread.thread; + + let tmp = (*thread).thread_id; + if tmp == 0 { + return 0; + } + // TODO(steed): Why does FUTEX_WAIT_PRIVATE not work? + let res = linux::futex(&mut (*thread).thread_id as *mut _ as *mut u32, + linux::FUTEX_WAIT, + (*thread).thread_id as u32, + ptr::null(), + ptr::null_mut(), + 0); + if res == -EAGAIN { + return 0; + } + if res < 0 { + return -res; + } + 0 +} + // Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs #[allow(non_snake_case)] pub fn WTERMSIG(status: c_int) -> c_int { diff --git a/src/linux/aarch64.rs b/src/linux/aarch64.rs index f94452eee..da0730e99 100644 --- a/src/linux/aarch64.rs +++ b/src/linux/aarch64.rs @@ -31,6 +31,8 @@ pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 1; +pub const MAP_ANONYMOUS: c_int = 0x20; + #[derive(Clone, Copy)] #[repr(C)] pub struct stat64 { diff --git a/src/linux/arm.rs b/src/linux/arm.rs index d4eb6d498..a4552dbba 100644 --- a/src/linux/arm.rs +++ b/src/linux/arm.rs @@ -31,6 +31,8 @@ pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 1; +pub const MAP_ANONYMOUS: c_int = 0x20; + // include/linux/types.h pub type ino_t = __kernel_ino_t; // include/uapi/asm-generic/posix_types.h diff --git a/src/linux/mips.rs b/src/linux/mips.rs index d3967cc1a..9bbd810e1 100644 --- a/src/linux/mips.rs +++ b/src/linux/mips.rs @@ -30,6 +30,8 @@ pub const SOCK_STREAM: c_int = 2; pub const SOL_SOCKET: c_int = 0xffff; +pub const MAP_ANONYMOUS: c_int = 0x0800; + #[derive(Clone, Copy)] #[repr(C)] pub struct stat64 { diff --git a/src/linux/mips64.rs b/src/linux/mips64.rs index 8a7afa80a..5dd607cb9 100644 --- a/src/linux/mips64.rs +++ b/src/linux/mips64.rs @@ -29,6 +29,8 @@ pub const SOCK_STREAM: c_int = 2; pub const SOL_SOCKET: c_int = 0xffff; +pub const MAP_ANONYMOUS: c_int = 0x0800; + #[derive(Clone, Copy)] #[repr(C)] pub struct stat64 { diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 2166dddbf..c844633f6 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -134,6 +134,25 @@ pub const TCP_NODELAY: c_int = 1; // include/linux/socket.h pub const MSG_NOSIGNAL: c_int = 0x4000; +// include/uapi/asm-generic/mman-common.h +pub const MAP_PRIVATE: c_int = 0x02; +pub const PROT_READ: c_int = 0x1; +pub const PROT_WRITE: c_int = 0x2; + +// include/uapi/linux/sched.h +pub const CLONE_CHILD_CLEARTID: c_ulong = 0x00200000; +pub const CLONE_FILES: c_ulong = 0x00000400; +pub const CLONE_FS: c_ulong = 0x00000200; +pub const CLONE_PARENT_SETTID: c_ulong = 0x00100000; +pub const CLONE_SETTLS: c_ulong = 0x00080000; +pub const CLONE_SIGHAND: c_ulong = 0x00000800; +pub const CLONE_SYSVSEM: c_ulong = 0x00040000; +pub const CLONE_THREAD: c_ulong = 0x00010000; +pub const CLONE_VM: c_ulong = 0x00000100; + +// include/uapi/linux/futex.h +pub const FUTEX_WAIT: c_int = 0; + // kernel/time/posix-timers.c #[inline(always)] pub unsafe fn clock_gettime(which_clock: clockid_t, @@ -1049,3 +1068,48 @@ pub unsafe fn recvfrom(fd: c_int, } recvfrom(fd, buf, size, flags, addr, addrlen) } + +// kernel/sys.c +pub unsafe fn prctl(option: c_int, + arg2: c_ulong, + arg3: c_ulong, + arg4: c_ulong, + arg5: c_ulong) + -> ssize_t +{ + syscall!(PRCTL, option, arg2, arg3, arg4, arg5) as ssize_t +} + +// kernel/sched/core.c +pub unsafe fn sched_yield() -> ssize_t { + syscall!(SCHED_YIELD) as ssize_t +} + +// kernel/sched/core.c +pub unsafe fn mmap(addr: *mut c_void, + length: size_t, + prot: c_int, + flags: c_int, + fd: c_int, + offset: off_t) + -> *mut c_void +{ + syscall!(MMAP, addr, length, prot, flags, fd, offset) as *mut c_void +} + +// kernel/time/hrtimer.c +pub unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> ssize_t { + syscall!(NANOSLEEP, rqtp, rmtp) as ssize_t +} + +// kernel/futex.c +pub unsafe fn futex(uaddr: *mut u32, + op: c_int, + val: u32, + utime: *const timespec, + uaddr2: *mut u32, + val3: u32) + -> c_int +{ + syscall!(FUTEX, uaddr, op, val, utime, uaddr2, val3) as c_int +} diff --git a/src/linux/powerpc.rs b/src/linux/powerpc.rs index 8d71a7375..d8d71c120 100644 --- a/src/linux/powerpc.rs +++ b/src/linux/powerpc.rs @@ -31,6 +31,8 @@ pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 1; +pub const MAP_ANONYMOUS: c_int = 0x20; + #[derive(Clone, Copy)] #[repr(C)] pub struct stat64 { diff --git a/src/linux/powerpc64.rs b/src/linux/powerpc64.rs index a432f0cb3..1a8b25028 100644 --- a/src/linux/powerpc64.rs +++ b/src/linux/powerpc64.rs @@ -31,6 +31,8 @@ pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 1; +pub const MAP_ANONYMOUS: c_int = 0x20; + #[derive(Clone, Copy)] #[repr(C)] pub struct stat64 { diff --git a/src/linux/sparc64.rs b/src/linux/sparc64.rs index 368cde51d..b96484294 100644 --- a/src/linux/sparc64.rs +++ b/src/linux/sparc64.rs @@ -30,3 +30,5 @@ pub const SOCK_STREAM: c_int = 1; pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 0xffff; + +pub const MAP_ANONYMOUS: c_int = 0x20; diff --git a/src/linux/types.rs b/src/linux/types.rs index c2c5fddfe..3700a68a8 100644 --- a/src/linux/types.rs +++ b/src/linux/types.rs @@ -10,10 +10,11 @@ pub type clockid_t = __kernel_clockid_t; pub type loff_t = __kernel_loff_t; pub type mode_t = __kernel_mode_t; pub type nlink_t = u32; +pub type off_t = __kernel_off_t; pub type pid_t = __kernel_pid_t; +pub type suseconds_t = __kernel_suseconds_t; pub type time_t = __kernel_time_t; pub type umode_t = c_ushort; -pub type suseconds_t = __kernel_suseconds_t; // include/linux/socket.h pub type sa_family_t = __kernel_sa_family_t; @@ -28,10 +29,11 @@ type __kernel_loff_t = c_longlong; type __kernel_long_t = c_long; type __kernel_mode_t = c_uint; type __kernel_off64_t = c_longlong; +type __kernel_off_t = c_long; type __kernel_pid_t = c_int; +type __kernel_suseconds_t = __kernel_long_t; type __kernel_time_t = __kernel_long_t; type __kernel_uid_t = c_uint; -type __kernel_suseconds_t = __kernel_long_t; // include/uapi/linux/time.h #[derive(Clone, Copy)] diff --git a/src/linux/x86.rs b/src/linux/x86.rs index c00536e34..90e005bab 100644 --- a/src/linux/x86.rs +++ b/src/linux/x86.rs @@ -31,6 +31,8 @@ pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 1; +pub const MAP_ANONYMOUS: c_int = 0x20; + // include/uapi/linux/net.h pub const SYS_SOCKET: c_ulong = 1; pub const SYS_BIND: c_ulong = 2; diff --git a/src/linux/x86_64.rs b/src/linux/x86_64.rs index 2b3a4b244..2d13017fd 100644 --- a/src/linux/x86_64.rs +++ b/src/linux/x86_64.rs @@ -31,6 +31,8 @@ pub const SOCK_DGRAM: c_int = 2; pub const SOL_SOCKET: c_int = 1; +pub const MAP_ANONYMOUS: c_int = 0x20; + pub type blksize_t = i64; #[derive(Clone, Copy)] diff --git a/src/sys/linux/mod.rs b/src/sys/linux/mod.rs index 2b90ba2af..7c61778b5 100644 --- a/src/sys/linux/mod.rs +++ b/src/sys/linux/mod.rs @@ -11,6 +11,8 @@ pub mod fd; pub mod fs; pub mod memchr; // Rust 1.15.0 +pub mod net; +// Rust 1.15.0 pub mod os_str; // Rust 1.15.0 pub mod path; @@ -20,9 +22,10 @@ pub mod pipe; pub mod process; pub mod os; pub mod rand; +pub mod stack_overflow; pub mod time; -// Rust 1.14.0 -pub mod net; +// Rust 1.15.0 +pub mod thread; pub use os::linux as platform; diff --git a/src/sys/linux/os.rs b/src/sys/linux/os.rs index 782b5a22c..96c6d84db 100644 --- a/src/sys/linux/os.rs +++ b/src/sys/linux/os.rs @@ -15,6 +15,10 @@ pub fn exit(code: i32) -> ! { unsafe { linux::exit_group(code) } } +pub fn page_size() -> usize { + unimplemented!(); +} + // TODO(steed): Fix this unsafety, should be *const c_char elements. static ENVIRON: [usize; 1] = [0]; diff --git a/src/sys/linux/stack_overflow.rs b/src/sys/linux/stack_overflow.rs new file mode 100644 index 000000000..38469e4e5 --- /dev/null +++ b/src/sys/linux/stack_overflow.rs @@ -0,0 +1,7 @@ +pub struct Handler(()); + +impl Handler { + pub unsafe fn new() -> Handler { + Handler(()) + } +} diff --git a/src/sys/linux/thread.rs b/src/sys/linux/thread.rs new file mode 100644 index 000000000..aab07641b --- /dev/null +++ b/src/sys/linux/thread.rs @@ -0,0 +1,377 @@ +// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use alloc::boxed::FnBox; +use cmp; +use ffi::CStr; +use io; +use libc; +use mem; +use ptr; +use sys::os; +use time::Duration; + +use sys_common::thread::*; + +pub struct Thread { + id: libc::pthread_t, +} + +// Some platforms may have pthread_t as a pointer in which case we still want +// a thread to be Send/Sync +unsafe impl Send for Thread {} +unsafe impl Sync for Thread {} + +// The pthread_attr_setstacksize symbol doesn't exist in the emscripten libc, +// so we have to not link to it to satisfy emcc's ERROR_ON_UNDEFINED_SYMBOLS. +#[cfg(not(target_os = "emscripten"))] +unsafe fn pthread_attr_setstacksize(attr: *mut libc::pthread_attr_t, + stack_size: libc::size_t) -> libc::c_int { + libc::pthread_attr_setstacksize(attr, stack_size) +} + +#[cfg(target_os = "emscripten")] +unsafe fn pthread_attr_setstacksize(_attr: *mut libc::pthread_attr_t, + _stack_size: libc::size_t) -> libc::c_int { + panic!() +} + +impl Thread { + pub unsafe fn new<'a>(stack: usize, p: Box) + -> io::Result { + let p = box p; + let mut native: libc::pthread_t = mem::zeroed(); + let mut attr: libc::pthread_attr_t = mem::zeroed(); + assert_eq!(libc::pthread_attr_init(&mut attr), 0); + + let stack_size = cmp::max(stack, min_stack_size(&attr)); + match pthread_attr_setstacksize(&mut attr, + stack_size) { + 0 => {} + n => { + assert_eq!(n, libc::EINVAL); + // EINVAL means |stack_size| is either too small or not a + // multiple of the system page size. Because it's definitely + // >= PTHREAD_STACK_MIN, it must be an alignment issue. + // Round up to the nearest page and try again. + let page_size = os::page_size(); + let stack_size = (stack_size + page_size - 1) & + (-(page_size as isize - 1) as usize - 1); + assert_eq!(libc::pthread_attr_setstacksize(&mut attr, + stack_size), 0); + } + }; + + let ret = libc::pthread_create(&mut native, &attr, thread_start, + &*p as *const _ as *mut _); + assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + + return if ret != 0 { + Err(io::Error::from_raw_os_error(ret)) + } else { + mem::forget(p); // ownership passed to pthread_create + Ok(Thread { id: native }) + }; + + extern fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void { + unsafe { start_thread(main); } + ptr::null_mut() + } + } + + pub fn yield_now() { + let ret = unsafe { libc::sched_yield() }; + debug_assert_eq!(ret, 0); + } + + #[cfg(any(target_os = "linux", + target_os = "android"))] + pub fn set_name(name: &CStr) { + const PR_SET_NAME: libc::c_int = 15; + // pthread wrapper only appeared in glibc 2.12, so we use syscall + // directly. + unsafe { + libc::prctl(PR_SET_NAME, name.as_ptr() as libc::c_ulong, 0, 0, 0); + } + } + + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "bitrig", + target_os = "openbsd"))] + pub fn set_name(name: &CStr) { + unsafe { + libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr()); + } + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub fn set_name(name: &CStr) { + unsafe { + libc::pthread_setname_np(name.as_ptr()); + } + } + + #[cfg(target_os = "netbsd")] + pub fn set_name(name: &CStr) { + use ffi::CString; + let cname = CString::new(&b"%s"[..]).unwrap(); + unsafe { + libc::pthread_setname_np(libc::pthread_self(), cname.as_ptr(), + name.as_ptr() as *mut libc::c_void); + } + } + #[cfg(any(target_env = "newlib", + target_os = "solaris", + target_os = "haiku", + target_os = "emscripten"))] + pub fn set_name(_name: &CStr) { + // Newlib, Illumos, Haiku, and Emscripten have no way to set a thread name. + } + #[cfg(target_os = "fuchsia")] + pub fn set_name(_name: &CStr) { + // FIXME: determine whether Fuchsia has a way to set a thread name. + } + + pub fn sleep(dur: Duration) { + let mut secs = dur.as_secs(); + let mut nsecs = dur.subsec_nanos() as libc::c_long; + + // If we're awoken with a signal then the return value will be -1 and + // nanosleep will fill in `ts` with the remaining time. + unsafe { + while secs > 0 || nsecs > 0 { + let mut ts = libc::timespec { + tv_sec: cmp::min(libc::time_t::max_value() as u64, secs) as libc::time_t, + tv_nsec: nsecs, + }; + secs -= ts.tv_sec as u64; + if libc::nanosleep(&ts, &mut ts) == -1 { + assert_eq!(os::errno(), libc::EINTR); + secs += ts.tv_sec as u64; + nsecs = ts.tv_nsec; + } else { + nsecs = 0; + } + } + } + } + + pub fn join(self) { + unsafe { + let ret = libc::pthread_join(self.id, ptr::null_mut()); + mem::forget(self); + debug_assert_eq!(ret, 0); + } + } + + /* + pub fn id(&self) -> libc::pthread_t { self.id } + + pub fn into_id(self) -> libc::pthread_t { + let id = self.id; + mem::forget(self); + id + } + */ +} + +/* +impl Drop for Thread { + fn drop(&mut self) { + let ret = unsafe { libc::pthread_detach(self.id) }; + debug_assert_eq!(ret, 0); + } +} +*/ + +#[cfg(all(not(all(target_os = "linux", not(target_env = "musl"))), + not(target_os = "freebsd"), + not(target_os = "macos"), + not(target_os = "bitrig"), + not(all(target_os = "netbsd", not(target_vendor = "rumprun"))), + not(target_os = "openbsd"), + not(target_os = "solaris")))] +#[cfg_attr(test, allow(dead_code))] +pub mod guard { + pub unsafe fn current() -> Option { None } + pub unsafe fn init() -> Option { None } +} + + +/* +#[cfg(any(all(target_os = "linux", not(target_env = "musl")), + target_os = "freebsd", + target_os = "macos", + target_os = "bitrig", + all(target_os = "netbsd", not(target_vendor = "rumprun")), + target_os = "openbsd", + target_os = "solaris"))] +#[cfg_attr(test, allow(dead_code))] +pub mod guard { + use libc; + use libc::mmap; + use libc::{PROT_NONE, MAP_PRIVATE, MAP_ANON, MAP_FAILED, MAP_FIXED}; + use sys::os; + + #[cfg(any(target_os = "macos", + target_os = "bitrig", + target_os = "openbsd", + target_os = "solaris"))] + unsafe fn get_stack_start() -> Option<*mut libc::c_void> { + current().map(|s| s as *mut libc::c_void) + } + + #[cfg(any(target_os = "android", target_os = "freebsd", + target_os = "linux", target_os = "netbsd"))] + unsafe fn get_stack_start() -> Option<*mut libc::c_void> { + let mut ret = None; + let mut attr: libc::pthread_attr_t = ::mem::zeroed(); + assert_eq!(libc::pthread_attr_init(&mut attr), 0); + #[cfg(target_os = "freebsd")] + let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr); + #[cfg(not(target_os = "freebsd"))] + let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr); + if e == 0 { + let mut stackaddr = ::ptr::null_mut(); + let mut stacksize = 0; + assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, + &mut stacksize), 0); + ret = Some(stackaddr); + } + assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + ret + } + + pub unsafe fn init() -> Option { + let psize = os::page_size(); + let mut stackaddr = match get_stack_start() { + Some(addr) => addr, + None => return None, + }; + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = (stackaddr as usize) % psize; + if remainder != 0 { + stackaddr = ((stackaddr as usize) + psize - remainder) + as *mut libc::c_void; + } + + // Rellocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + let result = mmap(stackaddr, psize, PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); + + if result != stackaddr || result == MAP_FAILED { + panic!("failed to allocate a guard page"); + } + + let offset = if cfg!(any(target_os = "linux", target_os = "freebsd")) { + 2 + } else { + 1 + }; + + Some(stackaddr as usize + offset * psize) + } + + #[cfg(target_os = "solaris")] + pub unsafe fn current() -> Option { + let mut current_stack: libc::stack_t = ::mem::zeroed(); + assert_eq!(libc::stack_getbounds(&mut current_stack), 0); + Some(current_stack.ss_sp as usize) + } + + #[cfg(target_os = "macos")] + pub unsafe fn current() -> Option { + Some((libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize - + libc::pthread_get_stacksize_np(libc::pthread_self()))) + } + + #[cfg(any(target_os = "openbsd", target_os = "bitrig"))] + pub unsafe fn current() -> Option { + let mut current_stack: libc::stack_t = ::mem::zeroed(); + assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), + &mut current_stack), 0); + + let extra = if cfg!(target_os = "bitrig") {3} else {1} * os::page_size(); + Some(if libc::pthread_main_np() == 1 { + // main thread + current_stack.ss_sp as usize - current_stack.ss_size + extra + } else { + // new thread + current_stack.ss_sp as usize - current_stack.ss_size + }) + } + + #[cfg(any(target_os = "android", target_os = "freebsd", + target_os = "linux", target_os = "netbsd"))] + pub unsafe fn current() -> Option { + let mut ret = None; + let mut attr: libc::pthread_attr_t = ::mem::zeroed(); + assert_eq!(libc::pthread_attr_init(&mut attr), 0); + #[cfg(target_os = "freebsd")] + let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr); + #[cfg(not(target_os = "freebsd"))] + let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr); + if e == 0 { + let mut guardsize = 0; + assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0); + if guardsize == 0 { + panic!("there is no guard page"); + } + let mut stackaddr = ::ptr::null_mut(); + let mut size = 0; + assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, + &mut size), 0); + + ret = if cfg!(target_os = "freebsd") { + Some(stackaddr as usize - guardsize) + } else if cfg!(target_os = "netbsd") { + Some(stackaddr as usize) + } else { + Some(stackaddr as usize + guardsize) + }; + } + assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + ret + } +} +*/ + +// glibc >= 2.15 has a __pthread_get_minstack() function that returns +// PTHREAD_STACK_MIN plus however many bytes are needed for thread-local +// storage. We need that information to avoid blowing up when a small stack +// is created in an application with big thread-local storage requirements. +// See #6233 for rationale and details. +#[cfg(target_os = "linux")] +#[allow(deprecated)] +fn min_stack_size(_: *const libc::pthread_attr_t) -> usize { + libc::PTHREAD_STACK_MIN +} + +// No point in looking up __pthread_get_minstack() on non-glibc +// platforms. +#[cfg(all(not(target_os = "linux"), + not(target_os = "netbsd")))] +fn min_stack_size(_: *const libc::pthread_attr_t) -> usize { + libc::PTHREAD_STACK_MIN +} + +#[cfg(target_os = "netbsd")] +fn min_stack_size(_: *const libc::pthread_attr_t) -> usize { + 2048 // just a guess +} diff --git a/src/sys_common/mod.rs b/src/sys_common/mod.rs index b269334e7..8fa4e33d9 100644 --- a/src/sys_common/mod.rs +++ b/src/sys_common/mod.rs @@ -2,6 +2,10 @@ pub mod io; // Rust 1.15.0 (close) pub mod net; +// Rust 1.15.0 +pub mod thread; + +pub mod util; /// A trait for viewing representations from std types #[doc(hidden)] diff --git a/src/sys_common/thread.rs b/src/sys_common/thread.rs new file mode 100644 index 000000000..3ee160da5 --- /dev/null +++ b/src/sys_common/thread.rs @@ -0,0 +1,22 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use alloc::boxed::FnBox; +use libc; +use sys::stack_overflow; + +pub unsafe fn start_thread(main: *mut libc::c_void) { + // Next, set up our stack overflow handler which may get triggered if we run + // out of stack. + let _handler = stack_overflow::Handler::new(); + + // Finally, let's run some code. + Box::from_raw(main as *mut Box)() +} diff --git a/src/sys_common/util.rs b/src/sys_common/util.rs new file mode 100644 index 000000000..a321e567c --- /dev/null +++ b/src/sys_common/util.rs @@ -0,0 +1,3 @@ +pub fn min_stack() -> usize { + 2 * 1024 * 1024 + 1 +} diff --git a/src/thread/mod.rs b/src/thread/mod.rs new file mode 100644 index 000000000..6308a2819 --- /dev/null +++ b/src/thread/mod.rs @@ -0,0 +1,259 @@ +#![stable(feature = "rust1", since = "1.0.0")] + +use any::Any; +use cell::UnsafeCell; +use ffi::{CStr, CString}; +use fmt; +use io; +use str; +use sync::Arc; +use sync::atomic::{AtomicUsize, Ordering}; +use sys::thread as imp; +use sys_common::util; +use sys_common::{AsInner, IntoInner}; +use time::Duration; + +#[stable(feature = "rust1", since = "1.0.0")] +#[derive(Debug)] +pub struct Builder { + // A name for the thread-to-be, for identification in panic messages + name: Option, + // The size of the stack for the spawned thread + stack_size: Option, +} + +impl Builder { + #[stable(feature = "rust1", since = "1.0.0")] + pub fn new() -> Builder { + Builder { + name: None, + stack_size: None, + } + } + #[stable(feature = "rust1", since = "1.0.0")] + pub fn name(mut self, name: String) -> Builder { + self.name = Some(name); + self + } + #[stable(feature = "rust1", since = "1.0.0")] + pub fn stack_size(mut self, size: usize) -> Builder { + self.stack_size = Some(size); + self + } + #[stable(feature = "rust1", since = "1.0.0")] + pub fn spawn(self, f: F) -> io::Result> where + F: FnOnce() -> T, F: Send + 'static, T: Send + 'static + { + let Builder { name, stack_size } = self; + + let stack_size = stack_size.unwrap_or(util::min_stack()); + + let my_thread = Thread::new(name); + let their_thread = my_thread.clone(); + + let my_packet : Arc>>> + = Arc::new(UnsafeCell::new(None)); + let their_packet = my_packet.clone(); + + let main = move || { + if let Some(name) = their_thread.cname() { + imp::Thread::set_name(name); + } + unsafe { + /* + thread_info::set(imp::guard::current(), their_thread); + let try_result = panic::catch_unwind(panic::AssertUnwindSafe(f)); + *their_packet.get() = Some(try_result); + */ + let result = f(); + *their_packet.get() = Some(Ok(result)); + } + }; + + Ok(JoinHandle(JoinInner { + native: unsafe { + Some(imp::Thread::new(stack_size, Box::new(main))?) + }, + thread: my_thread, + packet: Packet(my_packet), + })) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +pub fn spawn(f: F) -> JoinHandle where + F: FnOnce() -> T, F: Send + 'static, T: Send + 'static +{ + Builder::new().spawn(f).unwrap() +} + +#[stable(feature = "rust1", since = "1.0.0")] +pub fn yield_now() { + imp::Thread::yield_now() +} + +#[stable(feature = "rust1", since = "1.0.0")] +#[rustc_deprecated(since = "1.6.0", reason = "replaced by `std::thread::sleep`")] +pub fn sleep_ms(ms: u32) { + sleep(Duration::from_millis(ms as u64)) +} + +#[stable(feature = "thread_sleep", since = "1.4.0")] +pub fn sleep(dur: Duration) { + imp::Thread::sleep(dur) +} + +#[unstable(feature = "thread_id", issue = "21507")] +#[derive(Eq, PartialEq, Copy, Clone)] +pub struct ThreadId(u64); + +impl ThreadId { + // Generate a new unique thread ID. + fn new() -> ThreadId { + /* + static GUARD: mutex::Mutex = mutex::Mutex::new(); + static mut COUNTER: u64 = 0; + + unsafe { + GUARD.lock(); + + // If we somehow use up all our bits, panic so that we're not + // covering up subtle bugs of IDs being reused. + if COUNTER == ::u64::MAX { + GUARD.unlock(); + panic!("failed to generate unique thread ID: bitspace exhausted"); + } + + let id = COUNTER; + COUNTER += 1; + + GUARD.unlock(); + + ThreadId(id) + } + */ + static COUNT: AtomicUsize = AtomicUsize::new(0); + ThreadId(COUNT.fetch_add(1, Ordering::Relaxed) as u64) + } +} + +#[unstable(feature = "thread_id", issue = "21507")] +impl fmt::Debug for ThreadId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad("ThreadId { .. }") + } +} + +struct Inner { + name: Option, // Guaranteed to be UTF-8 + id: ThreadId, +} + +#[derive(Clone)] +#[stable(feature = "rust1", since = "1.0.0")] +pub struct Thread { + inner: Arc, +} + +impl Thread { + // Used only internally to construct a thread object without spawning + fn new(name: Option) -> Thread { + let cname = name.map(|n| { + CString::new(n).expect("thread name may not contain interior null bytes") + }); + Thread { + inner: Arc::new(Inner { + name: cname, + id: ThreadId::new(), + }) + } + } + #[unstable(feature = "thread_id", issue = "21507")] + pub fn id(&self) -> ThreadId { + self.inner.id + } + #[stable(feature = "rust1", since = "1.0.0")] + pub fn name(&self) -> Option<&str> { + self.cname().map(|s| unsafe { str::from_utf8_unchecked(s.to_bytes()) } ) + } + fn cname(&self) -> Option<&CStr> { + self.inner.name.as_ref().map(|s| &**s) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for Thread { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.name(), f) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +pub type Result = ::result::Result>; + +// This packet is used to communicate the return value between the child thread +// and the parent thread. Memory is shared through the `Arc` within and there's +// no need for a mutex here because synchronization happens with `join()` (the +// parent thread never reads this packet until the child has exited). +// +// This packet itself is then stored into a `JoinInner` which in turns is placed +// in `JoinHandle` and `JoinGuard`. Due to the usage of `UnsafeCell` we need to +// manually worry about impls like Send and Sync. The type `T` should +// already always be Send (otherwise the thread could not have been created) and +// this type is inherently Sync because no methods take &self. Regardless, +// however, we add inheriting impls for Send/Sync to this type to ensure it's +// Send/Sync and that future modifications will still appropriately classify it. +struct Packet(Arc>>>); + +unsafe impl Send for Packet {} +unsafe impl Sync for Packet {} + +struct JoinInner { + native: Option, + thread: Thread, + packet: Packet, +} + +impl JoinInner { + fn join(&mut self) -> Result { + self.native.take().unwrap().join(); + unsafe { + (*self.packet.0.get()).take().unwrap() + } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +pub struct JoinHandle(JoinInner); + +impl JoinHandle { + #[stable(feature = "rust1", since = "1.0.0")] + pub fn thread(&self) -> &Thread { + &self.0.thread + } + #[stable(feature = "rust1", since = "1.0.0")] + pub fn join(mut self) -> Result { + self.0.join() + } +} + +impl AsInner for JoinHandle { + fn as_inner(&self) -> &imp::Thread { self.0.native.as_ref().unwrap() } +} + +impl IntoInner for JoinHandle { + fn into_inner(self) -> imp::Thread { self.0.native.unwrap() } +} + +#[stable(feature = "std_debug", since = "1.16.0")] +impl fmt::Debug for JoinHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad("JoinHandle { .. }") + } +} + +fn _assert_sync_and_send() { + fn _assert_both() {} + _assert_both::>(); + _assert_both::(); +} From 38181bd9037e16da7bed48af911115d923ef884a Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Wed, 22 Feb 2017 01:01:06 +0100 Subject: [PATCH 02/12] Add x86 support for threads --- Cargo.toml | 2 +- examples/thread.rs | 1 - src/libc.rs | 171 ++++++++++++++++++++++++++++++++++++++++++-- src/linux/mod.rs | 31 +++++++- src/linux/x86.rs | 16 +++++ src/linux/x86_64.rs | 9 +++ src/rt.rs | 2 + 7 files changed, 225 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e04a34c2..a1b5a76cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ name = "std" version = "0.1.0" [dependencies] -sc = "0.1.5" +sc = { git = "git://github.com/tbu-/syscall.rs", branch = "temp" } [dependencies.ralloc] default-features = false diff --git a/examples/thread.rs b/examples/thread.rs index 789d0625f..51fa75189 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -1,6 +1,5 @@ use std::io::{self, Write}; use std::thread; -use std::time::Duration; fn main() { thread::spawn(|| { diff --git a/src/libc.rs b/src/libc.rs index 977c56308..7c826d179 100644 --- a/src/libc.rs +++ b/src/libc.rs @@ -124,6 +124,9 @@ pub struct pthread_attr_t { } struct thread { + // Required, because one cannot easily read out register containing the + // pointer to this structure on some platforms. + this: *mut thread, thread_id: pid_t, } @@ -178,6 +181,42 @@ pub unsafe fn pthread_getattr_np(pthread: pthread_t, } */ +pub mod internal { + use linux; + use super::*; + + pub struct Buffer(thread); + + #[cfg(target_arch = "x86")] + unsafe fn set_thread_pointer(thread_data: *mut thread) { + let mut user_desc = linux::user_desc { + entry_number: -1i32 as u32, + base_addr: thread_data as u32, + limit: 0xfffff, + flags: 0x51, + }; + let result = linux::set_thread_area(&mut user_desc); + if result < 0 { + panic!("set_thread_pointer: set_thread_area: {}", result); + } + asm!("mov $0,%gs"::"r"(((user_desc.entry_number << 3) | 3) as u16)); + } + + #[cfg(target_arch = "x86_64")] + unsafe fn set_thread_pointer(thread_data: *mut thread) { + let result = linux::arch_prctl(linux::ARCH_SET_FS, thread_data as c_ulong); + if result < 0 { + panic!("set_thread_pointer: arch_prctl: {}", result); + } + } + + pub unsafe fn init_main_thread(buffer: *mut Buffer) { + let buffer = &mut (*buffer).0; + buffer.this = buffer; + set_thread_pointer(buffer); + } +} + pub unsafe fn pthread_create(pthread: *mut pthread_t, attr: *const pthread_attr_t, start_routine: extern "C" fn(*mut c_void) -> *mut c_void, @@ -207,6 +246,7 @@ pub unsafe fn pthread_create(pthread: *mut pthread_t, let stack = map.offset(stack_size as isize); let thread = stack as *mut thread; + (*thread).this = thread; let child_tid = syscall_clone(start_routine, stack, @@ -235,6 +275,129 @@ extern { ctid: *mut pid_t) -> pid_t; } +#[cfg(target_arch = "x86")] +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in %eax, syscall arguments in %ebx, %ecx, %edx, + // %esi, %edi. The arguments are + // (flags: c_ulong, // %ebx + // child_stack: *mut c_void, // %ecx + // ptid: *mut c_int, // %edx + // newtls: c_ulong, // %esi + // ctid: *mut c_int) // %edi + // + // No registers are clobbered, %eax gets the return value. + // + // Only %eax, %ecx and %edx are caller-saved, so we must restore the value + // of all other registers before returning. + // + // The cdecl calling convention passes arguments on the stack, right to + // left. Since we push %ebp onto the stack in the very beginning, all + // offsets are increased by 4. The arguments are + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // 8(%ebp) + // child_stack: *mut c_void, // 12(%ebp) + // flags: c_ulong, // 16(%ebp) + // arg: *mut c_void, // 20(%ebp) + // ptid: *mut pid_t, // 24(%ebp) + // newtls: *mut c_void, // 28(%ebp) + // ctid: *mut pid_t) // 32(%ebp) + // + // Both ABIs return the function result in %eax. + // + // This means we need the following moves: + // 16(%ebp) -> %ebx // flags + // 12(%ebp) -> %ecx // child_stack + // 24(%ebp) -> %edx // ptid + // fancy -> %esi // newtls + // 32(%ebp) -> %edi // ctid + // + // We need to create a struct of type `struct user_desc` (see `clone(2)` + // and `set_thread_area(2)`) and store it in %esi. We do it by pushing it + // onto the parent stack. + // + // We save `fn_` in %ebp. + + asm!(" + # Stack frame + push %ebp + mov %esp,%ebp + + # Save registers + push %ebx + push %esi + push %edi + + mov 12(%ebp),%ecx # child_stack + and $$-16,%ecx # Align the stack + + # Push the parameter + sub $$16,%ecx # Keep the stack aligned + mov 20(%ebp),%edi # arg + mov %edi,(%ecx) + + # Construct the struct + # I don't know what these parameters do, but glibc and musl agree on + # these. + + # Bitfield, according to glibc: + # seg_32bit:1 = 1 + # contents:2 = 0 + # read_exec_only:1 = 0 + # limit_in_pages:1 = 1 + # seg_not_present:1 = 0 + # useable:1 = 1 + push $$0x51 + push $$0xfffff # limit + push 28(%ebp) # base_addr + xor %eax,%eax + mov %gs,%ax + shr $$3,%eax + push %eax # entry_number + + mov $$120,%eax # CLONE + mov 16(%ebp),%ebx # flags + mov 24(%ebp),%edx # ptid + mov %esp,%esi # newtls + mov 32(%ebp),%edi # ctid + + mov 8(%ebp),%ebp # fn_ + + int $$0x80 + + # CLONE returns 0 in the child thread, return if we're the parent. + test %eax,%eax + jnz __steed_clone_parent + + mov %ebp,%eax # fn_ + + # Mark the lowest stack frame + xor %ebp,%ebp + + # arg is already on the stack + call *%eax + + mov %eax,%ebx # status + mov $$1,%eax # EXIT + int $$0x80 + hlt + + __steed_clone_parent: + + # Pop the struct + add $$16,%esp + + # Restore registers + pop %edi + pop %esi + pop %ebx + + # Stack frame + pop %ebp + "); +} + #[cfg(target_arch = "x86_64")] #[inline(never)] #[naked] @@ -250,7 +413,7 @@ unsafe extern "C" fn __steed_clone() { // // The registers %rcx and %r11 are clobbered, %rax gets the return value. // - // The System V AMD64 API passes arguments in %rdi, %rsi, %rdx, %rcx, %r8, + // The System V AMD64 ABI passes arguments in %rdi, %rsi, %rdx, %rcx, %r8, // %r9, 8(%rsp). The arguments are // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // %rdi // child_stack: *mut c_void, // %rsi @@ -298,9 +461,9 @@ unsafe extern "C" fn __steed_clone() { # CLONE returns 0 in the child thread, return if we're the parent. test %rax,%rax - jnz __steed_clone_end + jnz __steed_clone_parent - # Mark the lowest stack frame? + # Mark the lowest stack frame xor %rbp,%rbp pop %rdi # arg @@ -313,7 +476,7 @@ unsafe extern "C" fn __steed_clone() { # Unreachable. hlt - __steed_clone_end: + __steed_clone_parent: "); } diff --git a/src/linux/mod.rs b/src/linux/mod.rs index c844633f6..4c9c9bb0b 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -1070,6 +1070,7 @@ pub unsafe fn recvfrom(fd: c_int, } // kernel/sys.c +#[inline(always)] pub unsafe fn prctl(option: c_int, arg2: c_ulong, arg3: c_ulong, @@ -1081,11 +1082,13 @@ pub unsafe fn prctl(option: c_int, } // kernel/sched/core.c +#[inline(always)] pub unsafe fn sched_yield() -> ssize_t { syscall!(SCHED_YIELD) as ssize_t } // kernel/sched/core.c +#[inline(always)] pub unsafe fn mmap(addr: *mut c_void, length: size_t, prot: c_int, @@ -1094,15 +1097,41 @@ pub unsafe fn mmap(addr: *mut c_void, offset: off_t) -> *mut c_void { - syscall!(MMAP, addr, length, prot, flags, fd, offset) as *mut c_void + #[cfg(target_pointer_width = "32")] + #[inline(always)] + unsafe fn mmap(addr: *mut c_void, + length: size_t, + prot: c_int, + flags: c_int, + fd: c_int, + offset: off_t) + -> *mut c_void + { + syscall!(MMAP2, addr, length, prot, flags, fd, offset / 4096) as *mut c_void + } + #[cfg(target_pointer_width = "64")] + #[inline(always)] + unsafe fn mmap(addr: *mut c_void, + length: size_t, + prot: c_int, + flags: c_int, + fd: c_int, + offset: off_t) + -> *mut c_void + { + syscall!(MMAP, addr, length, prot, flags, fd, offset) as *mut c_void + } + mmap(addr, length, prot, flags, fd, offset) } // kernel/time/hrtimer.c +#[inline(always)] pub unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> ssize_t { syscall!(NANOSLEEP, rqtp, rmtp) as ssize_t } // kernel/futex.c +#[inline(always)] pub unsafe fn futex(uaddr: *mut u32, op: c_int, val: u32, diff --git a/src/linux/x86.rs b/src/linux/x86.rs index 90e005bab..cec47b4aa 100644 --- a/src/linux/x86.rs +++ b/src/linux/x86.rs @@ -86,3 +86,19 @@ pub struct stat64 { } pub type blksize_t = i32; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct user_desc { + pub entry_number: c_uint, + pub base_addr: c_uint, + pub limit: c_uint, + pub flags: c_uint, +} + +// arch/x86/kernel/tls.c +#[inline(always)] +pub unsafe fn set_thread_area(u_info: *mut user_desc) -> ssize_t { + // TODO(steed)! + syscall!(SET_THREAD_AREA, u_info) as ssize_t +} diff --git a/src/linux/x86_64.rs b/src/linux/x86_64.rs index 2d13017fd..67c96a4bc 100644 --- a/src/linux/x86_64.rs +++ b/src/linux/x86_64.rs @@ -57,3 +57,12 @@ pub struct stat64 { pub st_ctime_nsec: c_long, __reserved: [c_long; 3], } + +// arch/x86/include/uapi/asm/prctl.h +pub const ARCH_SET_FS: c_int = 0x1002; + +// arch/x86/um/syscall_64.c +#[inline(always)] +pub unsafe fn arch_prctl(code: c_int, addr: c_ulong) -> ssize_t { + syscall!(ARCH_PRCTL, code, addr) as ssize_t +} diff --git a/src/rt.rs b/src/rt.rs index 3b4a054b0..e51d5a853 100644 --- a/src/rt.rs +++ b/src/rt.rs @@ -207,6 +207,8 @@ pub extern "C" fn start(sp: &'static Stack) -> ! { } unsafe { + let mut buffer = ::mem::uninitialized(); + ::libc::internal::init_main_thread(&mut buffer); ::sys::args::init(sp.argc(), sp.argv()); ::linux::exit_group(main(sp.argc(), sp.argv()) as i32) } From 705e9772d14f713ed5479ca30da0239bad93e087 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Fri, 24 Feb 2017 11:45:02 +0100 Subject: [PATCH 03/12] Add ARM support for threads --- Cargo.toml | 2 +- examples/thread.rs | 2 +- src/libc.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1b5a76cf..54672622e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ name = "std" version = "0.1.0" [dependencies] -sc = { git = "git://github.com/tbu-/syscall.rs", branch = "temp" } +sc = { git = "git://github.com/japaric/syscall.rs" } [dependencies.ralloc] default-features = false diff --git a/examples/thread.rs b/examples/thread.rs index 51fa75189..df2c03c6a 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -4,5 +4,5 @@ use std::thread; fn main() { thread::spawn(|| { io::stdout().write_all(b"Hello, world!\n").unwrap(); - }).join(); + }).join().unwrap(); } diff --git a/src/libc.rs b/src/libc.rs index 7c826d179..34fc1243e 100644 --- a/src/libc.rs +++ b/src/libc.rs @@ -187,6 +187,10 @@ pub mod internal { pub struct Buffer(thread); + #[cfg(target_arch = "arm")] + unsafe fn set_thread_pointer(thread_data: *mut thread) { + } + #[cfg(target_arch = "x86")] unsafe fn set_thread_pointer(thread_data: *mut thread) { let mut user_desc = linux::user_desc { @@ -211,8 +215,11 @@ pub mod internal { } pub unsafe fn init_main_thread(buffer: *mut Buffer) { - let buffer = &mut (*buffer).0; - buffer.this = buffer; + let buffer: *mut thread = &mut (*buffer).0; + *buffer = thread { + this: buffer, + thread_id: -1, + }; set_thread_pointer(buffer); } } @@ -275,6 +282,99 @@ extern { ctid: *mut pid_t) -> pid_t; } +#[cfg(target_arch = "arm")] +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in r7, syscall arguments in r0, r1, r2, r3, r4. + // The arguments are + // (flags: c_ulong, // r0 + // child_stack: *mut c_void, // r1 + // ptid: *mut c_int, // r2 + // newtls: c_ulong, // r3 + // ctid: *mut c_int) // r4 + // + // No registers are clobbered, r0 gets the return value. + // + // Only r0, r1, r2, r3 are caller-saved, so we must restore the value of + // all other registers before returning. + // + // The calling convention passes arguments on the stack, right to left. + // Since we push r4 to r7 onto the stack in the very beginning, all offsets + // are increased by 16. The arguments are + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // r0 + // child_stack: *mut c_void, // r1 + // flags: c_ulong, // r2 + // arg: *mut c_void, // r3 + // ptid: *mut pid_t, // [sp,#16] + // newtls: *mut c_void, // [sp,#20] + // ctid: *mut pid_t) // [sp,#24] + // + // Both ABIs return the function result in r0. + // + // This means we need the following moves: + // r2 -> r0 // flags + // r1 -> r1 // child_stack + // [sp,#16] -> r2 // ptid + // [sp,#20] -> r3 // newtls + // [sp,#24] -> r4 // ctid + // + // We save `fn_` in r5, `arg` in r6. + + asm!(" + @ Save r4 to r7 + stmfd sp!,{r4,r5,r6,r7} + + mov r5,r0 @ fn_ + mov r6,r3 @ arg + + mov r7,#120 @ CLONE + mov r0,r2 @ flags + @ child_stack + ldr r2,[sp,#16] @ ptid + ldr r3,[sp,#20] @ newtls + ldr r4,[sp,#24] @ ctid + + and r1,r1,#-16 @ Align the stack + + @ Do the syscall + svc 0 + + @ CLONE returns 0 in the child thread, return if we're the parent. + tst r0,r0 + bne __steed_clone_parent + + mov r0,r6 @ arg + + @ Do we need to execute `fn_` in thumb mode? + tst r5,#1 + bne __steed_clone_thumb + + @ pc (Program Counter) is always 2 instructions ahead. + mov lr,pc + mov pc,r5 @ fn_ + + @ The function will return here. + __steed_clone_exit: + mov r7,#1 @ EXIT + @ status + svc 0 + + __steed_clone_thumb: + + @ Again, pc is 2 instructions ahead. + mov lr,pc + bx r5 @ Start thumb mode + b __steed_clone_exit + + __steed_clone_parent: + + @ Restore r4 to r7 + ldmfd sp!,{r4,r5,r6,r7} + "); +} + #[cfg(target_arch = "x86")] #[inline(never)] #[naked] From 1b0d35f37185da66fa8e0039886443b21d5f5a71 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Fri, 24 Feb 2017 20:23:15 +0100 Subject: [PATCH 04/12] Add AArch64 support for threads --- src/libc.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/src/libc.rs b/src/libc.rs index 34fc1243e..6513094c8 100644 --- a/src/libc.rs +++ b/src/libc.rs @@ -182,17 +182,24 @@ pub unsafe fn pthread_getattr_np(pthread: pthread_t, */ pub mod internal { - use linux; use super::*; pub struct Buffer(thread); + #[cfg(target_arch = "aarch64")] + unsafe fn set_thread_pointer(thread_data: *mut thread) { + let _ = thread_data; // TODO(steed): Set thread-local pointer. + } + #[cfg(target_arch = "arm")] unsafe fn set_thread_pointer(thread_data: *mut thread) { + let _ = thread_data; // TODO(steed): Set thread-local pointer. } #[cfg(target_arch = "x86")] unsafe fn set_thread_pointer(thread_data: *mut thread) { + use linux; + let mut user_desc = linux::user_desc { entry_number: -1i32 as u32, base_addr: thread_data as u32, @@ -208,6 +215,8 @@ pub mod internal { #[cfg(target_arch = "x86_64")] unsafe fn set_thread_pointer(thread_data: *mut thread) { + use linux; + let result = linux::arch_prctl(linux::ARCH_SET_FS, thread_data as c_ulong); if result < 0 { panic!("set_thread_pointer: arch_prctl: {}", result); @@ -282,6 +291,72 @@ extern { ctid: *mut pid_t) -> pid_t; } +#[cfg(target_arch = "aarch64")] +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in x8, syscall arguments in x0, x1, x2, x3, x4. + // The arguments are + // (flags: c_ulong, // x0 + // child_stack: *mut c_void, // x1 + // ptid: *mut c_int, // x2 + // newtls: c_ulong, // x3 + // ctid: *mut c_int) // x4 + // + // No registers are clobbered, x0 gets the return value. + // + // We do not clobber any registers, so we don't need to save any. + // + // The calling convention passes arguments int registers, from x0 to x6. + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // x0 + // child_stack: *mut c_void, // x1 + // flags: c_ulong, // x2 + // arg: *mut c_void, // x3 + // ptid: *mut pid_t, // x4 + // newtls: *mut c_void, // x5 + // ctid: *mut pid_t) // x6 + // + // Both ABIs return the function result in x0. + // + // This means we need the following moves: + // x2 -> x0 // flags + // x1 -> x1 // child_stack + // x4 -> x2 // ptid + // x5 -> x3 // newtls + // x6 -> x4 // ctid + // + // We save `fn_` and `arg` on the child stack. + + asm!(" + // Align the child stack. + and x1,x1,#-16 + + // Save `fn_` and `arg` on the child stack. + stp x0,x3,[x1,#-16]! + + mov x8,#220 // CLONE + mov x0,x2 // flags + // child_stack + mov x2,x4 // ptid + mov x3,x5 // newtls + mov x4,x6 // ctid + svc #0 + + cbnz x0,__steed_clone_parent + + // Restore `fn_` and `arg`. + ldp x1,x0,[sp],#16 + blr x1 + + mov x8,#93 // EXIT + // status + svc #0 + + __steed_clone_parent: + "); +} + #[cfg(target_arch = "arm")] #[inline(never)] #[naked] @@ -300,9 +375,10 @@ unsafe extern "C" fn __steed_clone() { // Only r0, r1, r2, r3 are caller-saved, so we must restore the value of // all other registers before returning. // - // The calling convention passes arguments on the stack, right to left. - // Since we push r4 to r7 onto the stack in the very beginning, all offsets - // are increased by 16. The arguments are + // The calling convention passes the first four arguments in r0 to r4 and + // all further arguments on the stack, right to left. Since we push r4 to + // r7 onto the stack in the very beginning, all stack offsets are increased + // by 16. The arguments are // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // r0 // child_stack: *mut c_void, // r1 // flags: c_ulong, // r2 @@ -437,9 +513,7 @@ unsafe extern "C" fn __steed_clone() { mov 20(%ebp),%edi # arg mov %edi,(%ecx) - # Construct the struct - # I don't know what these parameters do, but glibc and musl agree on - # these. + # Construct a struct of type `user_desc` # Bitfield, according to glibc: # seg_32bit:1 = 1 From 05eb34a5884f37eb91b721e7317ad9cd9d72e94e Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sat, 25 Feb 2017 03:21:21 +0100 Subject: [PATCH 05/12] Factor out arch-specific stuff into submodules of `libc` --- src/libc.rs | 720 ----------------------------------- src/libc/internal/aarch64.rs | 68 ++++ src/libc/internal/arm.rs | 96 +++++ src/libc/internal/mod.rs | 23 ++ src/libc/internal/x86.rs | 136 +++++++ src/libc/internal/x86_64.rs | 90 +++++ src/libc/mod.rs | 323 ++++++++++++++++ 7 files changed, 736 insertions(+), 720 deletions(-) delete mode 100644 src/libc.rs create mode 100644 src/libc/internal/aarch64.rs create mode 100644 src/libc/internal/arm.rs create mode 100644 src/libc/internal/mod.rs create mode 100644 src/libc/internal/x86.rs create mode 100644 src/libc/internal/x86_64.rs create mode 100644 src/libc/mod.rs diff --git a/src/libc.rs b/src/libc.rs deleted file mode 100644 index 6513094c8..000000000 --- a/src/libc.rs +++ /dev/null @@ -1,720 +0,0 @@ -#![allow(non_camel_case_types)] - -use cmp; -use linux; -use mem; -use ptr; - -pub use ctypes::*; -pub use linux::errno::*; - -pub use linux::{gid_t, in_addr, in6_addr, ip_mreq, ipv6_mreq, off_t, off64_t}; -pub use linux::{pid_t, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6}; -pub use linux::{sockaddr_storage, sockaddr_un, socklen_t, stat64, suseconds_t}; -pub use linux::{time_t, timespec, timeval, uid_t}; - -pub use linux::{AF_INET, AF_INET6, AF_UNIX}; -pub use linux::{CLONE_CHILD_CLEARTID, CLONE_FILES, CLONE_FS}; -pub use linux::{CLONE_PARENT_SETTID, CLONE_SETTLS, CLONE_SIGHAND}; -pub use linux::{CLONE_SYSVSEM, CLONE_THREAD, CLONE_VM}; -pub use linux::{DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK}; -pub use linux::{F_DUPFD_CLOEXEC, F_DUPFD, F_GETFL, F_SETFL}; -pub use linux::{FIOCLEX, FIONBIO}; -pub use linux::{IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_LOOP}; -pub use linux::{IP_MULTICAST_TTL, IP_TTL}; -pub use linux::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP}; -pub use linux::{IPV6_MULTICAST_LOOP, IPV6_V6ONLY}; -pub use linux::{IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP}; -pub use linux::{MAP_ANONYMOUS, MAP_PRIVATE}; -pub use linux::{MSG_NOSIGNAL}; -pub use linux::{O_ACCMODE, O_APPEND, O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL}; -pub use linux::{O_LARGEFILE, O_NONBLOCK, O_PATH, O_RDONLY, O_RDWR, O_TRUNC}; -pub use linux::{O_WRONLY}; -pub use linux::{PROT_READ, PROT_WRITE}; -pub use linux::{S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFDIR, S_IFCHR}; -pub use linux::{S_IFIFO}; -pub use linux::{SHUT_RD, SHUT_RDWR, SHUT_WR}; -pub use linux::{SO_BROADCAST, SO_ERROR, SO_RCVTIMEO, SO_REUSEADDR}; -pub use linux::{SO_SNDTIMEO}; -pub use linux::{SOCK_CLOEXEC, SOCK_DGRAM, SOCK_STREAM}; -pub use linux::{SOL_SOCKET}; -pub use linux::{SEEK_CUR, SEEK_END, SEEK_SET}; -pub use linux::{TCP_NODELAY}; - -pub use linux::{accept, accept4, bind, close, connect, fdatasync, fstat64}; -pub use linux::{fsync, ftruncate64, getpeername, getsockname, getsockopt}; -pub use linux::{ioctl, link, listen, lstat64, mmap, nanosleep, prctl, pread64}; -pub use linux::{pwrite64, read, recvfrom, rename, rmdir, sched_yield, send}; -pub use linux::{sendto, setsockopt, socket, socketpair, shutdown, symlink}; -pub use linux::{unlink, write}; - -pub type mode_t = u32; - -// Rust 1.15.0 -// src/liblibc/src/unix/notbsd/linux/mod.rs -#[cfg(issue = "22")] -pub const EAI_SYSTEM: c_int = -11; - -// Rust 1.15.0 -// src/liblibc/src/unix/notbsd/linux/musl/mod.rs -pub const PTHREAD_STACK_MIN: size_t = 2048; - -pub const MAP_ANON: c_int = MAP_ANONYMOUS; - -pub unsafe fn strlen(cs: *const c_char) -> size_t { - let mut cs = cs; - let mut count = 0; - while *cs != 0 { - cs = cs.offset(1); - count += 1; - } - count -} - -#[inline(always)] -pub unsafe fn fcntl(fd: c_int, cmd: c_uint, arg: c_int) -> c_int { - linux::fcntl(fd, cmd, arg as c_ulong) as c_int -} - -#[inline(always)] -pub unsafe fn open64(pathname: *const c_char, flags: c_int, mode: c_int) -> c_int { - linux::open(pathname, flags | O_LARGEFILE, mode as linux::umode_t) -} - -#[inline(always)] -pub unsafe fn readlink(pathname: *const c_char, - buf: *mut c_char, - bufsiz: size_t) - -> ssize_t -{ - linux::readlink(pathname, - buf, - cmp::min(bufsiz, ::max_value() as size_t) as c_int) -} - -#[inline(always)] -pub unsafe fn fchmod(fd: c_int, mode: mode_t) -> c_int { - linux::fchmod(fd, mode as linux::umode_t) -} - -#[inline(always)] -pub unsafe fn chmod(filename: *const c_char, mode: mode_t) -> c_int { - linux::chmod(filename, mode as linux::umode_t) -} - -#[inline(always)] -pub unsafe fn mkdir(pathname: *const c_char, mode: mode_t) -> c_int { - linux::mkdir(pathname, mode as linux::umode_t) -} - -#[inline(always)] -pub unsafe fn lseek64(fd: c_int, offset: off64_t, whence: c_uint) -> off64_t { - let mut result_offset: off64_t = 0; - let result = linux::_llseek(fd, offset, &mut result_offset, whence); - if result >= 0 { - result_offset - } else { - result as off64_t - } -} - -#[derive(Clone, Copy)] -pub struct pthread_attr_t { - stack_size: usize, -} - -struct thread { - // Required, because one cannot easily read out register containing the - // pointer to this structure on some platforms. - this: *mut thread, - thread_id: pid_t, -} - -#[derive(Clone, Copy)] -pub struct pthread_t { - thread: *mut thread, -} - -pub unsafe fn pthread_attr_init(attr: *mut pthread_attr_t) -> c_int { - *attr = pthread_attr_t { - stack_size: 0, - }; - 0 -} - -pub unsafe fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> c_int { - pthread_attr_init(attr) -} - -pub unsafe fn pthread_attr_setstacksize(attr: *mut pthread_attr_t, - stacksize: size_t) - -> c_int -{ - (*attr).stack_size = stacksize; - 0 -} - -/* -pub unsafe fn pthread_attr_getstack(attr: *const pthread_attr_t, - stackaddr: *mut *mut c_void, - stacksize: *mut size_t) - -> c_int -{ - *stackaddr = ptr::null_mut(); - *stacksize = (*attr).stack_size; - 0 -} - -pub unsafe fn pthread_attr_getguardsize(attr: *const pthread_attr_t, - guardsize: *mut size_t) - -> c_int -{ - *guardsize = 0; - 0 -} - -pub unsafe fn pthread_getattr_np(pthread: pthread_t, - attr: *mut pthread_attr_t) - -> c_int -{ - pthread_attr_init(attr) -} -*/ - -pub mod internal { - use super::*; - - pub struct Buffer(thread); - - #[cfg(target_arch = "aarch64")] - unsafe fn set_thread_pointer(thread_data: *mut thread) { - let _ = thread_data; // TODO(steed): Set thread-local pointer. - } - - #[cfg(target_arch = "arm")] - unsafe fn set_thread_pointer(thread_data: *mut thread) { - let _ = thread_data; // TODO(steed): Set thread-local pointer. - } - - #[cfg(target_arch = "x86")] - unsafe fn set_thread_pointer(thread_data: *mut thread) { - use linux; - - let mut user_desc = linux::user_desc { - entry_number: -1i32 as u32, - base_addr: thread_data as u32, - limit: 0xfffff, - flags: 0x51, - }; - let result = linux::set_thread_area(&mut user_desc); - if result < 0 { - panic!("set_thread_pointer: set_thread_area: {}", result); - } - asm!("mov $0,%gs"::"r"(((user_desc.entry_number << 3) | 3) as u16)); - } - - #[cfg(target_arch = "x86_64")] - unsafe fn set_thread_pointer(thread_data: *mut thread) { - use linux; - - let result = linux::arch_prctl(linux::ARCH_SET_FS, thread_data as c_ulong); - if result < 0 { - panic!("set_thread_pointer: arch_prctl: {}", result); - } - } - - pub unsafe fn init_main_thread(buffer: *mut Buffer) { - let buffer: *mut thread = &mut (*buffer).0; - *buffer = thread { - this: buffer, - thread_id: -1, - }; - set_thread_pointer(buffer); - } -} - -pub unsafe fn pthread_create(pthread: *mut pthread_t, - attr: *const pthread_attr_t, - start_routine: extern "C" fn(*mut c_void) -> *mut c_void, - arg: *mut c_void) - -> c_int -{ - let _ = attr; - let flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND - | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS - | CLONE_CHILD_CLEARTID | CLONE_PARENT_SETTID; - - let align = 16; - let mask = align - 1; - let stack_size = ((*attr).stack_size + mask) & !mask; - - let map = mmap(ptr::null_mut(), - stack_size + mem::size_of::(), - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, - -1, - 0); - - // musl: src/internal/__syscall_ret.c - if map as usize > -4096isize as usize { - return -(map as c_int); - } - - let stack = map.offset(stack_size as isize); - let thread = stack as *mut thread; - (*thread).this = thread; - - let child_tid = syscall_clone(start_routine, - stack, - flags, - arg, - &mut (*thread).thread_id, - thread as *mut c_void, - &mut (*thread).thread_id); - if child_tid < 0 { - return -child_tid; - } - *pthread = pthread_t { - thread: thread, - }; - 0 -} - -extern { - #[link_name = "__steed_clone"] - fn syscall_clone(fn_: extern "C" fn(*mut c_void) -> *mut c_void, - child_stack: *mut c_void, - flags: c_ulong, - arg: *mut c_void, - ptid: *mut pid_t, - newtls: *mut c_void, - ctid: *mut pid_t) -> pid_t; -} - -#[cfg(target_arch = "aarch64")] -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { - // Syscall number is passed in x8, syscall arguments in x0, x1, x2, x3, x4. - // The arguments are - // (flags: c_ulong, // x0 - // child_stack: *mut c_void, // x1 - // ptid: *mut c_int, // x2 - // newtls: c_ulong, // x3 - // ctid: *mut c_int) // x4 - // - // No registers are clobbered, x0 gets the return value. - // - // We do not clobber any registers, so we don't need to save any. - // - // The calling convention passes arguments int registers, from x0 to x6. - // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // x0 - // child_stack: *mut c_void, // x1 - // flags: c_ulong, // x2 - // arg: *mut c_void, // x3 - // ptid: *mut pid_t, // x4 - // newtls: *mut c_void, // x5 - // ctid: *mut pid_t) // x6 - // - // Both ABIs return the function result in x0. - // - // This means we need the following moves: - // x2 -> x0 // flags - // x1 -> x1 // child_stack - // x4 -> x2 // ptid - // x5 -> x3 // newtls - // x6 -> x4 // ctid - // - // We save `fn_` and `arg` on the child stack. - - asm!(" - // Align the child stack. - and x1,x1,#-16 - - // Save `fn_` and `arg` on the child stack. - stp x0,x3,[x1,#-16]! - - mov x8,#220 // CLONE - mov x0,x2 // flags - // child_stack - mov x2,x4 // ptid - mov x3,x5 // newtls - mov x4,x6 // ctid - svc #0 - - cbnz x0,__steed_clone_parent - - // Restore `fn_` and `arg`. - ldp x1,x0,[sp],#16 - blr x1 - - mov x8,#93 // EXIT - // status - svc #0 - - __steed_clone_parent: - "); -} - -#[cfg(target_arch = "arm")] -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { - // Syscall number is passed in r7, syscall arguments in r0, r1, r2, r3, r4. - // The arguments are - // (flags: c_ulong, // r0 - // child_stack: *mut c_void, // r1 - // ptid: *mut c_int, // r2 - // newtls: c_ulong, // r3 - // ctid: *mut c_int) // r4 - // - // No registers are clobbered, r0 gets the return value. - // - // Only r0, r1, r2, r3 are caller-saved, so we must restore the value of - // all other registers before returning. - // - // The calling convention passes the first four arguments in r0 to r4 and - // all further arguments on the stack, right to left. Since we push r4 to - // r7 onto the stack in the very beginning, all stack offsets are increased - // by 16. The arguments are - // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // r0 - // child_stack: *mut c_void, // r1 - // flags: c_ulong, // r2 - // arg: *mut c_void, // r3 - // ptid: *mut pid_t, // [sp,#16] - // newtls: *mut c_void, // [sp,#20] - // ctid: *mut pid_t) // [sp,#24] - // - // Both ABIs return the function result in r0. - // - // This means we need the following moves: - // r2 -> r0 // flags - // r1 -> r1 // child_stack - // [sp,#16] -> r2 // ptid - // [sp,#20] -> r3 // newtls - // [sp,#24] -> r4 // ctid - // - // We save `fn_` in r5, `arg` in r6. - - asm!(" - @ Save r4 to r7 - stmfd sp!,{r4,r5,r6,r7} - - mov r5,r0 @ fn_ - mov r6,r3 @ arg - - mov r7,#120 @ CLONE - mov r0,r2 @ flags - @ child_stack - ldr r2,[sp,#16] @ ptid - ldr r3,[sp,#20] @ newtls - ldr r4,[sp,#24] @ ctid - - and r1,r1,#-16 @ Align the stack - - @ Do the syscall - svc 0 - - @ CLONE returns 0 in the child thread, return if we're the parent. - tst r0,r0 - bne __steed_clone_parent - - mov r0,r6 @ arg - - @ Do we need to execute `fn_` in thumb mode? - tst r5,#1 - bne __steed_clone_thumb - - @ pc (Program Counter) is always 2 instructions ahead. - mov lr,pc - mov pc,r5 @ fn_ - - @ The function will return here. - __steed_clone_exit: - mov r7,#1 @ EXIT - @ status - svc 0 - - __steed_clone_thumb: - - @ Again, pc is 2 instructions ahead. - mov lr,pc - bx r5 @ Start thumb mode - b __steed_clone_exit - - __steed_clone_parent: - - @ Restore r4 to r7 - ldmfd sp!,{r4,r5,r6,r7} - "); -} - -#[cfg(target_arch = "x86")] -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { - // Syscall number is passed in %eax, syscall arguments in %ebx, %ecx, %edx, - // %esi, %edi. The arguments are - // (flags: c_ulong, // %ebx - // child_stack: *mut c_void, // %ecx - // ptid: *mut c_int, // %edx - // newtls: c_ulong, // %esi - // ctid: *mut c_int) // %edi - // - // No registers are clobbered, %eax gets the return value. - // - // Only %eax, %ecx and %edx are caller-saved, so we must restore the value - // of all other registers before returning. - // - // The cdecl calling convention passes arguments on the stack, right to - // left. Since we push %ebp onto the stack in the very beginning, all - // offsets are increased by 4. The arguments are - // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // 8(%ebp) - // child_stack: *mut c_void, // 12(%ebp) - // flags: c_ulong, // 16(%ebp) - // arg: *mut c_void, // 20(%ebp) - // ptid: *mut pid_t, // 24(%ebp) - // newtls: *mut c_void, // 28(%ebp) - // ctid: *mut pid_t) // 32(%ebp) - // - // Both ABIs return the function result in %eax. - // - // This means we need the following moves: - // 16(%ebp) -> %ebx // flags - // 12(%ebp) -> %ecx // child_stack - // 24(%ebp) -> %edx // ptid - // fancy -> %esi // newtls - // 32(%ebp) -> %edi // ctid - // - // We need to create a struct of type `struct user_desc` (see `clone(2)` - // and `set_thread_area(2)`) and store it in %esi. We do it by pushing it - // onto the parent stack. - // - // We save `fn_` in %ebp. - - asm!(" - # Stack frame - push %ebp - mov %esp,%ebp - - # Save registers - push %ebx - push %esi - push %edi - - mov 12(%ebp),%ecx # child_stack - and $$-16,%ecx # Align the stack - - # Push the parameter - sub $$16,%ecx # Keep the stack aligned - mov 20(%ebp),%edi # arg - mov %edi,(%ecx) - - # Construct a struct of type `user_desc` - - # Bitfield, according to glibc: - # seg_32bit:1 = 1 - # contents:2 = 0 - # read_exec_only:1 = 0 - # limit_in_pages:1 = 1 - # seg_not_present:1 = 0 - # useable:1 = 1 - push $$0x51 - push $$0xfffff # limit - push 28(%ebp) # base_addr - xor %eax,%eax - mov %gs,%ax - shr $$3,%eax - push %eax # entry_number - - mov $$120,%eax # CLONE - mov 16(%ebp),%ebx # flags - mov 24(%ebp),%edx # ptid - mov %esp,%esi # newtls - mov 32(%ebp),%edi # ctid - - mov 8(%ebp),%ebp # fn_ - - int $$0x80 - - # CLONE returns 0 in the child thread, return if we're the parent. - test %eax,%eax - jnz __steed_clone_parent - - mov %ebp,%eax # fn_ - - # Mark the lowest stack frame - xor %ebp,%ebp - - # arg is already on the stack - call *%eax - - mov %eax,%ebx # status - mov $$1,%eax # EXIT - int $$0x80 - hlt - - __steed_clone_parent: - - # Pop the struct - add $$16,%esp - - # Restore registers - pop %edi - pop %esi - pop %ebx - - # Stack frame - pop %ebp - "); -} - -#[cfg(target_arch = "x86_64")] -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { - // Syscall number is passed in %rax, syscall arguments in %rdi, %rsi, %rdx, - // %r10, %r8. The arguments are - // (flags: c_ulong, // %rdi - // child_stack: *mut c_void, // %rsi - // ptid: *mut c_int, // %rdx - // ctid: *mut c_int // %r10 - // newtls: c_ulong) // %r8 - // - // The registers %rcx and %r11 are clobbered, %rax gets the return value. - // - // The System V AMD64 ABI passes arguments in %rdi, %rsi, %rdx, %rcx, %r8, - // %r9, 8(%rsp). The arguments are - // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // %rdi - // child_stack: *mut c_void, // %rsi - // flags: c_ulong, // %rdx - // arg: *mut c_void, // %rcx - // ptid: *mut pid_t, // %r8 - // newtls: *mut c_void, // %r9 - // ctid: *mut pid_t) // 8(%rsp) - // - // Both ABIs return the function result in %rax. - // - // This means we need the following moves: - // %rdx -> %rdi // flags - // %rsi -> %rsi // child_stack - // %r8 -> %rdx // ptid - // 8(%rsp) -> %r10 // ctid - // %r9 -> %r8 // newtls - // - // And to save `fn_`, we need - // %rdi -> %r9 // fn_ - // - // There's a cycle in there (%rdx -> %rdi -> %r9 -> %r8 -> %rdx), we break - // it at %rdi -> %r9 and move it to the scratch register %r11 instead. - - asm!(" - and $$-16,%rsi # Align the child stack to 16 bytes. - - # Push `arg` onto the child stack. - sub $$8,%rsi - mov %rcx,(%rsi) - - # Temporarily store `fn_` - mov %rdi,%r11 - - mov $$56,%rax # CLONE - mov %rdx,%rdi # flags - # child_stack - mov %r8,%rdx # ptid - mov 8(%rsp),%r10 # ctid - mov %r9,%r8 # newtls - - mov %r11,%r9 # fn_ (not for the syscall) - - syscall - - # CLONE returns 0 in the child thread, return if we're the parent. - test %rax,%rax - jnz __steed_clone_parent - - # Mark the lowest stack frame - xor %rbp,%rbp - - pop %rdi # arg - call *%r9 # fn_ - - mov %rax,%rdi # status - mov $$60,%rax # EXIT - syscall - - # Unreachable. - hlt - - __steed_clone_parent: - "); -} - -/* -#[inline(always)] -#[cfg(target_arch = "x86_64")] -unsafe fn thread_self() -> *mut thread { - let result; - asm!("mov %fs:0,$0":"=r"(result)); - result -} - -#[inline(always)] -pub unsafe fn pthread_self() -> pthread_t { - pthread_t { - thread: thread_self(), - } -} - -pub unsafe fn pthread_detach(thread: pthread_t) -> c_int { - unimplemented!(); -} -*/ - -pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) - -> c_int -{ - assert!(retval.is_null()); - let thread = pthread.thread; - - let tmp = (*thread).thread_id; - if tmp == 0 { - return 0; - } - // TODO(steed): Why does FUTEX_WAIT_PRIVATE not work? - let res = linux::futex(&mut (*thread).thread_id as *mut _ as *mut u32, - linux::FUTEX_WAIT, - (*thread).thread_id as u32, - ptr::null(), - ptr::null_mut(), - 0); - if res == -EAGAIN { - return 0; - } - if res < 0 { - return -res; - } - 0 -} - -// Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs -#[allow(non_snake_case)] -pub fn WTERMSIG(status: c_int) -> c_int { - status & 0x7f -} - -// Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs -#[allow(non_snake_case)] -pub fn WIFEXITED(status: c_int) -> bool { - (status & 0x7f) == 0 -} - -// Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs -#[allow(non_snake_case)] -pub fn WEXITSTATUS(status: c_int) -> c_int { - (status >> 8) & 0xff -} diff --git a/src/libc/internal/aarch64.rs b/src/libc/internal/aarch64.rs new file mode 100644 index 000000000..5c5617159 --- /dev/null +++ b/src/libc/internal/aarch64.rs @@ -0,0 +1,68 @@ +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in x8, syscall arguments in x0, x1, x2, x3, x4. + // The arguments are + // (flags: c_ulong, // x0 + // child_stack: *mut c_void, // x1 + // ptid: *mut c_int, // x2 + // newtls: c_ulong, // x3 + // ctid: *mut c_int) // x4 + // + // No registers are clobbered, x0 gets the return value. + // + // We do not clobber any registers, so we don't need to save any. + // + // The calling convention passes arguments int registers, from x0 to x6. + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // x0 + // child_stack: *mut c_void, // x1 + // flags: c_ulong, // x2 + // arg: *mut c_void, // x3 + // ptid: *mut pid_t, // x4 + // newtls: *mut c_void, // x5 + // ctid: *mut pid_t) // x6 + // + // Both ABIs return the function result in x0. + // + // This means we need the following moves: + // x2 -> x0 // flags + // x1 -> x1 // child_stack + // x4 -> x2 // ptid + // x5 -> x3 // newtls + // x6 -> x4 // ctid + // + // We save `fn_` and `arg` on the child stack. + + asm!(" + // Align the child stack. + and x1,x1,#-16 + + // Save `fn_` and `arg` on the child stack. + stp x0,x3,[x1,#-16]! + + mov x8,#220 // CLONE + mov x0,x2 // flags + // child_stack + mov x2,x4 // ptid + mov x3,x5 // newtls + mov x4,x6 // ctid + svc #0 + + cbnz x0,__steed_clone_parent + + // Restore `fn_` and `arg`. + ldp x1,x0,[sp],#16 + blr x1 + + mov x8,#93 // EXIT + // status + svc #0 + + __steed_clone_parent: + "); +} + +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed): Set thread-local pointer. +} diff --git a/src/libc/internal/arm.rs b/src/libc/internal/arm.rs new file mode 100644 index 000000000..7b2e894dd --- /dev/null +++ b/src/libc/internal/arm.rs @@ -0,0 +1,96 @@ +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in r7, syscall arguments in r0, r1, r2, r3, r4. + // The arguments are + // (flags: c_ulong, // r0 + // child_stack: *mut c_void, // r1 + // ptid: *mut c_int, // r2 + // newtls: c_ulong, // r3 + // ctid: *mut c_int) // r4 + // + // No registers are clobbered, r0 gets the return value. + // + // Only r0, r1, r2, r3 are caller-saved, so we must restore the value of + // all other registers before returning. + // + // The calling convention passes the first four arguments in r0 to r4 and + // all further arguments on the stack, right to left. Since we push r4 to + // r7 onto the stack in the very beginning, all stack offsets are increased + // by 16. The arguments are + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // r0 + // child_stack: *mut c_void, // r1 + // flags: c_ulong, // r2 + // arg: *mut c_void, // r3 + // ptid: *mut pid_t, // [sp,#16] + // newtls: *mut c_void, // [sp,#20] + // ctid: *mut pid_t) // [sp,#24] + // + // Both ABIs return the function result in r0. + // + // This means we need the following moves: + // r2 -> r0 // flags + // r1 -> r1 // child_stack + // [sp,#16] -> r2 // ptid + // [sp,#20] -> r3 // newtls + // [sp,#24] -> r4 // ctid + // + // We save `fn_` in r5, `arg` in r6. + + asm!(" + @ Save r4 to r7 + stmfd sp!,{r4,r5,r6,r7} + + mov r5,r0 @ fn_ + mov r6,r3 @ arg + + mov r7,#120 @ CLONE + mov r0,r2 @ flags + @ child_stack + ldr r2,[sp,#16] @ ptid + ldr r3,[sp,#20] @ newtls + ldr r4,[sp,#24] @ ctid + + and r1,r1,#-16 @ Align the stack + + @ Do the syscall + svc 0 + + @ CLONE returns 0 in the child thread, return if we're the parent. + tst r0,r0 + bne __steed_clone_parent + + mov r0,r6 @ arg + + @ Do we need to execute `fn_` in thumb mode? + tst r5,#1 + bne __steed_clone_thumb + + @ pc (Program Counter) is always 2 instructions ahead. + mov lr,pc + mov pc,r5 @ fn_ + + @ The function will return here. + __steed_clone_exit: + mov r7,#1 @ EXIT + @ status + svc 0 + + __steed_clone_thumb: + + @ Again, pc is 2 instructions ahead. + mov lr,pc + bx r5 @ Start thumb mode + b __steed_clone_exit + + __steed_clone_parent: + + @ Restore r4 to r7 + ldmfd sp!,{r4,r5,r6,r7} + "); +} + +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed): Set thread-local pointer. +} diff --git a/src/libc/internal/mod.rs b/src/libc/internal/mod.rs new file mode 100644 index 000000000..8e525d165 --- /dev/null +++ b/src/libc/internal/mod.rs @@ -0,0 +1,23 @@ +#[cfg(target_arch = "aarch64")] #[path = "aarch64.rs"] mod arch; +#[cfg(target_arch = "arm")] #[path = "arm.rs"] mod arch; +#[cfg(target_arch = "mips")] #[path = "mips.rs"] mod arch; +#[cfg(target_arch = "mips64")] #[path = "mips64.rs"] mod arch; +#[cfg(target_arch = "powerpc")] #[path = "powerpc.rs"] mod arch; +#[cfg(target_arch = "powerpc64")] #[path = "powerpc64.rs"] mod arch; +#[cfg(target_arch = "sparc64")] #[path = "sparc64.rs"] mod arch; +#[cfg(target_arch = "x86")] #[path = "x86.rs"] mod arch; +#[cfg(target_arch = "x86_64")] #[path = "x86_64.rs"] mod arch; + +use super::*; +use self::arch::*; + +pub struct Buffer(thread); + +pub unsafe fn init_main_thread(buffer: *mut Buffer) { + let buffer: *mut thread = &mut (*buffer).0; + *buffer = thread { + this: buffer, + thread_id: -1, + }; + set_thread_pointer(buffer as *mut _); +} diff --git a/src/libc/internal/x86.rs b/src/libc/internal/x86.rs new file mode 100644 index 000000000..84edd7c17 --- /dev/null +++ b/src/libc/internal/x86.rs @@ -0,0 +1,136 @@ +use linux; + +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in %eax, syscall arguments in %ebx, %ecx, %edx, + // %esi, %edi. The arguments are + // (flags: c_ulong, // %ebx + // child_stack: *mut c_void, // %ecx + // ptid: *mut c_int, // %edx + // newtls: c_ulong, // %esi + // ctid: *mut c_int) // %edi + // + // No registers are clobbered, %eax gets the return value. + // + // Only %eax, %ecx and %edx are caller-saved, so we must restore the value + // of all other registers before returning. + // + // The cdecl calling convention passes arguments on the stack, right to + // left. Since we push %ebp onto the stack in the very beginning, all + // offsets are increased by 4. The arguments are + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // 8(%ebp) + // child_stack: *mut c_void, // 12(%ebp) + // flags: c_ulong, // 16(%ebp) + // arg: *mut c_void, // 20(%ebp) + // ptid: *mut pid_t, // 24(%ebp) + // newtls: *mut c_void, // 28(%ebp) + // ctid: *mut pid_t) // 32(%ebp) + // + // Both ABIs return the function result in %eax. + // + // This means we need the following moves: + // 16(%ebp) -> %ebx // flags + // 12(%ebp) -> %ecx // child_stack + // 24(%ebp) -> %edx // ptid + // fancy -> %esi // newtls + // 32(%ebp) -> %edi // ctid + // + // We need to create a struct of type `struct user_desc` (see `clone(2)` + // and `set_thread_area(2)`) and store it in %esi. We do it by pushing it + // onto the parent stack. + // + // We save `fn_` in %ebp. + + asm!(" + # Stack frame + push %ebp + mov %esp,%ebp + + # Save registers + push %ebx + push %esi + push %edi + + mov 12(%ebp),%ecx # child_stack + and $$-16,%ecx # Align the stack + + # Push the parameter + sub $$16,%ecx # Keep the stack aligned + mov 20(%ebp),%edi # arg + mov %edi,(%ecx) + + # Construct a struct of type `user_desc` + + # Bitfield, according to glibc: + # seg_32bit:1 = 1 + # contents:2 = 0 + # read_exec_only:1 = 0 + # limit_in_pages:1 = 1 + # seg_not_present:1 = 0 + # useable:1 = 1 + push $$0x51 + push $$0xfffff # limit + push 28(%ebp) # base_addr + xor %eax,%eax + mov %gs,%ax + shr $$3,%eax + push %eax # entry_number + + mov $$120,%eax # CLONE + mov 16(%ebp),%ebx # flags + mov 24(%ebp),%edx # ptid + mov %esp,%esi # newtls + mov 32(%ebp),%edi # ctid + + mov 8(%ebp),%ebp # fn_ + + int $$0x80 + + # CLONE returns 0 in the child thread, return if we're the parent. + test %eax,%eax + jnz __steed_clone_parent + + mov %ebp,%eax # fn_ + + # Mark the lowest stack frame + xor %ebp,%ebp + + # arg is already on the stack + call *%eax + + mov %eax,%ebx # status + mov $$1,%eax # EXIT + int $$0x80 + hlt + + __steed_clone_parent: + + # Pop the struct + add $$16,%esp + + # Restore registers + pop %edi + pop %esi + pop %ebx + + # Stack frame + pop %ebp + "); +} + +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let mut user_desc = linux::user_desc { + entry_number: -1i32 as u32, + base_addr: thread_data as u32, + limit: 0xfffff, + flags: 0x51, + }; + let result = linux::set_thread_area(&mut user_desc); + if result < 0 { + panic!("set_thread_pointer: set_thread_area: {}", result); + } + asm!("mov $0,%gs"::"r"(((user_desc.entry_number << 3) | 3) as u16)); +} + diff --git a/src/libc/internal/x86_64.rs b/src/libc/internal/x86_64.rs new file mode 100644 index 000000000..1e0d25103 --- /dev/null +++ b/src/libc/internal/x86_64.rs @@ -0,0 +1,90 @@ +use libc::*; +use linux; + +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in %rax, syscall arguments in %rdi, %rsi, %rdx, + // %r10, %r8. The arguments are + // (flags: c_ulong, // %rdi + // child_stack: *mut c_void, // %rsi + // ptid: *mut c_int, // %rdx + // ctid: *mut c_int // %r10 + // newtls: c_ulong) // %r8 + // + // The registers %rcx and %r11 are clobbered, %rax gets the return value. + // + // The System V AMD64 ABI passes arguments in %rdi, %rsi, %rdx, %rcx, %r8, + // %r9, 8(%rsp). The arguments are + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // %rdi + // child_stack: *mut c_void, // %rsi + // flags: c_ulong, // %rdx + // arg: *mut c_void, // %rcx + // ptid: *mut pid_t, // %r8 + // newtls: *mut c_void, // %r9 + // ctid: *mut pid_t) // 8(%rsp) + // + // Both ABIs return the function result in %rax. + // + // This means we need the following moves: + // %rdx -> %rdi // flags + // %rsi -> %rsi // child_stack + // %r8 -> %rdx // ptid + // 8(%rsp) -> %r10 // ctid + // %r9 -> %r8 // newtls + // + // And to save `fn_`, we need + // %rdi -> %r9 // fn_ + // + // There's a cycle in there (%rdx -> %rdi -> %r9 -> %r8 -> %rdx), we break + // it at %rdi -> %r9 and move it to the scratch register %r11 instead. + + asm!(" + and $$-16,%rsi # Align the child stack to 16 bytes. + + # Push `arg` onto the child stack. + sub $$8,%rsi + mov %rcx,(%rsi) + + # Temporarily store `fn_` + mov %rdi,%r11 + + mov $$56,%rax # CLONE + mov %rdx,%rdi # flags + # child_stack + mov %r8,%rdx # ptid + mov 8(%rsp),%r10 # ctid + mov %r9,%r8 # newtls + + mov %r11,%r9 # fn_ (not for the syscall) + + syscall + + # CLONE returns 0 in the child thread, return if we're the parent. + test %rax,%rax + jnz __steed_clone_parent + + # Mark the lowest stack frame + xor %rbp,%rbp + + pop %rdi # arg + call *%r9 # fn_ + + mov %rax,%rdi # status + mov $$60,%rax # EXIT + syscall + + # Unreachable. + hlt + + __steed_clone_parent: + "); +} + +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let result = linux::arch_prctl(linux::ARCH_SET_FS, thread_data as c_ulong); + if result < 0 { + panic!("set_thread_pointer: arch_prctl: {}", result); + } +} diff --git a/src/libc/mod.rs b/src/libc/mod.rs new file mode 100644 index 000000000..0f7647aad --- /dev/null +++ b/src/libc/mod.rs @@ -0,0 +1,323 @@ +#![allow(non_camel_case_types)] + +use cmp; +use linux; +use mem; +use ptr; + +pub mod internal; + +pub use ctypes::*; +pub use linux::errno::*; + +pub use linux::{gid_t, in_addr, in6_addr, ip_mreq, ipv6_mreq, off_t, off64_t}; +pub use linux::{pid_t, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6}; +pub use linux::{sockaddr_storage, sockaddr_un, socklen_t, stat64, suseconds_t}; +pub use linux::{time_t, timespec, timeval, uid_t}; + +pub use linux::{AF_INET, AF_INET6, AF_UNIX}; +pub use linux::{CLONE_CHILD_CLEARTID, CLONE_FILES, CLONE_FS}; +pub use linux::{CLONE_PARENT_SETTID, CLONE_SETTLS, CLONE_SIGHAND}; +pub use linux::{CLONE_SYSVSEM, CLONE_THREAD, CLONE_VM}; +pub use linux::{DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK}; +pub use linux::{F_DUPFD_CLOEXEC, F_DUPFD, F_GETFL, F_SETFL}; +pub use linux::{FIOCLEX, FIONBIO}; +pub use linux::{IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_MULTICAST_LOOP}; +pub use linux::{IP_MULTICAST_TTL, IP_TTL}; +pub use linux::{IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP}; +pub use linux::{IPV6_MULTICAST_LOOP, IPV6_V6ONLY}; +pub use linux::{IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP}; +pub use linux::{MAP_ANONYMOUS, MAP_PRIVATE}; +pub use linux::{MSG_NOSIGNAL}; +pub use linux::{O_ACCMODE, O_APPEND, O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL}; +pub use linux::{O_LARGEFILE, O_NONBLOCK, O_PATH, O_RDONLY, O_RDWR, O_TRUNC}; +pub use linux::{O_WRONLY}; +pub use linux::{PROT_READ, PROT_WRITE}; +pub use linux::{S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFDIR, S_IFCHR}; +pub use linux::{S_IFIFO}; +pub use linux::{SHUT_RD, SHUT_RDWR, SHUT_WR}; +pub use linux::{SO_BROADCAST, SO_ERROR, SO_RCVTIMEO, SO_REUSEADDR}; +pub use linux::{SO_SNDTIMEO}; +pub use linux::{SOCK_CLOEXEC, SOCK_DGRAM, SOCK_STREAM}; +pub use linux::{SOL_SOCKET}; +pub use linux::{SEEK_CUR, SEEK_END, SEEK_SET}; +pub use linux::{TCP_NODELAY}; + +pub use linux::{accept, accept4, bind, close, connect, fdatasync, fstat64}; +pub use linux::{fsync, ftruncate64, getpeername, getsockname, getsockopt}; +pub use linux::{ioctl, link, listen, lstat64, mmap, nanosleep, prctl, pread64}; +pub use linux::{pwrite64, read, recvfrom, rename, rmdir, sched_yield, send}; +pub use linux::{sendto, setsockopt, socket, socketpair, shutdown, symlink}; +pub use linux::{unlink, write}; + +pub type mode_t = u32; + +// Rust 1.15.0 +// src/liblibc/src/unix/notbsd/linux/mod.rs +#[cfg(issue = "22")] +pub const EAI_SYSTEM: c_int = -11; + +// Rust 1.15.0 +// src/liblibc/src/unix/notbsd/linux/musl/mod.rs +pub const PTHREAD_STACK_MIN: size_t = 2048; + +pub const MAP_ANON: c_int = MAP_ANONYMOUS; + +pub unsafe fn strlen(cs: *const c_char) -> size_t { + let mut cs = cs; + let mut count = 0; + while *cs != 0 { + cs = cs.offset(1); + count += 1; + } + count +} + +#[inline(always)] +pub unsafe fn fcntl(fd: c_int, cmd: c_uint, arg: c_int) -> c_int { + linux::fcntl(fd, cmd, arg as c_ulong) as c_int +} + +#[inline(always)] +pub unsafe fn open64(pathname: *const c_char, flags: c_int, mode: c_int) -> c_int { + linux::open(pathname, flags | O_LARGEFILE, mode as linux::umode_t) +} + +#[inline(always)] +pub unsafe fn readlink(pathname: *const c_char, + buf: *mut c_char, + bufsiz: size_t) + -> ssize_t +{ + linux::readlink(pathname, + buf, + cmp::min(bufsiz, ::max_value() as size_t) as c_int) +} + +#[inline(always)] +pub unsafe fn fchmod(fd: c_int, mode: mode_t) -> c_int { + linux::fchmod(fd, mode as linux::umode_t) +} + +#[inline(always)] +pub unsafe fn chmod(filename: *const c_char, mode: mode_t) -> c_int { + linux::chmod(filename, mode as linux::umode_t) +} + +#[inline(always)] +pub unsafe fn mkdir(pathname: *const c_char, mode: mode_t) -> c_int { + linux::mkdir(pathname, mode as linux::umode_t) +} + +#[inline(always)] +pub unsafe fn lseek64(fd: c_int, offset: off64_t, whence: c_uint) -> off64_t { + let mut result_offset: off64_t = 0; + let result = linux::_llseek(fd, offset, &mut result_offset, whence); + if result >= 0 { + result_offset + } else { + result as off64_t + } +} + +#[derive(Clone, Copy)] +pub struct pthread_attr_t { + stack_size: usize, +} + +struct thread { + // Required, because one cannot easily read out register containing the + // pointer to this structure on some platforms. + this: *mut thread, + thread_id: pid_t, +} + +#[derive(Clone, Copy)] +pub struct pthread_t { + thread: *mut thread, +} + +pub unsafe fn pthread_attr_init(attr: *mut pthread_attr_t) -> c_int { + *attr = pthread_attr_t { + stack_size: 0, + }; + 0 +} + +pub unsafe fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> c_int { + pthread_attr_init(attr) +} + +pub unsafe fn pthread_attr_setstacksize(attr: *mut pthread_attr_t, + stacksize: size_t) + -> c_int +{ + (*attr).stack_size = stacksize; + 0 +} + +/* +pub unsafe fn pthread_attr_getstack(attr: *const pthread_attr_t, + stackaddr: *mut *mut c_void, + stacksize: *mut size_t) + -> c_int +{ + *stackaddr = ptr::null_mut(); + *stacksize = (*attr).stack_size; + 0 +} + +pub unsafe fn pthread_attr_getguardsize(attr: *const pthread_attr_t, + guardsize: *mut size_t) + -> c_int +{ + *guardsize = 0; + 0 +} + +pub unsafe fn pthread_getattr_np(pthread: pthread_t, + attr: *mut pthread_attr_t) + -> c_int +{ + pthread_attr_init(attr) +} +*/ + +pub unsafe fn pthread_create(pthread: *mut pthread_t, + attr: *const pthread_attr_t, + start_routine: extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void) + -> c_int +{ + let _ = attr; + let flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND + | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS + | CLONE_CHILD_CLEARTID | CLONE_PARENT_SETTID; + + let align = 16; + let mask = align - 1; + let stack_size = ((*attr).stack_size + mask) & !mask; + + let map = mmap(ptr::null_mut(), + stack_size + mem::size_of::(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0); + + // musl: src/internal/__syscall_ret.c + if map as usize > -4096isize as usize { + return -(map as c_int); + } + + let stack = map.offset(stack_size as isize); + let thread = stack as *mut thread; + (*thread).this = thread; + + let child_tid = syscall_clone(start_routine, + stack, + flags, + arg, + &mut (*thread).thread_id, + thread as *mut c_void, + &mut (*thread).thread_id); + if child_tid < 0 { + return -child_tid; + } + *pthread = pthread_t { + thread: thread, + }; + 0 +} + +extern { + // Defined in internal/.rs. + // + // Does the equivalent of + // + // ``` + // let result = CLONE(flags, child_stack, ptid, ctid, newtls); + // if result == 0 { + // EXIT(fn_(arg)); + // } else { + // result + // } + // ``` + // + // where CLONE and EXIT are the respective syscalls. Because it mangles + // with the stack (in two processes at once), it needs to be written in the + // target's assembly. + #[link_name = "__steed_clone"] + fn syscall_clone(fn_: extern "C" fn(*mut c_void) -> *mut c_void, + child_stack: *mut c_void, + flags: c_ulong, + arg: *mut c_void, + ptid: *mut pid_t, + newtls: *mut c_void, + ctid: *mut pid_t) -> pid_t; +} + +/* +#[inline(always)] +#[cfg(target_arch = "x86_64")] +unsafe fn thread_self() -> *mut thread { + let result; + asm!("mov %fs:0,$0":"=r"(result)); + result +} + +#[inline(always)] +pub unsafe fn pthread_self() -> pthread_t { + pthread_t { + thread: thread_self(), + } +} + +pub unsafe fn pthread_detach(thread: pthread_t) -> c_int { + unimplemented!(); +} +*/ + +pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) + -> c_int +{ + assert!(retval.is_null()); + let thread = pthread.thread; + + let tmp = (*thread).thread_id; + if tmp == 0 { + return 0; + } + // TODO(steed): Why does FUTEX_WAIT_PRIVATE not work? + let res = linux::futex(&mut (*thread).thread_id as *mut _ as *mut u32, + linux::FUTEX_WAIT, + (*thread).thread_id as u32, + ptr::null(), + ptr::null_mut(), + 0); + if res == -EAGAIN { + return 0; + } + if res < 0 { + return -res; + } + 0 +} + +// Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs +#[allow(non_snake_case)] +pub fn WTERMSIG(status: c_int) -> c_int { + status & 0x7f +} + +// Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs +#[allow(non_snake_case)] +pub fn WIFEXITED(status: c_int) -> bool { + (status & 0x7f) == 0 +} + +// Rust 1.15.0: src/liblibc/src/unix/notbsd/mod.rs +#[allow(non_snake_case)] +pub fn WEXITSTATUS(status: c_int) -> c_int { + (status >> 8) & 0xff +} From 83f4b8f879e4c5d4714c26d501e3faa3a0f651e2 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sun, 26 Feb 2017 00:30:47 +0100 Subject: [PATCH 06/12] Use some kind of anonymous labels in assembly --- src/libc/internal/aarch64.rs | 6 +++--- src/libc/internal/arm.rs | 10 +++++----- src/libc/internal/x86.rs | 4 ++-- src/libc/internal/x86_64.rs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libc/internal/aarch64.rs b/src/libc/internal/aarch64.rs index 5c5617159..262db0da5 100644 --- a/src/libc/internal/aarch64.rs +++ b/src/libc/internal/aarch64.rs @@ -14,7 +14,7 @@ unsafe extern "C" fn __steed_clone() { // // We do not clobber any registers, so we don't need to save any. // - // The calling convention passes arguments int registers, from x0 to x6. + // The calling convention passes arguments in registers, from x0 to x6. // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // x0 // child_stack: *mut c_void, // x1 // flags: c_ulong, // x2 @@ -49,7 +49,7 @@ unsafe extern "C" fn __steed_clone() { mov x4,x6 // ctid svc #0 - cbnz x0,__steed_clone_parent + cbnz x0,1f // Restore `fn_` and `arg`. ldp x1,x0,[sp],#16 @@ -59,7 +59,7 @@ unsafe extern "C" fn __steed_clone() { // status svc #0 - __steed_clone_parent: + 1: "); } diff --git a/src/libc/internal/arm.rs b/src/libc/internal/arm.rs index 7b2e894dd..977b312e7 100644 --- a/src/libc/internal/arm.rs +++ b/src/libc/internal/arm.rs @@ -59,13 +59,13 @@ unsafe extern "C" fn __steed_clone() { @ CLONE returns 0 in the child thread, return if we're the parent. tst r0,r0 - bne __steed_clone_parent + bne 3f mov r0,r6 @ arg @ Do we need to execute `fn_` in thumb mode? tst r5,#1 - bne __steed_clone_thumb + bne 2f @ pc (Program Counter) is always 2 instructions ahead. mov lr,pc @@ -77,14 +77,14 @@ unsafe extern "C" fn __steed_clone() { @ status svc 0 - __steed_clone_thumb: + 2: @ Again, pc is 2 instructions ahead. mov lr,pc bx r5 @ Start thumb mode - b __steed_clone_exit + b 2b - __steed_clone_parent: + 3: @ Restore r4 to r7 ldmfd sp!,{r4,r5,r6,r7} diff --git a/src/libc/internal/x86.rs b/src/libc/internal/x86.rs index 84edd7c17..7d2a96272 100644 --- a/src/libc/internal/x86.rs +++ b/src/libc/internal/x86.rs @@ -90,7 +90,7 @@ unsafe extern "C" fn __steed_clone() { # CLONE returns 0 in the child thread, return if we're the parent. test %eax,%eax - jnz __steed_clone_parent + jnz 1f mov %ebp,%eax # fn_ @@ -105,7 +105,7 @@ unsafe extern "C" fn __steed_clone() { int $$0x80 hlt - __steed_clone_parent: + 1: # Pop the struct add $$16,%esp diff --git a/src/libc/internal/x86_64.rs b/src/libc/internal/x86_64.rs index 1e0d25103..1cebc4b14 100644 --- a/src/libc/internal/x86_64.rs +++ b/src/libc/internal/x86_64.rs @@ -63,7 +63,7 @@ unsafe extern "C" fn __steed_clone() { # CLONE returns 0 in the child thread, return if we're the parent. test %rax,%rax - jnz __steed_clone_parent + jnz 1f # Mark the lowest stack frame xor %rbp,%rbp @@ -78,7 +78,7 @@ unsafe extern "C" fn __steed_clone() { # Unreachable. hlt - __steed_clone_parent: + 1: "); } From 46743c685d29cc5b508899c2ef2c8845ae951515 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sun, 26 Feb 2017 00:38:22 +0100 Subject: [PATCH 07/12] Add PowerPC support for threads --- src/libc/internal/powerpc.rs | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/libc/internal/powerpc.rs diff --git a/src/libc/internal/powerpc.rs b/src/libc/internal/powerpc.rs new file mode 100644 index 000000000..5ea122d00 --- /dev/null +++ b/src/libc/internal/powerpc.rs @@ -0,0 +1,90 @@ +#[inline(never)] +#[naked] +#[no_mangle] +unsafe extern "C" fn __steed_clone() { + // Syscall number is passed in r0, syscall arguments in r3, r4, r5, r6, r7. + // The arguments are + // (flags: c_ulong, // r3 + // child_stack: *mut c_void, // r4 + // ptid: *mut c_int, // r5 + // newtls: c_ulong, // r6 + // ctid: *mut c_int) // r7 + // + // No registers are clobbered, r3 gets the return value, the error flag + // goes into r0. + // + // We do not clobber any registers, so we don't need to save any. + // + // The calling convention passes arguments int registers, from r3 to r9. + // (fn_: extern "C" fn(*mut c_void) -> *mut c_void, // r3 + // child_stack: *mut c_void, // r4 + // flags: c_ulong, // r5 + // arg: *mut c_void, // r6 + // ptid: *mut pid_t, // r7 + // newtls: *mut c_void, // r8 + // ctid: *mut pid_t) // r9 + // + // Both ABIs return the function result in r3. + // + // This means we need the following moves: + // r5 -> r3 // flags + // r4 -> r4 // child_stack + // r7 -> r5 // ptid + // r8 -> r6 // newtls + // r9 -> r7 // ctid + // + // We save `fn_` and `arg` in r30 and r31 which we need to save first. + + asm!(" + # Save `fn_` and `arg` (while keeping the stack aligned). + stwu 30,-16(1) + stw 31,4(1) + mr 30,3 # fn_ + mr 31,6 # arg + + # Align the stack. + clrrwi 4,4,4 # Store in r4, load from r4, clear last 4 bits + # Mark the lowest stack frame. + li 0,0 + stwu 0,-16(4) + + li 0,120 # CLONE + mr 3,5 # flags + # child_stack + mr 5,7 # ptid + mr 6,8 # newtls + mr 7,9 # ctid + sc + + # PowerPC errno post-processing + bso- 1f + + # CLONE returns 0 in child, return if we're parent. + cmpwi cr7,3,0 + bne cr7,2f + + # We're the child. + mr 3,31 # arg + mtctr 30 # fn_ into the ctr register + bctrl # Call fn_ + + li 0,1 # SYS_EXIT + # status + sc + + # Parent + # Negate result if error occured. + 1: + neg 3,3 + 2: + + # Restore saved registers + lwz 30,0(1) + lwz 31,4(1) + addi 1,1,16 + "); +} + +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed): Set thread-local pointer. +} From 360456a2ca8540729a53fc199a12cd7b6fcbb467 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Fri, 14 Apr 2017 23:26:52 +0200 Subject: [PATCH 08/12] Fix typo --- src/libc/internal/arm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libc/internal/arm.rs b/src/libc/internal/arm.rs index 977b312e7..38f08be10 100644 --- a/src/libc/internal/arm.rs +++ b/src/libc/internal/arm.rs @@ -15,7 +15,7 @@ unsafe extern "C" fn __steed_clone() { // Only r0, r1, r2, r3 are caller-saved, so we must restore the value of // all other registers before returning. // - // The calling convention passes the first four arguments in r0 to r4 and + // The calling convention passes the first four arguments in r0 to r3 and // all further arguments on the stack, right to left. Since we push r4 to // r7 onto the stack in the very beginning, all stack offsets are increased // by 16. The arguments are From 9b9e4527aaad49f3b4ae6b00d3204426ebfa4e7a Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sat, 15 Apr 2017 10:00:54 +0200 Subject: [PATCH 09/12] Address pull request comments, add TODOs and issues --- Cargo.toml | 2 +- src/libc/internal/aarch64.rs | 2 +- src/libc/internal/arm.rs | 2 +- src/libc/internal/powerpc.rs | 2 +- src/libc/internal/x86.rs | 2 ++ src/libc/mod.rs | 4 +++- src/sys/linux/os.rs | 1 + src/sys/linux/stack_overflow.rs | 1 + src/sys/linux/thread.rs | 1 + src/thread/mod.rs | 2 ++ 10 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54672622e..cabe7ec6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ name = "std" version = "0.1.0" [dependencies] -sc = { git = "git://github.com/japaric/syscall.rs" } +sc = "0.2.0" [dependencies.ralloc] default-features = false diff --git a/src/libc/internal/aarch64.rs b/src/libc/internal/aarch64.rs index 262db0da5..66489cfd5 100644 --- a/src/libc/internal/aarch64.rs +++ b/src/libc/internal/aarch64.rs @@ -64,5 +64,5 @@ unsafe extern "C" fn __steed_clone() { } pub unsafe fn set_thread_pointer(thread_data: *mut ()) { - let _ = thread_data; // TODO(steed): Set thread-local pointer. + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. } diff --git a/src/libc/internal/arm.rs b/src/libc/internal/arm.rs index 38f08be10..3b2db6d2c 100644 --- a/src/libc/internal/arm.rs +++ b/src/libc/internal/arm.rs @@ -92,5 +92,5 @@ unsafe extern "C" fn __steed_clone() { } pub unsafe fn set_thread_pointer(thread_data: *mut ()) { - let _ = thread_data; // TODO(steed): Set thread-local pointer. + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. } diff --git a/src/libc/internal/powerpc.rs b/src/libc/internal/powerpc.rs index 5ea122d00..69d6a6d47 100644 --- a/src/libc/internal/powerpc.rs +++ b/src/libc/internal/powerpc.rs @@ -86,5 +86,5 @@ unsafe extern "C" fn __steed_clone() { } pub unsafe fn set_thread_pointer(thread_data: *mut ()) { - let _ = thread_data; // TODO(steed): Set thread-local pointer. + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. } diff --git a/src/libc/internal/x86.rs b/src/libc/internal/x86.rs index 7d2a96272..5e43a4458 100644 --- a/src/libc/internal/x86.rs +++ b/src/libc/internal/x86.rs @@ -125,6 +125,8 @@ pub unsafe fn set_thread_pointer(thread_data: *mut ()) { entry_number: -1i32 as u32, base_addr: thread_data as u32, limit: 0xfffff, + // This `flags` value is explained in the `asm!` block of + // `__steed_clone` above. flags: 0x51, }; let result = linux::set_thread_area(&mut user_desc); diff --git a/src/libc/mod.rs b/src/libc/mod.rs index 0f7647aad..69d108bef 100644 --- a/src/libc/mod.rs +++ b/src/libc/mod.rs @@ -198,6 +198,8 @@ pub unsafe fn pthread_create(pthread: *mut pthread_t, let mask = align - 1; let stack_size = ((*attr).stack_size + mask) & !mask; + // TODO(steed, #131): Make sure this is freed on normal and abnormal thread + // exit. let map = mmap(ptr::null_mut(), stack_size + mem::size_of::(), PROT_READ | PROT_WRITE, @@ -288,7 +290,7 @@ pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) if tmp == 0 { return 0; } - // TODO(steed): Why does FUTEX_WAIT_PRIVATE not work? + // TODO(steed, #130): Why does FUTEX_WAIT_PRIVATE not work? let res = linux::futex(&mut (*thread).thread_id as *mut _ as *mut u32, linux::FUTEX_WAIT, (*thread).thread_id as u32, diff --git a/src/sys/linux/os.rs b/src/sys/linux/os.rs index 96c6d84db..2bee0386d 100644 --- a/src/sys/linux/os.rs +++ b/src/sys/linux/os.rs @@ -16,6 +16,7 @@ pub fn exit(code: i32) -> ! { } pub fn page_size() -> usize { + // TODO(steed, #133): Implement me. unimplemented!(); } diff --git a/src/sys/linux/stack_overflow.rs b/src/sys/linux/stack_overflow.rs index 38469e4e5..de21bb640 100644 --- a/src/sys/linux/stack_overflow.rs +++ b/src/sys/linux/stack_overflow.rs @@ -2,6 +2,7 @@ pub struct Handler(()); impl Handler { pub unsafe fn new() -> Handler { + // TODO(steed, #132): Implement a stack overflow handler. Handler(()) } } diff --git a/src/sys/linux/thread.rs b/src/sys/linux/thread.rs index aab07641b..88dfd34ef 100644 --- a/src/sys/linux/thread.rs +++ b/src/sys/linux/thread.rs @@ -183,6 +183,7 @@ impl Thread { */ } +// TODO(steed, #134): Implement `Drop` for `Thread`. /* impl Drop for Thread { fn drop(&mut self) { diff --git a/src/thread/mod.rs b/src/thread/mod.rs index 6308a2819..9223328ed 100644 --- a/src/thread/mod.rs +++ b/src/thread/mod.rs @@ -60,6 +60,7 @@ impl Builder { imp::Thread::set_name(name); } unsafe { + // TODO(steed, #128): Add guard page to new threads. /* thread_info::set(imp::guard::current(), their_thread); let try_result = panic::catch_unwind(panic::AssertUnwindSafe(f)); @@ -110,6 +111,7 @@ pub struct ThreadId(u64); impl ThreadId { // Generate a new unique thread ID. fn new() -> ThreadId { + // TODO(steed, #129): Use `std`s implementation (commented out below). /* static GUARD: mutex::Mutex = mutex::Mutex::new(); static mut COUNTER: u64 = 0; From ee4292d2c38a9f12196c692f9f83e7d597f56015 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sat, 15 Apr 2017 16:38:19 +0200 Subject: [PATCH 10/12] Mark thread stuff as `unimplemented!()` on unsupported platforms --- src/libc/internal/mips.rs | 3 ++ src/libc/internal/mips64.rs | 3 ++ src/libc/internal/powerpc64.rs | 3 ++ src/libc/internal/sparc64.rs | 3 ++ src/libc/mod.rs | 69 ++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+) create mode 100644 src/libc/internal/mips.rs create mode 100644 src/libc/internal/mips64.rs create mode 100644 src/libc/internal/powerpc64.rs create mode 100644 src/libc/internal/sparc64.rs diff --git a/src/libc/internal/mips.rs b/src/libc/internal/mips.rs new file mode 100644 index 000000000..baf172305 --- /dev/null +++ b/src/libc/internal/mips.rs @@ -0,0 +1,3 @@ +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. +} diff --git a/src/libc/internal/mips64.rs b/src/libc/internal/mips64.rs new file mode 100644 index 000000000..baf172305 --- /dev/null +++ b/src/libc/internal/mips64.rs @@ -0,0 +1,3 @@ +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. +} diff --git a/src/libc/internal/powerpc64.rs b/src/libc/internal/powerpc64.rs new file mode 100644 index 000000000..baf172305 --- /dev/null +++ b/src/libc/internal/powerpc64.rs @@ -0,0 +1,3 @@ +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. +} diff --git a/src/libc/internal/sparc64.rs b/src/libc/internal/sparc64.rs new file mode 100644 index 000000000..baf172305 --- /dev/null +++ b/src/libc/internal/sparc64.rs @@ -0,0 +1,3 @@ +pub unsafe fn set_thread_pointer(thread_data: *mut ()) { + let _ = thread_data; // TODO(steed, #127): Set thread-local pointer. +} diff --git a/src/libc/mod.rs b/src/libc/mod.rs index 69d108bef..81edda62d 100644 --- a/src/libc/mod.rs +++ b/src/libc/mod.rs @@ -1,5 +1,12 @@ #![allow(non_camel_case_types)] +#![cfg_attr(not(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64")), + allow(unused))] + use cmp; use linux; use mem; @@ -183,6 +190,36 @@ pub unsafe fn pthread_getattr_np(pthread: pthread_t, } */ +#[cfg(not(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64")))] +pub unsafe fn pthread_create(pthread: *mut pthread_t, + attr: *const pthread_attr_t, + start_routine: extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void) + -> c_int +{ + if false { + let flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND + | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS + | CLONE_CHILD_CLEARTID | CLONE_PARENT_SETTID; + mmap(ptr::null_mut(), 0, PROT_READ | PROT_WRITE, MAP_PRIVATE | + MAP_ANON, -1, 0); + } + unimplemented!(); +} +// Heavily simplified (i.e. less features) version of pthread_create. +// musl: src/thread/pthread_create.c +// +// Doesn't care about signals, thread-local storage and perhaps other things +// yet. +#[cfg(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64"))] pub unsafe fn pthread_create(pthread: *mut pthread_t, attr: *const pthread_attr_t, start_routine: extern "C" fn(*mut c_void) -> *mut c_void, @@ -232,6 +269,11 @@ pub unsafe fn pthread_create(pthread: *mut pthread_t, 0 } +#[cfg(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64"))] extern { // Defined in internal/.rs. // @@ -280,6 +322,31 @@ pub unsafe fn pthread_detach(thread: pthread_t) -> c_int { } */ +#[cfg(not(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64")))] +pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) + -> c_int +{ + if false { + let thread = pthread.thread; + linux::futex(&mut (*thread).thread_id as *mut _ as *mut u32, + linux::FUTEX_WAIT, + (*thread).thread_id as u32, + ptr::null(), + ptr::null_mut(), + 0); + } + unimplemented!(); +} + +#[cfg(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64"))] pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) -> c_int { @@ -287,6 +354,8 @@ pub unsafe fn pthread_join(pthread: pthread_t, retval: *mut *mut c_void) let thread = pthread.thread; let tmp = (*thread).thread_id; + // 0 would mean that the thread has exited already (CLONE_CHILD_CLEARTID + // flag on the clone syscall). if tmp == 0 { return 0; } From 3e66df693172813c3fd1ae95daa0a1abab313ca1 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Sat, 15 Apr 2017 16:45:10 +0200 Subject: [PATCH 11/12] Add `thread` test to CI, on supported platforms --- ci/script.sh | 1 + examples/thread.rs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ci/script.sh b/ci/script.sh index 3440ee628..66e493282 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -23,6 +23,7 @@ main() { stderr system-time tcp_listen_connect + thread vec zero ) diff --git a/examples/thread.rs b/examples/thread.rs index df2c03c6a..1775d8446 100644 --- a/examples/thread.rs +++ b/examples/thread.rs @@ -1,7 +1,20 @@ -use std::io::{self, Write}; -use std::thread; +#[cfg(not(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64")))] +fn main() { +} +#[cfg(any(target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "x86", + target_arch = "x86_64"))] fn main() { + use std::io::{self, Write}; + use std::thread; + thread::spawn(|| { io::stdout().write_all(b"Hello, world!\n").unwrap(); }).join().unwrap(); From 835f2c23d302475fc3f80cc46f4b6f88b04fbbf0 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Fri, 21 Apr 2017 16:16:44 +0200 Subject: [PATCH 12/12] Use `global_asm!` instead of `#[naked]` functions This allows us to write the whole function in assembly without hacks. --- src/lib.rs | 1 + src/libc/internal/aarch64.rs | 12 +++++++----- src/libc/internal/arm.rs | 13 ++++++++----- src/libc/internal/powerpc.rs | 13 ++++++++----- src/libc/internal/x86.rs | 37 ++++++++++++++++++++---------------- src/libc/internal/x86_64.rs | 20 ++++++++++--------- 6 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e97d9e07f..da51fd913 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ #![feature(exact_size_is_empty)] #![feature(fnbox)] #![feature(fused)] +#![feature(global_asm)] #![feature(heap_api)] #![feature(int_error_internals)] #![feature(lang_items)] diff --git a/src/libc/internal/aarch64.rs b/src/libc/internal/aarch64.rs index 66489cfd5..555c92263 100644 --- a/src/libc/internal/aarch64.rs +++ b/src/libc/internal/aarch64.rs @@ -1,7 +1,5 @@ -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { +#[cfg(not(test))] +mod not_test { // Syscall number is passed in x8, syscall arguments in x0, x1, x2, x3, x4. // The arguments are // (flags: c_ulong, // x0 @@ -34,7 +32,10 @@ unsafe extern "C" fn __steed_clone() { // // We save `fn_` and `arg` on the child stack. - asm!(" + global_asm!(" + .globl __steed_clone + __steed_clone: + // Align the child stack. and x1,x1,#-16 @@ -60,6 +61,7 @@ unsafe extern "C" fn __steed_clone() { svc #0 1: + ret "); } diff --git a/src/libc/internal/arm.rs b/src/libc/internal/arm.rs index 3b2db6d2c..a39cf9331 100644 --- a/src/libc/internal/arm.rs +++ b/src/libc/internal/arm.rs @@ -1,7 +1,5 @@ -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { +#[cfg(not(test))] +mod not_test { // Syscall number is passed in r7, syscall arguments in r0, r1, r2, r3, r4. // The arguments are // (flags: c_ulong, // r0 @@ -38,7 +36,10 @@ unsafe extern "C" fn __steed_clone() { // // We save `fn_` in r5, `arg` in r6. - asm!(" + global_asm!(" + .globl __steed_clone + __steed_clone: + @ Save r4 to r7 stmfd sp!,{r4,r5,r6,r7} @@ -88,6 +89,8 @@ unsafe extern "C" fn __steed_clone() { @ Restore r4 to r7 ldmfd sp!,{r4,r5,r6,r7} + @ Return from the function. + mov pc,lr "); } diff --git a/src/libc/internal/powerpc.rs b/src/libc/internal/powerpc.rs index 69d6a6d47..1d7e675f8 100644 --- a/src/libc/internal/powerpc.rs +++ b/src/libc/internal/powerpc.rs @@ -1,7 +1,5 @@ -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { +#[cfg(not(test))] +mod not_test { // Syscall number is passed in r0, syscall arguments in r3, r4, r5, r6, r7. // The arguments are // (flags: c_ulong, // r3 @@ -35,7 +33,10 @@ unsafe extern "C" fn __steed_clone() { // // We save `fn_` and `arg` in r30 and r31 which we need to save first. - asm!(" + global_asm!(" + .globl __steed_clone + __steed_clone: + # Save `fn_` and `arg` (while keeping the stack aligned). stwu 30,-16(1) stw 31,4(1) @@ -82,6 +83,8 @@ unsafe extern "C" fn __steed_clone() { lwz 30,0(1) lwz 31,4(1) addi 1,1,16 + + blr "); } diff --git a/src/libc/internal/x86.rs b/src/libc/internal/x86.rs index 5e43a4458..02b03dc2d 100644 --- a/src/libc/internal/x86.rs +++ b/src/libc/internal/x86.rs @@ -1,9 +1,7 @@ use linux; -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { +#[cfg(not(test))] +mod not_test { // Syscall number is passed in %eax, syscall arguments in %ebx, %ecx, %edx, // %esi, %edi. The arguments are // (flags: c_ulong, // %ebx @@ -43,7 +41,10 @@ unsafe extern "C" fn __steed_clone() { // // We save `fn_` in %ebp. - asm!(" + global_asm!(" + .globl __steed_clone + __steed_clone: + # Stack frame push %ebp mov %esp,%ebp @@ -54,10 +55,10 @@ unsafe extern "C" fn __steed_clone() { push %edi mov 12(%ebp),%ecx # child_stack - and $$-16,%ecx # Align the stack + and $-16,%ecx # Align the stack # Push the parameter - sub $$16,%ecx # Keep the stack aligned + sub $16,%ecx # Keep the stack aligned mov 20(%ebp),%edi # arg mov %edi,(%ecx) @@ -70,15 +71,15 @@ unsafe extern "C" fn __steed_clone() { # limit_in_pages:1 = 1 # seg_not_present:1 = 0 # useable:1 = 1 - push $$0x51 - push $$0xfffff # limit + push $0x51 + push $0xfffff # limit push 28(%ebp) # base_addr xor %eax,%eax mov %gs,%ax - shr $$3,%eax + shr $3,%eax push %eax # entry_number - mov $$120,%eax # CLONE + mov $120,%eax # CLONE mov 16(%ebp),%ebx # flags mov 24(%ebp),%edx # ptid mov %esp,%esi # newtls @@ -86,7 +87,7 @@ unsafe extern "C" fn __steed_clone() { mov 8(%ebp),%ebp # fn_ - int $$0x80 + int $0x80 # CLONE returns 0 in the child thread, return if we're the parent. test %eax,%eax @@ -101,14 +102,16 @@ unsafe extern "C" fn __steed_clone() { call *%eax mov %eax,%ebx # status - mov $$1,%eax # EXIT - int $$0x80 + mov $1,%eax # EXIT + int $0x80 + + # Unreachable hlt 1: # Pop the struct - add $$16,%esp + add $16,%esp # Restore registers pop %edi @@ -117,6 +120,8 @@ unsafe extern "C" fn __steed_clone() { # Stack frame pop %ebp + + ret "); } @@ -133,6 +138,6 @@ pub unsafe fn set_thread_pointer(thread_data: *mut ()) { if result < 0 { panic!("set_thread_pointer: set_thread_area: {}", result); } - asm!("mov $0,%gs"::"r"(((user_desc.entry_number << 3) | 3) as u16)); + asm!("mov $0,%gs"::"r"(((user_desc.entry_number << 3) | 3) as u16)::"volatile"); } diff --git a/src/libc/internal/x86_64.rs b/src/libc/internal/x86_64.rs index 1cebc4b14..b75054292 100644 --- a/src/libc/internal/x86_64.rs +++ b/src/libc/internal/x86_64.rs @@ -1,10 +1,8 @@ use libc::*; use linux; -#[inline(never)] -#[naked] -#[no_mangle] -unsafe extern "C" fn __steed_clone() { +#[cfg(not(test))] +mod not_test { // Syscall number is passed in %rax, syscall arguments in %rdi, %rsi, %rdx, // %r10, %r8. The arguments are // (flags: c_ulong, // %rdi @@ -40,17 +38,20 @@ unsafe extern "C" fn __steed_clone() { // There's a cycle in there (%rdx -> %rdi -> %r9 -> %r8 -> %rdx), we break // it at %rdi -> %r9 and move it to the scratch register %r11 instead. - asm!(" - and $$-16,%rsi # Align the child stack to 16 bytes. + global_asm!(" + .globl __steed_clone + __steed_clone: + + and $-16,%rsi # Align the child stack to 16 bytes. # Push `arg` onto the child stack. - sub $$8,%rsi + sub $8,%rsi mov %rcx,(%rsi) # Temporarily store `fn_` mov %rdi,%r11 - mov $$56,%rax # CLONE + mov $56,%rax # CLONE mov %rdx,%rdi # flags # child_stack mov %r8,%rdx # ptid @@ -72,13 +73,14 @@ unsafe extern "C" fn __steed_clone() { call *%r9 # fn_ mov %rax,%rdi # status - mov $$60,%rax # EXIT + mov $60,%rax # EXIT syscall # Unreachable. hlt 1: + ret "); }