汇编语言的笔记

CPU寄存器

高速缓存

RAM内存

硬盘

内存模型:Heap(堆)

程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用malloc命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到10个字节内存,那么从起始地址0x1000开始给他分配,一直分配到地址0x100A,如果再要求得到22个字节,那么就分配到0x1020。

用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。

内存模型:stack(栈)

Stack 是由于函数运行而临时占用的内存区域。

Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0。

_add_a_and_b:
   push   %ebx
   mov    %eax, [%esp+8] 
   mov    %ebx, [%esp+12]
   add    %eax, %ebx 
   pop    %ebx 
   ret  

_main:
   push   3
   push   2
   call   _add_a_and_b 
   add    %esp, 8
   ret  
//在x86体系结构中,函数返回值通常是通过寄存器eax来传递的,
//这是一种约定俗成的做法。
//在函数执行完毕后,将返回值存储在eax寄存器中,并通过ret指令返回。

关于参数寄存器的命名,ebxeax是x86体系结构中的通用寄存器

它们的名称是固定的,与参数传递相关的寄存器也是固定的。

在函数调用过程中,参数通常通过特定的寄存器来传递,

如eax、ebx、ecx、edx等。

具体使用哪些寄存器来传递参数是根据调用约定来决定的,

不同的编程语言和操作系统可能有不同的约定。

在x86体系结构中,一般遵循以下约定:

前6个整数类型的参数依次使用寄存器ecx、edx、eax、ebx、esi和edi传递。

浮点类型的参数使用xmm0~xmm7寄存器传递。

其他参数则通过栈来传递。

需要注意的是,这些约定有时可能会因为特定的编译器、

操作系统或编程语言的不同而有所变化。

根据约定,程序从_main标签开始执行,这时会在 Stack 上为main建立一个帧,并将 Stack 所指向的地址,写入 ESP 寄存器。后面如果有数据要写入main这个帧,就会写在 ESP 寄存器所保存的地址。

汇编命令

(汇编)指令是CPU机器指令助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。

(汇编)伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码

两种不同风格的ARM指令

ARM官方的ARM汇编风格:指令一般用大写、Windows中IDE开发环境(如ADS、MDK等)常用。如: LDR R0, [R1]

GNU风格的ARM汇编:指令一般用小写字母、linux中常用。如:ldr r0, [r1]

GNU是一个自由软件运动的项目,全称为“GNU is Not Unix”。它的目标是创建一个完全自由且开放源代码的操作系统,即GNU操作系统。GNU项目由理查德·斯托曼(Richard Stallman)发起,并于1983年启动。GNU的目标是提供一个类Unix的操作系统,但与Unix不同的是,GNU完全使用自由软件的形式发布。GNU项目的旗舰产品是GNU工具集,其中包括了很多有名的软件,例如GNU编译器(GCC)和GNU调试器(GDB)。

ARM汇编特点1:LDR/STR架构

ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。

ldr(load register)指令将内存内容加载入通用寄存器。

str(store register)指令将寄存器内容存入内存空间中。

ldr/str组合用来实现 ARM CPU和内存数据交换

ARM汇编特点2:8种寻址方式

寄存器寻址 mov r1, r2

立即寻址 mov r0, #0xFF00

寄存器移位寻址 mov r0, r1, lsl #3

寄存器间接寻址 ldr r1, [r2]

