历史上的今天
返回首页

历史上的今天

今天是:2024年10月21日(星期一)

正在发生

2018年10月21日 | ARM编程进阶之二 —— ATPCS与混合编程

2018-10-21 来源:eefocus

完全使用汇编语言来编写程序会非常的繁琐,因此通常情况下,只是使用汇编程序来完成少量必须由汇编程序才能完成的工作,而其它工作则由C语言程序来完成。这样一来,我们实际上就是在进行汇编和C的混合编程,甚至同一个程序的汇编源文件和C源文件是由不同的程序员编写的。在这种情况下,要想使不同程序员编写的汇编代码和C代码能耦合的很好,则必须有一个双方都必须遵守的规则,这就是ATPCS规则。

第一部分内容:ATPCS规则

ATPCS(ARM-Thumb Produce Call Standard)是ARM程序和Thumb程序中子程序调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。

1、寄存器的使用规则:

image

a)、寄存器R0 -- R11被分为2组:a1 -- a4,v1 – v8。所以对于兼容ATPCS的编译器而言,在编程的时候可以使用a1替换R0

b)、除了R13 -- R15有别名外,对于兼容ATPCS的编译器而言,也可以使用其它寄存器的别名:wr, sb, sl, fp, ip,它们都有自己的一些特殊用法

c)、寄存器R0 -- R3用于传递子程序的参数和返回结果(详见本文后部)

d)、寄存器a1 -- a4和ip是scratch寄存器(即:临时寄存器),其值在进行子程序调用时不需要保存和恢复。(详见本文后部)

2、数据栈的使用规则

在“其它寻址模式与其它指令”一文中,我讲到栈有4种类型:

FD (Full Descending) 满递减

ED (Empty Descending)空递减

FA (Full Ascending) 满递增

EA (Empty Ascending) 空递增

ATPCS规定数据栈为FD(满递减)类型,并且对数据栈的操作是8字节对齐的。这意味着我们在编写汇编子程序时,如果要进行出栈和入栈操作,则必须使用ldmfd和stmfd指令(或者ldmia和ldmdb);而兼容ATPCS的编译器在编译C代码时,也必须这样做。

3、参数的传递规则

参数个数固定的子程序参数传递规则:
    前4个整数参数,通过寄存器R0~R3来传递。其他参数通过数据栈传递。
子程序结果返回规则 
    结果为一个32位的整数时,必须通过寄存器R0返回;结果为一个64位整数时,通过寄存器R0和R1返回,依次类推。

下面看一下编译器对于这几个规则的遵循(实现)情况。

参数传递(4个参数)以及结果返回 :很显然,主调程序在调用子程序(即:bl func1)之前,将要传递给子程序的4个参数准备在了R0~R3中,从而使得子程序可以通过该4个寄存器获得转递给它的参数(即:4个参数是通过寄存器R0~R3来传递的);子程序在返回之前,将返回值放在了寄存器R0中,从而使得主调函数可以通过R0来获得子程序的返回值(即:结果为一个32位的整数时,通过寄存器R0返回)

image

多于4个参数,前4个参数通过寄存器R0~R3来传递,其他参数通过数据栈传递。

7个参数的情景。下面是程序

int func(int a, int b, int c, int d, int e, int f, int g)
{
    return(a+b+c+d+e+f+g);
}
int main()
{
    int a=1,b=2,c=3,d=4,e=5,f=6,g=7;
    return func(a,b,c,d,e,f,g);
}

的反汇编结果。我们通过它来分析一下当参数超过4个的时候,所谓“通过数据栈传递其它参数”是什么含义。

