Lec 02 arm汇编语言基础
<h1 id="lecture-02-arm-汇编基础">Lecture 02: ARM 汇编基础</h1><h2 id="contents">Contents</h2>
<ul>
<li>为什么学习ARM/ISA汇编</li>
<li>从C到汇编</li>
<li>理解arm汇编</li>
<li>理解机器执行</li>
</ul>
<h3 id="1-为什么学习汇编和指令集架构">1 为什么学习汇编和指令集架构?</h3>
<p>1.令人困惑的应用表现</p>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_58bf8d5c20480e6c9d51b34d70329f04.png"></p>
<p>2.指令集架构ISA(Instruction Set Architecture)</p>
<ul>
<li>CPU向软件(应用程序和操作系统)提供的接口。</li>
<li>理解软件在CPU上的运行(OS设计,程序调试)。</li>
<li>操作系统包含体系结构相关的汇编代码。</li>
<li>操作系统启动代码(栈没有设置)</li>
<li>部分操作C语言无法表达。(e.g.获取系统状态,刷新TLB)</li>
<li>部分场景下汇编更加高效(e.g. <code>memcpy</code>)</li>
</ul>
<h3 id="22-从c语言到汇编">2.2 从C语言到汇编</h3>
<p>1.为什么硬件不能直接运行C</p>
<ul>
<li>硬件设计<br>
(1)高级语言表达能力很强<br>
(2)硬件理解高级语言复杂度过高难以高效设计。</li>
<li>机器指令<br>
(1)格式相对固定<br>
(2)功能相对简单<br>
(3)二进制编码</li>
</ul>
<p>2.编译过程<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_deb65ce0cabc55ab062dfec1b8a590f7.png"><br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_1650c3709bfcc4dec07ab980d2f15baf.png"><br>
二进制文件难以理解->汇编较为适合阅读。</p>
<h3 id="23-理解arm汇编">2.3 理解arm汇编</h3>
<ol>
<li>在完成程序编写后,程序被储存在磁盘中。</li>
<li>OS加载程序,将其放入内存,CPU中的PC指向当前需要执行的第一个汇编指令。每执行一个指令,PC=PC+4.</li>
<li>数据刚开始存储在磁盘。后来会加载到内存当中。CPU中具有特殊的存储单元:寄存器,用于临时存储数据。可以用load/store指令来搬运数据。<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_843225a75888463cd48d854a17341e78.png"><br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_00c9ddb5334b5f95e93cd8268c302e64.png"></li>
</ol>
<h3 id="24-常用汇编">2.4 常用汇编</h3>
<h4 id="241-数据搬运">2.4.1 数据搬运</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_743602afacf23f7ca982f4a8ef2c9a5e.png"></p>
<h4 id="242-算术指令">2.4.2 算术指令</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_069df11ea0e8024fdd4d49745876a982.png"></p>
<h4 id="243-移位指令">2.4.3 移位指令</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_f67193c13e53f493a81d726bd0b6ef54.png"></p>
<h4 id="244-逻辑运算指令">2.4.4 逻辑运算指令</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_03b90c69952224de0e3fbd3277255b2a.png"></p>
<h4 id="245-modified-register">2.4.5 Modified Register</h4>
<ol>
<li>z=z*48分解成z=z*3与z=z*16,这样可以采用右移,减少运行时间。(浮点乘法消耗时间远大于位移)<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_4246e492d92a045ade65c9d4aee00e68.png"></li>
<li>Modified register 优势<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_1d396fabb8dd8bb08d5fcc065dd2d060.png"></li>
<li>对操作数进行移位/位扩展。<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_a0fd555a4562325c50bd0e629c020ab9.png"></li>
</ol>
<h4 id="246-访存指令">2.4.6 访存指令</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_999b44f506d2ae3ddf33e04c4aaabc8b.png"></p>
<h4 id="247-内存结构">2.4.7 内存结构</h4>
<p>1.CPU视角下的内存:</p>
<ul>
<li>内存可以被视为一个很大的字节数组。</li>
<li>数组每个元素可以由唯一的地址来索引。<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_cb345b0350617af4d449686ed0b16e9f.png"></li>
</ul>
<p>2.内存地址</p>
<ul>
<li>内存数组的名称计为M。M为addr开始的内存单元的内容。addr为内存数组的索引。内存单元大小由上下文决定。</li>
<li>addr的具体格式由寻址模式决定。</li>
</ul>
<p>3.寻址模式<br>
(1)基地址模式(索引寻址)</p>
<ul>
<li><span class="math inline">\(\)</span></li>
</ul>
<p>(2)基地址+偏移量</p>
<ul>
<li><span class="math inline">\(\)</span></li>
</ul>
<p>(3)前索引寻址(寻址操作前更新基地址)</p>
<ul>
<li><span class="math inline">\(!\)</span>, <span class="math inline">\(r_b+=\text{offset}\)</span>,寻址<span class="math inline">\(M\)</span></li>
</ul>
<p>(4)后索引寻址(寻址操作后更新基地址)</p>
<ul>
<li><span class="math inline">\(,\text{offset}\)</span> 寻址<span class="math inline">\(M\)</span>;<span class="math inline">\(r_b+=\text{offset}\)</span>.</li>
</ul>
<p>(5)offset可以是</p>
<ul>
<li>立即数 #imm</li>
<li>64位通用寄存器<span class="math inline">\(r_i\)</span></li>
<li>修改过的寄存器。例如:移位运算<code>lsl #3</code>,位扩展<code>sxtw</code></li>
</ul>
<p>(4)example:</p>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_a0df68758354062423ec3468d4159c07.png"></p>
<h4 id="248-条件码分支指令">2.4.8 条件码,分支指令</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_ade9c5304b481d25f3863d1af86fe47c.png"></p>
<p>标签:<code>.L3</code>,<code>.L1</code><br>
分支指令 <code>.cbz</code>,<code>bne</code></p>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_940024c4f20659c5c048c4a78d1ac357.png"></p>
<p>1.条件码</p>
<ul>
<li>一组标识位的统称。</li>
<li>由PSTATE寄存器维护。</li>
<li>N(Negative),Z(zero),C(carry),V(overflow)</li>
<li>条件码保留之前相关指令的执行状态,其中有</li>
<li>带有s后缀的算术/逻辑指令(<code>subs</code>,<code>adds</code>)</li>
<li>比较指令</li>
</ul>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_3943652bce8a2101a34ccc21f66dc5d2.png"></p>
<p>2.条件码的设置</p>
<ul>
<li>第一类:通过s后缀数据处理指令隐式设置。</li>
</ul>
<pre><code class="language-armasm">adds Rd, Rn, Op2
</code></pre>
<p>相当于<code>t=a+b</code>。</p>
<ul>
<li>C:运算产生进位时设置。</li>
<li>Z:当t=0时被设置。</li>
<li>N:当t<0时被设置。</li>
<li>V:当运算产生有符号溢出时被设置。</li>
</ul>
<pre><code class="language-C">(a>0 && b>0 && t<0) || (a<0 && b<0 && t>=0)
</code></pre>
<ul>
<li>第二类:通过比较指令cmp显式设置。</li>
</ul>
<pre><code class="language-armasm">cmp src1, src2
</code></pre>
<p>计算<code>src1-src2</code>,不存储结果,只改变条件码。</p>
<ul>
<li>C:运算不产生借位时设置。</li>
<li>Z:当操作数相等时被设置。</li>
<li>N:当src1<src2时被设置。</li>
<li>V:当运算产生有符号溢出时被设置。</li>
</ul>
<p>3.跳转条件<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_5ff8509599fd4d409a659ea7ad9a9d1c.png"></p>
<p>4.跳转指令</p>
<ul>
<li>直接分支指令</li>
<li>标签对应地址作为跳转目标</li>
<li>无条件分支指令:<code>b <label></code></li>
<li>有条件分支指令:<code>bcond <label></code>, bcond=beq,bne,ble,...</li>
<li>间接分支指令</li>
<li>寄存器中地址作为跳转目标。<code>br reg</code>。</li>
</ul>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_dd98c80dd536849cfb515336c5155b6b.png"></p>
<h3 id="25-函数调用">2.5 函数调用</h3>
<h4 id="251-函数调用无条件跳转">2.5.1 函数调用=无条件跳转</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_f40d94dc7e2a6b096e4794fef8f89ec2.png"></p>
<h4 id="252-基本概念">2.5.2 基本概念</h4>
<ul>
<li>术语:</li>
</ul>
<ol>
<li>Caller 调用者</li>
<li>Callee 被调用者</li>
</ol>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_fcb9b25e2cf7c40d413666fb9ae24214.png"></p>
<h4 id="253-函数调用与返回指令">2.5.3 函数调用与返回指令</h4>
<p>函数调用<code>bl <label></code>,返回<code>ret</code></p>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_a78e02bcbb552d5b0e481632c9e6653d.png"></p>
<h4 id="函数调用">函数调用</h4>
<ul>
<li>指令:</li>
</ul>
<ol>
<li><code>bl <label></code> 直接调用函数</li>
<li><code>blr Rn</code> 间接调用,调用函数指针。</li>
</ol>
<ul>
<li>功能:</li>
</ul>
<ol>
<li>将返回地址存储在链接寄存器x30</li>
<li>跳转到被调用者的入口地址。</li>
</ol>
<h4 id="返回指令">返回指令</h4>
<ul>
<li>指令</li>
</ul>
<ol>
<li><code>ret</code> 不区分直接调用和间接调用。</li>
</ol>
<ul>
<li>功能</li>
</ul>
<ol>
<li>跳转到返回地址X30</li>
</ol>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_45e2f7d6f0670e639a5a609eb3cefcad.png"></p>
<h4 id="254-多级函数调用">2.5.4 多级函数调用</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_2871fc532cfb33a16c2451c74e7686d4.png"></p>
<ul>
<li>[ ]一级:cube调用square</li>
<li>cube中的bl指令将返回地址保存在LR(X30)中</li>
<li>square中的ret指令返回到LR(X30)记录的地址</li>
<li>[ ]二级:cube调用square,square调用foo</li>
<li>LR首先存储了square返回cube的地址</li>
<li>嵌套调用时发生覆盖:LR(X30)存储foo返回square的地址</li>
</ul>
<h4 id="255-函数栈帧">2.5.5 函数栈帧</h4>
<ul>
<li>
<p>栈桢:函数在运行期间使用的一段内存</p>
</li>
<li>
<p>生命周期:从被调用到返回前</p>
</li>
<li>
<p>作用:存放其局部状态,包括:<br>
(1) 存放返回地址<br>
(2) 存放上一个栈桢的位置<br>
(3) 存放局部变量</p>
</li>
<li>
<p>多级函数调用</p>
</li>
<li>
<p>例如,A调用B、B调用C</p>
</li>
<li>
<p>程序执行中存在多个未返回的函数</p>
</li>
<li>
<p>函数栈桢按照调用顺序排列<br>
(1) 先被调用者后返回,后被调用者先返回<br>
(2) 栈:先进后出,后进先出</p>
</li>
<li>
<p>CPU中的另一个特殊寄存器SP<br>
SP: Stack Pointer<br>
指向栈顶(低地址)</p>
</li>
</ul>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_bc4f0344913e346c67addcc81c9e88c1.png"></p>
<h4 id="256-函数调用返回过程中栈的变化">2.5.6 函数调用返回过程中栈的变化</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_ad61061d77c821a96eab4c6bf170669c.png"></p>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_a85ab4ac052abe2012a1a45de51762f1.png"></p>
<h4 id="257-帧指针fpx29寄存器">2.5.7 帧指针FP:X29寄存器</h4>
<ul>
<li>栈桢回溯</li>
<li>栈桢大小不一</li>
<li>如何找到上一个栈桢(如调试)<br>
(1) 保存x29(上一个栈桢的SP)<br>
(2) 将当前SP写入x29(让callee能保存)<br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_a142c4c703c29adc1bdaac4c373359ef.png"></li>
</ul>
<h4 id="258-函数的调用返回与栈">2.5.8 函数的调用,返回与栈</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_88d874c5ccba33eb1d4bd80f2b48e4cc.png"></p>
<h3 id="26-函数参数与返回值">2.6 函数参数与返回值</h3>
<h4 id="261-寄存器传递数据">2.6.1 寄存器传递数据</h4>
<ol>
<li>x0-x7寄存器传递前8个参数</li>
<li>x0作为返回值</li>
</ol>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_6dc514d31ba2fbed65993dde1f9adc45.png"></p>
<h4 id="262-传递数据">2.6.2 传递数据</h4>
<ul>
<li>调用者压到栈上的数据</li>
<li>第8个之后的参数</li>
<li>按声明顺序从右到左<br>
Why? 因为参数的数量无法确定。而编译器读取参数时只是读取sp指针以上的内存。因此可以得到每个参数的地址。反之则会导致每个参数的内存地址无法确定。</li>
<li>所有数据对齐到8字节</li>
<li>被调用者通过SP+</li>
</ul>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_b889b278973bc322b0568f371dd24f1b.png"></p>
<p>举例说明:</p>
<pre><code class="language-C">void proc(long a1, long *a1p,
int a2, int *a2p,
short a3, short *a3p,
char a4, char *a4p,
char a5, char *a5p)
{
*a1p += a1;
*a2p += a2;
*a3p += a3;
*a4p += a4;
*a5p += a5;
}
void caller(long *n)
{
proc (1,0x2000,3,0x4000,5,0x6000,7,0x8000,9,0xA000);
}
</code></pre>
<p>查看caller的汇编语言:</p>
<pre><code class="language-armasm">00000000000000a0 <_caller>:
a0: ff c3 00 d1 sub sp, sp, #48
a4: fd 7b 02 a9 stp x29, x30,
a8: fd 83 00 91 add x29, sp, #32
ac: a0 83 1f f8 stur x0,
b0: e9 03 00 91 mov x9, sp
b4: 28 01 80 52 mov w8, #9
b8: 28 01 00 39 strb w8,
bc: 08 00 94 d2 mov x8, #40960
c0: 28 05 00 f9 str x8,
c4: 20 00 80 d2 mov x0, #1
c8: 01 00 84 d2 mov x1, #8192
cc: 62 00 80 52 mov w2, #3
d0: 03 00 88 d2 mov x3, #16384
d4: a4 00 80 52 mov w4, #5
d8: 05 00 8c d2 mov x5, #24576
dc: e6 00 80 52 mov w6, #7
e0: 07 00 90 d2 mov x7, #32768
e4: 00 00 00 94 bl 0xe4 <_caller+0x44>
e8: fd 7b 42 a9 ldp x29, x30,
ec: ff c3 00 91 add sp, sp, #48
f0: c0 03 5f d6 ret
</code></pre>
<h3 id="27-寄存器保存">2.7 寄存器保存</h3>
<h4 id="271-通用寄存器保存">2.7.1 通用寄存器保存</h4>
<ul>
<li>不同函数共享同一批通用寄存器</li>
<li>因此能够通过寄存器传递参数和返回值</li>
<li>然而,不同的函数对通用寄存器的使用会存在冲突 — 覆盖</li>
<li>避免冲突的思路</li>
<li>函数在使用某个寄存器之前保存该寄存器的值,返回前恢复</li>
<li>保存在哪:函数栈桢中</li>
<li>效率问题:有时候可能无需保存<br>
如:一个函数内不调用其他函数<br>
编译器会尽可能减少冗余保存的代码</li>
</ul>
<h4 id="272-寄存器使用约定">2.7.2 寄存器使用约定</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_9be6c737c224e214f08778012db8a768.png"></p>
<ul>
<li>
<p>调用者保存的寄存器包括 X9~X15</p>
</li>
<li>
<p>调用者在调用前按需(仅考虑自己是否需要)进行保存<br>
调用者在被调用者返回后恢复这些寄存器的值</p>
</li>
<li>
<p>被调用者可以随意使用<br>
这些寄存器调用后的值可能发生改变</p>
</li>
<li>
<p>被调用者保存的寄存器包括 X19~X28</p>
</li>
<li>
<p>被调用者在使用前进行保存</p>
</li>
<li>
<p>被调用者在返回前进行恢复</p>
</li>
<li>
<p>调用者视角:这些寄存器的值在函数调用前后不会改变</p>
</li>
</ul>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_b162117da402aaebf92af7c02e4f59f9.png"></p>
<h4 id="273-举例理解">2.7.3 举例理解</h4>
<pre><code class="language-armasm">0000000000000000 <square>:
0: 1b007c00 mul w0, w0, w0
4: d65f03c0 ret
0000000000000008 <cube>:
8: a9be7bfd stp x29, x30, ! // 开辟栈帧,保留调用者的栈帧x29,保存返回地址x30
c: 910003fd mov x29, sp // 当前帧的栈顶地址写入x29
10: f9000bf3 str x19, //被调用者使用前保存调用者或之前保留的数据
14: 2a0003f3 mov w19, w0 // 使用数据参数
18: 94000000 bl 0 <square>
1c: 1b137c00 mul w0, w0, w19
20: f9400bf3 ldr x19, // 从内存中恢复数据
24: a8c27bfd ldp x29, x30, , #32 // 返回弹栈
28: d65f03c0 ret
</code></pre>
<h3 id="28-局部变量">2.8 局部变量</h3>
<h4 id="281-函数局部变量存放在函数栈桢中">2.8.1 函数局部变量存放在函数栈桢中</h4>
<ul>
<li>为什么不直接把局部变量存储在寄存器?</li>
<li>寄存器数量有限</li>
<li>数组和结构体等复杂数据结构</li>
<li>局部变量可能需要寻址 (如&a)</li>
</ul>
<h4 id="282-局部变量">2.8.2 局部变量</h4>
<ul>
<li>局部变量的分配</li>
<li>在分配栈帧时被一起分配</li>
<li>局部变量的释放</li>
<li>在返回前释放栈帧时释放</li>
<li>局部变量通过SP相对地址引用</li>
<li>(例如<code>ldr x1, </code>)</li>
</ul>
<h4 id="283-小结">2.8.3 小结</h4>
<p><img src="https://notes.sjtu.edu.cn/uploads/upload_31656cf3c41b7853b301e0d97a0214c4.png"><br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_96c727117b4e69cfa198c6d4e4c39faa.png"><br>
<img src="https://notes.sjtu.edu.cn/uploads/upload_d5cbdcce32902890c442e5d95583013a.png"></p><br><br>
来源:https://www.cnblogs.com/mumujun12345/p/18541591
頁:
[1]