ARM架构与编程——3.编程知识

参考:<ARM架构与编程> 韦东山

编程知识

一、进制

引入:对同一数值的不同描述
举例:有17个苹果
十进制:17
二进制:10001
八进制:21
十六进制:11
这些都表示同一个数值

1.1 进制的转换规则

X进制:逢X进1
10进制里,每一位的权重,从右往左数:个十百千万,也就是:10^0, 10^1, 102,103……
16进制里,每一位的权重,从右往左数,分别是:16^0, 16^1, 16^2, 16^3, 16^4, ……
8进制里,每一位的权重,从右往左数,分别是:8^0, 8^1, 8^2, 8^3, 8^4, ……
2进制里,每一位的权重,从右往左数,分别是:2^0, 2^1, 2^2, 2^3, 2^4, ……

10进制,逢10进1, 16= 1x10^1 + 6x10^0 = 16
16进制:逢16进1, 0x10= 1x16^1 + 0x16^0 = 16
8进制: 逢8进1, 020 = 2x8^1 + 0x8^0= 16
2进制: 逢2进1, 0b10000 = 1x2^4 + 0x2^3 + 0x2^2 + 0x2^1 +0x2^0 = 16
它们表示的值是一样的。

在代码里,
10进制这样写:123456789,每位最大值为9
16进制这样写:0x12ABCDEF, 每位最大值为15,A表示10,B表示11,C表示12,D表示13,E表示14,F表示15
8进制这样写:01234,每位最大值为7
2进制这样写:0b0110,每位最大值为1 (注意:C语言没有二进制数值的表示方法)

2.2 不同进制间的快速转换

记住权重:
在这里插入图片描述

16进制、2进制互转:1位十六进制对应4位二进制
在这里插入图片描述
8进制、2进制互转:1位八进制对应3位二进制
在这里插入图片描述
10进制与其它进制间的转换:10进制数除其它进制(2,8,或者16等)取余法

二、字节序与位操作

2.1 字节序

假设int a = 0x12345678;
16进制数中每位数值占据4 bit;在内存中,是以8个bit作为1byte。
因此0x12345678中每两位作为1byte, 其中0x78是低byte,0x12是高byte。
在内存中的存储方式有两种:

在这里插入图片描述
0x12345678的低位(0x78)存在低地址,即方式1,叫做小字节序(Little endian);
0x12345678的高位(0x12)存在低地址,即方式2,叫做大字节序(Big endian);
一般的arm芯片都是小字节序,有些处理器,可以置某个寄存器,让整个系统使用大字节序或小字节序。

2.2 位操作

2.2.1 逻辑移位

在嵌入式开发中,我们只涉及逻辑移位:不关心符号位,都是补0
算术移位,需要分有符号型值和无符号型值:

  • 对于无符号型值,算术移位等同于逻辑移位。
  • 对于有符号型值 ,算术左移等同于逻辑左移;算术右移补的是符号位,正数补0,负数补1。

逻辑左移

int a = 0x123;  int b = a<<2;  // b=0x48C

在这里插入图片描述
逻辑右移

int a = 0x123;  int b = a>>2;  // b=0x48

在这里插入图片描述

2.2.2 取反
xxxxxxxxxx unsigned int   a = 0x123;
unsigned int   b = ~a;   // b的每一位,都是a对应位的取反

在这里插入图片描述

2.2.3 位与
unsigned int a = 0x123;
unsigned int b = 0x456;
unsigned int c = a & b; // c等于a位与b,即:a,b的每一位进行与操作
两位相与,结果如下:
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

在这里插入图片描述

2.2.4 位或
unsigned int a = 0x123;
unsigned int b = 0x456;
unsigned int c = a | b; // c等于a位或b,即:a,b的每一位进行或操作
两位相与,结果如下:
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0

在这里插入图片描述

2.2.5 置位
unsigned int a = 0x123;
unsigned int b = a | (1<<7) | (1<<8);  // 设置a的bit7, bit8, 赋给b

在这里插入图片描述

2.2.6 清位
unsigned int a = 0x123;
unsigned int b = a & ~((1<<7) | (1<<8));  // 清除a的bit7, bit8, 赋给b

在这里插入图片描述

2.2.7 把某几位设置为某值

比如要把bit7设置为1,把bit8清除为0,这可以分两步操作:
先设置bit7,再清除bit8。
还有一种情况:bit[8:7]= val, 不知道val的取值是多少,怎么办?
先清除bit8、bit7,再或上val,代码如下:## 三、汇编、反汇编、机器码

unsigned int a = 0x123;
unsigned int b = a & ~(3<<7);  /* 清除bit7, bit8 */
b = b | (val << 7);            /* 设置bit7, bit8为val */

三、汇编、反汇编、机器码

3.1 程序处理的四个步骤

举例:
我们的第1个LED程序涉及2个文件:start.S、main.c,它们的处理过程如下: (start.o 为目标文件 led.elf 为可执行程序,可执行程序里面含有机器码及可执行的代码;通过反汇编可得到里面的汇编代码)

在这里插入图片描述

我们想深入理解ARM架构,想深入理解汇编与C,想深入理解栈的作用,想深入理解C语言的实质,就必须把最终的可执行程序,反汇编后,阅读得到的汇编代码。
现在只需要理解“汇编”、“反汇编”的概念:

  • 汇编
    汇编文件转换为目标文件(里面是机器码)。
  • 反汇编
    可执行文件(目标文件,里面是机器码),转换为汇编文件。
    在这里插入图片描述

3.2 Keil下怎么反汇编

在KEIL的User选项中,如下图添加这两项:

fromelf  --bin  --output=led.bin  Objects\led_c.axf
fromelf  --text  -a -c  --output=led.dis  Objects\led_c.axf

然后重新编译,即可得到二进制文件led.bin(以后会分析)反汇编文件led.dis
如下图操作:
在这里插入图片描述

3.3 GCC下反汇编

使用GCC工具链编译程序时,在Makefile中有这一句:

$(OBJDUMP) -D -m arm led.elf > led.dis # OBJDUMP = arm-linux-gnueabihf-objdump

它就是把可执行程序led.elf,反汇编,得到led.dis。

3.4 机器码与汇编

前面介绍过伪指令,伪指令是实际不存在的ARM命令,编译器在编译时转换成存在的ARM指令。
我们代码中的ldr r1, =0x????????这条伪指令的真实指令是什么呢?
对于我们使用的3款板子,汇编代码如下(如果你的板子不是这3款之一,请灵活变通,知识是一样的):

LDR SP, =(0x20000000+0x10000) // STM32F103
ldr sp, =(0x80000000+0x100000) // IMX6ULL
ldr sp, =0xc0000000 + 0x100000 // STM32MP157 A7

我们可以通过反汇编来查看, 只摘取前面一小段。
我们只摘取前面一小段,第一列是地址,第二列是机器码,第三列是汇编:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4.1 机器码与汇编示例

Thumb/Thumb2指令集
在这里插入图片描述
ARM指令集
在这里插入图片描述

3.4.2 解析LDR伪指令

为什么 PC=当前指令+4或8?

  • CORTEX M3/M4
    使用Thumb2指令集,一条指令是16位或32位。

  • CORTEX A7
    默认使用ARM指令集,一条指令是32位的。

  • 流水线
    ARM指令采用流水线机制:

    • 当前执行地址A的指令,
    • 同时已经在对下一条指令进行译码
    • 同时已经在读取下下一条指令:PC = A +4 (Thumb/Thumb2指令集)、PC = A + 8 (ARM指令集)
      在这里插入图片描述
3.4.3 总结
  • C
    为了方便人类方便使用,发明的高级语言,要转换为汇编。

  • 汇编
    为了解放人类的记忆,发明的“助记符”,不用去记各类机器码。最终要转换为机器码。

  • 机器码
    给CPU使用

四、C与汇编的深入分析

4.1 汇编怎样调用C函数

直接调用,在启动文件中,直接运用BL指令调用函数

