2019-08-01 17:43:38 weixin_40390964 阅读数 446

汇编是学习处理器以及操作系统时必须面对的一道坎,可以说汇编是从一个软件工程师的角度理解处理器的最好途径,由于我工作中主要使用C/C++,虽然也接触过一些汇编的编程,但是总的来说没有系统的学习过汇编这门语言,最近再学习操作系统相关的知识的时候发现嵌入式开发工程师系统的学习一下汇编是很有必要的。由于汇编的是最贴近处理器的一门语言,这也就导致了不同的处理器的汇编有多多少少的不同。换句话说,我们通常说说的汇编应该具体的指到某一款处理器的汇编。处理器使用最经典的当是8086处理器,它曾被无数次的写进教科书。可以说是现代大多数处理器的鼻祖,那么8086的汇编当然也是最经典最值得学习的一门汇编了。

说了这么多进入正题,笔者在网上找了一圈发现8086汇编的学习环境搭建基本都是再Windows下面进行,而在linux下的学习环境在网上还是比较难找,所以今天就写了这么一份环境搭建记录分享给大家。我的环境是ubuntu18.04,其他linux发行版方法都是通用的。

dosbox是一个免费开源跨平台的MS-DOS模拟器。用它可以编译运行8086汇编。ubuntu下安装dosbox只需要一句话:
$ sudo apt-get install dosbox

图中显示我已经安装过了。
安装好之后可以看一下版本:
$ dosbox -version

安装好之后可以直接运行:
$ dosbox

启动后出现的提示为 Z:\。上图可以看到dosbox虚拟机已经跑起来了。
接下来挂载驱动器c盘并进入c盘:
Z:\>mount c /home/ubuntu/masm
Z:\>c:

如上图成功将linux下的路径"/home/ubuntu/masm/"这个路径挂载为dosbox虚拟机的序盘,并进入到了c盘
 为了让虚拟机每一次启动都自动挂载并进入C盘,可以在~/.dosbox/dosbox-0.74.conf文件中添加启动脚本,只需要在该文件末尾添加:

添加启动脚本之后再次启动dosbox,c盘已经自动挂载好了:

虚拟机安装之后,进行汇编开发学习还需要masm、link、debug三个工具;其中masm是汇编工具、link是链接工具、debug是调试工具。这三个工具可以到网上下载,我这里提供一个我网盘上的一个分享链接(如果失效了大家再自行从网上找吧)https://pan.baidu.com/s/1BXVICTQY0WpXFuA5Bk5-Gw
解压将里面的文件全部复制到上述你所挂载到虚拟机的路径下,我这个例子中是:/home/ubuntu/masm/

目录中的文件我们用到的DEBUG.COM、LINK.EXE、MASM.EXE文件分别对应我们所需的debug、link、masm三个工具。
有了这几个工具之后,我们就可以进行汇编开发学习了,首先试一下debug:

在linux的/home/ubuntu/masm路径下敲一段最简单的汇编源码hello.asm:

在dosbox虚拟机下用masm编译:
c:\>masm hello.asm

连续敲三个回车之后编译就完成了,如上图编译过程中没有任何错误,
在linux的/home/ubuntu/masm路径多的HELLO.OBJ文件就是masm编译出来的:

在linux下file查看HELLO.OBJ这个文件,可以发现它是属于8086平台下的:
$file HELLO.OBJ

接下来进行链接的工作:
C:\>link hello.obj
连续敲三个回车之后,链接就完成了:

如上图所示链接的过程中抛出了一个警告,提示这个汇编程序没有栈段。当然是这样了,我们的演示的汇编代码也没有设置堆栈嘛。链接之后就生成了HELLO.EXE:

直接在虚拟机运行一下:
C:\>hello.exe

正常运行结束(啥也没输出,因为我们的代码也没输出啥)
到这里,linux下的8086汇编环境搭建就完成了。

2016-03-23 11:01:27 gscsdlz 阅读数 520

   8086汇编笔记——基础概述

一、简述

这个笔记是学习王爽老师写的汇编语言以后整理得出的,这是一本非常好的书,大家有兴趣可以买来看看。

感觉在学完汇编语言以后,最大的感受就是对计算机工作的过程有个大致了解,照着另一本书,抄写了一个简单的Linux。总之汇编语言是个特别好玩的东西,所以我把我的学习笔记放到这里,希望和大家一起学习。毕竟工科,毕竟语文有限,不对的地方还希望大家指出

二、基础知识

1.说说历史

早年计算机刚出来的时候,如果要给计算机编写一个程序是非常麻烦的,那时候使用的不是键盘,而是打了孔的纸条,用是否打孔表示0 1,这种方式肯定不行,但是即使是用键盘把0 1打出来相信也很复杂吧,虽然说我们的计算机最后识别的也就是0 1,但是对于人来说,一堆0 1根本无法识别,所以才有了汇编语言,我们把这些叫做机器指令的0 1,加上助记符,汇编指令与机器指令的区别只是在表示方式上,而不是现在的高级语言,完全就不一样了。

2.汇编语言

例如我们把1000100111011000 写成mov ax, bx,就比原来的0 1序列好记忆多了,可能很多人觉得明明就有高级语言C,为啥依然没有放弃汇编呢?就我写操作系统的经验来看,至少现在操作系统的启动仍然离不开汇编。

汇编语言是ASCII写的,计算机是不认识,所以需要由编译器将这种指令翻译成0 1

3.汇编语言组成

汇编指令:机器码的助记符,比如上面的mov

伪指令:没有对应的机器码,汇编编译器执行