基址变址寻址 ldr r1, [r2, #4]

多寄存器寻址 ldmia r1!, {r2-r7, r12}

堆栈寻址 stmfd sp!, {r2-r7, lr}

相对寻址 beq flag flag:

ARM汇编特点3:指令后缀

同一指令经常附带不同后缀,变成不同的指令。

经常使用的后缀有:

B(byte)功能不变,操作长度变为8位

H(half word)功能不变,长度变为16位

S(signed)功能不变,操作数变为有符号如 ldr ldrb ldrh ldrsb ldrsh

S(S标志)功能不变,影响CPSR标志位如 mov和movs movs r0, #0

ARM汇编特点4:条件执行后缀

ARM汇编特点5:多级指令流水线

为增加处理器指令流的速度,ARM使用多级流水线.,下图为3级流水线工作原理示意图。(S5PV210使用13级流水线,ARM11为8级)允许多个操作同时处理,而非顺序执行。

PC指向正被取指的指令,而非正在执行的指令

常用ARM指令1:数据处理指令

数据传输指令 mov mvn

算术指令 add sub rsb adc sbc rsc

逻辑指令 and orr eor bic

比较指令 cmp cmn tst teq

乘法指令 mvl mla umull umlal smull smlal

前导零计数 clz

常用ARM指令2:cpsr访问指令

mrs & msr

mrs用来读psr,msr用来写psr

CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。

常用ARM指令3:跳转(分支)指令

b & bl & bx

b 直接跳转(就没打开算返回)

bl branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用

bx跳转同时切换到ARM模式,一般用于异常处理的跳转。

常用ARM指令4:访存指令

ldr/str & ldm/stm & swp

单个字/半字/字节访问 ldr/str

多字批量访问 ldm/stm

swp r1, r2, [r0]

swp r1, r1, [r0]

ARM汇编中的立即数

合法立即数与非法立即数

ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。

合法立即数:经过任意位数的移位后非零部分可以用8位表示的即为合法立即数

常用ARM指令5:软中断指令

swi(software interrupt)

软中断指令用来实现操作系统中系统调用

协处理器cp15操作指令

mcr & mrc

mrc用于读取CP15中的寄存器mcr用于写入CP15中的寄存器

什么是协处理器

SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。

ARM设计上支持多达16个协处理器,但是一般SoC只实现其中的CP15.(cp:coprocessor)协处理器和MMU、cache、TLB等处理有关,功能上和操作系统的虚拟地址映射、cache管理等有关。

MRC & MCR的使用方法

mcr{<cond>} p15, <opcode_1>, <Rd>, <Crn>, <Crm>, {<opcode_2>}

opcode_1:对于cp15永远为0

Rd:ARM的普通寄存器

Crn:cp15的寄存器,合法值是c0~c15

Crm:cp15的寄存器,一般均设为c0

opcode_2:一般省略或为0

举例(来自于uboot)

mrc p15, 0, r0, c1, c0, 0

orr r0, r0, #1

mcr p15, 0, r0, c1, c0, 0

其他见uboot源码start.S中相关代码

协处理器学习要点

不必深究,将uboot中和kernel中起始代码中的一般操作搞明白即可。

只看一般用法,不详细区分参数细节,否则会陷入很多复杂未知中。

关键在于理解,而不在于记住。

为什么需要多寄存器访问指令

ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm

ldm(load register mutiple)

stm(store register mutiple)

举例(uboot start.S 537行)

stmia sp, {r0 - r12}

将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),

将r2存入该地址······直到r12内容放入(0x3001030),指令完成。

一个访存周期同时完成13个寄存器的读写

8种后缀

ia(increase after)先传输,再地址+4

ib(increase before)先地址+4,再传输

da(decrease after)先传输,再地址-4

db(decrease before)先地址-4,再传输

fd(full decrease)满递减堆栈ed(empty decrease)空递减堆栈

fa(·······) 满递增堆栈ea(·······)空递增堆栈

四种栈

空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出

满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针

增栈:栈指针移动时向地址增加的方向移动的栈

减栈:栈指针移动时向地址减小的方向移动的栈

!的作用

ldmia r0, {r2 - r3}

ldmia r0!, {r2 - r3}

感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。

^的作用

ldmfd sp!, {r0 - r6, pc}

ldmfd sp!, {r0 - r6, pc}^

^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。

总结

批量读取或写入内存时要用ldm/stm指令

各种后缀以理解为主,不需记忆,最常见的是stmia和stmfd

谨记:操作栈时使用相同的后缀就不会出错,不管是满栈还是空栈、增栈还是减栈

伪指令的意义

伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。

伪指令的意义在于指导编译过程。

伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。

gnu汇编中的一些符号

@ 用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//类似

# 做注释,一般放在行首,表示这一行都是注释而不是代码。

:以冒号结尾的是标号

. 点号在gnu汇编中表示当前指令的地址

# 立即数前面要加#或$,表示这是个立即数

常用gnu伪指令

.global _start @ 给_start外部链接属性

.section .text @ 指定当前段为代码段

.ascii .byte .short .long .word .quad .float .string @ 定义数据

.align 4 @ 以16字节对齐

.balignl 16 0xabcdefgh @ 16字节对齐填充

.equ @ 类似于C中宏定义

偶尔会用到的gnu伪指令

.end @标识文件结束

.include @ 头文件包含

.arm / .code32 @声明以下为arm指令

.thumb / .code16 @声明以下为thubm指令

最重要的几个伪指令

ldr 大范围的地址加载指令

adr 小范围的地址加载指令

adrl 中等范围的地址加载指令

nop 空操作

ARM中有一个ldr指令,还有一个ldr伪指令一般都使用ldr伪指令而不用ldr指令

(因为伪指令好用,伪指令支持非法立即数)

adr与ldr

adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;

adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里

ldr加载的地址和链接时给定的地址有关,由链接脚本决定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值