g++内联汇编
C\C++语言具有asm关键字
在我vscode中我使用的g++编译器进行编译的(g++跟gcc一样使用的是AT&T语法)
跟visualstudio中的MSCV编译器不同(MSCV使用的是intel语法)
其需要用asm关键字
asm(
"语句1\n\t"
"语句2\n\t"
)//"\n\t"用于间隔语句1和语句2
输入多个语句时可以用"\n\t"
注意在vscode使用内联汇编需要AT&T"汇编语法
立即数需要用"美元"符号--------------------$123
且各操作顺序是由前到后----------------------mov %eax,%ebx是将eax赋给ebx(寄存器需要使用%符号)
注意,我所用的是X64的汇编,使用指令的时候传递数据为8字节,所以对应寄存器都需加r(eax变为rax其余都是此规则)如要移动四字节就用对应指令+l(如mov-movl)
传参:
指令部分用%数字(标识第几个参数)------------%0 %1
指令下方用冒号加相应的-------------------:"类型"(变量名)以此类推
#include <stdio.h>
int main()
{
int src = 1;
int dst;
asm (
"movl %1, %0\n\t"
"add $1, %0\n\t"
"add $3, %0\n\t"
: "=r" (dst)
: "r" (src)
);
printf("%d\n", dst);
}
参考(2条消息) 常用嵌入式汇编限定符_植物奶哇咔咔的博客-CSDN博客
操作数
操作数是运算符作用于的实体,是表达式中的一个组成部分,它规定了指令中进行数字运算的量 。
表达式是操作数与操作符的组合。
分为
立即操作数:指令要操作的数据以常量的形式出现在指令中,称为立即数,它只能作为源操作数 。
寄存器操作数:指令要操作的数据存放在CPU中的寄存器里,指令中给出寄存器名即可 。
内存操作数:指令要操作的数据存放在内存某些单元中,指令中给出内存单元物理地址(实际上指令只给出了偏移地址,段地址采用隐含方式给出,也可以使用跨段方式指出当前段地址)
下图用于外部函数的参数传入内部的汇编中,也可用来将内部的参数传到外部的函数中具体用法是在汇编代码最后加上
:"限定符"(需要传入的外部变量)
:"限定符"(需要传出的外部变量)
暂时未搞清上下俩个的区别,因为上面如果怎加了加号如 :"+r"就变成了直接可读可写的。暂时归类为 上面的冒号可读可写,下面冒号只读.
如果要引入多个变量 在限定符后面加,"限定符"(需要传入的外部变量)就可以引用多个,用%0,%1,%2.....来调用。
分类 | 限定符 | 描述 |
---|---|---|
通用寄存器 | “a” | 将输入变量放入eax这里有一个问题:假设eax已经被使用,那怎么办?其实很简单:因为GCC 知道eax 已经被使用,它在这段汇编代码的起始处插入一条语句pushl %eax,将eax 内容保存到堆栈,然 后在这段代码结束处再增加一条语句popl %eax,恢复eax的内容 |
“b” | 将输入变量放入ebx | |
“c” | 将输入变量放入ecx | |
“d” | 将输入变量放入edx | |
“s” | 将输入变量放入esi | |
“d” | 将输入变量放入edi | |
“q” | 将输入变量放入eax,ebx,ecx,edx中的一个 | |
“r” | 将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个 | |
“A” | 把eax和edx合成一个64 位的寄存器(use long longs) | |
内存 | “m” | 内存变量 |
“o” | 操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址 | |
“V” | 操作数为内存变量,但寻址方式不是偏移量类型 | |
“ ” | 操作数为内存变量,但寻址方式为自动增量 | |
“p” | 操作数是一个合法的内存地址(指针) | |
寄存器或内存 | “g” | 将输入变量放入eax,ebx,ecx,edx中的一个,或者作为内存变量 |
“X” | 操作数可以是任何类型 | |
立即数 | “I” | 0-31之间的立即数(用于32位移位指令) |
“J” | 0-63之间的立即数(用于64位移位指令) | |
“N” | 0-255之间的立即数(用于out指令) | |
“i” | 立即数 | |
“n” | 立即数,有些系统不支持除字以外的立即数, 这些系统应该使用“n”而不是“i” | |
匹配 | “0”,“1”...“9” | 表示用它限制的操作数与某个指定的操作数匹配,也即该操作数就是指定的那个操作数,例如“0”去描述“%1”操作数,那么“%1”引用的其实就是“%0”操作数,注意作为限定符字母的0-9与指令中的“%0”-“%9”的区别,前者描述操作数,后者代表操作数。 |
& | 该输出操作数不能使用过和输入操作数相同的寄存器 | |
操作数类型 | “=” | 操作数在指令中是只写的(输出操作数) |
“+” | 操作数在指令中是读写类型的(输入输出操作数) | |
浮点数 | “f” | 浮点寄存器 |
“t” | 第一个浮点寄存器 | |
“u” | 第二个浮点寄存器 | |
“G” | 标准的80387浮点常数 | |
% | 该操作数可以和下一个操作数交换位置例如addl的两个操作数可以交换顺序(当然两个操作数都不能是立即数) | |
# | 部分注释,从该字符到其后的逗号之间所有字母被忽略 | |
* | 表示如果选用寄存器,则其后的字母被忽略c9ff95d8-f7d1-40e8-91a6-80c98643d2ea |
参数调用
int main()
{
int a=0,b=0,c=2,d=3,e=4,f=5,g=6,h=7,i=8,j=9,k=10,m=11;
asm(
"add %2,%0\n\t"
"add %13,%1\n\t"
:"+r"(a),"+r"(b),"+r"(c)
:"r"(d),"r"(e),"r"(f),"r"(g),"r"(h),"r"(i),"r"(j),"r"(k),"r"(m)
);//此处给出的输出参数三个都是可读可写的"+r"而不是只可写的"=r",当调用参数的数字,超过输入参数数量的就用下几个(超过数量)可读的参数
//如此处都为"+r",则%12就是a,%13就是b,%14就是c
printf("a=%d,b=%d,c=%d,d=%d",a,b,c,d);
return 0;
}
int main()
{
int a=0,b=0,c=2,d=3,e=4,f=5,g=6,h=7,i=8,j=9,k=10,m=11;
asm(
"add %2,%0\n\t"
"add %13,%1\n\t"
:"=r"(a),"+r"(b),"+r"(c)
:"r"(d),"r"(e),"r"(f),"r"(g),"r"(h),"r"(i),"r"(j),"r"(k),"r"(m)
);//此处a为只写所以不可读,%12为b,%13为c,%14就超出调用范围了
printf("a=%d,b=%d,c=%d,d=%d",a,b,c,d);
return 0;
}
此处b=b+b=2符合条件
此处b=b+c=3也符合条件所以推理成立
"超过参数数量的就从头读取下一个可读的参数"
注意
在内联汇编中,使用多组asm关键字会将eax清零其他寄存器没变
#include<stdio.h>
int main()
{
int a=0,b=1,c=2,eax=0;
int*d=&b;
asm(
"movl 4(%0),%%ebx\n\t"
:"+r"(d)
:
);
asm(
"movl %%eax,%0\n\t"
:"+r"(eax)
);
printf("eax=%x\n",eax);
asm(
"movl %ebx,%eax\n\t"
);
asm(
"movl %%eax,%0\n\t"
:"+r"(eax)
:
);
printf("eax=%x\n",eax);
printf("a=%x,b=%x,c=%x,d=%x",a,b,c,d);
return 0;
}
多段asm使eax清零了
#include<stdio.h>
int main()
{
int a=0,b=1,c=2,eax=0;
int*d=&b;
asm(
"movl 4(%0),%%ebx\n\t"
:"+r"(d)
:
);
asm(
"movl %%eax,%0\n\t"
:"+r"(eax)
);
printf("1.eax=%x\n",eax);
asm(
"movl %%ebx,%%eax\n\t"
"movl %%eax,%2"
:"+r"(a),"+r"(b),"+r"(eax)
:
);
printf("2.eax=%x\n",eax);
printf("a=%x,b=%x,c=%x,d=%x",a,b,c,d);
return 0;
}
在同段的eax却未清零
指针用法
#include<stdio.h>
int main()
{
int a=0,b=1,c=2,eax=0;
int*d=&b;
asm(
"movl 4(%0),%%ebx\n\t"
:"+r"(d)
:
);//4(%0)=d=&b
asm(
"movl %%eax,%0\n\t"
:"+r"(eax)
);
printf("1.eax=%x\n",eax);
asm(
"movl (%%ebx),%%eax\n\t"
"movl %%eax,%1"
:"+r"(a),"+r"(eax)
:
);//取用ebx存的地址对应的数据放入eax中
printf("2.eax=%x\n",eax);
printf("a=%x,b=%x,c=%x,d=%x",a,b,c,d);
return 0;
}
同时在使用指针用法的时候其修改了我未使用的参数,所以尽量不要在函数内加入未使用的参数,要使用的参数才列下去
"(地址)"-是取用地址内的数据
"数字(数据)"-是取用数据位置所在的多少字节如n(%0)就是取用%0的参数所在位置的n字节数据