其他符号:例如 + , - ,* 、 /这些也是由编译器识别

4.存储器

现代的计算机,一般是由CPU内部的寄存器,高速缓存(一般买CPU上面写的L1, L2什么的),内存,硬盘或者是光盘,其实还有就是显卡或者网卡都有自己的缓存,然后是BIOS的ROM

我们在计算机中,指令和数据并没有什么区别,就比如1101001这个串,可能是一个ASCII,可以是一个整数,甚至是一个指令,主要是看我们把这个数据往CPU内部的哪个器件放,放到ALU中就是数字,放到地址加法器中就是地址,放到指令队列中就是指令。

我们的存储器,都是一个一个的小格子构成,一个小格子只能代表0或者1,但是我们实际使用中,基本不会单独使用这一个,而是8个小格子一起形成一个Byte,通常将讲就是字节,也就是8位一个字节, 1 B = 8bit,这个B,还有更大的单位例如KB,MB,GB,这下是不是就很熟悉了。

5.CPU的读写与总线

首先要说明的是存储器,有自己的地址,一般从0开始编号。当CPU进行读写操作的时候,首先是通过地址总线访问指定地址,然后通过控制总线传输读操作指令,然后通过数据总线,把得到的数据传回CPU。这里涉及到三种总线。

主要说说地址总线,8086是20位地址总线,这是什么意思呢?不急,先说说并行传输串行传输,串行传输很常见,比如USB接口,数据时一个接一个的发过去的,但是并行接口是单位时间内所有数据同时发过去,20位地址总线,单次就能发送20个0 1过去,最大就是11111.,,,111,这个数字的大小就是1MB(计算机内部最小的单位是Byte不是bit,大家可以自己计算一下),所以说我们的8086能够访问的内存大小是1M。

我们现在的电脑有32位,有64位,32位的地址总线,大小就是4GB,这就是为什么32位的计算机内存不能大于4GB的原因,因为后面的空间CPU无法找到。

当然数据总线和地址总线类似,8086的数据总线宽度为16位,

控制总线是不同于上述两种的,控制总线根数越多,CPU对外界的控制能力越强

6.内存空间简述

计算机的外设,比如键盘,如果我们要读取键盘的按键,我们需要让CPU向I/O端口发出指令(具体的以后我们通过代码来实现)

刚刚给大家说的存储器的编号从0开始,但是CPU可以访问到显存的空间,以及BIOS的空间,那么这是怎做到的?

CPU并不会完全把我们的内存条当做主存储器,而是把各种存储器,放在一起当做一个逻辑的内存,也就是CPU工作是使用的内存,并不完全是我们的内存条,还有其他地方。虽然这些存储器在物理上是独立的,但是它们都和地址总线相连,CPU读写的时候,都会对它们通过控制总线发出指令。一般来说8086CPU内部逻辑存储器的划分,如图

假设要在屏幕上输出什么,直接向显存所在的地址写入数据即可。但是显存并不在我们的内存条里面,而是显卡内部通过地址总线和CPU连在一起,一定要注意CPU寻址是逻辑存储空间。

2007-07-31 10:23:00 f5key 阅读数 249

Linux下的汇编程序设计

关键词Linux下的汇编程序设计

最近我们在学汇编,找了本讲汇编的书,正好里边讲的是用GUN汇编器编译汇编语言的内容,就在网上查了一些相关的资料!

摘要:本文主要讲述了Linux下使用汇编的利弊,以及常用汇编工具的使用和语法特点。重点讲述了NASM

引言:

汇编语言是低级语言,与硬件和操作系统紧密联系。个人电脑以前都是用DOS,现在发展成了WINDOWS 98,而另一个操作系统Linux也正在崛起。下面比较一下这三个操作系统:

DOS

较稳定,速度快

无法充分发挥计算机性能,没有图形界面

较低

WINDOWS 98

操作简便,

应用软件多,硬件兼容性好

不稳定,经常死机,速度慢

Linux

性能优秀,非常稳定,界面美观,操作简便

缺乏软件厂商支持,应用软件少

免费

操作系统比较

由以上的比较可知,Linux操作系统本身具有较大优势,它的普及应该只是时间问题,所以如何在Linux下开发软件是我们计算机系学生必须学习与研究的一个课题。

Linux下的主要编程语言是C,同时Linux还支持其他许多编程语言,汇编语言作为最重要的编程语言之一,当然也包括在内。它能够完成许多其他语言所不能完成的功能。要学习Linux编程,就必须要学习Linux下的汇编程序设计。下面我就来介绍一下Linux下的汇编程序设计。

Linux汇编简介:

一、汇编语言的优缺点:

由于Linux是用C写的,所以C自然而然的就成为了Linux的标准编程语言。大部分人都把汇编给忽略了,甚至在因特网上找资料都是非常的困难,很多问题都需要靠自己来尝试。我认为这样对待汇编语言是不公平的,不能只看到它的缺点,当然也不能只看到它的优点,下面把它的优缺点作一个比较:

优点:汇编语言可以表达非常底层的东西

l 可以直接存取寄存器和I/O

l 编写的代码可以非常精确的被执行

l 可以编写出比一般编译系统高效的代码

l 可以作为不同语言或不同标准的接口

缺点:汇编语言是一个非常低级的语言

l 非常冗长单调,在DOS下编程时就可以体会到

l 易出BUG,且调试困难

l 代码不易维护

l 兼容性不好,与硬件关系非常紧密

总的来说,汇编语言要用在必须的地方,尽量少用汇编编写大型程序,多采用inline模式。

