Skip to content

Instantly share code, notes, and snippets.

@Azoy
Last active August 25, 2023 21:49
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Azoy/a39cd31285d6d2e5c5e0d370675c290d to your computer and use it in GitHub Desktop.
Save Azoy/a39cd31285d6d2e5c5e0d370675c290d to your computer and use it in GitHub Desktop.
Raw system calls in Swift
// macOS x86_64 syscall works as follows:
// Syscall id is moved into rax
// 1st argument is moved into rdi
// 2nd argument is moved into rsi
// 3rd argument is moved into rdx
// ... plus some more
// Return value is stored in rax (where we put syscall value)
// Mac syscall enum that contains the value to correctly call it
enum Syscall: Int {
case fork = 0x2000002 // 2
case write = 0x2000004 // 4
case `open` = 0x2000005 // 5
case close = 0x2000006 // 6
case getpid = 0x2000014 // 20
case getppid = 0x2000027 // 39
case execve = 0x200003B // 59
}
// Small wrapper over a numerical file descriptor
struct FileDescriptor {
// Backing value
let value: Int
// Standard Input
static let stdin: FileDescriptor = 0
// Standard Output
static let stdout: FileDescriptor = 1
// Standard Error
static let stderr: FileDescriptor = 2
}
extension FileDescriptor: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self.value = value
}
}
extension FileDescriptor {
// Nifty enum containing file descriptor modes
enum Mode: Int {
case read // O_RDONLY
case write // O_WRONLY
case readWrite // O_RDWR
}
}
// Perform a system call with no arguments
@discardableResult
func syscall0(_ syscall: Syscall) -> Int {
var call = syscall.rawValue
var result = 0
asm {
mov rax, call ; move syscall value into rax register
syscall
mov result, rax ; move result into result
}
return result
}
// Perform a system call with 1 argument
@discardableResult
func syscall1(_ syscall: Syscall, _ arg1: Int) -> Int {
var call = syscall.rawValue
var arg1 = arg1
var result = 0
asm {
mov rax, call
mov rdi, arg1
syscall
mov result, rax
}
return result
}
// Perform a system call with 2 arguments
@discardableResult
func syscall2(_ syscall: Syscall, _ arg1: Int, _ arg2: Int) -> Int {
var call = syscall.rawValue
var arg1 = arg1
var arg2 = arg2
var result = 0
asm {
mov rax, call
mov rdi, arg1
mov rsi, arg2
syscall
mov result, rax
}
return result
}
// Perform a system call with 3 arguments
@discardableResult
func syscall3(
_ syscall: Syscall,
_ arg1: Int,
_ arg2: Int,
_ arg3: Int
) -> Int {
var call = syscall.rawValue
var arg1 = arg1
var arg2 = arg2
var arg3 = arg3
var result = 0
asm {
mov rax, call
mov rdi, arg1
mov rsi, arg2
mov rdx, arg3
syscall
mov result, rax
}
return result
}
// Retrieve the current process id
func getPid() -> Int {
return syscall0(.getpid)
}
// Retrieves the parent process id
func getPPid() -> Int {
return syscall0(.getppid)
}
// Forks the current process and returns the child pid
func fork() -> Int {
let child = syscall0(.fork)
// Get the current pid, if its not equal to the forked child pid return the
// child pid, otherwise return 0
return getPid() == child ? 0 : child
}
// Writes to a file descriptor (Default is standard output)
@discardableResult
func write(_ msg: String, to fd: FileDescriptor = .stdout) -> Int {
return msg.withCString {
return syscall3(.write, fd.value, Int(bitPattern: $0), msg.count + 1)
}
}
// Open a file at the given path (this ignores errors at the moment...)
//
// Returns the FileDescriptor
func open(_ path: String, mode: FileDescriptor.Mode) -> FileDescriptor {
return path.withCString {
let fd = syscall3(.open, Int(bitPattern: $0), mode.rawValue, 0)
return FileDescriptor(value: fd)
}
}
// Closes a given file descriptor
@discardableResult
func close(_ fd: FileDescriptor) -> Int {
return syscall1(.close, fd.value)
}
// Execute a process (arguments and environment dont work atm...)
@discardableResult
func execve(
_ path: String,
arguments: [String] = [],
environment: [String] = []
) -> Int {
return path.withCString {
return syscall3(.execve, Int(bitPattern: $0), /*nullptr*/ 0, /*nullptr*/ 0)
}
}
write("Hello world!\n")
write(String(getPid()) + "\n")
let textFile = open("/path/to/your/favorite/textfile.txt", mode: .write)
write("This is my text file now!\n", to: textFile)
close(textFile)
let child = fork()
// Check if this is the child process
if child == 0 {
write("Parent PID: " + String(getPPid()) + "\n")
}
// Both child and parent are now handing off to execute this process
execve("/path/to/your/favorite/executable")
@dmhts
Copy link

dmhts commented Jul 5, 2020

Hey, thanks for this example. Where one could find the implementation of the asm function?

@trufae
Copy link

trufae commented Aug 25, 2023

Hey, thanks for this example. Where one could find the implementation of the asm function?

i wonder the same

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment