5.ARM汇编与C混合汇编

ARM汇编与C混合汇编

  • 一、ATPCS标准

    • 概念

      • ATPCS:ARM-THUMB Procedure Call Standard (ARM-Thumb过程调用标准)
    • 参数规则

      • 函数参数传递的时候,前4个参数通过r0-r3来传递,超过4个的参数通过栈来传递(C/C++中函数参数不超过4个)
      • 参数超过4个时通过压入栈来实现,需要考虑编译器执行效率的问题(寄存器效率比内存高
    • 返回值规则

      • 函数返回值通过r0带回
      • #include <stdio.h>
        
        int function(void)
        {
        	//局部变量:栈区分配空间,函数调用结束的时候,栈空间释放
        	int data = 100;
        	return data;
        }
        
        int main(int argc, const char *argv[])
        {
        	int a = function();
        	printf("a = %d\n", a); //a: 100
        	return 0;
        }
        问题:为什么data作为局部变量,还能将值传出来?
            通过寄存器获取,将data的值放到r0寄存器中
        	在调用后,获取的返回值从r0中读取即可
        	栈区空间正常释放,r0寄存器中的值不会消失
        
        #include <stdio.h>
        
        int *function(void)
        {
        	int data = 100;
        	return &data;
        }
        
        int main(int argc, const char *argv[])
        {
        	int *a = function();
        	printf("*a = %d\n", *a); //*a: 100或者Segmentation fault (core dumped)段错误(访问非法地址)
        	return 0;
        }
        问题:为什么*a打印出来还是100?
        	栈空间内容没有被释放,因为栈空间没有被重新划分
        	在打印之前调用其他函数,栈空间会被重新划分
        
    • 栈模式规则

      • main函数编译完后,就是一个标签label
  • 二、混合编程优势

    • 1、C和汇编能很容易的混合

      • 可实现在C中无法实现的处理器功能
        使用新的或不支持的指令
        产生更高效的代码
    • 2、直接链接变量和程序

      • 确定符合程序调用规范,输入/输出相关的符号
    • 3、编译器也可保留内嵌汇编

      • 大多数ARM指令都可以实现
        内嵌汇编代码可由编译器的优化器来传递
  • 三、汇编调用C语言

    • 操作

      • 1、项目中新建test.c文件
      • 2、将test.c文件加入到项目中
      • 3、编写test.c代码
      • 4、编译查看
        • 若没有发现栈空间的操作,是编译器对代码进行了优化,优化的越多,程序效率越高,有时候却有不确定性。
        • 选择优化等级
          • 魔法棒=>CC=>Optimization(默认Level1优化)
          • default=>默认Level1、No Optimization=>O0、Level1=>O1、Level2(Speed)=>O2、Level2(Size)=>Os、Level3=>O3
          • 编译器优化等级
        • C语言中操作系统做了设置,创建进程的时候对sp进行设置
    • 例子1(==传入参数<=4个 >寄存器传递

      • //asm.s文件
        .global _start
        
        _start:
        @将r0-r3分别赋值O-3,然后作为参数传入到func(int a,int b,int c,int d)
        @函数中,求和保存到sum中,然后函数调用结束将sum返回,返回值保存到r2寄存器中
        	ldr sp,=0x40001ff0 @在对栈操作之前,必须先设置sp(Stack Pointer)的值
        	mov r0,#0 @参数1
        	mov r1,#1 @参数2
        	mov r2,#2 @参数3
        	mov r3,#3 @参数4
        	
        	bl sum @跳转到sum函数,返回的结果在r0中
        	mov r2,r0 @返回值保存到r2寄存器中
        
        stop:
        	b stop
        
        //test.c文件
        int sum(int a, int b, int c, int d)
        {
        	int sum = 0;
        	sum = a + b + c + d;
        	return sum;
        }
        
    • 例子2(==传入参数>4个 >栈传递

      • //asm.s文件
        .global _start
        
        _start:
        @在例子1的基础上将传参数量增加到6个
        	ldr sp,=0x40001ff0 @在对栈操作之前,必须先设置sp(Stack Pointer)的值
        	mov r0,#0 @参数1
        	mov r1,#1 @参数2
        	mov r2,#2 @参数3
        	mov r3,#3 @参数4
            mov r4,#4 @参数5
            mov r5,#5 @参数6
            
            stmfd sp!,{r4,r5} @将r4、r5入栈(满栈递减模式)
        	
        	bl sum @跳转到sum函数,返回的结果在r0中
        	mov r2,r0 @返回值保存到r2寄存器中
        
        stop:
        	b stop
        
        //test.c文件
        int sum(int a, int b, int c, int d, int e, int f)
        {
        	int sum = 0;
        	sum = a + b + c + d + e + f;
        	return sum;
        }
        
  • 四、C程序中调用汇编

    • 格式:

      • asm(
        "指令1\n"
        "指令2\n"
        ……
        :输出列表
        :输入列表
        :修改列表(通用的寄存器)
        );
        
      • 输出列表
        • int b,c;
          :"=r"(b),"=r"(c)
          
        • 将寄存器中的值输出到变量
      • 输入列表
        • int a = 10;
          int b = 20;
          :"r"(a),"r"(b)
          
        • 将变量输入到寄存器中
      • 修改列表
        • //在指令中使用了r0,r1,r2修改列表
          :"r0","r1","r2"
          
        • 内联汇编的时候,发生修改的寄存器
        • 为什么会有修改列表?
          • 因为编译器会翻译C语言使用到的寄存器,它用什么寄存器是知道的,内联汇编之后,改变寄存器的值,就需要将编译器考虑进去
      • 如果没有三个表可以空着,C变量的引用,从输出列表到输入列表开始编号∶第一个C变量%0,第二个C变量%1,…
    • 示例

      • //asm.s文件
        .global _start
        
        _start:
        @C程序中调用汇编
        	ldr sp,=0x40001ff0 @在对栈操作之前,必须先设置sp(Stack Pointer)的值
        	mov r0,#1
        	mov r1,#2
        	bl addTwoNum
        	mov r2,r0
            
        stop:
        	b stop
        
        //test.c文件
        int addTwoNum(int a,int b)
        {
        	int c;
        	asm(
        	"add r0,%1,%2\n" 	//add r0,a,b ==> r0 = a + b
        	"mov %0,r0\n" 		//mov c,r0 ==> c = r0
        	:"=r"(c) 			//输出列表%0
        	:"r"(a),"r"(b) 		//输入列表%1,%2
        	:"r0"
        	);
        	return c;
        }
        
      • C语言不能直接操作寄存器,如果想操作得使用内联汇编的方式
    • IRQ使能示例

      • 使能IRQ异常,需要操作CPSR寄存器
      • //asm.s文件
        .global _start
        
        _start:
        @使能IRQ异常
        	ldr sp,=0x40001ff0 @在对栈操作之前,必须先设置sp(Stack Pointer)的值
        	bl enable_irq
        	mov r1,r0
            
        stop:
        	b stop
        
        //test.c文件
        int enable_irq(void)//使能IRQ(I置0)清0取反要用与
        {
        	int status;
        	asm(
        	"mrs r0,cpsr\n" 		//将CPSR寄存器中的值读取到r0中
        	"mov r1,#1\n" 			//r1 = 1
        	"bic r0,r0,r1,lsl #7\n" //r0 = r0 &~(r1 << 7) 将CPSR第7位(I)置0(默认为1)
        	"msr cpsr,r0\n" 		//将r0的值储存到CPSR中
        	"mov %0,r0\n" 			//status在代码中有自己的编号 %0
        	:"=r"(status) 			//输出status变量
        	: 						//输入列表为空
        	:"r0","r1"
        	);
        	return status;
        }
        
    • IRQ禁止示例

      • //asm.s文件
        .global _start
        
        _start:
        @使能IRQ异常
        	ldr sp,=0x40001ff0 @在对栈操作之前,必须先设置sp(Stack Pointer)的值
        	bl enable_irq
            bl disable_irq
        	mov r1,r0
            
        stop:
        	b stop
                
        //test.c文件
        int disable_irq(void)//禁止IRQ(I置1)某位置1要用或
        {
        	int status;
        	asm(
        	"mrs r0,cpsr\n" 				//将CPSR寄存器中的值读取到r0中
        	"mov r1,#1\n" 					//r1 = 1
        	"orr r0,r0,r1,lsl #7\n" 		//r0 = r0 | (r1 << 7) 将CPSR第7位(I)置1
        	"msr cpsr,r0\n" 				//将r0的值储存到CPSR中
        	"mov %0,r0\n" 					//status在代码中有自己的编号 %0
        	:"=r"(status) 					//输出status变量
        	: 								//输入列表为空
        	:"r0","r1"
        	);
        	return status;
        }
        
  • 五、volatile关键字

    • gcc优化

      • 优化思想
        • 如果已经将这个变量所对应的内存数据读到寄存器中,当再次需要读这个变量所在对应的内存的数据的时候,编译器已经在寄存器中存放过值了。不优化时,每次操作之前都会从内存中读值。
        • 为了提高效率,编译器会直接使用上一次寄存器中的值,而不是从内存中读取
      • 优化级别
        • 一级优化(O1)
        • 二级优化[ O2(Speed)/Os(Size) ]
        • 三级优化(O3)
      • 优化产生的问题
        • 如果内存中的值已经被其他的执行单元做了更改,而优化之后的代码每次从寄存器中读值就会带来寄存器中的值和内存中的值不一致的问题(多线程或中断时全局变量的改变问题,裸机开发中中断场合可能发生改变)
        • 解决方法:volatile关键字修饰不需要优化的参数
    • volatile作用

      • volatile(易改变的)修饰一个变量,防止编译器优化(本质)
        告诉编译器每次在使用这个变量的时候必须从变量所在的内存中重新读取数据
    • 面试问题

      • 在有中断处理函数代码中使用全局变量,有什么需要注意的地方?
        • 定义全局变量的时候,需要加volatile修饰
  • 六、异常处理

    • 异常

      • 5种异常模式(UDF、ABT、IRQ、FIRQ、SVC)、7种异常类型
      • 复位(Reset)和软中断(Software Interrupt)都进入SVC管理模式
        数据终止和预取终止都进入ABT终止模式
      • 异常向量表
    • 举例(生病)

      • 生病后会进入生病的状态(保存之前健康的状态)
      • 去对应的科室看病
      • 看完病后跳转到对应的部门进行治疗
      • 治疗完毕后,回到之前的状态,位置
    • ARM核硬件上自动做的事情

      • 异常产生时
        • (1)CPSR 的值拷贝到异常模式的 SPSR
        • (2) 设置 CPSR 的相应位
          • 改变处理器状态进入ARM态
          • 改变处理器模式进入相应的异常模式
          • 设置中断禁止位禁止相应中断 (中断产生的时候 )
        • (3) 将 PC 保存到异常模式的 LR
        • (4) 将 PC 的值设置到异常向量表相应位置
      • 异常处理返回时
        • 从异常模式的 SPSR恢复CPSR的值
        • 从异常模式的 LR恢复PC的值
    • 异常处理流程图

    • 程序员做的事情

      • (1)设置异常向量表(在异常向量表中写跳转指令,跳转到指定的异常处理函数)
      • (2)告诉ARM核异常向量表的基地址
        • [1]Cortex-A系列之前,异常向量表可以存放在0x0000 ,0000(低地址)或0xffff,0000(高地址)
          cp15(协处理器)的c1(寄存器)决定异常向量存放在高地址还是低地址
        • [2]Cortex-A系列之后,异常向量表可以存放在任意位置
          cp15(协处理器)的c12(寄存器)保存异常向量表的基地址
      • (3)编写异常处理函数
        • [1]设置sp寄存器
        • [2]将通用的寄存器(r0~r12)进行压栈保护
        • [3]异常处理的事情
        • [4]异常返回
          • A.恢复r0~r12 (出栈)
          • B.恢复cpsr(spsr –> cpsr)
          • C.恢复pc(lr –> pc)
    • 软中断异常(SoftWare Interrupt)

      • 概念
        • 产生一个异常陷阱,跳转到SWI硬件向量
          SWI处理程序可以检测SWI号,从而决定采取何种操作。
          通过SWI机制,运行在用户模式下的应用程序,可请求操作系统执行一系列特权操作。
        • 软件中断
      • 语法
        • SWI{< cond >} < SWI number >
          软中断号(SWI number)在编译后,翻译成的机器码,其中0~23位是软中断号
      • 软中断有什么用?
        • 用户空间(非特权模式)进入内核空间(特权模式)
        • 非特权模式无法自己切换到特权模式,只能通过异常
          • arm平台通过swi指令,x86通过int指令
          • 软中断号区分不同的系统调用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值