二、汇编语言工具:

DOS下常用的工具MASMTASMLinux下就用不起来了,Linux有自己的汇编工具,而且种类非常的多。其中Gas可以算是标准配置,每一种Linux中都包括有Gas,但是GAS采用的不是我们通常在DOS下采用的汇编语法,它采用的是ATT的语法格式,与intel语法格式有很大的不同。

如果要采用与DOS接近的语法格式,就必须用另一种汇编工具NASMNASM基本与MASM相同,但也有不少地方有较大区别,特别涉及到操作系统原理时,与DOS可以说是截然不同。

Linux汇编程序设计:

一、Hello,world!

几乎所有的语言入门篇都是以“Hello,world!”为例,那么我也以Hello,world!为例开始。

;-------------NASM's standalone Hello-World.asm for Linux --------

section .text

extern puts

global main

 

main:    

   push dword msg                ;stash the location of msg on the stack.

   call puts                     ;call the 'puts' routine (libc?) 

   add esp, byte 4               ;clean the stack?

   ret                           ;exit.

 

msg:

   db "Hello World!",0            

 

编译:

      nasm –f elf hello.asm

        gcc –o hello hello.o

 

说明:这个程序实际上是调用了,Linux系统的puts函数,原理与调用DOS下C语言的函数相同,先用Extern声明puts是外部函数,再把参数(即msg的地址)压入堆栈,最后Call函数实现输出。

我们再来看一个程序:

section .text

global main

 

main:    

        mov eax,4           ;4号调用

        mov ebx,1           ;ebx送1表示stdout

        mov ecx,msg    ;字符串的首地址送入ecx

        mov edx,14     ;字符串的长度送入edx

        int 80h                 ;输出字串

        mov eax,1           ;1号调用

        int 80h                 ;结束

msg:

   db "Hello World!",0ah,0dh            

(编译同上一个程序)

 

这个程序与DOS程序十分相似,它用的是linux中的80h中断,相当于DOS下的21h中断,只是因为Linux32位操作系统,所以采用了EAXEBX等寄存器。但是Linux作为一个多用户的操作系统与DOS又是有着非常大的区别的。要写出有特色的程序,不了解操作系统和硬件是不行的。下面我介绍一下Linux操作系统。

二、Linux操作系统简介:

操作系统实际是抽象资源操作到具体硬件操作细节之间的接口。对Linux这样的多用户操作系统来说,它需要避免用户对硬件的直接访问,并防止用户之间的互相干扰。所以Linux接管了BIOS调用和端口输入输出,关于端口输入输出方面请参阅Linux IO-Port-Programming HOWTO。而要通过Linux对硬件硬件进行访问就需要用到System Call,实际上是许多C的函数,可以在汇编程序中调用,调用方法与DOS下的汇编完全相同,而且用ASM汇编时不用链接额外的库函数。

LinuxDOS的主要区别在于内存管理、进程(DOS下无进程概念)、文件系统,其中内存管理和进程与汇编编程的关系比较密切:

1、内存管理:

对任一台计算机而言,其内存以及其他资源都是有限的。为了让有限的物理内存满足应用程序对内存的大需求量,Linux采用了称为“虚拟内存”的内存管理方式。Linux将内存划分为容易处理的“内存页”,在系统运行过程中,应用程序对内存的需求大于物理内存时,Linux可将暂时不用的内存页交换到硬盘上,这样,空闲的内存页可以满足应用程序的内存需求,而应用程序却不会注意到内存交换的发生。

2、进程

进程实际是某特定应用程序的一个运行实体。在Linux系统中,能够同时运行多个进程,Linux通过在短的时间间隔内轮流运行这些进程而实现“多任务”。这一短的时间间隔称为“时间片”,让进程轮流运行的方法称为“调度”,完成调度的程序称为调度程序。通过多任务机制,每个迸程可认为只有自己独占计算机,从而简化程序的编写,每个进程有自己单独的地址空间,并且只能由这一进程访问,这样,操作系统避免了进程之间的互相干扰以及“坏”程序对系统可能造成的危害。

为了完成某特定任务,有时需要综合两个程序的功能,例如一个程序输出文本,而另一个程序对文本进行排序。为此,操作系统还提供进程间的通讯机制来帮助完成这样的任务。Linux中常见的进程间通讯机制有信号、管道、共享内存、信号量和套接字等。

三、Linux下的汇编工具:

Linux下的汇编工具可谓百家争鸣,不像DOS下都要给MASMTASM给控制了。但是Linux下每一种汇编工具都有很大的区别,要想全部掌握几乎是不可能的,下面我介绍几种常用的汇编工具,重点介绍NASM及其使用和语法。

1、GCC

GCC其实是GNUC语言产品,但它支持Inline Assemble,在GCCinline assemble使用就像宏一样,但它比宏能更清楚更准确的表达机器的工作状态。

C是汇编编程的一个高度概括,它可以减少许多汇编中的麻烦,特别是在GCC这个C编译器中,assemble似乎起不了多大的作用。

2、GAS

GASLinux各版本中基本的汇编工具,但它采用的是AT&T的语法标准与Intel的语法标准有很大的不同,对于DOS编程的我们来说,学习起来是非常困难的。当然如果要精通Linux下的汇编编程,学习GAS也是非常必要的,具体的语法标准可以参看Using GNU Assembler

3、GASP

GASPGAS的扩展,它增强了GAS对宏的支持。

4、NASM

NASMlinux中语法与DOS最为相像的一种汇编工具。虽说如此,它与MASM也是有着很大区别的。

l NASM的使用格式如下:

Nasm –f >format< >filename< -o >filename<

例如:

Nasm -f elf hello.asm

将把hello.asm汇编成ELF object文件,而

Nasm -f bin hello.asm -o hello.com

会把hello.asm汇编成二进制可执行文件hello.com

Nasm –h

将会列出NASM命令行的完整说明。

NASM不会有任何输出,除非有错误发生。

-f 在Linux下主要有aout和ELF两种,如果你不确定你的Linux系统应该用AOUT还是ELF,可以在NASM目录中输入  File nasm ,如果输出nasm: ELF 32-bit LSB executable i386 (386 and up) Version 1表示是ELF,如果输出nasm: Linux/i386 demand-paged executable (QMAGIC)表示是aout。
 

l NASMMASM的主要不同:

首先与linux系统一样,nasm是区分大小写的,Hellohello将是不同的标识符,如果要汇编到DOSOS/2,需要加入UPPERCASE参数。

其次,nasm中内存操作数都是以[ ]表示。

MASM

        foo       equ 1 
        bar       dw 2
         mov ax,foo 
         mov ax,bar

将被汇编成完全不同的指令,虽然它们在MASM中的表达方式完全一样。而NASM完全避免了这种混乱,它使用的是这样的规则:所有对内存的操作都必须通过[ ]来实现。例如上例中对bar的操作就要写成如下形式 mov ax,[bar]。由此可见,nasm中对offset的使用也是没有必要的(nasm中无offset)。Nasm[ ]的使用与masm也有所不同,所有的表达式都必须写在[ ]中,下面举两个例子来说明:

Masm

Nasm

Mov ax,table[di]

Mov ax,[table+di]

Mov ax,es:[di]

Mov ax,[es:di]

Mov ax,[di]+1

Mov ax,[di+1]

Nasm 中不存储变量类型,原因很简单masm中通过[ ]寻址方式的变量也必须要指定类型。Nasm中不支LODS, MOVS, STOS, SCAS, CMPS, INS, OUTS,只支持lodsb、lodsw等已经指定类型的操作。Nasm中不再有assume操作,段地址完全取决于存入段寄存器的值。

关于NASM的使用方法及语法还可以参阅NASM使用手册

结论:

我认为不论是在Windows/DOS下还是在Linux下完完全全用汇编编一个大型程序已经是不可能了,也不会有人愿意去这样做。在windows下我们可以用VC,在Linux/Xwindows下我们可以用C甚至C++ Builder,但是像VCC++ Builder之类的工具尽量隐藏了底层的调用,同时也阻隔了成为高手的机会,因为编出来的程序无法了解它的执行过程也就使编程中最重要的“可预测”性变得很低。正因为如此汇编才有它存在的必要性,同时还有一个更重要的原因,正如《超级解霸》的作者梁肇新所说:“编程序的重点不是“编”,而是调试程序,理论上的完美在实现的时候会遇到很多细节问题,这些问题必须调试才能解决。我的编程习惯是一天写五天调试,《超级解霸》是调试出来的,而不是写出来的。调试就涉及到汇编的问题,不进行汇编级的调试是不彻底的,也不能让人放心。

【作者: Liberal】【访问统计:】【2006年04月6日 星期四 18:20】【 加入博采】【打印

2010-05-11 08:41:00 iamwenyifuxing 阅读数 1975

第一部分 Linux下ARM汇编语法

 

      尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作 ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,可以从GNU的站点 (www.gnu.org)上下载有关规范。

一. Linux汇编行结构


任何汇编行都是如下结构:
[:] [} @ comment
[:] [} @ 注释
Linux ARM 汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始

【例1】定义一个"add"的函数,返回两个参数的和。
.section .text, “x”
.global add @ give the symbol add external linkage
add:
ADD r0, r0, r1   @ add input arguments
MOV pc, lr @ return from subroutine
@ end of program



二. Linux 汇编程序中的标号

 

 

 

    标号只能由a~z,A~Z,0~9,“.”,_等字符组成。当标号为0~9的数字时为局部标号,局部标号可以重复出现,使用方法如下:
? 标号f: 在引用的地方向前的标号
? 标号b: 在引用的地方向后的标号

 

【例2】使用局部符号的例子,一段循环程序
1:
   subs r0,r0,#1        @每次循环使r0=r0-1
   bne 1f       @跳转到1标号去执行
局部标号代表它所在的地址,因此也可以当作变量或者函数来使用。

 


三. Linux汇编程序中的分段
(1).section伪操作
        用户可以通过.section伪操作来自定义一个段,格式如下:
      .section section_name [, "flags"[, %type[,flag_specific_arguments]]]
      每一个段以段名为开始, 以下一个段名或者文件结尾为结束。这些段都有缺省的标志(flags),连接器可以识别这些标志。(与armasm中的AREA相同)。
下面是ELF格式允许的段标志
含义
a 允许段
w 可写段
x 执行段

 

【例3】定义段
.section .mysection @自定义数据段,段名为 “.mysection”
.align   2
strtemp:
.ascii   "Temp string /n/0"

 


(2)汇编系统预定义的段名
.text   @代码段
.data   @初始化数据段
.bss   @未初始化数据段
.sdata @
.sbss   @
需要注意的是,源程序中.bss段应该在.text之前。


 
四. 定义入口点
    汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点
【例4】定义入口点
.section.data
.section .bss
.section .text
.globl _start
_start:


 


五. Linux汇编程序中的宏定义
格式如下:
.macro 宏名 参数名列表 @伪指令.macro定义一个宏
    宏体