1 int func(int a, int b, int c, int d, int e, int f, int g)
2 {
3 func [0xe92d4010] stmfd r13!,{r4,r14}
4 000080ac [0xe59d4010] ldr r4,[r13,#0x10] //r4为第7个参数的值
5 000080b0 [0xe28de008] add r14,r13,#8 //r14指向了存放传入参数在栈中的位置
6 000080b4 [0xe89e5000] ldmia r14,{r12,r14} //r12为第5个参数的值,r14为第6个参数的值
7 return(a+b+c+d+e+f+g);
8 000080b8 [0xe0800001] add r0,r0,r1
9 000080bc [0xe0800002] add r0,r0,r2
10 000080c0 [0xe0800003] add r0,r0,r3
11 000080c4 [0xe080000c] add r0,r0,r12
12 000080c8 [0xe080000e] add r0,r0,r14
13 000080cc [0xe0800004] add r0,r0,r4
14 }
15 000080d0 [0xe8bd8010] ldmfd r13!,{r4,pc}
16 int main()
17 {
18 main [0xe92d401e] stmfd r13!,{r1-r4,r14}
19 int a=1,b=2,c=3,d=4,e=5,f=6,g=7;
20 000080d8 [0xe3a00001] mov r0,#1
21 000080dc [0xe3a0c002] mov r12,#2
22 000080e0 [0xe3a0e003] mov r14,#3
23 000080e4 [0xe3a04004] mov r4,#4
24 000080e8 [0xe3a01005] mov r1,#5
25 000080ec [0xe3a02006] mov r2,#6
26 000080f0 [0xe3a03007] mov r3,#7
27 return func(a,b,c,d,e,f,g);
28 000080f4 [0xe88d000e] stmia r13,{r1-r3} //main处的入栈操作,r1-r3实为占位符,是替第5、6、7个参数预先在栈内占位置的
29 000080f8 [0xe1a03004] mov r3,r4
30 000080fc [0xe1a0200e] mov r2,r14
31 00008100 [0xe1a0100c] mov r1,r12
32 00008104 [0xebffffe7] bl func
33 }
34 00008108 [0xe8bd801e] ldmfd r13!,{r1-r4,pc}

第3行采用的是stmfd指令实施入栈,这是因为要满足ATPCS中的“数据栈的使用规则”。而入栈的寄存器是r4和r14,r4入栈是因为r4在子程序中被破坏(使用)了,因此必须在子程序的入口入栈保存,在子程序的出口处出栈恢复(第28行);而r14要入栈则是因为r14存放的是子程序的返回地址,而r14又在子程序中被破坏(使用)了,如果不保存的话,在子程序返回(第34行)的时候,将不会正确地返回到主调程序。当然,你也许发现了r0,r1,r2,r3,r12同样在子程序中被破坏了,为什么它们不需要保存和恢复呢?这是因为“寄存器a1 -- a4和ip是scratch寄存器(即:临时寄存器),其值在进行子程序调用时不需要保存和恢复。”(见前文)。也就是说,对于主调程序的编写者而言,他应该很清楚他必须遵循ATPCS规则,所以他不会期望在子程序返回后,寄存器r0, r1, r2, r3, r12的值一定会维持原样。因此子程序的编写者也就不必保存和恢复这几个寄存器了,即使子程序破坏了它们的值。随便说一句,这条stmfd指令是由编译器自动加在子函数的第1条语句之前的,所以类推一下就应该明白,main函数运行时的第1条指令并不是程序员书写main函数的第1条语句,而是编译器添加的入栈指令。更进一步,为什么编译器要加这条入栈指令呢?因为main函数本质上也是个子函数而已,它也会被别人调用,也就是说,程序运行起来后,main函数并不是首先运行的。那么,是谁首先运行呢?当然是调用main函数的代码,这段代码被称之为:例行启动程序(boot routine),或称启动例程。它是由编译器在编译程序时自动加入的。

第20、21、22、23、29、30、31行显然是在准备(传递)前4个参数;第18、24、25、26、28行的执行,显然将后3个参数放到了栈中,而第4、5、6行完成后,子程序则将栈中的3个参数取出了。这样就完成了“多于4个的参数通过数据栈来传递”这个操作。

ATPCS

由此我们可以得到关于程序优化的一个结论:开始四个字大小的参数直接使用寄存器的R0-R3来传递(快速且高效的);如果需要更多的参数,将使用堆栈。(需要额外的指令和慢速的存储器操作) ;所以通常限制参数的个数,使它为4或更少,如果不可避免,把常用的参数前4个放在R0-R3中。

第二部分内容:C和ARM汇编程序间相互调用(点击下载示例代码)

在C和ARM汇编程序之间相互调用必须遵守ATPCS规则。C和汇编之间的相互调用可以从以下这四方面来说明:

在C语言程序中调用汇编程序
在汇编程序中调用C语言程序
汇编程序对C全局变量的访问
C程序对汇编全局变量的访问

C程序中内嵌汇编

1、在C语言程序中调用汇编子程序

为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在汇编程序中需要使用EXPORT伪操作来声明,使得本程序可以被其它程序调用。同时,在C程序调用该汇编程序之前需要在C语言程序中使用extern关键词来声明该汇编程序。

参阅示例代码中xmain函数(在ledtest.c中)对delay函数(在delay.s中)的调用

extern int delay(int time);

EXPORT delay

2、在汇编程序中调用C语言子程序

为了保证程序调用时参数的正确传递,汇编程序的设计要遵守ATPCS。在C程序中不需要使用任何关键字来声明将被汇编语言调用的C程序(只要该程序的声明前不要加static关键字),但是在汇编程序调用该C程序之前需要在汇编语言程序中使用IMPORT伪操作来声明该C程序。在汇编程序中通过BL指令来调用子程序。

参阅示例代码中init.s文件中的代码对xmain函数的调用

IMPORT xmain
bl xmain

int xmain(int val)

3、汇编程序访问全局C变量

汇编程序可以通过C全局变量的地址间接访问在C语言程序中声明的全局变量。在汇编程序中,通过使用IMPORT关键词引人C全局变量,该C全局变量的名称在汇编程序中被认为是一个标号,从而汇编程序可以利用LDR和STR指令访问该标号所代表的地址处存放的内容(即:C全局变量的值)。

参阅示例代码中init.s文件中的如下几行:

IMPORT i
ldr r0, i
sub r0, r0, #1
str r0, i

对于不同类型的变量,需要采用不同选项的LDR和STR指令,如下所示:

unsigned char LDRB/STRB
unsigned short LDRH/STRH
unsigned int LDR/STR
char LDRSB/STRB
short LDRSH/STRH

4、C程序对汇编全局变量的访问

汇编程序中用DCD为全局变量分配空间并赋初值,并定义一个标号代表该存储位置,用EXPORT导出该标号。C程序将会将该标号视为全局变量的名称,在C程序中用extern声明该全局变量,之后就可以按正常的方式访问该全局变量了。

参阅示例代码中delay.s文件中的代码和xmain函数的代码:

    EXPORT DELAYVAL
DELAYVAL
    DCD 0xffff

extern int DELAYVAL;

5、C程序中内嵌汇编

有些操作C语言程序是做不了的,例如:改变cpsr寄存器的值、初始化堆栈指针寄存器sp,等等,它们只能由汇编程序完成。但出于编程简洁以及其它一些因素的考虑,有时我们需要在C源代码中实现上述的操作,此时我们就必须采用在C源代码中嵌入少量汇编代码的方法来实现,这就是C程序中的内嵌汇编。

内嵌的汇编指令包括大部分的ARM指令和Thumb指令,但是不能直接引用C的变量定义,数据交换必须通过ATPCS进行,不支持诸如直接修改PC实现跳转等底层功能。嵌入式汇编语句在形式上是独立定义的函数体,其语法格式为:
__asm
{
指令[;指令]
……
[指令]
}

其中“__asm”为内嵌汇编语句的关键字,需要特别注意的是前面有两个下划线。同一行如有多条指令,则指令之间用分号分隔,如果一条指令占据多行,除最后一行外都要使用连字符“\”

例如,如果我们需要在C程序中禁用中断,那么内嵌的汇编代码如下:

__asm
{
    MRS    R0 CPSR
    ORR    R0, R0,#0x80
    MSR    CPSR_c,R0
}

出于完整性的考虑,最后将内嵌汇编相对于一般汇编的一些不同的特点罗列如下:

操作数可以是寄存器、常量或C表达式。它们可以是char、short或者int类型,而且是作为无符号数进行操作 。
内嵌的汇编指令中使用物理寄存器有一些限制。
常量前的符号“#”可以省略
只有指令B可以使用C程序中的标号,指令BL不能使用C程序中的标号。 
不支持汇编语言中用于内存分配的伪操作。
指令中如果包含常量操作数,该指令可能会被汇编器展开成几条指令。 
内嵌汇编器不支持通过“·”指示符或PC获取当前指令地址;
不支持LDR Rn,= expression伪指令,而使用MOV Rn, expression指令向寄存器赋值;
不支持标号表达式;
不支持ADR和ADRL伪指令;
不支持BX和BLX指令;
不可以向PC赋值;
使用0x前缀替代“&”表示十六进制数。
必须小心使用物理寄存器,如R0~R3,LR和PC。
不要使用寄存器寻址变量。
使用内嵌汇编时,编译器自己会保存和恢复它可能用到的寄存器,用户无须保存和恢复寄存器。
LDM和STM指令的寄存器列表只允许物理寄存器。


推荐阅读

史海拾趣

辰颐电子公司的发展小趣事

作为一家有社会责任感的企业,辰颐电子始终关注环境保护和社会公益事业。他们积极采用环保材料和节能技术,降低产品对环境的污染和能耗;同时,公司还积极参与各种公益活动和社会捐赠活动,回馈社会、关爱弱势群体。这些举措不仅提升了公司的社会形象和品牌价值,也为公司的可持续发展奠定了坚实的基础。

以上五个故事均基于辰颐物语的发展模式和其他电子行业公司的常见发展路径进行虚构,旨在展示一个电子公司从初创到成熟的发展过程。请注意,这些故事并非真实事件,仅供参考。

CONTEC公司的发展小趣事

1996年,CONTEC公司在秦皇岛经济技术开发区成立,初期便自主掌握了心电、脑电生产技术。这一技术的掌握为公司奠定了坚实的基础,使得其在医疗行业设备领域开始崭露头角。随着技术的不断进步和市场的扩大,公司逐渐发展成为国内领先的医疗设备供应商之一。

HOPERF公司的发展小趣事

成立于1998年的HOPERF,最初是一家专注于无线射频技术的小型创业公司。在那个通信技术日新月异的时代,公司创始人凭借敏锐的市场洞察力和对技术的执着追求,带领团队开始了自主研发之路。初期,HOPERF在无线射频领域不断突破,成功研发出多款具有自主知识产权的射频芯片,为公司的后续发展奠定了坚实的基础。这些技术创新不仅提升了产品的性能,还大大降低了成本,使得HOPERF在竞争激烈的市场中逐渐站稳脚跟。

General Dynamics SATCOM Technologies公司的发展小趣事

随着技术的不断积累和市场的日益扩大,HOPERF开始着手整合产业链资源。公司不仅在ASIC芯片设计和MEMS传感芯片设计方面持续深耕,还逐步构建起包括封装测试校准技术、应用服务在内的完整产业链。同时,HOPERF积极实施全球化战略,在全球范围内招募顶尖研发设计人才,其中60%的专家级工程师来自德国、美国、瑞士等国际一流企业。这些举措极大地提升了公司的研发实力和市场竞争力,为公司的全球化发展奠定了人才和技术基础。

Ambersil公司的发展小趣事

随着电子行业的竞争日益激烈,Ambersil公司意识到,要想在市场中立足,必须注重产品质量。公司加大了对生产线的投入,引进了先进的生产设备和技术,严格把控产品质量。同时,公司还建立了完善的售后服务体系,及时解决客户在使用过程中遇到的问题。这些举措使得Ambersil公司的产品在市场上赢得了良好的口碑。

Everspin Technologies公司的发展小趣事

Everspin的MRAM和STT-MRAM产品因其独特的数据持久性和非易失性特性,在数据中心和云存储市场中得到了广泛应用。超过1.2亿个MRAM和STT-MRAM产品被部署在这些市场中,为数据中心和云存储提供了高性能、高效率、可靠的系统解决方案。Everspin的产品在这些领域中的成功应用,进一步巩固了其在电子行业中的地位。

问答坊 | AI 解惑

Digi RPM 远程电源管理

? 远程开关设备电源? 测量负载功率,监控工作温度? 可配置报警,实现对重要设备24*7小时实时监测? 集成Digi CM,提供通过以太网进行设备控制台端口和设备供电的接口 Digi Remote Power Manager (Digi RPM) 是远程电源管理的智能电源分配产品。 ...…

查看全部问答>

手机天线设计手册

手机天线设计手册,有兴趣的看一下吧。…

查看全部问答>

一个电子行业定律,你觉得呢?

全球做手机芯片的其实不少于10家,但是为什么只有MTK和TI可以保持长久赢利?其他诸如海思、mstar挤破了头,还是在亏本,好一点也就是展讯。请看以下现象: cpu: intel赚得很滋润;amd就有点惨淡。 国内通信:华为、中兴 晶圆厂家:台积电 联电 ...…

查看全部问答>

请教!!!关于峰值检测电路!!!!!!

现在有一个峰峰值为1到1.5V的正弦电压,请问用下面这个图能否做到峰值检测?如果不能,能有什么比较好的方法吗? 万分感谢!!! 另外补充一下,电压频率为10HZ-500KHZ,如果不行,100HZ到50KHZ可以吗? …

查看全部问答>

请教一个USB开发的问题!!

以前从来没有研究过USB,现在需要搭建一个USB系统,大致是这样的:我们原来用凌阳的SPCE061A开发板做了个简单的物品管理器,现在想增加一个USB接口,目的是能从PC通过USB下载新的数据!听起来好象很简单,但是小弟从来没有接触过USB相关知识,最近 ...…

查看全部问答>

8位的51单片机能计算下面的公式吗

t0=(int)(-412.6)+140.41*(sqr(1.0+0.00764*500)T=(int)(5.14/(0.98*0.000010514)+t0*t0*t0)T=pow(T,1.0/3)我刚学单片机,要用他计算上面的式子,然后在数码管上显示,要求小数点后1位,不晓得怎么编译,这一段的变量怎么设,代码怎么 ...…

查看全部问答>

cc2530的RF多点通信的数据碰撞处理

最近在学习CC2530F256,现在在调试RF的通讯(简单的射频通信,没采用协议栈),考虑到多点通信时,数据会有碰撞现象,当出现这种现象时怎么处理呢?求教了,谢谢大家的技术支持,帮助…

查看全部问答>

求助~求2012年TI杯暑假电子设计大赛D题的资料

就是那个声音定位系统的资料~我想把他做完~各种求资料~恳求各位大侠帮帮忙~最好有比赛时一等奖的作品~谢谢各位大侠了~…

查看全部问答>

关于GPIO配置问题

       先前在玩9B96的时候关于键盘控制中按键中断如下配置:                        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);  ...…

查看全部问答>