为何C内联汇编需同时使用earlyclobber与匹配约束?
关于Linux内核__copy_user中GCC汇编约束的疑问
我在Linux内核的arch/x86/lib/usercopy_32.c文件中遇到如下代码:
#define __copy_user(to, from, size) do { int __d0, __d1, __d2; __asm__ __volatile__( " cmp $7,%0\n" " jbe 1f\n" " movl %1,%0\n" " negl %0\n" " andl $7,%0\n" " subl %0,%3\n" "4: rep; movsb\n" " movl %3,%0\n" " shrl $2,%0\n" " andl $3,%3\n" " .align 2,0x90\n" "0: rep; movsl\n" " movl %3,%0\n" "1: rep; movsb\n" "2:\n" ".section .fixup,\"ax\"\n" "5: addl %3,%0\n" " jmp 2b\n" "3: lea 0(%3,%0,4),%0\n" " jmp 2b\n" ".previous\n" _ASM_EXTABLE(4b,5b) _ASM_EXTABLE(0b,3b) _ASM_EXTABLE(1b,2b) : "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2) : "3"(size), "0"(size), "1"(to), "2"(from) : "memory"); } while (0)
这段代码本质是将size字节的数据从from地址复制到to地址。我的疑问是:earlyclobber的作用是避免输入与输出使用同一寄存器,而匹配约束要求二者使用同一寄存器,为何要同时使用这两种约束?这二者看似矛盾,其背后原因是什么?
这其实是GCC汇编约束里的常见技巧,核心逻辑是先读取输入值,再覆盖对应寄存器,拆解来看:
匹配约束(如
"3"(size)、"0"(size)):告诉编译器,输出约束里的第N个位置和输入约束里的第N个位置共用同一个寄存器。比如"0"(size)是让输出的%0(对应ecx寄存器)预先加载输入的size值,这样汇编代码一开始就能直接用这个寄存器里的输入值,省去了额外的寄存器拷贝操作。earlyclobber(
=&c、=&D、=&S):告诉编译器,这些输出寄存器会在汇编代码执行过程中被提前修改——也就是在所有输入操作完成前,寄存器的值就会被覆盖。但这里的关键是:输入值已经在汇编代码启动前被加载到这些寄存器里了,且汇编一开始就读取了这些输入值,后续修改寄存器不会影响输入的使用,因为输入已经被读取完毕。
拿size对应的%0举例:
- 输入约束
"0"(size)让编译器把size的值放到ecx(即%0)中; - 汇编第一行
cmp $7,%0就读取了这个值; - 紧接着
movl %1,%0就修改了%0的值,这时候=&c的作用就是提醒编译器:别把其他输入值放到ecx里,我马上要改这个寄存器,不然会破坏还没用到的输入数据。
总结来说,匹配约束是让输入值提前进驻输出寄存器,earlyclobber是禁止编译器把其他输入放到这个即将被修改的寄存器里,二者配合既节省了寄存器拷贝的开销,又避免了输入值被意外覆盖,完全不存在矛盾。
内容的提问来源于stack exchange,提问作者huangjl
相关产品推荐
相关产品推荐

