820 likes | 1.13k Views
第4章 汇编语言程序设计. 本章主要内容: 汇编语言源程序格式 汇编语言上机过程 汇编语言与 C 语言混合编程技术 Linux 下的 ARM 汇编 程序优化与性能测试. 文件类型. 扩展名. 汇编语言源文件. . s. C 语言源文件. . c. C++ 源文件. . cpp. 引入文件. . INC. 头文件. . h. ARM 源程序文件. 汇编源程序示例Ⅰ. 汇编源程序示例 Ⅱ. 汇编源程序示例 Ⅲ. 汇编源程序示例 Ⅳ. ARM 的汇编语言程序一般由几个段组成,每个段均由 AREA 伪操作定义。
E N D
第4章 汇编语言程序设计 本章主要内容: • 汇编语言源程序格式 • 汇编语言上机过程 • 汇编语言与C语言混合编程技术 • Linux下的ARM汇编 • 程序优化与性能测试
文件类型 扩展名 汇编语言源文件 .s C语言源文件 .c C++源文件 .cpp 引入文件 .INC 头文件 .h ARM源程序文件
汇编源程序示例Ⅳ • ARM的汇编语言程序一般由几个段组成,每个段均由AREA伪操作定义。 • 段可以分为多种,如代码段、数据段、通用段,每个段又有不同的属性,象代码段的默认属性为READONLY,数据段的默认属性为READWRITE。 • 本程序定义了两个段,第一个段为代码段codesec,它在存储器中存放用于程序执行的代码以及main函数的本地字符串;第二个段为数据段constdatasec,存放了全局的字符串,由于本程序没有对数据进行写操作,该数据段定义属性为READONLY。
汇编语言的行构成Ⅰ 格式: • [标签] 指令/伪操作/伪指令 操作数 [;语句的注释] • 所有的标签必须在一行的开头顶格写,前面不能留空格,后面也不能跟C语言中的标签一样加上“:”; • ARM汇编器对标识符的大小写敏感书写标号及指令时字母的大小写要一致; • 注释使用“;”符号,注释的内容从“;”开始到该行的结尾结束。
汇编语言的行构成Ⅱ • 标签 标签是一个符号,可以代表指令的地址、变量、数据的地址和常量。一般以字母开头,由字母、数字、下划线组成。当符号代表地址时又称标号,可以以数字开头,其作用范围为当前段或者在下一个ROUT伪操作之前。 • 指令/伪操作 指令/伪操作是指令的助记符或者定义符,它告诉ARM的处理器应该执行什么样的操作或者告诉汇编程序伪指令语句的伪操作功能。
汇编语言的标号 • 标号代表地址。标号分为段内标号和和段外标号。段内标号的地址值在汇编时确定,段外编号的地址值在链接时确定 。 • 在程序段中,标号代表其所在位置与段首地址的偏移量。根据程序计数器(PC)和偏移量计算地址即程序相对寻址。 • 在映像中定义的标号代表标号到映像首地址的偏移量。映像的首地址通常被赋予一个寄存器,根据该寄存器值与偏移量计算地址即寄存器相对寻址。 • 此外在宏中也可以使用局部符号。局部标号是0~99的十进位数开始,可以重复定义。 局部标号引用格式: %{F|B}{A|T} N{routname}
汇编语言的常量 程序中的常量是指其值在程序的运行过程中不能被改变的量。 • 数字常量:数字常量有3种表示方式: • 十进制数,如1、2、123 • 十六进制数,如 0x123,0xabc • n进制数,形式为n_XXX,n的范围是2到9,XXX是具体数字 • 字符常量:由单引号及中间的字符组成,包括C语言中的转义字符,如’a’,’\n’ • 字符串常量:由一对双引号及中间的字符串表示,中间也可以使用C语言中的转义字符,比如:“abcdef\0xa\r\n” • 逻辑常量:{TRUE},{FALSE},注意带大括号
汇编程序的变量代换Ⅰ 这里所说的变量,是相对于汇编程序的“变量”,是用于汇编程序进行处理的,但一旦编译到程序中,则不会改变,成为常量。 • 如果在字符串变量的前面有一个$字符,在汇编时编译器将用该字符串变量的内容代替该串变量。 • 如果在数字变量前面有一个代换操作符“$”,编译器会将该数字变量的值转换为十六进制的字符串,并用该十六进制的字符串代换“$”后的数字变量。 • 如果需要将“$”字符 加入到字符串中,可以用“$$”代替,此时编译器将不再进行变量代换,而是把“$$”看作一个“$”. • 一般的,在两个“|”之间的“$”并不进行变量的代换,但如果“|”在双引号内,则将进行变量代换。 • 使用“.”来表示字符串中变量名的结束。
伪指令 • 在ARM汇编语言源程序中有些特殊助记符,它们没有相对应的操作码或者机器码,通常称为伪指令,它们所完成的操作称为伪操作。伪指令在源程序中的作用是为完成汇编程序作各种准备工作的,由汇编程序在源程序的汇编期间进行处理,仅在汇编过程中起作用。 • 符号定义伪指令 • 数据定义伪指令 • 汇编控制伪指令 • 信息报告伪指令 • 宏指令以及其他伪指令。
符号定义伪指令Ⅰ • 用于定义ARM汇编程序中的变量、对变量赋值以及定义寄存器的别名等。 • 用于定义局部变量的LCLA、LCLL、LCLS; • 用于定义全局变量的GBLA、GBLL、GBLS ; • 用于对变量赋值的SETA、SETL、SETS ; • 为通用寄存器列表定义名称的RLIST。
符号定义伪指令Ⅱ 1. LCLA、LCLL、LCLS • 格式:LCLA/LCLL/LCLS 局部变量名 • 说明:LCLA、LCLL、LCLS伪指令用于定义一个汇编程序中的局部变量,并初始化,其中: • LCLA定义一个局部的数字变量,初始化为0; • LCLL定义一个局部的逻辑变量,初始化为F; • LCLS定义一个局部的字符串变量,初始化为空串; • 这三条伪指令用于声明局部变量,在其局部作用范围内变量名必须唯一。
符号定义伪指令Ⅲ 2. GBLA、GBLL、GBLS • 格式:GBLA/GBLL/GBLS 变量名 • 说明:GBLA、GBLL、GBLS伪操作定义一个汇编程序中的全局变量,并初始化,其中: • GBLA定义一个全局数字变量,并初始化为0; • GBLL定义一个全局逻辑变量,并初始化为“F”; • GBLS定义一个全局字符串变量,并初始化为空串; • 这三条伪指令用于定义全局变量,因此在整个程序范围内变量名必须唯一。
符号定义伪指令Ⅳ 3. SETA、SETL、SETS • 格式:变量名 SETA/SETL/SETS 表达式 • 说明:SETA:给一个数字变量赋值; • SETL:给一个逻辑变量赋值; • SETS:给一个字符串变Ⅳ量赋值; • 格式中的变量名必须为已经定义过的全局或局部变量,表达式为将要赋给变量的值。
符号定义伪指令Ⅴ 4. RLIST • 格式:名称 RLIST {寄存器列表} • 说明:RLIST可用于对一个通用寄存器列表定义名称,该名称可在ARM指令LDM/STM中使用。在LDM/STM指令中,列表中的寄存器为根据寄存器的编号由低到高访问次序,与列表中的寄存器排列次序无关。
数据定义伪指令Ⅰ • 用于为数据分配存储单元,同时也可完成已分配存储单元的初始化。 • DCB • DCW/DCWU • DCD/DCDU • DCQ/DCQU • DCFS/DCFSU • DCFD/DCFDU • SPACE • FIELD • MAP
数据定义伪指令Ⅱ • 1.DCB: 标号 DCB 表达式 说明:DCB用于分配一块字节单元并用伪指令中指定的表达式进行初始化。其中,表达式可以为使用双引号的字符串或0——255的数字,DCB可用“=”代替。 • 2. DCW/DCWU: 标号 DCW/DCWU 表达式 说明:DCW分配一段半字存储单元并用表达式值初始化,它定义的存储空间是半字对齐的。
数据定义伪指令Ⅲ • 3. DCD/DCDU: 标号 DCD/DCDU 表达式 说明:DCD伪指令用于分配一块字存储单元并用伪指令中指定的表达式初始化,它定义的存储空间是字对齐的。DCD也可用“&”代替。 • 4. DCQ/DCQU: 标号 DCQ/DCQU 表达式 说明:DCQ用于分配一块以8个字节为单位的存储区域并用伪指令中指定的表达式初始化,它定义的存储空间是字对齐的。DCQU功能跟DCQ类似,只是分配的存储单元不严格字对齐。
数据定义伪指令Ⅳ • 5. DCFD/DCFDU: 标号 DCFD/DCFDU 表达式 说明:DCFD用于为双精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化,它定义的存储空间是字对齐的,每个双精度的浮点数占据两个字单元。DCFDU功能跟DCFD类似,只是分配的存储单元不严格字对齐。 • 6. DCFS/DCFSU: 标号 DCFS/DCFSU 表达式 说明:DCFS用于为单精度的浮点数分配一片连续的字存储单元并用表达式初始化,它定义的存储空间是字对齐的,每个单精度浮点数使用一个字单元.DCFSU功能跟DCFS类似,只是分配的存储单元不严格字对齐。
数据定义伪指令Ⅴ • 7.SPACE: 标号 SPACE 表达式 说明:SPACE用于分配一片连续的存储区域并初始化为0,表达式为要分配的字节数,SPACE也可用“%”代替。 • 8.MAP: MAP 表达式 [,基址寄存器] 说明:MAP定义一个结构化的内存表的首地址, “^”可以用来代替MAP。 • 9.FILED: 标号 FIELD 字节数 说明:FIELD用于定义一个结构化内存表中的数据域,“#” 可用来代替FILED。
汇编控制伪指令Ⅰ • 汇编控制伪操作用于指引汇编程序的执行流程: • MACRO、MEND • IF、ELSE、ENDIF • WHILE、WEND • MEXIT
汇编控制伪指令Ⅱ • 1.MACRO、MEND MACRO [$标号] 宏名 [$参数1,$参数2,……] 指令序列 MEND 说明:MACRO 表明一个宏定义的开始,MEND则表示一个宏的结束,MACRO、MEND前呼后应可以将一段代码定义为一个整体,又称宏,然后就可以在程序中通过宏的名称及参数调用该段代码。 MACRO和MEND之间的代码称为宏定义体,在宏定义体的第一行声明宏的原型,宏的原型包含宏名、所需的参数。在源程序被编译时,汇编器将宏调用展开,用宏定义中的指令序列替换程序中的宏调用,并将实际参数的值传递给宏定义中的参数。 注意宏操作可以嵌套使用,并可以在编译时用选项加以控制。
汇编控制伪指令Ⅲ • 2. IF、ELSE、ENDIF IF 逻辑表达式 代码段1 ELSE 代码段2 ENDIF 说明:IF、ELSE、ENDIF伪操作能根据逻辑表达式的成立与否决定是否在编译时加入某个指令序列。IF、ELSE、ENDIF可以分别用“[”、“|”、“]”代替。IF、ELSE、ENDIF伪指令可以嵌套使用。
汇编控制伪指令Ⅲ • 3. WHILE、WEND: WHILE 逻辑表达式 代码段 WEND 说明:WHILE和WEND伪指令能根据逻辑表达式的成立与否决定是否循环执行这个代码段。WHILE、WEND伪指令可以嵌套使用。 • 4. MEXIT: MEXIT 说明:MEXIT用于从宏中退出。
其他伪指令Ⅰ • 1. ASSERT: ASSERT 逻辑表达式 说明:ASSERT用来表示程序的编译必须满足一定的条件,如果逻辑表达式不满足,则编译器会报错。 • 2. ALIGN: ALIGN [表达式[,偏移量]] 说明:ALIGN伪操作可以通过填充字节使当前的位置满足一定的对齐方式。其中,表达式的值为2的幂,如1、2、4、8、16等,用于指定对齐方式。如果伪操作中没有指定表达式,则编译器会将当前位置对齐到下一个字的位置。偏移量也是个数字表达式,如果存在偏移量,则当前位置的自动对齐到:2的表达式值次方+偏移量。
其他伪指令Ⅱ • 3. AREA: AREA 段名 属性,…… 说明:AREA用于定义一个代码段、数据段或者特定属性的段。如果段名以数字开头,那么该段名需用“|”字符括起来,如|7wolf|,用C的编译器产生的代码一般也用“|”括起来。属性部分表示该代码段/数据段的相关属性,多个属性可以用“,”分隔。 常见属性如下: • DATA:定义数据段。 • CODE:定义代码段。 • READONLY:表示本段为只读。 • READWRITE:表示本段可读写。 • ALIGN=表达式:对齐方式为2表达式次方,例如:表达式=3,则对齐方式为8字节对齐。表达式的取值范围为0——31。 • COMMON属性:定义一个通用段,这个段不包含用户代码和数据。
其他伪指令Ⅲ • 4. CODE16、CODE32: CODE16/CODE32 说明:CODE16伪操作指示编译器后面的代码为16位的Thumb指令。CODE32伪操作指示编译器后面的代码为32位的ARM指令。 • 5. ENTRY: ENTRY 说明:ENTRY用于指定汇编程序的入口。在一个完整的汇编程序中至少要有一个ENTRY,程序中也可以有多个,此时,程序的真正入口点可在链接时指定,但在一个源文件里最多只能有一个ENTRY或者没有ENTRY。
其他伪指令Ⅳ • 6.END: END 说明:“END”告诉编译器已经到了源程序的结尾。 • 7. EQU: 名称 EQU 表达式[,类型] 说明:EQU用于将程序中的数字常量、标号、基于寄存器的值赋予一个等效的名称,这一点类似于C语言中的#define,可用“*”代替EQU。 如果表达式为32位的常量,我们可以指定表达式的数据类型,类型域可以有以下三种:CODE16/CODE32/DATA
其他伪指令Ⅴ • 8.EXPORT: EXPORT 标号[,WEAK] 说明:EXPORT 在程序中声明一个全局标号,其他的文件中的代码可以该标号可被引用。用户也可以用GLOBAL代替EXPORT。[,WEAK]可选项声明其他文件有同名的标号,则该同名标号优先于该标号被引用。 • 9. IMPORT: IMPORT 标号 [,WEAK] 说明:IMPORT告诉编译器这个标号要在当前源文件中使用,但标号是在其他的源文件中定义的。不管当前源文件是否使用过该标号,这个标号都会加入到当前源文件的符号表中。 [,WEAK]选项表示如果所有的源文件都没有找到这个标号的定义,编译器也不会提示错误信息。编译器在多数情况下将该标号置为0,如果这个标号被B或BL指令引用,则将B或BL指令替换为NOP操作。
其他伪指令Ⅵ • 10.EXTERN: EXTERN 标号 [,WEAK] 说明:EXTERN告诉编译器所使用的标号要在当前源文件中引用,但该标号是在其他的源文件中定义的。与IMPORT不同的是,如果当前源文件实际上没有引用该标号,该标号就不会被加入到当前文件的符号表中。[,WEAK]选项意义同IMPORT。 • 11.RN: 名称 RN 表达式 说明:RN用于给一个寄存器定义一个别名,以便程序员记忆该寄存器的功能。其中,名称为给寄存器定义的别名,表达式为寄存器的编码。
其他伪指令Ⅶ • 12. GET/INCLUDE: GET 文件名 说明:GET将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置展开进行汇编处理。INCLUDE和GET作用等效的。我们通常这样使用这个伪指令:在某源文件中定义一些宏指令,用MAP和FIELD定义结构化的数据类型,用EQU定义常量的符号名称,然后用GET/INCLUDE将这个源文件包含到其他的源文件中。GET/INCLUDE只能用于包含源文件,包含其他文件则需要使用INCBIN伪指令。 • 13.INCBIN: INCBIN 文件名 说明:INCBIN将一个数据文件或者目标文件包含到当前的源文件中,编译时被包含的文件不作任何变动的存放在当前文件中,编译器从后面开始继续处理。
其他伪指令Ⅷ • 14.ROUT: [名称] ROUT 说明:ROUT可以给一个局部变量定义作用范围。在程序中未使用该伪指令时,局部变量的作用范围为所在的AREA,而使用ROUT后,局部变量的作为范围为当前ROUT和下一个ROUT之间。
汇编语言上机过程 用ARM汇编语言编写的源程序,要使之运行必须经过以下几个步骤: • 1.编辑汇编源程序,保存为文件名后缀是“.s”的文件; • 2.调用汇编程序对源程序进行汇编,生成目标文件; • 3.把目标文件链接成ELF格式的可执行映像文件(生成可以放进ARM软件仿真器进行调试的映象文件),该文件可以进行调试; • 4.用fromelf工具把可执行的ELF映像文件转换成二进制映像文件; • 5.下载可执行的二进制映像文件到ARM的目标板;
ARM的开发工具ADS1.2 ADS1.2是ARM公司推出的一套ARM汇编、C、C++的集成开发环境。包含了几个有用的开发工具,包括: • CodeWarrior IDE for the ARM Developer Suite :为ARM的程序员管理、开发软件工程项目提供了一个简单直观、灵活的用户界面。 • AXD Debuger:AXD是一个功能强大、使用方便的调试器。
编辑汇编语言源程序 • 可以使用简单的Windows自带的记事本程序来编辑ARM的汇编程序: 单击开始菜单程序附件记事本 敲入汇编代码保存为.s文件 • 也可以使用CodeWarrior IDE来编辑汇编程序: 单击File菜单的New菜单项单击Project标签页单击工具栏的New Text按钮敲入汇编代码保存为hello.s文件单击Project菜单,选择“Add *.s to project”
编译汇编语言源程序Ⅰ • ARM的编译器有如下几种 • armcc:ARM C编译器,具有优化功能,兼容ANSI C • tcc:Thumb的C编译器,同样具有优化功能兼容ANSI C。 • armcpp:ARM C++编译器,遵循ANSI C++或者EC++标准 • tcpp:Thubm的C++编译器,遵循ANSI C++或者EC++标准 • armasm:支持ARM和Thumb的汇编器 这些编译器输出的是ELF格式的目标文件,可以包括RAWF2格式的调试信息。同时通过特殊的控制选项可以输出汇编语言文件或者列表文件。
编译汇编语言源程序Ⅱ • 4种ARM C和C++编译器(armcc,tcc, armcpp和 tcpp)的命令通用语法: compiler [PCS-options] [source-language] [search-paths] [preprocessor-options] [output-format] [target-options] [debug-options] [code-generation-options] [warning-options] [additional-checks] [error-options] [source] [-via filename] 用户通过命令行操作选项指引编译器运行。所有的选项都是以符号”-”开始,有些选项后面还跟有参数。在大多数情况下,ARM C和 C++编译器允许在选项和参数之间存在空格。
编译汇编语言源程序Ⅲ • PCS-options:指定了要使用的过程调用标准; • source-language:指定了编译器可以接受的编写源程序的语言种类。对于C编译器默认的语言是ANSI C,对于C++编译器默认是ISO标准C++; • search-paths:该选项指定了对包含的文件(包括源文件和头文件)的搜索路径; • preprocessor-options:该选项指定了预处理器的行为,其中包括预处理器的输出和宏定义等特性; • output-format:该选项指定了编译器的输出格式,可以使用该项生成汇编语言输出列表文件和目标文件; • target-options:该选项指定目标处理器或ARM体系结构; • debug-options:该选项指定调试信息表是否生成,和该调试信息表生成时的格式;
编译汇编语言源程序Ⅳ • code-generation-options:该选项指定了例如优化,字节顺序和由编译器产生的数据对齐格式等选项; • warning-options:该选项决定警告信息是否产生; • additional-checks:该选项指定了几个能用于源码的附加检查,例如检查数据流异常,检查没有使用的声明等; • error-options:该选项可以关闭指定的可恢复的错误,或者将一些指定的错误降级为警告; • source:该选项提供了包含有C或C++源代码的一个或多个文件名,默认的,编译器在当前路径寻找源文件和创建输出文件。如果源文件是用汇编语言编写的(也就是说该文件的文件名是以.s作为扩展名),汇编器将被调用来处理这些源文件。 • -via filename :WINDOWS系统对命令行的长度有限制,可以通过filename的文件读取命令行选项。用户可以对-via进行嵌套调用,在文件filename中再通过-via filename2包含了另外一个文件。
编译汇编语言源程序Ⅴ • armcc的编译选项如下: • -c 只编译而不进行连接 • -C 预处理时不删除注释 • -D<symbol> 定义symbol,作用相当于define • -E 只处理C的源代码 • -f<options> 打开编译器自定义的选项 • -g<options> 在目标文件中生成调试信息以供调试器使用 • -I<directory> directory是头文件所在的路径 • -J<directory> 不包含编译器默认的包含路径而使用directory • -o<file> 输出文件名为file • -OO 最小优化,包含最多的调试信息 • -O1 限制优化,包含部分调试信息 • -O2 最大的优化 • -S 生成汇编语言文件 • -U<symbol> 功能相当于#undef symbol • -W<options> 禁止所有或部分警告信息
C语言armcc编译示例(1) armcc –c –g hello.c –o hello.o • 把hello.c源文件编译成包括DWARF格式调试表的ELF格式的目标文件hello.o。目标文件是32位的arm码。 • -c告诉编译器,只编译不链接; • -g告诉编译器增加源码级别的调试信息 • ELF: Executable and Linking Format • DWARF: Debugging With Attributed Record Formats
C语言armcc编译示例(2) armcc –thumb –c –g hello.c –o hello.o • 把hello.c源文件编译成包括DWARF格式调试表的ELF格式的目标文件hello.o。目标文件是16位的thumb码。 • -c告诉编译器,只编译不链接; • -g告诉编译器增加源码级别的调试信息 • ELF: Executable and Linking Format • DWARF: Debugging With Attributed Record Formats
汇编语言armasm编译示例(1) armasm –32 –g hello.s –o hello.o • 把hello.s源文件编译成包括DWARF格式调试表的ELF格式的目标文件hello.o。 • 目标文件是32位的arm码。 • -g: 告诉编译器增加源码级别的调试信息
汇编语言armasm编译示例(2) armcc –16 –g hello.c –o hello.o • 把hello.c源文件编译成包括DWARF格式调试表的ELF格式的目标文件hello.o。 • 目标文件是16位的thumb码。 • -g告诉编译器增加源码级别的调试信息
连接装配汇编程序Ⅰ • 使用armlink程序对ARM的汇编源程序进行连接,它也可以将多个.o目标文件连接生成最终的可执行文件。 • 术语 : • 映像文件(image):是指一个可执行文件,在执行的时候被加载到处理器中。一个映像文件可以有多个线程。它是ELF(Executable and linking format)格式的。 • 段(Section):描述映像文件的代码或数据块。 • RO:Read-only缩写。 • RW:是Read-write缩写。 • ZI:是Zero-initialized缩写。
连接装配汇编程序Ⅱ • Read Only Position Independent(ROPI):只读数据中代码和的地址在运行时候可以改变。 • Read Write Position Independent(RWPI):一个段中的可读/写的数据地址在运行期间可以改变。 • 输入段(input section):它包含着代码,初始化数据或描述了在应用程序运行之前必须要初始化为0的一段内存。 • 输出段(output section):它包含了一系列具有相同的RO,RW或ZI属性的输入段。 • 域(Regions):在一个映像文件中,一个域包含了1至3个输出段。多个域组织在一起,就构成了最终的映像文件。 • 加载时地址:映像文件载入存储器时的地址。 • 运行时地址:映像文件运行时的地址。
连接装配汇编程序Ⅲ • 命令语法: armlink [-partial] [-help] [-o file] [-elf] [-vsn] [-reloc][-ro-base address] [-ropi] [-rw-base address] [-rwpi] [-split] [-scatter file][-debug|-nodebug][-remove RO/RW/ZI/DBG]|-noremove] [-entry location ] [-keep section-id] [-first section-id] [-last section-id] [-libpath pathlist] [-scanlib|-noscanlib] [-locals|-nolocals] [-callgraph] [-info topics] [-map] [-symbols] [-symdefs file] [-edit file] [-xref] [-xreffrom object(section)] [-xrefto object(section)] [-errors file] [-list file] [-verbose] [-unmangled |-mangled] [-match crossmangled][-via file] [-strict] [-unresolved symbol][-MI|-LI|-BI] [input-file-list]