`
tiandirensoon
  • 浏览: 594145 次
文章分类
社区版块
存档分类
最新评论

Linux下的AT&T语法(即GNU as 汇编语法)入门

 
阅读更多

学习这么长时间,一直在C语言这一层面上钻研和打拼,日积月累,很多关于C的疑惑在书本和资料中都难以找到答案。程序员是追求完美的一个种群,其头 脑中哪怕是存在一点点的思维黑洞都会让其坐卧不宁。不久前在itput论坛上偶得《Computer Systems A Programmer's Perspective》(以下称CS.APP)这本经典好书,中文有翻译的《深入理解计算机系统》。是遂连夜拜读以求解惑。虽说书中没有能正面的回答我的一些疑惑,但是它却为我指明了一条通向 “无惑”之路 -- 这就是打开汇编之门。

汇编语言是一门非常接近机器语言的语言,其语句与机器指令之间的对应关系更加简单和清晰。打开汇 编之门不仅仅能解除高级语言给你带来的疑惑,它更能让你更加的理解现代计算机的运行体系,还有一点更加重要的是它给你带来的是一种自信的感觉,减少了你在 高处摇摇欲坠的恐惧,响应了侯捷老师的“勿在浮沙筑高台”的号召。现在学习汇编的目的已与以前大大不同了。正如CS.APP中所说那样“程序员学习汇编的 需求随着时间的推移也发生了变化,开始时是要求程序员能直接用汇编编写程序,现在则是要求能够阅读和理解优化编译器产生的代码”。能阅读和理解,这也恰恰 是我的需求和目标。

以前接触过汇编,主要是Microsoft MASM宏汇编,不过那时的认识高度不够加上态度不端正,错失了一个很好的学习机会。现在绝大部分时间是使用GCC在Unix系列平台上工作,选择汇编语 言当然是GNU汇编了,恰好CS.APP中使用的也是GNU的汇编语法。由于学习汇编的主要目的还是“解惑”,所以形式上多是以C代码和汇编代码的比较。

1、汇编让你看到更多
随 着你使用的语言的层次的提高,你眼中的计算机将会越来越模糊,你的关注点也越来越远离语言本身而靠近另一端“问题域”,比如通过JAVA,你更多看到的是 其虚拟机,而看不到真实的计算机;通过C,你看到的也仅仅是内存一层;到了汇编语言,你就可以深入到寄存器一层自由发挥了。汇编程序员眼里的“独特风景” 包括:
a) “程序计数器(%eip)” -- 一个特殊寄存器,其中永远存储下一条将要执行的指令的地址;
b) 整数寄存器 -- 共8个,分别是%eax、%ebx、%ecx、%edx、%esi、%ebi、%esp和%ebp,它们可以存整数数据,可以存地址,也可以记录程序状态 等。早期每个寄存器都有其特殊的用途,现在由于像linux这样的平台多采用“平面寻址[1]”,寄存器的特殊性已经不那么明显了。
c) 条件标志寄存器 -- 保存最近执行的算术指令的状态信息,用来实现控制流中的条件变化。
d) 浮点数寄存器 -- 顾名思义,用来存放浮点数。
虽说寄存器的特殊性程度已经弱化,但是实际上每个编译器在使用这些寄存器时还是遵循一定的规则的,以后再说。

2、初窥汇编
下面是一个简单的C函数:
void dummy() {
int a = 1234;
int b = a;
}
我们使用gcc加-S选项将之转换成汇编代码如下(省略部分内容):
movl $1234, -4(%ebp)
movl -4(%ebp), %eax
movl %eax, -8(%ebp)
看 了一眼又一眼,还是看不懂,只是发现些熟悉的内容,因为上面提过如%ebp、%eax等。这只是个引子,让我们感性的认识一下汇编的“容貌”。我们一点点 地来看。咋看一眼汇编代码长得似乎很相似,没错,汇编代码就是一条一条的“指令+操作数”的语句的集合。汇编指令是固定的,每条指令都有其固定的用途,而 操作数表示则有多种类型。

1) 操作数表示
大部分汇编指令都有一个或多个操作数,包括指令操作中的源和目的。一条标准的指令格式大 致是这样的:“指令 + 源操作数 + 目的操作数”,其中源操作数可以是立即数、从寄存器中读出的数或从内存中读出的数;而目的操作数则可以是寄存器或内存。按这么一分类,操作数就大致有三 种:
a) 立即数表示法 -- 如“movl $1234, -4(%ebp)”中的“$1234”,就是一个立即数作为操作数,按照GNU汇编语法,立即数表示为“$+整数”。立即数常用来表示代码中的一些常数, 如上例中的“$1234”。注意一点的是立即数不能作为目的操作数。
b) 寄存器表示法 -- 这种比较简单,它就是表示寄存器之内容。如上面的“movl -4(%ebp), %eax”中的%eax就是使用寄存器表示法作源操作数,而“movl %eax, -8(%ebp)”中的%eax则是使用寄存器表示法作目的操作数。
c) 内存引用表示法 -- 计算出的该操作数的值表示的是相应的内存地址。汇编指令根据这个内存地址访问相应的内存位置。如上例“movl -4(%ebp), %eax”中的“-4(%ebp)”,其表示的内存地址为(%ebp寄存器中的内容-4)得到的值。

2) 数据传送指令
汇编语言中最最常用的指令 -- 数据传送指令,也是我们接触的第一种类别的汇编指令。其指令的格式为:“mov 源操作数, 目的操作数”。
mov 系列支持从最小一个字节到最大双字的访问与传送。其中movb用来传送一字节信息,movw用来传送二字节,即一个字的信息,movl用来传送双字信息。 这些不详说了。除此以外mov系列还提供两个带位扩展的指令movsbl和movzbl

==============================================================

汇编语言作为一种高效的,而且紧密结合硬件平台的编程语言,在操作系统,嵌入式开发等领域都有着十分重要的作用。正因为汇编依赖于硬件结构(CPU指令码),因此不同体系结构上的汇编语言也大相径庭。本文简单介绍了Linux下的AT&T语法(即GNU as 汇编语法),以及在Linux下汇编的基本方法。

AT&T语法起源于AT&T贝尔实验室,是在当时用于实现Unix系统的处理器操作码语法之上而形成的,AT&T语法和Intel语法主要区别如下:
AT&T使用$表示立即数,Intel不用,因此表示十进制2时,AT&T为$2,而Intel就是2
AT&T在寄存器前加%,比如eax寄存器表示为%eax
AT&T 处理操作数的顺序和Intel相反,比如,movl %eax, %ebx是将eax中的值传递给ebx,而Intel是这样的mov ebx, eax
AT&T在助记符的后面加上一个单独字符表示操作中数据的长度,比如movl $foo, %eax等同于Intel的mov eax, word ptr foo
长跳转和调用的格式不同,AT&T为ljmp $section, $offset,而Intel为jmp section:offset
主要的区别就是这些,其他的细节还有很多,下面给出一个具体的例子来说明

#cpuid.s Sample program

.section .data

output:
.ascii "The processor Vendor ID is 'xxxxxxxxxxxx'\n"

.section .text
.globl _start

_start:

movl $0, %eax

cpuid

movl $output, %edi

movl %ebx, 28(%edi)

movl %edx, 32(%edi)

movl %ecx, 36(%edi)

movl $4, %eax

movl $1, %ebx

movl $output, %ecx

movl $42, %edx

int $0x80

movl $1, %eax

movl $0, %ebx

int $0x80


这个程序的作用是查询CPU的厂商ID,其中:

,ascii定义字符串(和Intel格式完全不同).section是声明段的语句,.data和.text是段名,分别为数据段和代码段, _start是gas(GNU汇编器)的默认入口标签,表示程序从这里开始执行。.globl将_start声明成了外部程序访问的标签。cpuid为指令请求CPU的指定信息,该指令用eax作为输入,ebx,edx,ecx作为输出,这里将0作为cpuid的输入指令,请求返回CPU的厂商ID字符串。返回的结果,一个12字节的字符串,分别存储在三个寄存器中,其中ebx存放低4位,edx中间4位,ecx高4位(注意顺序!)。接下来定义一个指针edi,edi指向output的开始地址,然后接着的3条语句将output里的x替换为厂商信息。28(%edi)中的28表示偏移量,即整个地址为%edi里的地址加上28个字节,这个地址正好是output里第一个x的地址。再接下来就是打印结果了,这里用到了Linux的一个系统调用(int 0x80),该系统调用的参数分别为:eax 系统调用号,ebx 要写入的文件描述符,ecx 字符串首地址,edx 字符串长度,程序里这些个参数的值分别为4,1(标准输出),output的地址和42。最后再次调用1号系统调用-退出函数,返回shell,这次 ebx中的值是返回给shell的退出代码,0表示无异常

然后汇编连接运行程序:
[root@zieckey-laptop src]# as -o cpuid.o cpuid.s
[root@zieckey-laptop src]# ld cpuid.o -o cpuid
[root@zieckey-laptop src]# ./cpuid
The processor Vendor ID is 'GenuineIntel'
[root@zieckey-laptop src]#

本人的电脑是Pentium M的CPU所以返回的结果是GenuineIntel。

几点说明:

1)Linux的标准汇编环境为as,ld,gdb,gprof,objdump等GNU开发调试工具,除了gdb外,其他全部随binutils包发布。其中as使用的是AT&T语法。在Linux下也可以使用Nasm来进行Intel格式的汇编程序编写

2)Linux下汇编的系统调用为int 0x80,和DOS下的int 21h大同小异,只不过传递参数不同

3)段声明语句.section不需要像Intel格式那样在段结尾的时候加上段结束标志(SEGMENT/ENDS),下一个段的开始自动标志着上个段的结束

4)简单程序的入口标签不是必须要定义的,ld会自己判断入口,但是会给出警告

===========================================例子2

例 2. 求一组数的最大值的汇编程序

#PURPOSE: This program finds the maximum number of a
#   set of data items.
#
#VARIABLES: The registers have the following uses:
#
# %edi - Holds the index of the data item being examined
# %ebx - Largest data item found
# %eax - Current data item
#
# The following memory locations are used:
#
# data_items - contains the item data. A 0 is used
# to terminate the data
#
 .section .data
data_items:   #These are the data items
 .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

 .section .text
 .globl _start
_start:
 movl $0, %edi   # move 0 into the index register
 movl data_items(,%edi,4), %eax # load the first byte of data
 movl %eax, %ebx  # since this is the first item, %eax is
   # the biggest

start_loop:   # start loop
 cmpl $0, %eax   # check to see if we've hit the end
 je loop_exit
 incl %edi   # load next value
 movl data_items(,%edi,4), %eax
 cmpl %ebx, %eax  # compare values
 jle start_loop  # jump to loop beginning if the new
    # one isn't bigger
 movl %eax, %ebx  # move the value as the largest
 jmp start_loop  # jump to loop beginning

loop_exit:
 # %ebx is the status code for the exit system call
 # and it already has the maximum number
 movl $1, %eax   #1 is the exit() syscall
 int $0x80

汇编、链接、执行:

$ as max.s -o max.o
$ ld max.o -o max
$ ./max
$ echo $?

这个程序在一组数中找到一个最大的数,并把它作为程序的退出状态。这组数在.data段给出:

data_items:
 .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

.long指示声明一组数,每个数占32位,相当于C语言中的数组。这个数组开头有一个标号data_items,汇编器会把数组的首地址作为data_items符号所代表的地址,data_items类似于C语言中的数组名。data_items这个标号没有用.globl声明,因为它只在这个汇编程序内部使用,链接器不需要知道这个名字的存在。除了.long之外,常用的数据声明还有:

  • .byte,也是声明一组数,每个数占8位

  • .ascii,例如.ascii "Hello world",声明了11个数,取值为相应字符的ASCII码。注意,和C语言不同,这样声明的字符串末尾是没有'\0'字符的,如果需要以'\0'结尾可以声明为.ascii "Hello world\0"

data_items数组的最后一个数是0,我们在一个循环中依次比较每个数,碰到0的时候让循环终止。在这个循环中:

  • edi寄存器保存数组中的当前位置,每次比较完一个数就把edi的值加1,指向数组中的下一个数。

  • ebx寄存器保存到目前为止找到的最大值,如果发现有更大的数就更新ebx的值。

  • eax寄存器保存当前要比较的数,每次更新edi之后,就把下一个数读到eax中。

_start:
 movl $0, %edi

初始化edi,指向数组的第0个元素。

movl data_items(,%edi,4), %eax

这条指令把数组的第0个元素传送到eax寄存器中。data_items是数组的首地址,edi的值是数组的下标,4表示数组的每个元素占4字节,那么数组中第edi个元素的地址应该是data_items + edi * 4,从这个地址读数据,写成指令就是上面那样,这种地址的表示方式在下一节还会详细解释。

movl %eax, %ebx

ebx的初始值也是数组的第0个元素。下面我们进入一个循环,在循环的开头用标号start_loop表示,循环的末尾之后用标号loop_exit表示。

start_loop:
 cmpl $0, %eax
 je loop_exit

比较eax的值是不是0,如果是0就说明到达数组末尾了,就要跳出循环。cmpl指令将两个操作数相减,但计算结果并不保存,只是根据计算结果改变eflags寄存器中的标志位。如果两个操作数相等,则计算结果为0,eflags中的ZF位置1。je是一个条件跳转指令,它检查eflags中的ZF位,ZF位为1则发生跳转,ZF位为0则不跳转,继续执行下一条指令。可见条件跳转指令和比较指令是配合使用的,前者改变标志位,后者根据标志位做判断,如果参与比较的两数相等则跳转,je的e就表示equal。

incl %edi
 movl data_items(,%edi,4), %eax

edi的值加1,把数组中的下一个数传送到eax寄存器中。

cmpl %ebx, %eax
 jle start_loop

把当前数组元素eax和目前为止找到的最大值ebx做比较,如果前者小于等于后者,则最大值没有变,跳转到循环开头比较下一个数,否则继续执行下一条指令。jle也是一个条件跳转指令,le表示less than or equal。

movl %eax, %ebx
 jmp start_loop

更新了最大值ebx然后跳转到循环开头比较下一个数。jmp是一个无条件跳转指令,什么条件也不判断,直接跳转。loop_exit标号后面的指令用exit系统调用退出程序。

分享到:
评论

相关推荐

    AT&T汇编语法总结

    基本满足需要,AT&T汇编正式linux下gcc编译后产生的 GNU汇编语言

    gnu 汇编语法手册

    gnu 汇编语法手册是pdf格式的,也是英文版的,在您阅读的同时,有助于英文水平地提高

    GNU_ARM汇编语法

    GNU汇编语法基础,根据GNU汇编指南所写的摘要。

    GNU 汇编语言介绍

    介绍关于GNU AS汇编语法 .

    GNU_ARM汇编入门.pdf

    GNU_ARM汇编入门

    GNU汇编gas权威手册 using as

    GNU汇编gas权威手册 using as 是GNU官方的as使用手册,包括详尽的GNU汇编编写语法规则,和使用的伪指令。如果你需要看linux的汇编代码,那么这是最好的参考资料之一。

    Linux 桌面玩家指南:08. 使用 GCC 和 GNU Binutils 编写能在 x86 实模式运行的 16 位代码 -

    1、GNU AS 汇编器使的汇编语采的是 AT&T 语法,该 2、在以上代码中我全部使的都是 16 位的指令,如 3、在以上代码中使了函数 display_st

    GNU ARM汇编

    Linux下ARM汇编语法尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作 ARM的协处理器等。初始化完成后就可以跳转到C代码执行

    GNU ARM汇编指令介绍

    GNU官方的ARM汇编指令手册,是GNU对ARM指令的扩展,如果你对linux内核或是uboot的汇编代码的一些语法不是很理解,有必要看一下。

    汇编语言程序设计for linux

    非常适合linux下的c/c++程序员继续学习汇编语言。该书使用环境:IA-32处理器,Linux操作系统,gnu工具,at&t语法。都是太喜欢的东西。真的很不错。为了您看完可以交流评论下,所以小收5分。

    Linux_ARM_汇编.doc

    第一部分 Linux下ARM汇编语法 尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最...需要注意的是,GNU的汇编器遵循AT&T的汇编语法,可以从GNU的站点(www.gnu.org)上下载有关规范。 ……………………

    Using-GNU-Assembler

    Using-GNU-Assembler,介绍at&t汇编的语法

    ARM GNU常用汇编语言.rar_gnu汇编_gun_嵌汇编 gun style_汇编_汇编语言gun

    介绍GUN平台汇编语言的常用语法和标识符,便于进行汇编语言的阅读,编写和分析

    参考资料—GNU ARM汇编语法1

    //为代码行中间的注释符号,comment 为具体的注释语句,其它注释符号参见第 6 节“特殊字符”下面定义一个"add"函数的例子:.section .tex

    as命令 GUN汇编器

    as命令是二进制工具集GNU Binutils的一员,是GNU推出的一款汇编语言编译器集,用于将汇编代码编译为二进制代码,它支持多种不同类型的处理器。 语法格式:as [参数] [文件] 常用参数: -ac 忽略失败条件 -ad ...

    ARM标准汇编与GNU汇编.doc

    前段时间看arm的汇编,发现很多有一个小点,但是借来的书上的语法却没有,问同学也不知道,于是在网上查了一番才发现我书上看到的是arm的标准汇编,而有小点的gnu的汇编,于是将收集到的资料整理后放到这里来。

    Linux中的汇编语言

    讲述了linux中的gnu汇编方式,区别intel的汇编语法格式。

    asm-utils:在i386 gnu汇编语言中为FreeBSD,macOS和GNULinux实现的一些UNIX实用程序

    asm-utils asm-utils是一小部分精选的Unix实用程序的集合,这些实用程序以32位x86汇编语言(AT&T语法)实现,适用于FreeBSD,NetBSD,macOS(<= Mojave 10.14)和GNU / Linux。该项目是出于教育目的而开始的,...

    windows下面的一个完整arm gnu工程

    windows下面的一个完整arm gnu工程,由keil的arm工程改编。生成的elf文件可以load到keil...汇编语言遵从AT&T asm语法规则,对学习ARM的启动,汇编、编译、链接等底层知识很有益处。(需要安装window下面的gnu-arm工具链)

    18342075米家龙实验一1

    1. AT&T 汇编语言与已经学过的 mips 汇编语言相比,语法格式相近,但具体的指令以及参数有很大 2. AT&T 汇编语言通过与 C 语言内联,能够更加地

Global site tag (gtag.js) - Google Analytics