-
Notifications
You must be signed in to change notification settings - Fork 25
/
ch18s01.html
37 lines (34 loc) · 12.9 KB
/
ch18s01.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>1. 最简单的汇编程序</title><link rel="stylesheet" href="styles.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Linux C编程一站式学习" /><link rel="up" href="ch18.html" title="第 18 章 x86汇编程序基础" /><link rel="prev" href="ch18.html" title="第 18 章 x86汇编程序基础" /><link rel="next" href="ch18s02.html" title="2. x86的寄存器" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">1. 最简单的汇编程序</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch18.html">上一页</a> </td><th width="60%" align="center">第 18 章 x86汇编程序基础</th><td width="20%" align="right"> <a accesskey="n" href="ch18s02.html">下一页</a></td></tr></table><hr /></div><div class="sect1" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2769131"></a>1. 最简单的汇编程序</h2></div></div></div><div class="example"><a id="id2769136"></a><p class="title"><b>例 18.1. 最简单的汇编程序</b></p><div class="example-contents"><pre class="programlisting">#PURPOSE: Simple program that exits and returns a
# status code back to the Linux kernel
#
#INPUT: none
#
#OUTPUT: returns a status code. This can be viewed
# by typing
#
# echo $?
#
# after running the program
#
#VARIABLES:
# %eax holds the system call number
# %ebx holds the return status
#
.section .data
.section .text
.globl _start
_start:
movl $1, %eax # this is the linux kernel command
# number (system call) for exiting
# a program
movl $4, %ebx # this is the status number we will
# return to the operating system.
# Change this around and it will
# return different things to
# echo $?
int $0x80 # this wakes up the kernel to run
# the exit command</pre></div></div><br class="example-break" /><p>把这个程序保存成文件<code class="literal">hello.s</code>(汇编程序通常以<code class="literal">.s</code>作为文件名后缀),用汇编器(Assembler)<a id="id2769149" class="indexterm"></a><code class="literal">as</code>把汇编程序中的助记符翻译成机器指令,生成目标文件<code class="literal">hello.o</code>:</p><pre class="screen">$ as hello.s -o hello.o</pre><p>然后用链接器(Linker,或Link Editor)<a id="id2769187" class="indexterm"></a><code class="literal">ld</code>把目标文件<code class="literal">hello.o</code>链接成可执行文件<code class="literal">hello</code>:</p><pre class="screen">$ ld hello.o -o hello</pre><p>为什么用汇编器翻译成机器指令了还不行,还要有一个链接的步骤呢?链接主要有两个作用,一是修改目标文件中的信息,对地址做重定位,在<a class="xref" href="ch18s05.html#asm.executable">第 5.2 节 “可执行文件”</a>详细解释,二是把多个目标文件合并成一个可执行文件,在<a class="xref" href="ch19s02.html#asmc.main">第 2 节 “<code class="literal">main</code>函数和启动例程”</a>详细解释。我们这个例子虽然只有一个目标文件,但也需要经过链接才能成为可执行文件。</p><p>现在执行这个程序,它只做了一件事就是退出,退出状态是4,<a class="xref" href="ch03s02.html#func.ourfirstfunc">第 2 节 “自定义函数”</a>讲过在Shell中可以用特殊变量<code class="literal">$?</code>得到上一条命令的退出状态:</p><pre class="screen">$ ./hello
$ echo $?
4</pre><p>所以这段汇编代码相当于在C程序的<code class="literal">main</code>函数中<code class="literal">return 4;</code>。为什么会相当呢?我们在<a class="xref" href="ch19s02.html#asmc.main">第 2 节 “<code class="literal">main</code>函数和启动例程”</a>详细解释。</p><p>下面逐行分析这个汇编程序。首先,<code class="literal">#</code>号表示单行注释,类似于C语言的<code class="literal">//</code>注释。</p><pre class="programlisting"> .section .data</pre><p>汇编程序中以<code class="literal">.</code>开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)<a id="id2769314" class="indexterm"></a>或伪操作(Pseudo-operation)<a id="id2769324" class="indexterm"></a>,由于它不是真正的指令所以加个“<span class="quote">伪</span>”字。<code class="literal">.section</code>指示把代码划分成若干个段(Section)<a id="id2769345" class="indexterm"></a>,程序被操作系统加载执行时,每个段被加载到不同的地址,操作系统对不同的页面设置不同的读、写、执行权限。<code class="literal">.data</code>段保存程序的数据,是可读可写的,相当于C程序的全局变量。本程序中没有定义数据,所以<code class="literal">.data</code>段是空的。</p><pre class="programlisting"> .section .text</pre><p><code class="literal">.text</code>段保存代码,是只读和可执行的,后面那些指令都属于<code class="literal">.text</code>段。</p><pre class="programlisting"> .globl _start</pre><p><code class="literal">_start</code>是一个符号(Symbol)<a id="id2769400" class="indexterm"></a>,符号在汇编程序中代表一个地址,可以用在指令中,汇编程序经过汇编器的处理之后,所有的符号都被替换成它所代表的地址值。在C语言中我们通过变量名访问一个变量,其实就是读写某个地址的内存单元,我们通过函数名调用一个函数,其实就是跳转到该函数第一条指令所在的地址,所以变量名和函数名都是符号,本质上是代表内存地址的。</p><p><code class="literal">.globl</code>指示告诉汇编器,<code class="literal">_start</code>这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号(在<a class="xref" href="ch18s05.html#asm.relocatable">第 5.1 节 “目标文件”</a>详细解释)。<code class="literal">_start</code>就像C程序的<code class="literal">main</code>函数一样特殊,是整个程序的入口,链接器在链接时会查找目标文件中的<code class="literal">_start</code>符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个<code class="literal">_start</code>符号并且用<code class="literal">.globl</code>声明。如果一个符号没有用<code class="literal">.globl</code>声明,就表示这个符号不会被链接器用到。</p><pre class="programlisting">_start:</pre><p>这里定义了<code class="literal">_start</code>符号,汇编器在翻译汇编程序时会计算每个数据对象和每条指令的地址,当看到这样一个符号定义时,就把它后面一条指令的地址作为这个符号所代表的地址。而<code class="literal">_start</code>这个符号又比较特殊,它所代表的地址是整个程序的入口地址,所以下一条指令<code class="literal">movl $1, %eax</code>就成了程序中第一条被执行的指令。</p><pre class="programlisting"> movl $1, %eax</pre><p>这是一条数据传送指令,这条指令要求CPU内部产生一个数字1并保存到<code class="literal">eax</code>寄存器中。<code class="literal">mov</code>的后缀l表示long,说明是32位的传送指令。这条指令不要求CPU读内存,1这个数是在CPU内部产生的,称为立即数(Immediate)<a id="id2769538" class="indexterm"></a>。在汇编程序中,立即数前面要加$,寄存器名前面要加%,以便跟符号名区分开。以后我们会看到<code class="literal">mov</code>指令还有另外几种形式,但数据传送方向都是一样的,第一个操作数总是源操作数,第二个操作数总是目标操作数。</p><pre class="programlisting"> movl $4, %ebx</pre><p>和上一条指令类似,生成一个立即数4并保存到<code class="literal">ebx</code>寄存器中。</p><pre class="programlisting"> int $0x80</pre><p>前两条指令都是为这条指令做准备的,执行这条指令时发生以下动作:</p><div class="orderedlist"><ol type="1"><li><p><code class="literal">int</code>指令称为软中断指令,可以用这条指令故意产生一个异常,上一章讲过,异常的处理和中断类似,CPU从用户模式切换到特权模式,然后跳转到内核代码中执行异常处理程序。</p></li><li><p><code class="literal">int</code>指令中的立即数0x80是一个参数,在异常处理程序中要根据这个参数决定如何处理,在Linux内核中<code class="literal">int $0x80</code>这种异常称为系统调用(System Call)<a id="id2769615" class="indexterm"></a>。内核提供了很多系统服务供用户程序使用,但这些系统服务不能像库函数(比如<code class="literal">printf</code>)那样调用,因为在执行用户程序时CPU处于用户模式,不能直接调用内核函数,所以需要通过系统调用切换CPU模式,经由异常处理程序进入内核,用户程序只能通过寄存器传几个参数,之后就要按内核设计好的代码路线走,而不能由用户程序随心所欲,想调哪个内核函数就调哪个内核函数,这样可以保证系统服务被安全地调用。在调用结束之后,CPU再切换回用户模式,继续执行<code class="literal">int $0x80</code>的下一条指令,在用户程序看来就像函数调用和返回一样。</p></li><li><p><code class="literal">eax</code>和<code class="literal">ebx</code>的值是传递给系统调用的两个参数。<code class="literal">eax</code>的值是系统调用号,Linux的各种系统调用都是由<code class="literal">int $0x80</code>指令引发的,内核需要通过<code class="literal">eax</code>判断用户要调哪个系统调用,<code class="literal">_exit</code>的系统调用号是1。<code class="literal">ebx</code>的值是传给<code class="literal">_exit</code>的参数,表示退出状态。大多数系统调用完成之后会返回用户空间继续执行后面的指令,而<code class="literal">_exit</code>系统调用比较特殊,它会终止掉当前进程,而不是返回用户空间继续执行。</p></li></ol></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">x86汇编的两种语法:intel语法和AT&T语法</h3><p>x86汇编一直存在两种不同的语法,在intel的官方文档中使用intel语法,Windows也使用intel语法,而UNIX平台的汇编器一直使用AT&T语法,所以本书使用AT&T语法。<code class="literal">movl %edx,%eax</code>这条指令如果用intel语法来写,就是<code class="literal">mov eax,edx</code>,寄存器名不加%号,源操作数和目标操作数的位置互换,字长也不是用指令的后缀l表示而是用另外的方式表示。本书不详细讨论这两种语法之间的区别,读者可以参考<a class="xref" href="bi01.html#bibli.assemblyhowto" title="Linux Assembly HOWTO(http://tldp.org/HOWTO/Assembly-HOWTO/)很不幸,目前tldp.org被我们伟大的防火墙屏蔽了,请自己找代理访问">[<abbr class="abbrev">AssemblyHOWTO</abbr>]</a>。</p><p>介绍x86汇编的书很多,UNIX平台的书都采用AT&T语法,例如<a class="xref" href="bi01.html#bibli.groundup" title="Programming from the Ground Up: An Introduction to Programming using Linux Assembly Language">[<abbr class="abbrev">GroudUp</abbr>]</a>,其它书一般采用intel语法,例如<a class="xref" href="bi01.html#bibli.x86assembly" title="Introduction to 80x86 Assembly Language and Computer Architecture">[<abbr class="abbrev">x86Assembly</abbr>]</a>。</p></div><div class="simplesect" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2769776"></a>习题</h3></div></div></div><p>1、把本节例子中的<code class="literal">int $0x80</code>指令去掉,汇编、链接也能通过,但是执行的时候出现段错误,你能解释其原因吗?</p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch18.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch18.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch18s02.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第 18 章 x86汇编程序基础 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 2. x86的寄存器</td></tr></table></div></body></html>