diff --git a/include/muteki.h b/include/muteki.h index b6b0264..38f10ab 100644 --- a/include/muteki.h +++ b/include/muteki.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/muteki/syscall.h b/include/muteki/syscall.h new file mode 100644 index 0000000..89e7951 --- /dev/null +++ b/include/muteki/syscall.h @@ -0,0 +1,35 @@ +#ifndef __MUTEKI_SYSCALL_H__ +#define __MUTEKI_SYSCALL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SYSCALL_HAS_STACK_ARGS (0x80000000ul) + +/** + * @brief Call arbitrary syscall with arbitrary number of arguments. + * @details + * This will reformat the stack frame, write an SVC instruction to an unused space of the stack, and jump to it. + * + * The higher 8 bits of the syscall number can encode special meanings. Currently setting the highest bit + * (SYSCALL_HAS_STACK_ARGS) signals the function to take account of arguments allocated on the stack. That is, + * that bit should be set whenever there's more than 3 syscall arguments passed to this function. + * + * @warning Due to limitation of the Besta syscall scheme, this function is not thread-safe. Therefore to call it + * across multiple threads, a lock must be created and used across all threads that use this function. + * + * @param number The syscall number. + * @param ... Syscall arguments. + * @return Return value of the syscall. Can be casted to recover values of different type. Note that 64-bit values + * are not supported and only the lower 32-bit of such values can be retrieved. + */ +long syscall(long number, ...); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __MUTEKI_SYSCALL_H__ diff --git a/meson.build b/meson.build index 961dbe6..a563ba4 100644 --- a/meson.build +++ b/meson.build @@ -46,7 +46,8 @@ syscalls_split = custom_target('syscalls_split', # Static library that bundles both sdk and krnl shims. static_library( 'muteki-shims', - syscalls_split, + [syscalls_split, 'src/shims/syscall.c'], + include_directories: ['include/'], install : true, c_args : c_flags, link_args : ld_flags, diff --git a/src/shims/syscall.c b/src/shims/syscall.c new file mode 100644 index 0000000..3b0ca02 --- /dev/null +++ b/src/shims/syscall.c @@ -0,0 +1,75 @@ +#include "muteki/syscall.h" + +#if defined(__clang__) + +// clang: Ignore unused parameter warning and clangd's warning on asm(). +#define NO_EXPECTED_WARNINGS_BEGIN \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunused-parameter\"") \ + _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") +#define NO_EXPECTED_WARNINGS_END \ + _Pragma("clang diagnostic pop") + +#elif defined(__GNUC__) // !defined(__clang__) + +// gcc: Ignore unused parameter warning. +#define NO_EXPECTED_WARNINGS_BEGIN \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") +#define NO_EXPECTED_WARNINGS_END \ + _Pragma("GCC diagnostic pop") + +#else // !defined(__GNUC__) + +// No-op +#define NO_EXPECTED_WARNINGS_BEGIN +#define NO_EXPECTED_WARNINGS_END + +#endif // defined(__clang__) || defined(__GNUC__) + +__attribute__((used)) static unsigned int trampoline_area[] = {0xe1a00000}; + +NO_EXPECTED_WARNINGS_BEGIN +__attribute__((naked, target("arch=armv4t"))) +long syscall(long number, ...) { + // Starts as sysno a0 a1 a2 | a3 ... or sysno a0 a1 a2 | ... + asm( + // sysno x a1 a2 | a0 a3 ... or sysno x a1 a2 | a0 ... + "push {r1}\n\t" + // sysno a1 a2 x | a0 a3 ... or sysno a1 a2 x | a0 ... + "mov r1, r2\n\t" + "mov r2, r3\n\t" + // If passing 3+ arguments + "tst r0, #0x80000000\n\t" + "beq 1f\n\t" + // x a1 a2 x | sysno a0 a3 ... + "push {r0}\n\t" + // a0 a1 a2 x | sysno x a3 ... + "ldr r0, [sp, #4]\n\t" + // a0 a1 a2 a3 | sysno x x ... + "ldr r3, [sp, #8]\n\t" + // x a1 a2 a3 | sysno x a0 ... + "str r0, [sp, #8]\n\t" + // sysno a1 a2 a3 | x x a0 ... + "ldr r0, [sp]\n\t" + // sysno a1 a2 a3 | a0 ... + "add sp, sp, #8\n" + // Fall through + + "1:\n\t" + // sysno x a2 a3? | a1 lr a0 ... + "push {r1, lr}\n\t" + + "bic r0, r0, #0xff000000\n\t" + "orr r1, r0, #0xef000000\n\t" + + // sysno svc a2 a3? | a1 lr a0 ... + "ldr r0, =trampoline_area\n\t" + // trampoline svc a2 a3? | a1 lr a0 ... + "str r1, [r0]\n\t" + // trampoline a1 a2 a3? | lr a0 ... + "pop {r1}\n\t" + "bx r0" + ); +} +NO_EXPECTED_WARNINGS_END diff --git a/src/syscall.s b/src/syscall.s new file mode 100644 index 0000000..f280e02 --- /dev/null +++ b/src/syscall.s @@ -0,0 +1,47 @@ + .arm + .cpu arm7tdmi + .global syscall + .type syscall, %function + +@ Starts as sysno a0 a1 a2 | a3 ... or sysno a0 a1 a2 | ... +syscall: + @ sysno x a1 a2 | a0 a3 ... or sysno x a1 a2 | a0 ... + push {r1} + @ sysno a1 a2 x | a0 a3 ... or sysno a1 a2 x | a0 ... + mov r1, r2 + mov r2, r3 + @ If passing 3+ arguments + tst r0, #0x80000000 + beq _syscall_insert_lr + @ x a1 a2 x | sysno a0 a3 ... + push {r0} + @ a0 a1 a2 x | sysno x a3 ... + ldr r0, [sp, #4] + @ a0 a1 a2 a3 | sysno x x ... + ldr r3, [sp, #8] + @ x a1 a2 a3 | sysno x a0 ... + str r0, [sp, #8] + @ sysno a1 a2 a3 | x x a0 ... + ldr r0, [sp] + @ sysno a1 a2 a3 | a0 ... + add sp, sp, #8 + @ Fall through + +_syscall_insert_lr: + @ sysno a1 a2 a3? | lr a0 ... + push {lr} + + bic r0, r0, #0xff000000 + orr r0, r0, #0xef000000 + + @ sysno a1 a2 a3? | trampoline[(svc sysno) nop nop] lr a0 ... + str r0, [sp, #-12] + ldr r0, =_nop + str r0, [sp, #-8] + str r0, [sp, #-4] + @ trampoline a1 a2 a3 | trampoline[(svc sysno) nop nop] lr a0 ... + sub r0, sp, #12 + bx r0 + +_nop: + mov r0, r0