1

I am playing with my own fibers; more for educational sake.

My platform is x86-64 & Linux.

Here is my context-switching routine

__taskflow_switch_cpu_context: 
        // preserve the $rbp, plays together with the `-fno-omit-frame-pointer`
        pushq %rbp
        movq %rsp, %rbp

        // save the registers 
        pushq %r15
        pushq %r14
        pushq %r13
        pushq %r12
        pushq %rbx

        movq %rsp, (%rdi) // memorize a leaving fiber's $rsp
        movq (%rsi), %rsp // restore a new coming fiber's $rsp 

        // restore the registers in the reverse order
        popq %rbx
        popq %r12
        popq %r13
        popq %r14
        popq %r15

        // preserved %rbp no longer needed
        popq %rbp

        retq

The code above works well. It does change a context; it does allow nested functions to be called.. well, almost.

When I have a printfn("some pattern %i\n", no_matter_how_you_get_an_int()) call inside my fiber, it falls with a segmentation fault. However, the printfn("no patterns here\n") works well too.

I though it is because of an %rbp doing a gigantic jump, but seems I was mistaken.

P.S. Once a fiber is created, a correct return address is pushed manually and correct offsets are ensured. Roughly here it is:

  /*                                                                               
    x86 stack grows towards lesser memory addresses;                               
    so `res->stack + res->stack_size` is it's very beginning.                      
  */
  uintptr_t *stack = (uintptr_t *)(res->stack + res->stack_size - sizeof(void *));
  // An initial return addess.                                                     
  *stack = (uintptr_t)fn; // A correct return address is there now.
// more unrelated code 
  res->cpu_context->rsp = (res->stack + res->stack_size - sizeof(void *) * 7);  // Ensures some room for the callee/caller-save registers altogether with a parent `rbp`.

Once again: since it works fine for "non-printfn" calls, I assume the initial stack layout is all right.

P.P.S. -fno-omit-frame-pointer is there too.

P.P.P.S. As Peter Cordes says in the comments, movaps seems to be the problem here (see the attached screenshot from my GDB session). enter image description here

Zazaeil
  • 3,900
  • 2
  • 14
  • 31
  • 1
    Saving/restoring RBP correctly is necessary whether or not you or the caller uses it as a frame pointer. It's always a call-preserved register in the calling convention, regardless of GCC options. Your code looks fine, saving / restoring *all* the call-preserved registers including RBP. The `movq %rsp, %rbp` instruction is harmless but useless (except maybe for debugging if you stop inside this function?) – Peter Cordes Oct 18 '22 at 12:27
  • 2
    When your code crashes, what instruction is it on? Is it inside printf, on a misaligned `movaps` to or from the stack? If so, you're probably allocating your fiber stacks misaligned. (See [glibc scanf Segmentation faults when called from a function that doesn't align RSP](https://stackoverflow.com/q/51070716) for an example of the kind of symptom.) `printfn("no patterns here\n")` compiles to `call puts`, not `call printf`, so it's a different, simpler function in libc that might not happen to use any 16-byte copies. – Peter Cordes Oct 18 '22 at 12:30
  • @PeterCordes yeah, your're right; it is `movaps`. I am not familiar with this instruction yet, will go & study docs. AFAIU, `sub rsp, 8` & `add rsp, 8` must replace my `pushq %rbp` & `popq %rbp` to ensure alignment, but I still have to learn how and why it is needed at the first place. Note: I updated the original post. – Zazaeil Oct 18 '22 at 13:37
  • That Q&A is about hand-writing a function that calls printf. For you, the problem is the address you pass on the first switch to a new fiber: if you call a function, RSP on entry to that function needs to be RSP%16 == 8, as if there was a return address on the stack. – Peter Cordes Oct 18 '22 at 13:44
  • And is there simple / commonly used way to get it done? Or do I have to ensure it manually also? That's not something obvious to me. – Zazaeil Oct 18 '22 at 13:45
  • 1
    I'm not sure exactly how you're getting execution started in a new fiber, but probably it's a matter of changing the `sizeof(void*)*7` to a `sizeof(void*)*8` or something if you're using your switch function to get it to "return" somewhere that wasn't actually called. Since you have a ` - sizeof(void *)` in the initial `uintptr_t *stack`, so it's pointing at readable memory instead of one-past-the-end ready for a push. – Peter Cordes Oct 18 '22 at 13:46
  • @PeterCordes you made my day, thanks, not only it works, but also I now I know what I did not know and know where to study it. – Zazaeil Oct 18 '22 at 13:56

0 Answers0