at& t asm inline c ++问题

时间:2009-12-24 02:30:49

标签: c gcc assembly x86 inline-assembly

我的代码

const int howmany = 5046;
char buffer[howmany];
    asm("lea     buffer,%esi"); //Get the address of buffer
    asm("mov     howmany,%ebx");         //Set the loop number
    asm("buf_loop:");                      //Lable for beginning of loop
    asm("movb     (%esi),%al");             //Copy buffer[x] to al
    asm("inc     %esi");                   //Increment buffer address
    asm("dec     %ebx");                   //Decrement loop count
    asm("jnz     buf_loop");              //jump to buf_loop if(ebx>0)

我的问题

我正在使用gcc编译器。出于某种原因,我的缓冲区/ howmany变量在我的asm眼中是不确定的。我不知道为什么。我只想将缓冲区数组的起始地址移动到esi寄存器中,在将每个元素复制到al寄存器时循环“howmany”次。

3 个答案:

答案 0 :(得分:7)

您是否在gcc中使用内联汇编程序? (如果没有,在其他C ++编译器中,究竟是什么?)

如果是gcc,请参阅详细信息here,特别是此示例:

    asm ("leal (%1,%1,4), %0"
         : "=r" (five_times_x)
         : "r" (x) 
         );

%0%1指的是C级变量,它们被列为第二个(用于输出)和第三个(用于输入)参数到asm。在您的示例中,您只有“输入”,因此您有一个空的第二个操作数(传统上使用该冒号之后的注释,例如/* no output registers */,以更明确地指示)。

答案 1 :(得分:1)

声明类似

的数组的部分
int howmany = 5046;
char buffer[howmany];

无效的C ++。在C ++中,不可能声明具有“变量”或运行时大小的数组。在C ++数组声明中,大小始终是编译时常量。

如果您的编译器允许此数组声明,则表示它将其实现为扩展。在这种情况下,您必须自己进行研究,以弄清楚它如何在内部实现这样一个运行时大小的数组。我猜想内部buffer将被实现为指针,而不是真正的数组。如果我的猜测是正确的并且它确实是一个指针,那么将数组地址加载到esi的正确方法可能是

mov buffer,%esi

而不是lea,就像您的代码一样。 lea仅适用于“普通”编译时大小的数组,但不适用于运行时大小的数组。

另一个问题是你的代码中是否真的需要一个运行时大小的数组。可能是你错误地做到了吗?如果您只是将howmany声明更改为

const int howmany = 5046;

数组将变为“普通”C ++数组,您的代码可能会按原样开始工作(即使用lea)。

答案 2 :(得分:1)

所有这些asm指令都需要在相同的 asm语句中,如果你想确定它们是连续的(没有编译器生成的代码),你需要声明输入/输出/ clobber操作数,或者你将踩到编译器的寄存器。

