Skip to content

Instantly share code, notes, and snippets.

@Verdagon
Created April 24, 2022 16:10
Show Gist options
  • Save Verdagon/fe458483dc7d1d992d499c750b45c091 to your computer and use it in GitHub Desktop.
Save Verdagon/fe458483dc7d1d992d499c750b45c091 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <setjmp.h>
_Thread_local void* current_coroutine_args = NULL;
size_t rotate_left(size_t x, int bits) {
return (x << bits) | (x >> ((sizeof(size_t) * 8) - bits));
}
size_t rotate_right(size_t x, int bits) {
return (x >> bits) | (x << ((sizeof(size_t) * 8) - bits));
}
typedef struct SubFuncArgs {
size_t yield_destination_scrambled;
} SubFuncArgs;
void subFunc() {
int y = 20;
printf("y address after switch: %p\n", &y);
jmp_buf* yield_destination = (jmp_buf*)(rotate_right(((SubFuncArgs*)current_coroutine_args)->yield_destination_scrambled, 17) ^ 0x1337133713371337);
printf("Jumping back to old stack! buf at: %p\n", yield_destination);
longjmp(*yield_destination, 1);
// Should be impossible to get here
fprintf(stderr, "Unreachable!\n");
exit(1);
}
int main() {
printf("jump buf size: %lu\n", sizeof(jmp_buf));
int x = 10;
printf("x address before switch: %p\n", &x);
char* newStack = (char*)malloc(8 * 1024 * 1024);
char* coroutine_stack_frame_top = newStack + 8 * 1024 * 1024;
printf("allocated new stack: %p to %p\n", newStack, coroutine_stack_frame_top);
jmp_buf yield_destination;
// current_coroutine_entry = subFunc;
size_t yield_destination_scrambled =
rotate_left(((size_t)(&yield_destination) ^ 0x1337133713371337), 17);
SubFuncArgs args = { yield_destination_scrambled };
current_coroutine_args = &args;
printf("About to jump to %p, buf at: %p\n", coroutine_stack_frame_top, &yield_destination);
register void* old_stack_pointer = NULL;
__asm__ __volatile__(
#ifdef __x86_64__
"movq %%rsp,%[rs]"
#elif __i386__
"movl %%esp,%[rs]"
#elif __arm__
// sp is an alias for r13
"mov %%sp,%[rs]"
#endif
: [rs] "=r" (old_stack_pointer)
: /* no input */
);
if (setjmp(yield_destination) == 0) {
// setjmp returns zero when we first just call it to save the location.
// Later on, someone might jump back into that if-condition (just after the setjmp call)
// but they'll supply something other than zero, so they wont get inside this block.
register char* top = coroutine_stack_frame_top;
register void(*funcToCall)() = subFunc;
asm volatile(
#ifdef __x86_64__
//"mov "
"mov %[rs], %%rsp \n"
#elif __i386__
"mov %[rs], %%esp \n"
#elif __arm__
// sp is an alias for r13
"mov %[rs], %%sp \n"
#endif
"call *%[bz] \n"
: [ rs ] "+r" (top), [ bz ] "+r" (funcToCall) ::
);
// Accessing any locals after this point seems to be a big mistake,
// because with -fomit-frame-pointer on, it will calculate those locals'
// addresses by offsetting from the stack pointer... which we just messed with.
// So, don't access any locals here.
// Don't call any functions, because they could be inlined, and access locals.
// In fact, don't do anything here.
// Don't even look over here.
// Put it all in the assembly above.
// Unreachable, all coroutines know to longjmp back.
return 1;
} else {
// Someone jumped to the saved location!
printf("got the longjmp!\n");
}
printf("done with program!\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment