汇编语言语法详解
<p data-path-to-node="0">众所周知,汇编是一门比较古老的语言。国内吶,向来讲究学以致用,当然这不是说这个经世致用的思想不行。按说大学都开这门课的,</p><p data-path-to-node="0">但是老师当年自作主张,觉得学这门课在社会上用不到,把汇编语言这门课给换掉了。人家都说老师是引路人,可想而知,</p>
<p data-path-to-node="0">在需要汇编知识的时候方恨读汇编迟。今天所谈,尽量通用且不考虑具体架构,从<strong data-path-to-node="0" data-index-in-node="24">汇编语言的核心逻辑结构</strong>入手。无论是在 x86、ARM</p>
<p data-path-to-node="0">还是 RISC-V 上,汇编语法的本质都是对机器码的文本化描述。</p>
<p data-path-to-node="1">汇编代码的一行通常由四个部分组成:</p>
<p data-path-to-node="1"><code data-path-to-node="1" data-index-in-node="18">[标号:] 指令助记符 [操作数] [;注释]</code></p>
<h2 data-path-to-node="3">1. 指令助记符 (Mnemonics)</h2>
<p data-path-to-node="4">指令是汇编的核心,通常分为以下四大类:</p>
<h3 data-path-to-node="5">数据传送 (Data Movement)</h3>
<p data-path-to-node="6">这类指令负责在寄存器、内存和立即数之间“搬运”数据。</p>
<ul data-path-to-node="7">
<li>
<p data-path-to-node="7,0,0"><strong data-path-to-node="7,0,0" data-index-in-node="0"><code data-path-to-node="7,0,0" data-index-in-node="0">MOV</code> / <code data-path-to-node="7,0,0" data-index-in-node="6">LDR</code> / <code data-path-to-node="7,0,0" data-index-in-node="12">STR</code></strong>: 将数据从源移动到目的地。</p>
</li>
<li>
<p data-path-to-node="7,1,0"><strong data-path-to-node="7,1,0" data-index-in-node="0"><code data-path-to-node="7,1,0" data-index-in-node="0">PUSH</code> / <code data-path-to-node="7,1,0" data-index-in-node="7">POP</code></strong>: 栈操作,本质上是修改栈指针(SP)并移动数据。</p>
</li>
<li>
<p data-path-to-node="7,2,0"><strong data-path-to-node="7,2,0" data-index-in-node="0"><code data-path-to-node="7,2,0" data-index-in-node="0">LEA</code> (Load Effective Address)</strong>: x86 特有,用于计算地址而非读取内容(常被用来做快速加法)。</p>
</li>
</ul>
<h3 data-path-to-node="8">算术与逻辑运算 (Arithmetic & Logic)</h3>
<p data-path-to-node="9">这是 CPU 的 ALU(算术逻辑单元)负责的部分。</p>
<ul data-path-to-node="10">
<li>
<p data-path-to-node="10,0,0"><strong data-path-to-node="10,0,0" data-index-in-node="0"><code data-path-to-node="10,0,0" data-index-in-node="0">ADD</code> / <code data-path-to-node="10,0,0" data-index-in-node="6">SUB</code></strong>: 加减法。</p>
</li>
<li>
<p data-path-to-node="10,1,0"><strong data-path-to-node="10,1,0" data-index-in-node="0"><code data-path-to-node="10,1,0" data-index-in-node="0">MUL</code> / <code data-path-to-node="10,1,0" data-index-in-node="6">DIV</code></strong>: 乘除法。</p>
</li>
<li>
<p data-path-to-node="10,2,0"><strong data-path-to-node="10,2,0" data-index-in-node="0"><code data-path-to-node="10,2,0" data-index-in-node="0">AND</code> / <code data-path-to-node="10,2,0" data-index-in-node="6">OR</code> / <code data-path-to-node="10,2,0" data-index-in-node="11">XOR</code> / <code data-path-to-node="10,2,0" data-index-in-node="17">NOT</code></strong>: 位运算。</p>
</li>
<li>
<p data-path-to-node="10,3,0"><strong data-path-to-node="10,3,0" data-index-in-node="0"><code data-path-to-node="10,3,0" data-index-in-node="0">CMP</code></strong>: 比较指令,本质是执行减法但不保存结果,只修改<strong data-path-to-node="10,3,0" data-index-in-node="27">标志寄存器</strong>。</p>
</li>
</ul>
<h3 data-path-to-node="11">流程控制 (Control Flow)</h3>
<p data-path-to-node="12">改变程序执行顺序(修改 PC/IP 寄存器)。</p>
<ul data-path-to-node="13">
<li>
<p data-path-to-node="13,0,0"><strong data-path-to-node="13,0,0" data-index-in-node="0"><code data-path-to-node="13,0,0" data-index-in-node="0">JMP</code> / <code data-path-to-node="13,0,0" data-index-in-node="6">B</code></strong>: 无条件跳转。</p>
</li>
<li>
<p data-path-to-node="13,1,0"><strong data-path-to-node="13,1,0" data-index-in-node="0"><code data-path-to-node="13,1,0" data-index-in-node="0">JZ</code> / <code data-path-to-node="13,1,0" data-index-in-node="5">JE</code> / <code data-path-to-node="13,1,0" data-index-in-node="10">BNE</code></strong>: 条件跳转(依据上次运算的标志位,如“零标志位 Z”)。</p>
</li>
<li>
<p data-path-to-node="13,2,0"><strong data-path-to-node="13,2,0" data-index-in-node="0"><code data-path-to-node="13,2,0" data-index-in-node="0">CALL</code> / <code data-path-to-node="13,2,0" data-index-in-node="7">BL</code></strong>: 调用函数(保存返回地址到栈或链接寄存器)。</p>
</li>
<li>
<p data-path-to-node="13,3,0"><strong data-path-to-node="13,3,0" data-index-in-node="0"><code data-path-to-node="13,3,0" data-index-in-node="0">RET</code></strong>: 函数返回。</p>
</li>
</ul>
<h2 data-path-to-node="15">2. 操作数 (Operands)</h2>
<p data-path-to-node="16">指令操作的对象,通常有三种类型:</p>
<ul>
<li>
<p data-path-to-node="17,0,0"><strong data-path-to-node="17,0,0" data-index-in-node="0">立即数 (Immediate)</strong>: 硬编码在指令中的常数。例如 <code data-path-to-node="17,0,0" data-index-in-node="31">MOV R0, #100</code> (ARM) 或 <code data-path-to-node="17,0,0" data-index-in-node="52">mov eax, 100</code> (x86)。</p>
</li>
<li>
<p data-path-to-node="17,1,0"><strong data-path-to-node="17,1,0" data-index-in-node="0">寄存器 (Register)</strong>: CPU 内部存储单元。如 <code data-path-to-node="17,1,0" data-index-in-node="29">eax</code>, <code data-path-to-node="17,1,0" data-index-in-node="34">rbp</code>, <code data-path-to-node="17,1,0" data-index-in-node="39">r0</code>, <code data-path-to-node="17,1,0" data-index-in-node="43">x1</code>。</p>
</li>
<li>
<p data-path-to-node="17,2,0"><strong data-path-to-node="17,2,0" data-index-in-node="0">内存地址 (Memory)</strong>: 访问 RAM 中的数据。通常用方括号 <code data-path-to-node="17,2,0" data-index-in-node="34">[]</code> 表示寻址,例如 <code data-path-to-node="17,2,0" data-index-in-node="45"></code> 或 <code data-path-to-node="17,2,0" data-index-in-node="58"></code>。</p>
</li>
</ul>
<h2 data-path-to-node="19">3. 寻址方式 (Addressing Modes)</h2>
<p data-path-to-node="20">这是汇编中最灵活也最容易出错的地方。</p>
<ul data-path-to-node="21">
<li>
<p data-path-to-node="21,0,0"><strong data-path-to-node="21,0,0" data-index-in-node="0">寄存器寻址</strong>: <code data-path-to-node="21,0,0" data-index-in-node="7">MOV EAX, EBX</code> (直接从寄存器读写)。</p>
</li>
<li>
<p data-path-to-node="21,1,0"><strong data-path-to-node="21,1,0" data-index-in-node="0">基址变址寻址</strong>: <code data-path-to-node="21,1,0" data-index-in-node="8"></code>。</p>
<ul data-path-to-node="21,1,1">
<li>
<p data-path-to-node="21,1,1,0,0">例如:<code data-path-to-node="21,1,1,0,0" data-index-in-node="3">MOV EAX, </code>(访问局部变量)。</p>
</li>
<li>
<p data-path-to-node="21,1,1,1,0">例如:<code data-path-to-node="21,1,1,1,0" data-index-in-node="3">MOV EAX, </code>(访问数组元素)。</p>
</li>
</ul>
</li>
<li>
<p data-path-to-node="21,2,0"><strong data-path-to-node="21,2,0" data-index-in-node="0">相对寻址</strong>: 跳转指令常用,跳转到当前指令位置的前后偏移量。</p>
</li>
</ul>
<h2 data-path-to-node="23">4. 标志寄存器与条件执行 (Flags)</h2>
<p data-path-to-node="24">每次运算后,CPU 会自动更新一组状态位(EFLAGS / APSR / CSR):</p>
<ul data-path-to-node="25">
<li>
<p data-path-to-node="25,0,0"><strong data-path-to-node="25,0,0" data-index-in-node="0">ZF (Zero Flag)</strong>: 结果是否为 0。</p>
</li>
<li>
<p data-path-to-node="25,1,0"><strong data-path-to-node="25,1,0" data-index-in-node="0">SF (Sign Flag)</strong>: 结果是否为负。</p>
</li>
<li>
<p data-path-to-node="25,2,0"><strong data-path-to-node="25,2,0" data-index-in-node="0">CF (Carry Flag)</strong>: 无符号运算是否有进位。</p>
</li>
<li>
<p data-path-to-node="25,3,0"><strong data-path-to-node="25,3,0" data-index-in-node="0">OF (Overflow Flag)</strong>: 有符号运算是否溢出。</p>
</li>
</ul>
<p data-path-to-node="26"><strong data-path-to-node="26" data-index-in-node="0">条件执行逻辑:</strong></p>
<p data-path-to-node="26">汇编通过指令后缀或条件跳转利用这些位。例如 <code data-path-to-node="26" data-index-in-node="30">JNE</code> (Jump if Not Equal) 实际上检查的是 <code data-path-to-node="26" data-index-in-node="62">ZF == 0</code>。</p>
<h2 data-path-to-node="28">5. 伪指令 (Directives)</h2>
<p data-path-to-node="29">这些不是真正的 CPU 指令,而是给<strong data-path-to-node="29" data-index-in-node="18">汇编器</strong>(如 NASM, GAS)看的指令,用于定义数据和段。</p>
<ul data-path-to-node="30">
<li>
<p data-path-to-node="30,0,0"><strong data-path-to-node="30,0,0" data-index-in-node="0">.section / .data / .text</strong>: 定义代码段或数据段。</p>
</li>
<li>
<p data-path-to-node="30,1,0"><strong data-path-to-node="30,1,0" data-index-in-node="0">.global</strong>: 声明全局符号,让链接器能找到。</p>
</li>
<li>
<p data-path-to-node="30,2,0"><strong data-path-to-node="30,2,0" data-index-in-node="0">DB / DW / DD</strong>: 分别定义字节 (1B)、字 (2B)、双字 (4B) 级别的数据。</p>
</li>
<li>
<p data-path-to-node="30,3,0"><strong data-path-to-node="30,3,0" data-index-in-node="0">.equ / %define</strong>: 定义常量。</p>
</li>
</ul>
<h2 data-path-to-node="32">6. 两种主流风格对比</h2>
<p data-path-to-node="33">在 GDB 中,你可能会看到两种风格:</p>
<table data-path-to-node="34">
<thead>
<tr>
<td><strong>特性</strong></td>
<td><strong>Intel 格式 (Windows/NASM)</strong></td>
<td><strong>AT&T 格式 (Linux/GAS)</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td><span data-path-to-node="34,1,0,0"><strong data-path-to-node="34,1,0,0" data-index-in-node="0">顺序</strong></span></td>
<td><span data-path-to-node="34,1,1,0"><code data-path-to-node="34,1,1,0" data-index-in-node="0">指令 目的, 源</code></span></td>
<td><span data-path-to-node="34,1,2,0"><code data-path-to-node="34,1,2,0" data-index-in-node="0">指令 源, 目的</code></span></td>
</tr>
<tr>
<td><span data-path-to-node="34,2,0,0"><strong data-path-to-node="34,2,0,0" data-index-in-node="0">寄存器</strong></span></td>
<td><span data-path-to-node="34,2,1,0"><code data-path-to-node="34,2,1,0" data-index-in-node="0">eax</code></span></td>
<td><span data-path-to-node="34,2,2,0"><code data-path-to-node="34,2,2,0" data-index-in-node="0">%eax</code></span></td>
</tr>
<tr>
<td><span data-path-to-node="34,3,0,0"><strong data-path-to-node="34,3,0,0" data-index-in-node="0">立即数</strong></span></td>
<td><span data-path-to-node="34,3,1,0"><code data-path-to-node="34,3,1,0" data-index-in-node="0">10</code></span></td>
<td><span data-path-to-node="34,3,2,0"><code data-path-to-node="34,3,2,0" data-index-in-node="0">$10</code></span></td>
</tr>
<tr>
<td><span data-path-to-node="34,4,0,0"><strong data-path-to-node="34,4,0,0" data-index-in-node="0">宽度后缀</strong></span></td>
<td><span data-path-to-node="34,4,1,0">无 (使用 <code data-path-to-node="34,4,1,0" data-index-in-node="6">ptr</code>)</span></td>
<td><span data-path-to-node="34,4,2,0"><code data-path-to-node="34,4,2,0" data-index-in-node="0">l</code> (long), <code data-path-to-node="34,4,2,0" data-index-in-node="10">w</code> (word) 等</span></td>
</tr>
<tr>
<td><span data-path-to-node="34,5,0,0"><strong data-path-to-node="34,5,0,0" data-index-in-node="0">例子</strong></span></td>
<td><span data-path-to-node="34,5,1,0"><code data-path-to-node="34,5,1,0" data-index-in-node="0">mov eax, 1</code></span></td>
<td><span data-path-to-node="34,5,2,0"><code data-path-to-node="34,5,2,0" data-index-in-node="0">movl $1, %eax</code></span></td>
</tr>
</tbody>
</table>
<p data-path-to-node="35"><strong data-path-to-node="35" data-index-in-node="0">总结:</strong></p>
<p data-path-to-node="35">掌握汇编语法的关键在于追踪<strong data-path-to-node="35" data-index-in-node="17">数据的流动方向</strong>以及<strong data-path-to-node="35" data-index-in-node="26">栈指针(SP)的变化</strong>。在 GDB 中,建议始终开启 <code data-path-to-node="35" data-index-in-node="52">layout regs</code>,每走一步(<code data-path-to-node="35" data-index-in-node="69">si</code>)就盯着寄存器看,这比背诵语法要高效得多。</p>
<p>参考资料:</p>
<p>《简明 X86 汇编语言教程》--司徒彦南 2002 年 4 月 8 日</p>
<p id="汇编语言">汇编语言</p><br><br>
来源:https://www.cnblogs.com/guxuanqing/p/19875680
頁:
[1]