首页 / 资讯中心 / 文章详情

Armv8汇编复习知识地图:寄存器、栈帧与SIMD速查

Armv8汇编复习知识地图:寄存器、栈帧与SIMD速查 ★ FEATURED ARTICLE
简介《现代汇编语言ARMv8》面向系统软件、嵌入式开发与底层性能优化学习者是一部围绕汇编编程展开的入门与进阶资料重点解决从体系结构概念到32位、64位及SIMD指令集实践之间的衔接问题。包内为1个PDF文件约7.22MB便于检索适合课程参考与自学。资料先说明A、R、M三个子架构定位差异再展开AArch32的Thumb-2与ARM指令集、AArch64高性能架构以及SIMD在科学计算、图形处理和数据处理中的应用同时交代汇编语言在操作系统、驱动程序和应用软件开发中的价值。内容覆盖架构概述、指令集架构、汇编语言编程、AArch32/AArch64与SIMD等主题有助于把零散概念串联成可操作的底层编程知识。已有298人学习适合希望建立完整知识框架、理解底层执行机制并提升汇编阅读与编写能力的中高级读者。1. 知识地图Armv8 汇编复习的一条主线把 Armv8 汇编当成一堆指令去背基本上一周就忘干净。真正撑得住复习的是三层结构最底层是寄存器与寻址模式它决定你写的每一行能不能落到内存上中间层是整数核心与栈帧它决定函数调用、循环、条件分支写不写得对最上层是数据布局、浮点与 SIMD它决定你能不能把数组、矩阵、图像这类真实数据算对而不是算歪。这份笔记就按这三层往上走中间单列一章讲最容易翻车的地方最后收成一张能直接照着查的清单。适合已经能看懂简单汇编、正在系统整理或准备考试的人也适合写过 C 但没系统看过编译器输出的人。2. 架构底座寄存器组、数据类型与寻址模式的速查表这一章是全篇的地基。很多人卡在后面的栈参数和 SIMD追根溯源是这里没背熟不知道哪个寄存器跨调用要保留不知道LDR的四种寻址写回时机不知道Q0和D0到底是不是同一块物理存储。把这些当成必须条件反射的内容记下来后面的章节才读得动。2.1 通用寄存器与 APSR写汇编前必须背下来的约束Armv8-A 的 AArch32 执行状态有 16 个 32 位通用寄存器 R0–R15其中三个有固定角色R13 是栈指针 SPR14 是链接寄存器 LRR15 是程序计数器 PC。剩下的按 AAPCS 调用约定分工这张表要能默写寄存器别名角色跨函数调用是否保留R0–R3a1–a4传参、返回值否调用者保存R4–R11v1–v8局部变量、长期存活的值是被调用者保存R12IP链接器与 veneer 暂存否R13SP栈指针是R14LR返回地址否R15PC程序计数器不算常规寄存器最容易记混的是 R12它不属于参数寄存器也不属于被调用者保存寄存器写函数时随手拿它当临时变量跨过一次 PLT 跳转就可能被改写。另一个坑在 R9不同平台对它的约定不一样移植代码时别默认它安全。程序状态寄存器 APSR 的关键位如下条件执行和 SIMD 比较都依赖它位名称含义典型来源31N结果为负算术或逻辑结果的最高位30Z结果为零同上29C进位、借位、移位溢出位加减法与桶形移位器28V有符号溢出加减法27Q饱和溢出黏滞位VQADD 一类饱和指令19:16GE逐字节大于等于标志SIMD 比较指令注意Q 位是黏滞的一旦被置 1 就不会自动清零必须显式写MSR APSR_nzcvq, r0清掉否则下一轮饱和判断会带着上一轮的脏状态这是排查“结果偶尔错一次”时最常被忽略的一条。2.2 数据类型与内存寻址模式地址算错一半从这里开始AArch32 里的数据宽度有四档基础类型加一档打包类型。关键在于寄存器本身不区分有无符号符号性体现在加载指令上。类型宽度无符号加载有符号加载说明字节8 位LDRBLDRSB有符号加载会做符号扩展半字16 位LDRHLDRSH半字访问要求 2 字节对齐字32 位LDRLDR最常用4 字节对齐双字64 位LDRDLDRD需偶数编号寄存器对打包64/128 位VLD1VLD1SIMD 专用通道可拆寻址模式是第二个必须条件反射的点。四种写法看着像写回时机完全不同ldr r0, [r1] 前索引地址就是 r1r1 不变 ldr r0, [r1, #8] 前索引加偏移地址 r18r1 不变 ldr r0, [r1, #8]! 前索引写回地址 r18且 r1 更新为 r18 ldr r0, [r1], #8 后索引先用 r1 取数再让 r1 r18 ldr r0, [r1, r2, lsl #2] 寄存器偏移地址 r1 r2*4缩放只支持 0/1/2/3数组遍历习惯用后索引因为“取完自动前移”正好对应指针步进结构体字段访问用前索引加立即数偏移可读性最好前索引写回多用于一次性跳转并记住新位置。寄存器偏移里的移位量只能是 0 到 3也就是最大乘 8元素宽度超过 8 字节时必须先单独算好偏移再传进来这一点写矩阵代码时经常被忘掉。提示A32 状态下读取 R15 得到的值是当前指令地址加 8Thumb 状态下是加 4。字面量池加载依赖这个偏移手写 PC 相对寻址时算错一次就会取到错误常量。2.3 浮点与 SIMD 寄存器的重叠视图浮点和 SIMD 用的是同一组物理寄存器只是视图不同。这层重叠关系是后面所有“数据莫名其妙被改”问题的根源视图数量宽度关系S0–S313232 位单精度标量视图D0–D313264 位D0 的低 32 位就是 S0高 32 位是 S1Q0–Q1516128 位Q0 等于 D0 与 D1 拼接也就是 S0 到 S3也就是说往 Q0 写一次 128 位数据等于同时改写了 D0、D1 和 S0–S3 四个单精度寄存器。反过来用 S1 存了一个中间结果下一次VLD1.8 {q0}, [r0]就会把它覆盖掉。在纯标量浮点代码里很少踩这个坑一旦浮点和 SIMD 混写这就是最常见的数据踩踏来源。3. 整数核心编程算术、栈帧与条件分支的复习重点整数部分是整份资料里篇幅最大的一块也是最像“手艺”的一块。它的难点不在指令数量而在于同一個语义有多种写法选错不会报错只会在边界值上给你一个错误结果。这一章按“算术指令怎么选、加载与移动怎么分、栈怎么摆、条件怎么用”四条线梳理。3.1 加减乘除与位运算什么时候该用哪条先把指令按语义归类比一条条背有效得多指令语义关键限制ADD / SUB32 位加减立即数受 8 位旋转编码限制ADC / SBC带进位加减多字长运算必备RSB反向减RSB r0, r1, #0即取负常量在前时可省一条 MOVMUL32 位乘只保留低 32 位结果溢出会被静默截断MLA乘加r0 r1*r2 r3常用于地址计算UMULL / SMULL32×32 得 64 位结果放寄存器对低位在前SDIV / UDIV32 位除法Armv8-A 必备不再可选AND / ORR / EOR / BIC位运算BIC 是“与非”清位比 AND 取反更省立即数限制是最容易出问题的地方。AArch32 的立即数只有 12 位编码空间实际能表示的是“8 位值循环右移偶数位”所以MOV r0, #0x1234直接汇编不过去得拆成两条movw r0, #0x1234 低 16 位立即数不受旋转编码限制 movt r0, #0xABCD 高 16 位最终 r0 0xABCD1234 umull r4, r5, r0, r1 无符号 64 位乘(r5:r4) r0 * r1 smull r4, r5, r0, r1 有符号版本负数时高低位完全不同 sdiv r6, r0, r1 有符号除法向零取整 mls r7, r6, r1, r0 余数r7 r0 - r6*r1UMULL和SMULL的选择必须跟着数据类型走。用无符号指令算有符号数负数会被当成极大的正数结果的高 32 位完全是垃圾值。除法的余数没有单独指令要用MLS反推这也是写取模运算时的标准写法。位运算里BIC值得单独记要清掉某几位BIC r0, r0, #0xF0比“先构造掩码再 AND 取反”少一条指令。3.2 加载与移动指令的语义差异新手最容易把MOV和LDR当成同一类东西。它们的区别是MOV在寄存器之间搬运或者搬一个能编码的立即数LDR从内存取数包括从字面量池取常量。mov r1, #0xFF 8 位立即数直接编码进指令 mvn r2, #0 按位取反得到 0xFFFFFFFF ldr r0, 0xDEADBEEF 伪指令汇编器在附近放常量再用 PC 相对 LDR 取 ldr r3, [r0] 真正的内存访存ldr r0, imm这条伪指令在复习里出现的频率极高。它看起来像加载立即数实际生成的是“往字面量池存一个常量 一条 PC 相对加载”。它安全、通用但会多一次内存访问放在热点循环里就要换成MOVW/MOVT。多寄存器加载LDM/STM则是函数出入口的标准动作PUSH和POP就是STMDB SP!和LDMIA SP!的别名理解这层别名后面算栈偏移才不会乱。3.3 栈参数、局部存储与帧指针的三层结构AAPCS 规定前四个整型参数走 R0–R3从第五个开始压栈返回值放 R064 位结果用 R0:R1。栈是满递减的SP 指向最后一个已用单元压栈先减后存。这三层结构——参数区、保存区、局部区——要能在纸上画出来。 int calc(int a, int b, int c, int d, int e, int f) calc: push {r4-r6, r11, lr} 保存被调用者寄存器与返回地址共 5 个字 mov r11, sp 建立帧指针此后局部区变化不影响取参 sub sp, sp, #16 开 16 字节局部区保持 8 字节对齐 ldr r4, [r11, #20] 第 5 个参数 e跳过 5 个压栈字 ldr r5, [r11, #24] 第 6 个参数 f add r4, r4, r5 add r0, r0, r4 结果写回 r0 mov sp, r11 回收局部区 pop {r4-r6, r11, pc} 直接弹回 PC省一条 bx lr偏移量是这里唯一需要动脑的地方push压了 5 个字共 20 字节帧指针指向保存 LR 的位置而调用者的 SP 在压栈前正好指向第 5 个参数所以e在r11 20f在r11 24。参数个数一变这两个偏移全部要重算这也是为什么考试里特别爱考“六个参数和八个参数时偏移差多少”。建立帧指针会多占一个寄存器、多两条指令但在函数体里开了变长局部区、或者要在调试器里回溯调用栈时它是唯一的后悔药。写叶函数不再调用其他函数时可以省掉帧指针用 SP 直接取参代价是局部区大小必须固定。3.4 条件标志、比较与循环控制比较类指令不产生结果只改标志CMP做减法、CMN做加法、TST做与、TEQ做异或。之后靠条件后缀决定要不要执行。这张表建议整张背下来考试和实际调试都用得上后缀含义标志条件EQ / NE相等 / 不等Z1 / Z0CS 或 HS无符号大于等于C1CC 或 LO无符号小于C0HI无符号大于C1 且 Z0LS无符号小于等于C0 或 Z1MI / PL负数 / 非负N1 / N0VS / VC溢出 / 未溢出V1 / V0GE / LT有符号大于等于 / 小于NV / N≠VGT / LE有符号大于 / 小于等于Z0 且 NV / Z1 或 N≠V有符号和无符号用的是两组不同后缀混用是最经典的错误BGT判的是有符号大于如果操作数该按无符号理解比如长度、索引必须换成BHI。循环的标准写法是把减法和标志更新合成一条 int32_t sum_array(const int32_t* a, int32_t n) sum_array: mov r2, #0 累加器清零 cmp r1, #0 bxle lr n 0 直接返回 loop: ldr r3, [r0], #4 取一个 word指针后移 4 add r2, r2, r3 subs r1, r1, #1 减一并更新标志省掉一条 CMP bne loop Z0 就继续 mov r0, r2 bx lrSUBS加BNE是循环的固定搭配能少一条指令。如果目标平台是 Thumb-2条件执行不能直接挂在任意指令上要用IT块把最多四条指令包起来ITTT GT这样的写法要数清楚字母个数与指令条数一致这是手写 Thumb-2 时的高频失误点。4. 数据布局与浮点编程数组、矩阵、结构体、标量浮点的固定套路从这一章开始代码从“算一个数”变成“算一片数据”。好消息是套路极其固定算出元素地址、加载、运算、写回四步循环。坏消息是地址算错时程序不会崩只会给你一片看上去合理的结果。浮点部分同理指令不多但精度、舍入和标志搬运三件事必须一次做对。4.1 一维数组与多数组协同一维数组的核心公式只有一条元素地址 基址 索引 × 元素宽度。宽度是 4 就用后索引 #本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站