Last active
August 25, 2023 21:49
-
-
Save Azoy/a39cd31285d6d2e5c5e0d370675c290d to your computer and use it in GitHub Desktop.
Raw system calls in Swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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") |
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
Hey, thanks for this example. Where one could find the implementation of the
asm
function?