.endm   @.endm表示宏结束
    如果宏使用参数,那么在宏体中使用该参数时添加前缀“/”。宏定义时的参数还可以使用默认值。
可以使用.exitm伪指令来退出宏。
【例5】宏定义
.macro SHIFTLEFT a, b
.if /b ”表示不相等,其他的符号如:+、-、*、/、%、、>>、|、&、^、!、==、>=、 {,}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。(与armasm中的SPACE功能相同)


 

.word   {,} …
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)
可以使用.word把标识符作为常量使用
例如:
Start:
valueOfStart:
    .word Start
这样程序的开头Start便被存入了内存变量valueOfStart中。

 


 

 

七.hword   {,} …
插入一个16-bit的数据队列。(与armasm中的DCW相同)

 

八. GNU ARM汇编特殊字符和语法
代码行中的注释符号: ‘@’
整行注释符号: ‘#’
语句分离符号: ‘;’
直接操作数前缀: ‘#’ 或 ‘$’


第二部分 GNU的编译器和调试工具
一. 编译工具
1.编辑工具介绍
    GNU 提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、连接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm- linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux- objcopy。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可 以采用缺省选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写连接脚本文件,用连接器生成最终目标文件(elf格式)用二进制转换工具生成可下载的二进制代码
(1)编写C、C++语言或汇编源程序
      通常汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,读者可以从GNU的站点(
www.gnu.org)上下载有关规范。汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点(见下文关于连接脚本的说明)。
(2)用gcc或g++生成目标文件
      如果应用程序包括多个文件,就需要进行分别编译,最后用连接器连接起来。如笔者的引导程序包括3个文件:init.s(汇编代码、初始化硬件)xmrecever.c(通信模块,采用Xmode协议)和flash.c(Flash擦写模块)。分别用如下命令生成目标文件: arm-linux-gcc-c-O2-oinit.oinit.s arm-linux-gcc-c-O2-oxmrecever.oxmrecever.c arm-linux-gcc-c-O2-oflash.oflash.c 其中-c命令表示只生成目标代码,不进行连接;-o命令指明目标文件的名称;-O2表示采用二级优化,采用优化后可使生成的代码更短,运行速度更快。如果项目包含很多文件,则需要编写makefile文件。关于makefile的内容,请感兴趣的读者参考相关资料。
(3)编写连接脚本文件
      gcc 等编译器内置有缺省的连接脚本。如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的连接脚本文件。编写连接脚本,首先要对目标文件的格式有一定了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符 串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的目标代码中还包括.fini(析构函数代码)和. init(构造函数代码)等。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址 开始放置这些段。例如连接文件link.lds为:
ENTRY(begin)
SECTION
{
.=0x30000000;
.text:{*(.text)}
.data:{*(.data)}
.bss:{*(.bss)}
}
    其中,ENTRY(begin)指明程序的入口点为begin标号;.=0x00300000指明目标代码的起始地址为0x30000000,这一段地址为 MX1的片内RAM;.text:{*(.text)}表示从0x30000000开始放置所有目标文件的代码段,随后的.data:{* (.data)}表示数据段从代码段的末尾开始,再后是.bss段。
(4)用连接器生成最终目标文件
       有了连接脚本文件,如下命令可生成最终的目标文件:arm-linux-ld –no stadlib –o bootstrap.elf -Tlink.lds init.o xmrecever.o flash.o
其中,ostadlib表示不连接系统的运行库,而是直接从begin入口;-o指明目标文件的名称;-T指明采用的连接脚本文件(也可以使用-Ttext address,address表示执行区地址);最后是需要连接的目标文件列表。
(5)生成二进制代码
    连接生成的elf文件还不能直接下载执行,通过objcopy工具可生成最终的二进制文件:
arm-linux-objcopy –O binary bootstrap.elf bootstrap.bin
其中-O binary指定生成为二进制格式文件。Objcopy还可以生成S格式的文件,只需将参数换成-O srec。还可以使用-S选项,移除所有的符号信息及重定位信息。如果想将生成的目标代码反汇编,还可以用objdump工具:
arm-linux-objdump -D bootstrap.elf
至此,所生成的目标文件就可以直接写入Flash中运行了。
2.Makefile实例
example: head.s   main.c
arm-linux-gcc -c -o head.o head.s
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Tlink.lds head.o ain.o -o example.elf
arm-linux-objcopy -O binary -S example_tmp.o example
arm-linux-objdump -D -b binary -m arm   example >ttt.s
二. 调试工具
Linux 下的GNU调试工具主要是gdb、gdbserver和kgdb。其中gdb和gdbserver可完成对目标板上Linux下应用程序的远程调试。 gdbserver是一个很小的应用程序,运行于目标板上,可监控被调试进程的运行,并通过串口与上位机上的gdb通信。开发者可以通过上位机的gdb输入命令,控制目标板上进程的运行,查看内存和寄存器的内容。gdb5.1.1以后的版本加入了对ARM处理器的支持,在初始化时加入- target==arm参数可直接生成基于ARM平台的gdbserver。gdb工具可以从ftp: //ftp.gnu.org/pub/gnu/gdb/上下载。
对于Linux内核的调试,可以采用kgdb工具,同样需要通过串口与上位机上的gdb通信,对目标板的Linux内核进行调试。可以从
http://oss.sgi.com/projects/kgdb/上了解具体的使用方法。
参考资料:
1. Richard Blum,Professional Assembly Language
2. GNU ARM 汇编快速入门,
http://blog.chinaunix.net/u/31996/showart.php?id=326146
3. ARM GNU 汇编伪指令简介,http://www.cppblog.com/jb8164/archive/2008/01/22/41661.aspx
4. GNU汇编使用经验,http://blog.chinaunix.net/u1/37614/showart_390095.html
5. GNU的编译器和开发工具,http://blog.ccidnet.com/blog-htm-do-showone-uid-34335-itemid-81387-type-blog.html
6. 用GNU工具开发基于ARM的嵌入式系统,http://blog.163.com/liren0@126/blog/static/32897598200821211144696/
7. objcopy命令介绍,http://blog.csdn.net/junhua198310/archive/2007/06/27/1669545.aspx

 

 

 


2008-06-01 16:24:00 denlee 阅读数 17168

第一部分 Linux下ARM汇编语法
尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,可以从GNU的站点(www.gnu.org)上下载有关规范。

一. Linux汇编行结构
任何汇编行都是如下结构:
[:] [} @ comment
[:] [} @ 注释
Linux ARM 汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始。
【例1】定义一个"add"的函数,返回两个参数的和。
.section .text, “x”
.global add @ give the symbol add external linkage
add:
ADD r0, r0, r1  @ add input arguments
MOV pc, lr @ return from subroutine
@ end of program

二. Linux 汇编程序中的标号
标号只能由a~z,A~Z,0~9,“.”,_等字符组成。当标号为0~9的数字时为局部标号,局部标号可以重复出现,使用方法如下:
 标号f: 在引用的地方向前的标号
 标号b: 在引用的地方向后的标号
【例2】使用局部符号的例子,一段循环程序
1:
    subs r0,r0,#1        @每次循环使r0=r0-1
    bne 1f         @跳转到1标号去执行
局部标号代表它所在的地址,因此也可以当作变量或者函数来使用。

三. Linux汇编程序中的分段
(1).section伪操作
用户可以通过.section伪操作来自定义一个段,格式如下:
 .section section_name [, "flags"[, %type[,flag_specific_arguments]]]
每一个段以段名为开始, 以下一个段名或者文件结尾为结束。这些段都有缺省的标志(flags),连接器可以识别这些标志。(与armasm中的AREA相同)。

下面是ELF格式允许的段标志
<标志> 含义
a 允许段
w 可写段
x 执行段

【例3】定义段
 .section .mysection @自定义数据段,段名为 “.mysection”
 .align  2
 strtemp:
 .ascii  "Temp string /n/0"


(2)汇编系统预定义的段名
.text  @代码段
.data  @初始化数据段
.bss  @未初始化数据段
.sdata @
.sbss  @
需要注意的是,源程序中.bss段应该在.text之前。
四. 定义入口点
汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。
【例4】定义入口点
.section.data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:
<instruction code goes here>

五. Linux汇编程序中的宏定义
格式如下:
 .macro 宏名 参数名列表   @伪指令.macro定义一个宏
   宏体
 .endm  @.endm表示宏结束
如果宏使用参数,那么在宏体中使用该参数时添加前缀“/”。宏定义时的参数还可以使用默认值。
可以使用.exitm伪指令来退出宏。
【例5】宏定义
.macro SHIFTLEFT a, b
.if /b < 0
MOV /a, /a, ASR #-/b
.exitm
.endif
MOV /a, /a, LSL #/b
.endm

六. Linux汇编程序中的常数
(1)十进制数以非0数字开头,如:123和9876;
(2)二进制数以0b开头,其中字母也可以为大写;
(3)八进制数以0开始,如:0456,0123;
(4)十六进制数以0x开头,如:0xabcd,0X123f;
(5)字符串常量需要用引号括起来,中间也可以使用转义字符,如: “You are welcome!/n”;
(6)当前地址以“.”表示,在汇编程序中可以使用这个符号代表当前指令的地址;
(7)表达式:在汇编程序中的表达式可以使用常数或者数值, “-”表示取负数, “~”表示取补,“<>”表示不相等,其他的符号如:+、-、*、/、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、||跟C语言中的用法相似。

七. Linux下ARM汇编的常用伪操作
在前面已经提到过了一些为操作,还有下面一些为操作:
 数据定义伪操作: .byte,.short,.long,.quad,.float,.string/.asciz/.ascii,重复定义伪操作.rept,赋值语句.equ/.set ;
 函数的定义 ;
 对齐方式伪操作 .align;
 源文件结束伪操作.end;
 .include伪操作;
 if伪操作;
 .global/ .globl 伪操作 ;
 .type伪操作 ;
 列表控制语句 ;
 区别于gas汇编的通用伪操作,下面是ARM特有的伪操作 :.reg ,.unreq ,.code ,.thumb ,.thumb_func ,.thumb_set, .ltorg ,.pool
1. 数据定义伪操作
(1) .byte:单字节定义,如:.byte 1,2,0b01,0x34,072,'s' ;
(2) .short:定义双字节数据,如:.short 0x1234,60000 ;
(3) .long:定义4字节数据,如:.long 0x12345678,23876565
(4) .quad:定义8字节,如:.quad 0x1234567890abcd
(5) .float:定义浮点数,如:
  .float 0f-314159265358979323846264338327/
    95028841971.693993751E-40                 @ - pi
(6) .string/.asciz/.ascii:定义多个字符串,如:
   .string "abcd", "efgh", "hello!"
   .asciz "qwer", "sun", "world!"
   .ascii "welcome/0"
需要注意的是:.ascii伪操作定义的字符串需要自行添加结尾字符'/0'。
(7) .rept:重复定义伪操作, 格式如下:
               .rept 重复次数
               数据定义
               .endr  @结束重复定义
     例如:
                .rept 3
                .byte 0x23
                .endr
(8) .equ/.set: 赋值语句, 格式如下:
                .equ(.set) 变量名,表达式
     例如:
                .equ abc 3  @让abc=3

2.函数的定义伪操作
(1)函数的定义,格式如下:
         函数名:
         函数体
         返回语句
一般的,函数如果需要在其他文件中调用, 需要用到.global伪操作将函数声明为全局函数。为了不至于在其他程序在调用某个C函数时发生混乱,对寄存器的使用我们需要遵循APCS准则。函数编译器将处理为函数代码为一段.global的汇编码。
(2)函数的编写应当遵循如下规则:
 a1-a4寄存器(参数、结果或暂存寄存器,r0到r3 的同义字)以及浮点寄存器f0-f3(如果存在浮点协处理器)在函数中是不必保存的;
 如果函数返回一个不大于一个字大小的值,则在函数结束时应该把这个值送到 r0 中;
 如果函数返回一个浮点数,则在函数结束时把它放入浮点寄存器f0中;
 如果函数的过程改动了sp(堆栈指针,r13)、fp(框架指针,r11)、sl(堆栈限制,r10)、lr(连接寄存器,r14)、v1-v8(变量寄存器,r4 到 r11)和 f4-f7,那么函数结束时这些寄存器应当被恢复为包含在进入函数时它所持有的值。

3. .align .end .include .incbin伪操作
(1).align:用来指定数据的对齐方式,格式如下:
                .align [absexpr1, absexpr2]
     以某种对齐方式,在未使用的存储区域填充值. 第一个值表示对齐方式,4, 8,16或     32. 第二个表达式值表示填充的值。
(2).end:表明源文件的结束。
(3).include:可以将指定的文件在使用.include 的地方展开,一般是头文件,例如:
                .include “myarmasm.h”
(4).incbin伪操作可以将原封不动的一个二进制文件编译到当前文件中,使用方法如下:
            .incbin "file"[,skip[,count]]
     skip表明是从文件开始跳过skip个字节开始读取文件,count是读取的字数.

4. .if伪操作
根据一个表达式的值来决定是否要编译下面的代码, 用.endif伪操作来表示条件判断的结束, 中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码。
.if有多个变种:
 .ifdef symbol           @判断symbol是否定义
 .ifc string1,string2   @字符串string1和string2是否相等,字符串可以用单引号括起来
 .ifeq expression       @判断expression的值是否为0
.ifeqs string1,string2  @判断string1和string2是否相等,字符 串必须用双引号括起来
.ifge expression         @判断expression的值是否大于等于0
.ifgt absolute expression  @判断expression的值是否大于0
.ifle expression         @判断expression的值是否小于等于0
.iflt absolute expression  @判断expression的值是否小于0
.ifnc string1,string2     @判断string1和string2是否不相等, 其用法跟.ifc恰好相反。
.ifndef symbol, .ifnotdef symbol  @判断是否没有定义symbol,  跟.ifdef恰好相反
.ifne expression          @如果expression的值不是0, 那么编译器将编译下面的代码
.ifnes string1,string2    @如果字符串string1和string2不相 等, 那么编译器将编译下面的代码.

5. .global  .type   .title   .list
(1).global/ .globl :用来定义一个全局的符号,格式如下:
       .global symbol  或者  .globl symbol
(2).type:用来指定一个符号的类型是函数类型或者是对象类型, 对象类型一般是数据, 格式如下:
            .type 符号, 类型描述
【例6】
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 10
【例7】
.section .text
.type asmfunc, @function
.globl asmfunc
asmfunc:

mov pc, lr

(3)列表控制语句:
.title:用来指定汇编列表的标题,例如:
            .title “my program”
.list:用来输出列表文件.

6. ARM特有的伪操作
(1) .reg: 用来给寄存器赋予别名,格式如下:
                   别名 .req 寄存器名
(2) .unreq: 用来取消一个寄存器的别名,格式如下:
       .unreq 寄存器别名
  注意被取消的别名必须事先定义过,否则编译器就会报错,这个伪操作也可以用来取消系统预制的别名, 例如r0, 但如果没有必要的话不推荐那样做。
(3) .code伪操作用来选择ARM或者Thumb指令集,格式如下:
           .code 表达式
  如果表达式的值为16则表明下面的指令为Thumb指令,如果表达式的值为32则表明下面的指令为ARM指令.
(4) .thumb伪操作等同于.code 16, 表明使用Thumb指令, 类似的.arm等同于.code 32
(5) .force_thumb伪操作用来强制目标处理器选择thumb的指令集而不管处理器是否支持
(6) .thumb_func伪操作用来指明一个函数是thumb指令集的函数
(7) .thumb_set伪操作的作用类似于.set, 可以用来给一个标志起一个别名, 比.set功能增加的一点是可以把一个标志标记为thumb函数的入口, 这点功能等同于.thumb_func
(8) .ltorg用于声明一个数据缓冲池(literal pool)的开始,它可以分配很大的空间。
(9) .pool的作用等同.ltorg
(9).space <number_of_bytes> {,<fill_byte>}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。(与armasm中的SPACE功能相同)
(10).word <word1> {,<word2>} …
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)
可以使用.word把标识符作为常量使用
 例如:
  Start:
  valueOfStart:
   .word Start
 这样程序的开头Start便被存入了内存变量valueOfStart中。
(11).hword <short1> {,<short2>} …
插入一个16-bit的数据队列。(与armasm中的DCW相同)

八. GNU ARM汇编特殊字符和语法
代码行中的注释符号: ‘@’
整行注释符号: ‘#’
语句分离符号: ‘;’
直接操作数前缀: ‘#’ 或 ‘$’

第二部分 GNU的编译器和调试工具

一. 编译工具
1.编辑工具介绍
GNU提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、连接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm-linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux- objcopy。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可以采用缺省选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写连接脚本文件,用连接器生成最终目标文件(elf格式),用二进制转换工具生成可下载的二进制代码。
(1)编写C、C++语言或汇编源程序
通常汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,读者可以从GNU的站点(www.gnu.org)上下载有关规范。汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点(见下文关于连接脚本的说明)。

(2)用gcc或g++生成目标文件
如果应用程序包括多个文件,就需要进行分别编译,最后用连接器连接起来。如笔者的引导程序包括3个文件:init.s(汇编代码、初始化硬件)xmrecever.c(通信模块,采用Xmode协议)和flash.c(Flash擦写模块)。
分别用如下命令生成目标文件: arm-linux-gcc-c-O2-oinit.oinit.s arm-linux-gcc-c-O2-oxmrecever.oxmrecever.c arm-linux-gcc-c-O2-oflash.oflash.c 其中-c命令表示只生成目标代码,不进行连接;-o命令指明目标文件的名称;-O2表示采用二级优化,采用优化后可使生成的代码更短,运行速度更快。如果项目包含很多文件,则需要编写makefile文件。关于makefile的内容,请感兴趣的读者参考相关资料。
(3)编写连接脚本文件
gcc等编译器内置有缺省的连接脚本。如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的连接脚本文件。编写连接脚本,首先要对目标文件的格式有一定了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的目标代码中还包括.fini(析构函数代码)和. init(构造函数代码)等。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址开始放置这些段。例如连接文件link.lds为:
ENTRY(begin)
SECTION
{
.=0x30000000;
.text:{*(.text)}
.data:{*(.data)}
.bss:{*(.bss)}
}
其中,ENTRY(begin)指明程序的入口点为begin标号;.=0x00300000指明目标代码的起始地址为0x30000000,这一段地址为MX1的片内RAM;.text:{*(.text)}表示从0x30000000开始放置所有目标文件的代码段,随后的.data:{* (.data)}表示数据段从代码段的末尾开始,再后是.bss段。
(4)用连接器生成最终目标文件
有了连接脚本文件,如下命令可生成最终的目标文件:
arm-linux-ld –no stadlib –o bootstrap.elf -Tlink.lds init.o xmrecever.o flash.o
其中,ostadlib表示不连接系统的运行库,而是直接从begin入口;-o指明目标文件的名称;-T指明采用的连接脚本文件(也可以使用-Ttext address,address表示执行区地址);最后是需要连接的目标文件列表。
(5)生成二进制代码
连接生成的elf文件还不能直接下载执行,通过objcopy工具可生成最终的二进制文件:
arm-linux-objcopy –O binary bootstrap.elf bootstrap.bin
其中-O binary指定生成为二进制格式文件。Objcopy还可以生成S格式的文件,只需将参数换成-O srec。还可以使用-S选项,移除所有的符号信息及重定位信息。如果想将生成的目标代码反汇编,还可以用objdump工具:
 arm-linux-objdump -D bootstrap.elf
至此,所生成的目标文件就可以直接写入Flash中运行了。

2.Makefile实例
example: head.s  main.c
 arm-linux-gcc -c -o head.o head.s
 arm-linux-gcc -c -o main.o main.c
 arm-linux-ld -Tlink.lds head.o ain.o -o example.elf
 arm-linux-objcopy -O binary -S example_tmp.o example
 arm-linux-objdump -D -b binary -m arm  example >ttt.s

二. 调试工具
Linux下的GNU调试工具主要是gdb、gdbserver和kgdb。其中gdb和gdbserver可完成对目标板上Linux下应用程序的远程调试。gdbserver是一个很小的应用程序,运行于目标板上,可监控被调试进程的运行,并通过串口与上位机上的gdb通信。开发者可以通过上位机的gdb输入命令,控制目标板上进程的运行,查看内存和寄存器的内容。gdb5.1.1以后的版本加入了对ARM处理器的支持,在初始化时加入- target==arm参数可直接生成基于ARM平台的gdbserver。gdb工具可以从ftp: //ftp.gnu.org/pub/gnu/gdb/上下载。
对于Linux内核的调试,可以采用kgdb工具,同样需要通过串口与上位机上的gdb通信,对目标板的Linux内核进行调试。可以从http://oss.sgi.com/projects/kgdb/上了解具体的使用方法。


参考资料:
1. Richard Blum,Professional Assembly Language
2. GNU ARM 汇编快速入门,http://blog.chinaunix.net/u/31996/showart.php?id=326146
3. ARM GNU 汇编伪指令简介,http://www.cppblog.com/jb8164/archive/2008/01/22/41661.aspx
4. GNU汇编使用经验,http://blog.chinaunix.net/u1/37614/showart_390095.html
5. GNU的编译器和开发工具,http://blog.ccidnet.com/blog-htm-do-showone-uid-34335-itemid-81387-type-blog.html
6. 用GNU工具开发基于ARM的嵌入式系统,http://blog.163.com/liren0@126/blog/static/32897598200821211144696/
7. objcopy命令介绍,http://blog.csdn.net/junhua198310/archive/2007/06/27/1669545.aspx

没有更多推荐了,返回首页