一:80386寄存器
8个32-bit寄存器:%eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp;
8个16-bit寄存器:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp;
8个8-bit寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl;它们事实是寄存器%ax,%bx,%cx,%dx的高8bit和低8bit
6个段寄存器:%cs(code),%ds(data),%ss(stack),%es(extend),%fs, %gs;
3个控制寄存器:%cr0,%cr2,%cr3;
6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7;
2个测试寄存器:%tr6, %tr7
8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7);
eax 累加和结果寄存器
ebx 数据基址寄存器
ecx 循环计数器寄存器
edx i/o指针寄存器
esi 源变址寄存器
edi 目的变址寄存器
esp 栈顶指针寄存器
ebp 栈底指针寄存器cs 代码段寄存器
ds 数据段寄存器
ss 堆栈段寄存器
es,fs及gs 附加数据段寄存器IP:指令指针寄存器,将CS:IP获取到的指令存储EIP(指令寄存器)中
二:内联汇编基本格式为:
asm ( 汇编程序模板
: 输出操作数
: 输入操作数
: 修饰寄存器列表
);
由于占位符前面使用一个百分号(%),为了区别占位符和寄存器,GCC规定C/C++表达式的内联汇编中, "Instruction List"中直接写出的寄存器前必须使用两个百分号(%%)
占位符:GCC规定一个内联汇编语句最多可以有10个Input/Output操作表达式,按照它们被列出的顺序依次赋予编号0到9,对于占位符中的数字而言,和这些编号是一一对应的。
三:约束修饰符:
当使用约束时,对于更精确的控制超过了对约束作用的需求,GCC 给我们提供了约束修饰符。最常用的约束修饰符为:
- "=" : 意味着对于这条指令,操作数为只写的;旧值会被忽略并被输出数据所替换。
- "&" : 意味着这个操作数为一个早期改动的操作数,其在该指令完成前通过使用输入操作数被修改了。因此,这个操作数不可以位于一个被用作输出操作数或任何内存地址部分的寄存器。如果在旧值被写入之前它仅用作输入而已,一个输入操作数可以为一个早期改动操作数
- "%":只能用于Input操作表达式中,用于向GCC声明:当前Input操作表达式中的C/C++表达式可以和下一个Input操作数表达式中的C/C++表达式互换,这个修饰符号一般用于符合交换律运算,比如加(+),乘(*),与(&),或(|)等等。
- 例:asm("addl %1, %0\n\t" :"=r"(__out): "%r"(__in1), "0"(__in1));
3.1. 寄存器操作数约束
r : Register(s)
a: %eax, %ax, %al
b: %ebx, %bx, %bl
c: %ecx, %cx, %cl
d: %edx, %dx, %dl
S: %esi, %si
D: %edi, %di
3.2. 内存操作数约束
当操作数位于内存时,任何对它们的操作将直接发生在内存位置,这与寄存器约束相反,后者首先将值存储在要修改的寄存器中,然后将它写回到内存位置。但寄存器约束通常用于一个指令必须使用它们或者它们可以大大提高处理速度的地方。当需要在 “asm” 内更新一个 C 变量,而又不想使用寄存器去保存它的值,使用内存最为有效。
asm("sidt %0\n\t" : :"m"(loc));
3.3. 匹配(数字)约束
在某些情况下,一个变量可能既充当输入操作数,也充当输出操作数。可以通过使用匹配约束在 "asm" 中指定这种情况。
asm("incl %0\n\t" :"=a"(var):"0"(var));
例:
void *memcpy_asm(void *dst, const void *src, size_t n)
{
int d0=0, d1=0, d2=0;
asm volatile(
"shr $2, %0 \n\t"
"rep ; movsl \n\t"
"movl %4,%%ecx \n\t"
"andl $3,%%ecx \n\t"
"jz 1f \n\t"
"rep ; movsb \n\t"
"1:"
/*分别表示第零个操作数(%0)–到第二个(%2)操作数*/
: "=&c"(d0),"=&D"(d1),"=&S"(d2)
/*分别表示第三个操作数(%3)到第六个操作数(%6);其中%3个=第%0个;%5==%1;%6==%2*/
: "0"(n),"g"(n),"1"((long)dst),"2"((long)src)
:"memory");
return dst;
}
寄存器约束:
通用寄存器:"a":表示使用%eax/%ax/%al
"b":表示使用%ebx/%bx/%bl
"c":表示使用%ecx/%cx/%cl
"d":表示使用%edx/%dx/%dl
"D":表示使用%edi/%di
"S":表示使用%esi/%si
"r":表示使用一个通用寄存器,有GCC在%eax/%ax/%al,%ebx/%bx/%bl,%ecx/%cx/%cl,%edx/%dx/%dl中选择一个GCC认为合适的。
"q":表示通用寄存器,和约束"r"的意义相同。
"A":把eax和edx合成一个64位的寄存器(use long longs)内存:
"m" : 允许一个内存操作数,可以使用机器普遍支持的任一种地址,不需要借助寄存器。
"o" : 允许一个内存操作数,但只有当地址是可偏移的。即,该地址加上一个小的偏移量可以得到一个有效地址。
"V" : 一个不允许偏移的内存操作数。换言之,任何适合 "m" 约束而不适合 "o" 约束的操作数。
" ":操作数位内存变量,但是寻址方式为自动增量
"p":操作数是一个合法的内存地址(指针)寄存器或内存:
"g" : 允许任一寄存器、内存或者立即整形操作数,不包括通用寄存器之外的寄存器。
"X":操作数可以是任何类型立即数:
"i":表示输入表达式是一个立即数(整数),不需要借助寄存器。
"n" : 允许一个带有已知数字的立即整形操作数。许多系统不支持汇编时期的常量,因为操作数少于一个字宽。对于此种操作数,约束应该使用 'n' 而不是'i'。
"I":0~31之间的立即数(用于32位移位指令)
"J":0~63之间的立即数(用于64位移位指令)
"N":0~255之间的立即数(用于out指令)
"K":0xff
"L":0xFFFF
"M":0,1,2或3(lea指令的移位)浮点数约束:
"f":表示使用浮点数寄存器
"t":表示使用第一个浮点数寄存器
"u":表示使用第二个浮点数寄存器
"F":表示输入表达式是一个立即数(浮点数),不需要借助寄存器。
"G":标准的80387浮点数匹配占位符约束:
"0","1",..."9":表示用它限制的操作数与某个指定的操作数匹配,也即该操作数
就是指定的那个操作数,例如用"0"去描述"%1"操作数,那么"%1"引用的其实就是"%0"操作数,注意作为限定符字母的 0~9 与指令中的"%0"-"%9"的区别,前者描述操作数,后者代表操作数。
四:存储器操作数
在Intel语法中,基址寄存器包含在"["和"]"中,然而在AT&T中,它们变为"("和")"。另外,在 Intel 语法中, 间接内存引用为:
"section:[base + index*scale + disp]"
在 AT&T中变为:
"section:disp(base, index, scale)"。
需要牢记的一点是,当一个常量用于disp或scale,不能添加 "$" 前缀。
其中:base和index是任意的32-bit base和index寄存器。scale可以取值1,2,4,8。如果不指定scale,则默认值为1。section可以指定任意的段寄存器作为段前缀。
例:
(1)-4(%ebp):base=%ebp,disp=-4,section没有指定,由于base=%ebp,所以默认的section=%ss,index,scale没有指定,则index为0。
(2)foo(,%eax,4):index=%eax,scale=4,disp=foo。其他域没有指定。这里默认section=%ds。
(3)foo(,1):这个表达式引用的是指针foo指向的地址所存放的值。注意这个表达式中没有base和index,并且只有一个逗号,这是一种异常语法,但却合法。
现在我们看到了Intel语法和AT&T 语法之间的一些主要差别。我仅仅写了它们差别的一部分而已。关于更完整的信息,请参考 GNU 汇编文档。现在为了更好地理解,我们可以看一些示例。