您不能将leamov用于/来自C变量名称(除了在编译器的asm输出中实际定义的全局/静态符号外,但即便如此,你通常也不应该这样做。

不要使用mov指令来设置输入,而是要求编译器使用输入操作数约束为您执行此操作。如果是GNU C内联asm语句的第一个或最后一个指令,通常意味着你做错了并编写了低效的代码。

而BTW,GNU C ++允许使用C99风格的可变长度数组,因此允许howmany为非const,甚至设置为不会优化为常量的方式。任何可以编译GNU样式的内联asm的编译器也支持可变长度数组。

如何正确编写循环

如果这看起来过于复杂,那么https://gcc.gnu.org/wiki/DontUseInlineAsm。在asm中编写一个独立的函数,这样你就可以学习asm,而不必学习gcc及其复杂但强大的inline-asm接口。你基本上必须知道asm并理解编译器才能正确使用它(使用正确的约束来防止在启用优化时出现破坏)。

请注意使用%[ptr]等命名操作数而不是%2%%ebx。让编译器选择使用哪些寄存器通常是件好事,但对于x86,你可以使用除"r"以外的字母,例如"=a"用于rax / eax / ax / al。请参阅https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html以及inline-assembly tag wiki中的其他链接。

我还使用buf_loop%=:在标签上附加一个唯一的数字,所以如果优化器克隆函数或将其内联到多个位置,文件仍然会聚集。

源+编译器asm输出on the Godbolt compiler explorer

void ext(char *);

int foo(void) 
{
    int howmany = 5046;   // could be a function arg
    char buffer[howmany];
    //ext(buffer);

    const char *bufptr = buffer;  // copy the pointer to a C var we can use as a read-write operand
    unsigned char result;
    asm("buf_loop%=:  \n\t"                 // do {
        "   movb     (%[ptr]), %%al \n\t"   // Copy buffer[x] to al
        "   inc     %[ptr]        \n\t"
        "   dec     %[count]      \n\t"
        "   jnz     buf_loop      \n\t"      // } while(ebx>0)
       :   [res]"=a"(result)      // al = write-only output
         , [count] "+r" (howmany) // input/output operand, any register
         , [ptr] "+r" (bufptr)
       : // no input-only operands
       : "memory"   // we read memory that isn't an input operand, only pointed to by inputs
    );
    return result;
}

我使用%%al作为如何显式写入寄存器名称的示例:扩展Asm(带有操作数)需要一个双%来获取asm输出中的文字%。您也可以使用%[res]%0并让编译器在其asm输出中替换%al。 (然后你没有理由使用特定的注册约束,除非你想利用cbwlodsb或类似的东西。)result是{{1}所以编译器会为它选择一个字节寄存器。如果您想要更宽的操作数的低字节,可以使用unsigned char作为例子。

这会使用效率低下的%b[count] clobber 。您不需要编译器将所有溢出到内存中,只是为了确保内存中"memory"的内容与C抽象机器状态匹配。 (通过在寄存器中传递指针来保证。)

gcc7.2 buffer[]输出:

-O3

如果没有内存clobber或输入约束, pushq %rbp movl $5046, %edx movq %rsp, %rbp subq $5056, %rsp movq %rsp, %rcx # compiler-emitted to satisfy our "+r" constraint for bufptr # start of the inline-asm block buf_loop18: movb (%rcx), %al inc %rcx dec %edx jnz buf_loop # end of the inline-asm block movzbl %al, %eax leave ret 会在内联asm块之前出现,在内联asm使用now-stale指针之前释放该堆栈内存。在错误的时间运行的信号处理程序会破坏它。

更有效的方法是使用虚拟内存操作数,告诉编译器整个数组是leave语句的只读内存输入。请参阅{{3}更多关于这个灵活的数组成员技巧,告诉编译器你读取所有数组而不明确指定长度。

在C中你可以在一个强制转换中定义一个新类型,但你不能在C ++中,因此asm而不是一个非常复杂的输入操作数。

using

我还使用了匹配约束,因此int bar(unsigned howmany) { //int howmany = 5046; char buffer[howmany]; //ext(buffer); buffer[0] = 1; buffer[100] = 100; // test whether we got the input constraints right //using input_t = const struct {char a[howmany];}; // requires a constant size using flexarray_t = const struct {char a; char x[];}; const char *dummy; unsigned char result; asm("buf_loop%=: \n\t" // do { " movb (%[ptr]), %%al \n\t" // Copy buffer[x] to al " inc %[ptr] \n\t" " dec %[count] \n\t" " jnz buf_loop \n\t" // } while(ebx>0) : [res]"=a"(result) // al = write-only output , [count] "+r" (howmany) // input/output operand, any register , "=r" (dummy) // output operand in the same register as buffer input, so we can modify the register : [ptr] "2" (buffer) // matching constraint for the dummy output , "m" (*(flexarray_t *) buffer) // whole buffer as an input operand //, "m" (*buffer) // just the first element: doesn't stop the buffer[100]=100 store from sinking past the inline asm, even if you used asm volatile : // no clobbers ); buffer[100] = 101; return result; } 可以直接作为输入,同一寄存器中的输出操作数意味着我们可以修改该寄存器。我们通过使用bufferfoo()中获得了相同的效果,然后使用读写约束来告诉编译器该C变量的新值是我们在寄存器中留下的值。无论哪种方式,我们都将一个值放在一个超出范围而不被读取的死C变量中,但匹配的约束方式对于您不想修改输入值的宏(并且不需要您输入的类型:const char *bufptr = buffer;也可以正常工作。)

int dummybuffer[100] = 100;分配是为了表明它们都出现在asm中,而不是跨越inline-asm合并(如果省略{{1}则确实会发生这种情况输入操作数)。 IDK为什么没有优化buffer[100] = 101;;它应该是死的。另请注意,"m" 不会阻止此重新排序,因此它不能替代buffer[100] = 101; clobber或使用正确的约束。