2015-04-13 2 views
0

Я пытаюсь создать реализацию зеленой нити на основе this tutorial. Однако моя функция переключения дает мне segfault, потому что код для загрузки регистров не запускается в конце функции. Вот мой код:Segfault inline assembly

void ThreadSwitch(Thread in, Thread out) { 
    if (!out && !in) { 
      return; 
    } 
    if (out) { 
     // save registers for out  
    } 
    if (in) { 
     SetCurrentThread(in); 
     mtx_lock(&in->mutex); 
     uint64_t rsp = in->cpu.rsp; 
     uint64_t r15 = in->cpu.r15; 
     uint64_t r14 = in->cpu.r14; 
     uint64_t r13 = in->cpu.r13; 
     uint64_t r12 = in->cpu.r12; 
     uint64_t rbx = in->cpu.rbx; 
     uint64_t rbp = in->cpu.rbp; 
     mtx_unlock(&in->mutex); 
     asm volatile("mov %[rsp], %%rsp\n" 
        "mov %[r15], %%r15\n" 
        "mov %[r14], %%r14\n" 
        "mov %[r13], %%r13\n" 
        "mov %[r12], %%r12\n" 
        "mov %[rbx], %%rbx\n" 
        "mov %[rbp], %%rbp\n" : : [rsp] "r"(rsp), [r15] "r"(r15), [r14] "r"(r14), [r13] "r"(r13), [r12] "r"(r12), [rbx] "r"(rbx), [rbp] "r"(rbp)); 
    } 
} 

Xcode говорит о том, что встроенный ассемблер вызывает Segfault, но мой lldb разборку выглядит следующим образом (вы можете игнорировать 95% из них, только при условии для контекста):

0x1000f88b4: movq -0x8(%rbp), %rdi 
    0x1000f88b8: callq 0x1000f83a0    ; SetCurrentThread at thread.cc:21 
    0x1000f88bd: movq -0x8(%rbp), %rdi 
    0x1000f88c1: addq $0x50, %rdi 
    0x1000f88c8: callq 0x1000f7b80    ; mtx_lock at tct.c:106 
    0x1000f88cd: movq -0x8(%rbp), %rdi 
    0x1000f88d1: movq (%rdi), %rdi 
    0x1000f88d4: movq %rdi, -0x18(%rbp) 
    0x1000f88d8: movq -0x8(%rbp), %rdi 
    0x1000f88dc: movq 0x8(%rdi), %rdi 
    0x1000f88e0: movq %rdi, -0x20(%rbp) 
    0x1000f88e4: movq -0x8(%rbp), %rdi 
    0x1000f88e8: movq 0x10(%rdi), %rdi 
    0x1000f88ec: movq %rdi, -0x28(%rbp) 
    0x1000f88f0: movq -0x8(%rbp), %rdi 
    0x1000f88f4: movq 0x18(%rdi), %rdi 
    0x1000f88f8: movq %rdi, -0x30(%rbp) 
    0x1000f88fc: movq -0x8(%rbp), %rdi 
    0x1000f8900: movq 0x20(%rdi), %rdi 
    0x1000f8904: movq %rdi, -0x38(%rbp) 
    0x1000f8908: movq -0x8(%rbp), %rdi 
    0x1000f890c: movq 0x28(%rdi), %rdi 
    0x1000f8910: movq %rdi, -0x40(%rbp) 
    0x1000f8914: movq -0x8(%rbp), %rdi 
    0x1000f8918: movq 0x30(%rdi), %rdi 
    0x1000f891c: movq %rdi, -0x48(%rbp) 
    0x1000f8920: movq -0x8(%rbp), %rdi 
    0x1000f8924: addq $0x50, %rdi 
    0x1000f892b: movl %eax, -0x54(%rbp) 
    0x1000f892e: callq 0x1000f7de0    ; mtx_unlock at tct.c:264 
    0x1000f8933: movq -0x18(%rbp), %rdi   ; beginning of inline asm 
    0x1000f8937: movq -0x20(%rbp), %rcx 
    0x1000f893b: movq -0x28(%rbp), %rdx 
    0x1000f893f: movq -0x30(%rbp), %rsi 
    0x1000f8943: movq -0x38(%rbp), %r8 
    0x1000f8947: movq -0x40(%rbp), %r9 
    0x1000f894b: movq -0x48(%rbp), %r10 
    0x1000f894f: movq %rdi, %rsp 
    0x1000f8952: movq %rcx, %r15 
    0x1000f8955: movq %rdx, %r14 
    0x1000f8958: movq %rsi, %r13 
    0x1000f895b: movq %r8, %r12 
    0x1000f895e: movq %r9, %rbx 
    0x1000f8961: movq %r10, %rbp    ; end of inline asm 
-> 0x1000f8964: movl %eax, -0x58(%rbp) 
    0x1000f8967: addq $0x60, %rsp 
    0x1000f896b: popq %rbp 
    0x1000f896c: retq 

Segfault происходит, когда он пытается получить доступ к материалу обратно в стек, что имеет смысл, потому что он просто отключил стек. Но почему компилятор вставляет это? Компилятор также сохраняет% eax в стеке на 0x1000f892b. Открывает ли компилятор регистр? Потому что он не использует% rax в inline asm. Есть ли обходной путь?

Это Apple LLVM version 6.0 (clang-600.0.57) на OSX 10.10.2, если это поможет.

Заранее спасибо.

+0

Непонятно, что делает компилятор с 'eax' там (возможно, отключен оптимизатор?), Однако адрес' -0x58 (% rbp) 'должен быть действительным, так как вы возвращаетесь к потоку, который сам выполняется код, который ранее устанавливал '% rbp'. – Jester

+0

Я попытаюсь отключить оптимизацию (я использую настройку отладки CMake), но этот код не работает при первом запуске потока. Стек в потоке настроен так, что эта функция (ThreadSwitch) вернется в целевую функцию потока. –

+0

Возможно, было бы разумно отметить эту функцию как «голый», поэтому компилятор не будет генерировать инструкции кадров стека до и после тела функции (пролог и эпилог). GCC имеет '__attribute __ ((голый))' для этого, возможно, ребята из CLang добавили его как обратную совместимость. Но это также затрудняет использование локальных переменных, поскольку снова вы не изменяете указатель стека и перезаписываете переменные вызывающего абонента. – myaut

ответ

0

Настоятельно рекомендую вам не писать программы, зависящие от неопределенного поведения.

Включение и выключение встроенной сборки не допускается, поскольку компилятор не может проанализировать поток управления, о котором он не знает, при создании нитей вы прыгаете в оператор asm из ниоткуда, а затем оставляете его. Чтобы избежать этих неявных переходов, вам нужно сохранить и восстановить регистры, включая% rip, в том же самом выражении asm.

Все регистры, которые изменяют оператор asm, должны быть указаны как выходы или clobbers, для процедуры переключения потока, которая является всеми регистрами, значения которых не сохраняются, поскольку они изменены другими потоками. Если вы этого не сделаете, компилятор неправильно предположит, что они не изменены.

Оператор asm должен избегать перезаписывать свои входы до их использования, в вашем коде нет ничего запрещающего компилятору хранить переменную r12 в регистре% r14.

Ваш замок либо бессмыслен, либо неадекватен.

Гораздо проще написать свою функцию целиком в сборке, как в учебнике, который вы цитируете.

Смежные вопросы