BL main

怎样传递参数
在arm中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。
约定r0-r15寄存器的用途:

  • r0-r3
    调用者和被调用者之间传参数

  • r4-r11
    函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们。
    在这里插入图片描述

// 示例:
int delay(unsigned int d)
{
	while (d--);
    return 0;
}

在这里插入图片描述
另一种delay函数如何实现
在汇编中调用delay:

ldr  r0, =1000000   /* 给delay函数传参数,保存在r0里 */
bl delay
cmp r0, #0          /* 返回值保存在r0中 */

在这里插入图片描述

4.2 C函数的反汇编码阅读

要解决这几个问题:

  • 为什么调用C函数前要设置栈?
  • 函数调用是通过栈来实现的。。。调用完函数返回时,需要通过栈里的地址来返回。 同时,函数的参数也是通过栈来传递。当程序处理比较复杂时,就要利用到栈来保存中间结果之类的,所以要先设置栈

  • 栈的作用是?
    • 保存现场/上下文

    现场/上下文,意思就相当于案发现场,总有一些现场的情况,要记录下来的,否则被别人破坏掉之后,你就无法恢复现场了。而此处说的现场,就是指CPU运行的时候,用到了一些寄存器,比如r0,r1,r2等等,对于这些寄存器的值,如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了,因为其函数执行也要用到这些寄存器。因此,在函数调用之前,应该将这些寄存器等现场,暂时保持起来(入栈push),等调用函数执行完毕返回后(出栈pop),再恢复现场。这样CPU就可以正确的继续执行了。
    保存寄存器的值,一般用的是push指令,将对应的某些寄存器的值,一个个放到栈中,把对应的值压入到栈里面,即所谓的压栈。然后待被调用的子函数执行完毕的时候,再调用pop,把栈中的一个个的值,赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从栈中弹出去,即所谓的出栈。
    其中保存的寄存器中,也包括lr的值(因为用bl指令进行跳转的话,那么之前的pc的值是存在lr中的),然后在子程序执行完毕的时候,再把栈中的lr的值pop出来,赋值给pc,这样就实现了子函数的正确的返回。

    • 传递参数

    C语言进行函数调用的时候,常常会传递给被调用的函数一些参数,对于这些C语言级别的参数,被编译器翻译成汇编语言的时候,就要找个地方存放一下,并且让被调用的函数能够访问,否则就没发实现传递参数了。对于找个地方放一下,分两种情况。一种情况是,本身传递的参数不多于4个,就可以通过寄存器传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。另一种情况是,参数多于4个时,寄存器不够用,就得用栈了。

    • 临时变量保存在栈中

    包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

  • C函数传参
  • C函数执行过程体验
    在这里插入图片描述

4.3 Flash上的内容

4.3.1 反汇编示例

在这里插入图片描述

4.3.2 烧写在Flash上的内容
地址Flash内容
0x0800000000000000
0x0800000408000009
0x08000008f8dfd004
0x0800000cf000f80c
0x0800001020010000
0x08000014bf00b501
0x080000181e419800
…………
4.3.3 启动流程

上电后:

  • 设置栈:CPU会从0x08000000读取值,用来设置SP(我们的程序里再次设置了SP) —>(LDR SP,[PC, #4])
  • 跳转:CPU从0x08000004得到地址值,根据它的BIT0切换为ARM状态或Thumb状态,然后跳转
    • 对于cortex M3/M4,它只支持Thumb状态,所以0x08000004上的值bit0必定是1
    • 0x08000004上的值 = Reset_Handler + 1
  • 从Reset_Handler继续执行
  • F103为例:板子上电后去0x08000000位置读到机器码,然后这个值将会被设置到SP栈寄存器里面;然后在Reset_Handler中的第一条指令去设置栈,如果不想执行这条指令,可以把栈的值放到0x08000000的位置;设置好栈后去0x08000000位置去读取到机器码的值,然后这个值将作为函数的地址,赋给PC,bit0为1表示跳过去执行的是Thumb指令;程序也就从0x08000000的位置开始执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值