'asm'操作数有不可能的限制

时间:2015-11-24 08:37:14

标签: c gcc assembly archlinux xen

我正在尝试在Arch linux上编译xen并收到以下错误:

src/stacks.c:342:5: error: 'asm' operand has impossible constraints
 asm volatile(
 ^

以下是导致错误的方法的代码:

void
run_thread(void (*func)(void*), void *data)
{
ASSERT32FLAT();
if (! CONFIG_THREADS || ! ThreadControl)
    goto fail;
struct thread_info *thread;
thread = memalign_tmphigh(THREADSTACKSIZE, THREADSTACKSIZE);
if (!thread)
    goto fail;

dprintf(DEBUG_thread, "/%08x\\ Start thread\n", (u32)thread);
thread->stackpos = (void*)thread + THREADSTACKSIZE;
struct thread_info *cur = getCurThread();
hlist_add_after(&thread->node, &cur->node);
asm volatile(
    // Start thread
    "  pushl $1f\n"                 // store return pc
    "  pushl %%ebp\n"               // backup %ebp
    "  movl %%esp, (%%edx)\n"       // cur->stackpos = %esp
    "  movl (%%ebx), %%esp\n"       // %esp = thread->stackpos
    "  calll *%%ecx\n"              // Call func

    // End thread
    "  movl %%ebx, %%eax\n"         // %eax = thread
    "  movl 4(%%ebx), %%ebx\n"      // %ebx = thread->node.next
    "  movl (%5), %%esp\n"          // %esp = MainThread.stackpos
    "  calll %4\n"                  // call __end_thread(thread)
    "  movl -4(%%ebx), %%esp\n"     // %esp = next->stackpos
    "  popl %%ebp\n"                // restore %ebp
    "  retl\n"                      // restore pc
    "1:\n"
    : "+a"(data), "+c"(func), "+b"(thread), "+d"(cur)
    : "m"(*(u8*)__end_thread), "m"(MainThread)
    : "esi", "edi", "cc", "memory");
return;

fail:
    func(data);
}

我不确定发生了什么。知识渊博的人是否可以帮助查看它并告诉我这里是否存在明显的问题?

更新

您可以通过执行以下操作来解决此错误:

  1. 将COMMONCFLAGS + = $(调用cc-option,$(CC), - fstack-check = no,)添加到seabios makefile中(如果你从git AUR构建xen,那么location应该是xen / src / xen -4.5.1 /工具/固件/ seabios-DIR-远程/生成文件)

  2. 转到stacks.c并将movl(%5),%% esp更改为movl%5,%% esp

2 个答案:

答案 0 :(得分:3)

直接原因可能是您没有通过优化开关直接或间接启用-fomit-frame-pointer。因此,编译器用尽寄存器,因为eaxebxecxedx用于参数,esiedi是clobbers并且ebp是帧指针。因此,解决方案是确保启用此选项。

显然this code is part of SeaBIOS(感谢Michael Petch找到它)。 __end_thread只有一个函数,而不是一个函数指针,可以从那个投射魔法的存在中得到。因此,我认为这个结构的重点是解决任何最终的名称修改问题。不幸的是,它为此目的牺牲了一个注册表。如果您知道您的环境没有破坏函数名称,您可以使用这个更简单的版本,它不需要额外的寄存器,并且应该在带有帧指针的调试版本中编译良好:

asm volatile(
    // Start thread
    "  pushl $1f\n"                 // store return pc
    "  pushl %%ebp\n"               // backup %ebp
    "  movl %%esp, (%%edx)\n"       // cur->stackpos = %esp
    "  movl (%%ebx), %%esp\n"       // %esp = thread->stackpos
    "  calll *%%ecx\n"              // Call func

    // End thread
    "  movl %%ebx, %%eax\n"         // %eax = thread
    "  movl 4(%%ebx), %%ebx\n"      // %ebx = thread->node.next
    "  movl (%4), %%esp\n"          // %esp = MainThread.stackpos
    "  call __end_thread\n"         // call __end_thread(thread)
    "  movl -4(%%ebx), %%esp\n"     // %esp = next->stackpos
    "  popl %%ebp\n"                // restore %ebp
    "  retl\n"                      // restore pc
    "1:\n"
    : "+a"(data), "+c"(func), "+b"(thread), "+d"(cur)
    : "m"(MainThread)
    : "esi", "edi", "cc", "memory");

答案 1 :(得分:1)

我完全重写了asm语句。基本问题是该语句要么使用clobbers,要么用作除EBP之外的每个寄存器的输入/输出操作数。禁用优化并使用-fno-omit-frame-pointer时,不存在用于存储评估表达式(u8*)__end_thread的结果的寄存器。这是一件好事,因为它是因为如果帧指针可用,它将生成calll (%ebp),这不是这里实际需要的。

以下asm语句不会尝试分配所有未使用的寄存器和clobber,而是使除EBP之外的每个寄存器成为输出操作数。这使编译器可以更自由地分配输入寄存器。

int dummy;
asm volatile("push 1f\n\t"
         "push %%ebp\n\t"
         "mov %%esp, %[cur_stackpos]\n\t"
         "mov %[thread_stackpos], %%esp\n\t"
         "call *%[func]\n\t"
         "mov %p[mainthread_stackpos], %%esp\n\t"
         "mov %[thread], %%eax\n\t"
         "call %c[end_thread]\n\t"
         "mov 4(%[thread]),%%eax\n\t"
         "mov -4(%%eax),%%esp\n\t"
         "pop %%ebp\n\t"
         "pop %%eax\n\t"
         "jmp *%%eax\n\t"
         "1:\n"
         : 
         [data] "+a" (data),
         "=b" (dummy), "=c" (dummy), "=d" (dummy),
         "=S" (dummy), "=D" (dummy)
         :
         [func] "r" (func),
         [cur_stackpos] "m" (cur->stackpos),
         [thread_stackpos] "rm" (thread->stackpos),
         [mainthread_stackpos] "i" (&MainThread.stackpos),
         [thread] "bSD" (thread),
         [end_thread] "i" (__end_thread)
         : 
         "memory", "cc");

我已为"i"[mainthread_stackpos]操作数使用[end_thread]约束和操作数修饰符,以确保这些操作数是简单标签。编译器不能将它们放在寄存器或堆栈中。这有点偏执,使用没有操作数修饰符的"m"约束也可以。至少在编译器执行与*(u8*)__end_thread一样的意外操作之前。说到这一点,我只用__end_thread替换了它,因为演员和取消引用似乎毫无意义。

我还将ret语句替换为pop %eax; jmp *%eax,因为这应该更快。 ret语句将始终被错误预测,因为地址不会成为返回堆栈缓冲区,但至少有机会预测jmp *eax。它会跳转到下一条指令或1:中的switch_stacks标签。