<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Assembly</title>
	<atom:link href="https://blog.gmem.cc/category/work/assembly/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 17 Apr 2026 09:20:32 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>汇编语言学习笔记</title>
		<link>https://blog.gmem.cc/assembly</link>
		<comments>https://blog.gmem.cc/assembly#comments</comments>
		<pubDate>Wed, 04 Feb 2004 05:05:26 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Assembly]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=36241</guid>
		<description><![CDATA[<p>X84-64 x86-64（也叫x64或amd64）是x86/IA32指令集的64-bit版本。 编译器 可以使用NASM，它针对x86系列处理器： [crayon-69e212612b0c5582736388/] 常用指令 很多指令会带有b w l q后缀，分别表示操作寄存器的1 2 4 8字节。 指令 C助记符 说明 赋值操作 mov src, dst dst = <a class="read-more" href="https://blog.gmem.cc/assembly">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/assembly">汇编语言学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">X84-64</span></div>
<p>x86-64（也叫x64或amd64）是x86/IA32指令集的64-bit版本。</p>
<div class="blog_h2"><span class="graybg">编译器</span></div>
<p>可以使用NASM，它针对x86系列处理器：</p>
<pre class="crayon-plain-tag">sudo apt-get install nasm</pre>
<div class="blog_h2"><span class="graybg">常用指令</span></div>
<p><span style="background-color: #c0c0c0;">很多指令会带有b w l q后缀，分别表示操作寄存器的1 2 4 8字节</span>。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">指令</td>
<td style="width: 150px; text-align: center;">C助记符</td>
<td style="text-align: center;">说明</td>
</tr>
<tr>
<td colspan="3"><em>赋值操作</em></td>
</tr>
</thead>
<tbody>
<tr>
<td>mov src, dst</td>
<td>dst = src </td>
<td>
<p>从源复制值到目标，源可以是一个直接量、寄存器、内存地址，目标可以是寄存器或内存地址。<span style="background-color: #c0c0c0;">源/目标不能同时是内存地址</span></p>
<p>movb movw movl movq分别表示移动1 2 4 8字节（整数的低位）</p>
<pre class="crayon-plain-tag">mov $0, %eax              ; %eax = 0
movb %al, 0x409892        ; write to address 0x409892 low-byte of %eax
mov 8(%rsp), %eax         ; %eax = value read from address %rsp + 8</pre>
</td>
</tr>
<tr>
<td>movs</td>
<td> </td>
<td>
<p>mov时，如果是位宽小的数据拷贝到位宽大的目标，则使用sign-extend填充额外字节
<p><pre class="crayon-plain-tag">movsbl %al, %edx    ; copy 1-byte %al, sign-extend into 4-byte %edx </pre>
</td>
</tr>
<tr>
<td>cltq</td>
<td> </td>
<td>专门操作%rax，等价于<pre class="crayon-plain-tag">movslq %eax,%rax</pre></td>
</tr>
<tr>
<td>movz</td>
<td> </td>
<td>mov时，如果是位宽小的数据拷贝到位宽大的目标，则使用zero填充额外字节<br />
<pre class="crayon-plain-tag">movzbl %al, %edx    ; copy 1-byte %al, zero-extend into 4-byte %edx</pre></p>
<p>需要注意：<span style="background-color: #c0c0c0;">用mov将32bit值拷贝到寄存器时，高32bit自动清零</span>，相当于自动movz。下面的指令： </p>
<pre class="crayon-plain-tag">mov %ebx, %ebx</pre>
<p>看似无意义，实际上会将高32bit清零 </p>
</td>
</tr>
<tr>
<td>lea src, dst</td>
<td>dst = src</td>
<td>即load effective address指令，源是一个内存地址，将计算得到的地址（不是数据）拷贝到目标：<br />
<pre class="crayon-plain-tag">lea 0x20(%rsp), %rdi      ; %rdi = %rsp + 0x20 (no dereference!)
lea (%rdi,%rdx,1), %rax   ; %rax = %rdi + %rdx</pre></p>
<p>该指定不会对源地址进行解引用操作 </p>
</td>
</tr>
<tr>
<td colspan="3"><strong><em>算术操作</em></strong></td>
</tr>
<tr>
<td>add src, dst</td>
<td>dst += src</td>
<td> </td>
</tr>
<tr>
<td>sub src, dst</td>
<td>dst -= src</td>
<td> </td>
</tr>
<tr>
<td>imul src, dst  </td>
<td>dst *= src</td>
<td><pre class="crayon-plain-tag">imul src</pre>这种特殊用法，假设乘法的另外一个操作数位于%rax，计算得到128bit结果，高64bit存放到%rdx</td>
</tr>
<tr>
<td>neg dst</td>
<td>dst = -dst</td>
<td> </td>
</tr>
<tr>
<td> and src, dst</td>
<td>dst &amp;= src</td>
<td> </td>
</tr>
<tr>
<td>or src, dst</td>
<td>dst |= src</td>
<td> </td>
</tr>
<tr>
<td>xor src, dst</td>
<td>dst ^= src</td>
<td>按位异或</td>
</tr>
<tr>
<td>not dst</td>
<td>dst = ~dst</td>
<td>按位取反</td>
</tr>
<tr>
<td>shl count, dst</td>
<td>dst &lt;&lt;= count</td>
<td>左移，同义词sal</td>
</tr>
<tr>
<td>sar count, dst</td>
<td>dst &gt;&gt;= coun</td>
<td>算术右移</td>
</tr>
<tr>
<td>shr count, dst</td>
<td>dst &gt;&gt;= coun</td>
<td>逻辑右移</td>
</tr>
<tr>
<td colspan="3"><strong><em>条件分支</em></strong></td>
</tr>
<tr>
<td>cmp op2, op1</td>
<td>result = op1 - op2</td>
<td>
<p>属于算术指令，计算结果丢弃，在%eflags中设置条件代码（结果为0也就是操作数相等则ZF置1）。操作数可以是直接量、内存地址（最多一个操作数）、寄存器</p>
</td>
</tr>
<tr>
<td>test op2, op1</td>
<td>result = op1 &amp; op2</td>
<td>属于逻辑指令，计算结果丢弃，在%eflags中设置条件代码（结果为0则ZF置1）。操作数可以是直接量、内存地址（最多一个操作数）、寄存器</td>
</tr>
<tr>
<td>jmp target</td>
<td> </td>
<td>无条件跳转</td>
</tr>
<tr>
<td>je target</td>
<td>if (ZF=1) goto</td>
<td>如果相等，则跳转</td>
</tr>
<tr>
<td>jne target</td>
<td>if (ZF=0) goto</td>
<td>如果不等，则跳转</td>
</tr>
<tr>
<td>jl target</td>
<td>if (SF!=OF) goto</td>
<td>如果小于，则跳转</td>
</tr>
<tr>
<td>jle target</td>
<td>if (ZF=0 || SF!=OF) goto</td>
<td>如果小于等于，则跳转</td>
</tr>
<tr>
<td>jg target</td>
<td>if (ZF=0 and SF=O) goto</td>
<td>如果大于，则跳转</td>
</tr>
<tr>
<td>jge target</td>
<td>if (SF=OF) goto</td>
<td>如果大于等于，则跳转</td>
</tr>
<tr>
<td>js</td>
<td>if (SF=1) goto</td>
<td> </td>
</tr>
<tr>
<td>jns</td>
<td>if (SF=0) goto</td>
<td> </td>
</tr>
<tr>
<td>sete dst</td>
<td> </td>
<td>如果ZF=1，则将dst寄存器的值设置为1</td>
</tr>
<tr>
<td>setge dst</td>
<td> </td>
<td>如果大于等于，则将dst寄存器的值设置为1</td>
</tr>
<tr>
<td colspan="3"><strong><em>函数调用栈</em></strong></td>
</tr>
<tr>
<td>push dst</td>
<td> </td>
<td>
<p>将直接量、寄存器或者内存地址压入栈中，操作数变成新的栈顶，%rsp寄存器的值因而递减</p>
<pre class="crayon-plain-tag">push %rbx         ; 将%rbx的值压栈
pushq $0x3        ; 将直接量3压栈
sub $0x10, %rsp   ; 调整栈指针，增加10字节</pre>
</td>
</tr>
<tr>
<td>pop dst</td>
<td> </td>
<td>
<p>弹出栈顶的值，存入目标寄存器，%rsp寄存器的值因而递增
<pre class="crayon-plain-tag">pop %rax          ; 将栈顶的值存放到%rax
add $0x10, %rsp   ; 从栈顶移除16字节</pre>
</td>
</tr>
<tr>
<td>callq funcaddr</td>
<td>funcaddr()</td>
<td>通过地址调用指定的函数。它指令<span style="background-color: #c0c0c0;">将返回地址（当前函数下一条指令的地址）压栈（%rip），然后跳转到目标函数上继续运行</span></td>
</tr>
<tr>
<td>retq </td>
<td>return</td>
<td>弹出栈顶作为%rip的值，导致<span style="background-color: #c0c0c0;">当前函数调用返回</span></td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">寄存器</span></div>
<p>常用的寄存器包括16个通用寄存器，两个特殊用途寄存器。每个寄存器都是64bit大小，通过一个伪名，可以饮用低32/16/8位部分。
<p>下表列出这些寄存器的惯用法。-owned标注出所有者，<span style="background-color: #c0c0c0;">所有者（函数调用时）可以自由使用寄存器，覆盖它的值，非所有者则必须复制值到其它地方才能安全使用</span></p>
<table class="table table-condensed full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 80px; text-align: center;">寄存器</td>
<td style="text-align: center;">惯例用法</td>
<td style="width: 80px; text-align: center;">低32位</td>
<td style="width: 80px; text-align: center;">低16位</td>
<td style="width: 80px; text-align: center;">低8位</td>
</tr>
</thead>
<tbody>
<tr>
<td><strong>%rax</strong></td>
<td>用于返回值，被调用者拥有</td>
<td>%eax</td>
<td>%ax</td>
<td>%al</td>
</tr>
<tr>
<td>%rdi</td>
<td>1st argument, callee-owned</td>
<td>%edi</td>
<td>%di</td>
<td>%dil</td>
</tr>
<tr>
<td>%rsi</td>
<td>2nd argument, callee-owned</td>
<td>%esi</td>
<td>%si</td>
<td>%sil</td>
</tr>
<tr>
<td>%rdx</td>
<td>3rd argument, callee-owned</td>
<td>%edx</td>
<td>%dx</td>
<td>%dl</td>
</tr>
<tr>
<td>%rcx</td>
<td>4th argument, callee-owned</td>
<td>%ecx</td>
<td>%cx</td>
<td>%cl</td>
</tr>
<tr>
<td>%r8</td>
<td>5th argument, callee-owned</td>
<td>%r8d</td>
<td>%r8w</td>
<td>%r8b</td>
</tr>
<tr>
<td>%r9</td>
<td>6th argument, callee-owned</td>
<td>%r9d</td>
<td>%r9w</td>
<td>%r9b</td>
</tr>
<tr>
<td>%r10</td>
<td>Scratch/temporary, callee-owned</td>
<td>%r10d</td>
<td>%r10w</td>
<td>%r10b</td>
</tr>
<tr>
<td>%r11</td>
<td>Scratch/temporary, callee-owned</td>
<td>%r11d</td>
<td>%r11w</td>
<td>%r11b</td>
</tr>
<tr>
<td><strong>%rsp</strong></td>
<td>
<p>用于栈指针，调用者拥有。push/pop指令添加/删除栈的元素</p>
<p>直接操作该寄存器，可以实现添加、删除一系列变量</p>
<p>注意：<span style="background-color: #c0c0c0;">栈是向下增长的，也就是向低地址方向增长</span></p>
</td>
<td>%esp</td>
<td>%sp</td>
<td>%spl</td>
</tr>
<tr>
<td>%rbx</td>
<td>Local variable, caller-owned</td>
<td>%ebx</td>
<td>%bx</td>
<td>%bl</td>
</tr>
<tr>
<td>%rbp</td>
<td>Local variable, caller-owned</td>
<td>%ebp</td>
<td>%bp</td>
<td>%bpl</td>
</tr>
<tr>
<td>%r12</td>
<td>Local variable, caller-owned</td>
<td>%r12d</td>
<td>%r12w</td>
<td>%r12b</td>
</tr>
<tr>
<td>%r13</td>
<td>Local variable, caller-owned</td>
<td>%r13d</td>
<td>%r13w</td>
<td>%r13b</td>
</tr>
<tr>
<td>%r14</td>
<td>Local variable, caller-owned</td>
<td>%r14d</td>
<td>%r14w</td>
<td>%r14b</td>
</tr>
<tr>
<td>%r15</td>
<td>Local variable, caller-owned</td>
<td>%r15d</td>
<td>%r15w</td>
<td>%r15b</td>
</tr>
<tr>
<td><strong>%rip</strong></td>
<td>
<p>指令寄存器，指向下一条需要执行的指令地址</p>
<p>调用函数时，该寄存器的值被压栈，以便函数调用完毕后能够返回</p>
</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td><strong>%eflags</strong></td>
<td>
<p>Status/condition code bits</p>
<p>这个特殊的寄存器，用于存放一系列的boolean标记位 —— 条件代码（condition codes），大部分算术运算符都会更新这些代码</p>
<p>条件跳转（conditional jump）指令会读取条件代码，来确定是否跳转到某个分支</p>
<p>条件代码包括：</p>
<p style="padding-left: 30px;">ZF 零标记，<strong><span style="background-color: #c0c0c0;">如果上一次比较/算术操作符产生“相等”或零，则设置为1</span></strong><br />SF 正负标记<br />OF 溢出标记，有符号<br />CF carry标记，无符号</p>
</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">地址模式</span></div>
<p>由于CISC的特性，x86-64支持多种不同的地址模式。地址模式用于计算内存地址，以便读写之。下面的汇编指令说明了如何向各种地址模式写入数据：</p>
<pre class="crayon-plain-tag">movl $1, 0x604892         ; 直接地址，地址是常量
movl $1, (%rax)           ; 间接地址，地址存放在寄存器中

movl $1, -24(%rbp)        ; 直接地址+置换  base %rbp + displacement -24

movl $1, 8(%rsp, %rdi, 4) ;  indirect with displacement and scaled-index (address = base %rsp + displ 8 + index %rdi * scale 4)

movl $1, (%rax, %rcx, 8)  ; (special case scaled-index, displ assumed 0)

movl $1, 0x8(, %rdx, 4)   ; (special case scaled-index, base assumed 0)

movl $1, 0x4(%rax, %rcx)  ; (special case scaled-index, scale assumed 1)</pre>
<div class="blog_h2"><span class="graybg">函数调用</span></div>
<p>通过callq指令可以发起函数调用，被调用函数中使用retq指令来返回。</p>
<p><span style="background-color: #c0c0c0;">调用者必须将前6个参数存入%rdi, %rsi, %rdx, %rcx, %r8, %r9这些寄存器中</span>。如果<span style="background-color: #c0c0c0;">参数超过6个，额外的参数压栈</span>。</p>
<pre class="crayon-plain-tag">mov $0x3, %rdi    ; 第一个参数存入 %rdi
mov $0x7, %rsi    ; 第二个参数存入 %rsi
callq binky       ; 移交控制权给binky函数</pre>
<p>被调用函数运行结束后，它应该将返回值写入到%rax，并清理掉栈，最后使用retq指令：</p>
<pre class="crayon-plain-tag">mov $0x0, %eax    ; 设置返回值
add $0x10, %rsp   ; 解除栈分配
retq              ; 从当前函数返回，回到caller继续执行</pre>
<div class="blog_h2"><span class="graybg">跳转执行</span></div>
<p>条件分支、静态跳转、函数调用，都会导致跳转执行。</p>
<p>跳转执行的目标，<span style="background-color: #c0c0c0;">通常是在编译阶段就确定下来的绝对地址</span>。 但是，很多情况下，目标仅到运行时才能知道，这种情况下目标地址被编译器存放在寄存器中。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/assembly">汇编语言学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/assembly/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
