当前位置: 首页 > 科技观察

C语言与汇编如何相互调用?嵌入式工程师必须掌握

时间:2023-03-13 18:49:18 科技观察

1.gcc内联汇编内联汇编就是在C中直接使用汇编语句进行编程,使程序可以实现一些C语言在C程序中无法完成的任务,例如在下面这个情况下,必须使用内联汇编或嵌入式汇编。程序中使用了饱和算法。该程序需要在协处理器上运行。对程序状态寄存器的操作是在C程序中完成的。格式:__asm____volatile__("asmcode":output:input:changedregisters);以asm或__asm__开头,括号+分号,括号内的内容写汇编指令。命令+\n\t用双引号括起来。参数"asmcode"主要填写汇编代码:"movr0,r0\n\t""movr1,r1\n\t""movr2,r2""output(asm->C)"用于定义输出参数,通常只能是一个变量::"constraint"(variable)"constraint"用于定义变量的存储位置:r表示使用任何可用的寄存器m表示使用变量的内存地址+readableandwritable=write-only&表示输出操作数不能使用输入部分使用的寄存器,只能用“+&”或“=&”来使用“input(C->asm)”来定义输入参数,可以是变量或立即数::"constraint"(variable/immediate)"constraint"用于定义变量的存储位置:r表示使用任何可用的寄存器(包括立即数和变量)m表示usethememoryaddressofthevariablei表示使用立即数注意:使用__asm__和__volatile__表示编译器不会检查后面的内容,而是直接hand它交给汇编程序。如果你想让编译器为你优化,__volatile__可以不加。没有asm代码,不能省略""。没有前面和中间的部分,相应地不能省略:没有变化的部分,相应地必须省略:最后;不能省略,对于C语言来说,这是一条语句。汇编代码必须放在字符串中,不能直接按字符串中间换行。字符串必须用换行符合并成一条指令,也可以使用\t来保持汇编中指令的整洁例1:无参数,无返回值这种情况下输出和输入可以省略:asm(//汇编指令"mrsr0,cpsr\n\t""bicr0,r0,#0x80\n\t""msrcpsr,r0\n\t");例2:内联汇编有参数和返回值做加法,求a+b,结果存在于cinta=100,b=200,c=0;asm("add%0,%1,%2\n\t":"=r"(c):"r"(a),"r"(b):"内存");%0对应变量c%1对应变量a%2对应变量b例3:有参数2和一个返回值让内联汇编做加法,求a+b,结果存入sum,存入a-b中dasmvolatile("add%[op1],%[op2],%[op3]\n\t""sub%[op4],%[op2],%[op3]\n\t":[op1]"=r"(sum),[op4]"=r"(d):[op2]"r"(a),[op3]"r"(b):"memory");%0对应变量c%1对应变量a%2对应变量b3.ATPCS规则:(ARM,thumberprogramcallspecification)为了使单独编译的C语言程序和汇编程序能够相互调用,必须指定一定的规则来进行调用子程序。ATPCS是ARM程序和THUMB程序调用子程序的基本规则。基本ATPCS规定了调用子程序时的一些基本规则,包括以下三个方面:各寄存器的使用规则和对应的名称。使用数据堆栈的规则。参数传递规则。1、寄存器的使用必须满足以下规则:1)子程序通过寄存器R0-R3传递参数。此时寄存器R0-R3可以记录为A1-A4。被调用的子程序在返回前不需要恢复寄存器R0-R3的内容。2)在子程序中,寄存器R4~R11用于保存局部变量。此时寄存器R4~R11可以记录为V1~V8。如果子程序中使用了寄存器V1~V8中的某些寄存器,则子程序进入时必须保存这些寄存器的值,返回前必须恢复这些寄存器的值;没有必要执行子程序中未使用的寄存器。这些操作。在Thumb程序中,通常只有寄存器R4-R7可以用来保存局部变量。3)寄存器R12作为程序调用时的临时寄存器(用来保存SP,函数返回时用于出栈),记为ip。此用法规则通常用于链接子程序之间的代码段。4)寄存器R13作为数据栈指针,记为sp。寄存器R13不能在子程序中用作其他用途。进入子程序时寄存器sp的值必须等于退出子程序时的值。5)寄存器R14称为连接寄存器,记为lr。它用于保存子程序的返回地址。如果返回地址保存在子程序中,寄存器R14可以用作其他用途。6)寄存器R15为程序计数器,记为pc。它不能用于其他目的。ATPCS下ARM寄存器的命名:2.堆栈使用规则:ATPCS规定堆栈为FD类型,即满减堆栈。而栈的操作是8字节对齐的。对于汇编程序来说,如果目标文件中包含外部调用,则必须满足以下条件:外部接口的数据栈必须是8位对齐的,即输入汇编代码后,必须保证直到汇编程序调用外部代码之间,数据栈的栈指针变为偶数个字;在汇编器中使用PRESERVE8伪操作来告诉链接器该汇编器是8字节对齐的。3、参数传递规则:根据参数个数是否固定,子程序可分为参数个数固定的子程序和参数个数可变的子程序。这两个子程序的参数传递规则是不同的。1.参数个数可变的子程序的参数传递规则对于参数个数可变的子程序,当参数个数不超过4个时,可以使用寄存器R0~R3进行参数传递,当参数个数参数个数超过4个,也可以用数据栈来传递参数。传递参数时,把所有的参数都当作字数据存储在连续的存储单元中。然后,将每个姓名数据依次传送到寄存器R0、R1、R2、R3;如果参数超过4个,则将剩余的字数据转移到数据栈中,入栈顺序与参数顺序相反,即最后一个字数据先入栈。根据以上规则,一个浮点数参数可以通过寄存器或数据栈传递,也可以一半通过寄存器传递,另一半通过数据栈传递。示例:voidfunc(a,b,c,d,e)a--r0b--r1c--r2d--r3e--stack2.参数个数固定的子程序参数传递规则对于参数个数固定的子程序,参数如果系统包含用于浮点运算的硬件组件,则传递不同于具有可变数量参数的子例程参数传递规则。浮点型参数将按照以下规则传递:(1)每个浮点型参数按顺序处理;(2)为每个浮点参数分配FP寄存器;一组连续的FP寄存器。第一个整型参数通过寄存器R0~R3传递,其他参数通过数据栈传递。三、子程序结果返回规则1、当结果为32位整数时,可通过寄存器R0返回2、当结果为64位整数时,可通过R0、R1返回,依此类推。3、对于位数较多的结果,需要调用内存进行调用。例如:使用r0接收返回值intfunc1(intm,intn)m--r0n--r1返回值给r0“为什么有些编程规范要求自定义函数的参数不能超过4个?”答:因为参数超过4,需要入栈和出栈,入栈和出栈需要增加很多指令周期。对于参数比较多的情况,我们可以把数据封装成一个结构体,然后传递结构体变量的地址。4、C语言与汇编相互调用C语言与汇编相互调用时,要特别注意遵守相应的ATPCS规则。一、C调用汇编实例1:C调用汇编文件中的一个函数,有返回值。简化代码如下,代码结构可以参考《7. 从0开始学ARM-GNU伪指令、代码编译,lds使用》。;.asmadd:addr2,r0,r1movr0,r2MOVpc,lrmain.cexternintadd(inta,intb);printf("%d\n",add(2,3));a->r0,b->r1返回值通过r0返回计算结果给c代码示例2,用汇编实现一个strcopy函数;.asm.globalstrcopystrcopy:;R0指向目标字符串;R1指向源字符串LDRBR2,[R1],#1;加载字字符和更新源字符字符串指针地址STRBR2,[R0],#1;存储字符并更新目标字符串指针地址CMPR2,#0;判断是否为字符串结尾BNEstrcopy;如果不是,则程序跳转到strcopy,继续循环MOVpc,lr;程序返回//.c#includeexternvoidstrcopy(char*des,constchar*src);intmain(){constchar*srcstr="yikoulinux";chardesstr[]="test";strcopy(desstr,srcstr);返回0;}2。汇编调用C//.cintfcn(inta,intb,intc,intd,inte){returna+b+c+d+e;};.asm;.text.global_start_start:STRlr,[sp,#-4]!;保存返回地址lrADDR1、R0、R0;计算2*i(第二个参数)ADDR2、R1、R0;计算3*i(第三个参数)ADDR3、R1、R2;计算5*iSTRR3,[SP,#-4]!;第五个参数通过堆栈传递ADDR3、R1、R1;计算4*i(第四个参数)BLfcn;调用C程序ADDsp,sp,#4;从栈中删除第5个参数.end假设当程序进入f时,R0中的值为i;intf(inti){returnfcn(i,2*i,3*i,4*i,5*i);}读者加深理解,以内核中的例子为例:arch/arm/kernel/setup.cvoidnotracecpu_init(void){unsignedintcpu=smp_processor_id();----获取CPUIDstructstack*stk=&stacks[cpu];---获取CPU的irqabt和und堆栈指针...#ifdefCONFIG_THUMB2_KERNEL#definePLC"r"----Thumb-2下msr指令不是允许使用立即数,只能使用寄存器#else#definePLC"I"#endif__asm__("msrcpsr_c,%1\n\t"----让CPU进入IRQmode"addr14,%0,%2\n\t"------r14registersavesstk->irq"movsp,r14\n\t"-----设置IRQmode的栈为stk->irq"msrcpsr_c,%3\n\t""addr14,%0,%4\n\t""movsp,r14\n\t"----设置abtmode的栈为stk->abt"msrcpsr_c,%5\n\t""addr14,%0,%6\n\t""movsp,r14\n\t"----设置undmode的栈为stk->und"msrcpsr_c,%7"---返回SVCmode:----上面是代码,下面是输出部分为空:"r"(stk),---对应上面代码中的%0PLC(PSR_F_BIT|PSR_I_BIT|IRQ_MODE),---对应%1"I"(offsetof(structstack,irq[0]))上面代码中----对应上面代码中的%2PLC(PSR_F_BIT|PSR_I_BIT|ABT_MODE),----依此类推,"I"(offsetof(structstack,abt[0])),PLC(PSR_F_BIT|PSR_I_BIT|UND_MODE),"I"(offsetof(structstack,und[0])),PLC(PSR_F_BIT|PSR_I_BIT|SVC_MODE):"r14");----以上为输入操作数列表,r14是clobberedregister的列表}