第 3 章 程序设计基础
DESCRIPTION
第 3 章 程序设计基础. GNU 集成编译环境 GCC(GNU Compiler Collection) 是一种面向嵌入式领域、支持多种编程语言、支持多种 CPU 的交叉编译工具。 本章主要介绍: GCC 编译过程 C/C++ 交叉编译器 arm-elf-gcc 交叉汇编器 arm-elf-as 交叉连接器 arm-elf-l d 工程管理器 make 汇编语言编程 混合编程. 3.1 GNU GCC 简介. - PowerPoint PPT PresentationTRANSCRIPT
第 第 3 3 章 程序设计基础章 程序设计基础
GNUGNU 集成编译环境集成编译环境 GCC(GNU Compiler GCC(GNU Compiler Collection)Collection) 是一种面向嵌入式领域、支持多种是一种面向嵌入式领域、支持多种编程语言、支持多种编程语言、支持多种 CPUCPU 的交叉编译工具。的交叉编译工具。
本章主要介绍:本章主要介绍:GCCGCC 编译过程编译过程C/C++C/C++ 交叉编译器交叉编译器 arm-elf-gccarm-elf-gcc交叉汇编器 交叉汇编器 arm-elf-as arm-elf-as 交叉连接器交叉连接器 arm-elf-l darm-elf-l d工程管理器工程管理器 makemake汇编语言编程汇编语言编程混合编程混合编程
3.1 GNU GCC3.1 GNU GCC 简介简介GNU GCCGNU GCC 是一套面向嵌入式领是一套面向嵌入式领
域的交叉编译工具,支持多种编程语域的交叉编译工具,支持多种编程语言、多种优化选项并且能够支持分步言、多种优化选项并且能够支持分步编译、支持多种反汇编方式、支持多编译、支持多种反汇编方式、支持多种调试信息格式种调试信息格式,,目前支持目前支持 X86X86 、、ARM7ARM7 、、 StrongARMStrongARM 、、 PPC4XXPPC4XX 、、MPC8XXMPC8XX 、、 MIPS R3000MIPS R3000 等多种等多种 CCPUPU 。 。
GNU GCCGNU GCC 的基本功能包括:的基本功能包括:
输出预处理后的输出预处理后的 C/C++C/C++ 源程序(展开源程序(展开头文件和替换宏)头文件和替换宏)输出输出 C/C++C/C++ 源程序的汇编代码源程序的汇编代码输出二进制目标文件输出二进制目标文件生成静态库生成静态库生成可执行程序生成可执行程序转换文件格式转换文件格式
3.1.1 GCC 3.1.1 GCC 组成组成1. C/C++1. C/C++ 交叉编译器交叉编译器 arm-elf-gccarm-elf-gcc
arm-elf-gccarm-elf-gcc 是编译的前端程序,它通过调是编译的前端程序,它通过调用其他程序来实现将程序源文件编译成目标文件用其他程序来实现将程序源文件编译成目标文件的功能。的功能。
编译时,它首先调用预处理程序编译时,它首先调用预处理程序 (cpp)(cpp) 对输对输入的源程序进行处理,然后调用 入的源程序进行处理,然后调用 cc1 cc1 将预处理将预处理后的程序编译成汇编代码,最后由后的程序编译成汇编代码,最后由 arm-elf-asarm-elf-as 将将汇编代码编译成目标代码。汇编代码编译成目标代码。
arm-elf-gccarm-elf-gcc 具有丰富的命令选项,可以控具有丰富的命令选项,可以控制编译的各个阶段,满足用户的各种编译需求。制编译的各个阶段,满足用户的各种编译需求。
22.. 汇编器 汇编器 arm-elf-asarm-elf-as
arm-elf-asarm-elf-as 将汇编语言程序转换为将汇编语言程序转换为 EELF (Executable and Linking FormatLF (Executable and Linking Format ,,执行时链接文件格式执行时链接文件格式 )) 格式的可重定位目格式的可重定位目标代码,这些目标代码同其它目标模块或标代码,这些目标代码同其它目标模块或函数库易于定位和链接。函数库易于定位和链接。
arm-elf-asarm-elf-as 产生一个交叉参考表和一产生一个交叉参考表和一个标准的符号表,产生的代码和数据能够个标准的符号表,产生的代码和数据能够放在多个区 放在多个区 (Section)(Section) 中。中。
3. 3. 连接器连接器 arm-elf-ldarm-elf-ldarm-elf-ldarm-elf-ld 根据链接定位文件根据链接定位文件 LinkcmdLinkcmd
ss 中的代码区、数据区、中的代码区、数据区、 BSSBSS 区和栈区等定区和栈区等定位信息,将可重定位的目标模块链接成一个位信息,将可重定位的目标模块链接成一个单一的、绝对定位的目标程序。单一的、绝对定位的目标程序。
该目标程序是该目标程序是 ELFELF 格式,并且可以包含格式,并且可以包含调试信息。调试信息。
arm-elf-ldarm-elf-ld 会产生一个内存映象文件会产生一个内存映象文件 MMap.txt,ap.txt, 该文件显示所有目标模块、区和符号该文件显示所有目标模块、区和符号的绝对定位地址。的绝对定位地址。
它也产生交叉参考列表,显示参考每个它也产生交叉参考列表,显示参考每个全局符号的目标模块。全局符号的目标模块。
arm-elf-ldarm-elf-ld 支持将多个目标模块链接成一支持将多个目标模块链接成一个单一的、绝对定位的目标程序,也能够依此个单一的、绝对定位的目标程序,也能够依此对目标模块进行链接,这个特性称为对目标模块进行链接,这个特性称为增量链接增量链接(Incremental Linking)(Incremental Linking) 。。
假如输入文件是一个函数库,假如输入文件是一个函数库, arm-elf-ldarm-elf-ld会自动从函数库装载被其它目标模块参考的函会自动从函数库装载被其它目标模块参考的函数模块。数模块。
arm-elf-ldarm-elf-ld 与其它链接程序与其它链接程序相比相比,能提供,能提供更有帮助的诊断信息。许多链接器遇到第一个更有帮助的诊断信息。许多链接器遇到第一个错误即放弃链接,而错误即放弃链接,而 arm-elf-ldarm-elf-ld 只要有可能都只要有可能都继续执行,帮助用户识别其它错误,有时甚至继续执行,帮助用户识别其它错误,有时甚至能获得输出代码。 能获得输出代码。
4. 4. 库管理器库管理器 arm-elf-ararm-elf-ar
arm-elf-ararm-elf-ar 将多个可重定位的目标模块将多个可重定位的目标模块归档为一个函数库文件。归档为一个函数库文件。
采用函数库文件,应用程序能够从该文采用函数库文件,应用程序能够从该文件中自动装载要参考的函数模块,同时将应件中自动装载要参考的函数模块,同时将应用程序中频繁调用的函数放入函数库文件中,用程序中频繁调用的函数放入函数库文件中,易于应用程序的开发管理。易于应用程序的开发管理。
arm-elf-ararm-elf-ar 支持支持 ELFELF 格式的函数库文件格式的函数库文件 ..
5. 5. 工程管理器工程管理器 MAKEMAKE
MakeMake 是用于自动编译、链接程序的实用工是用于自动编译、链接程序的实用工具具 ,, 使用使用 makemake 后就不需要手工的编译每个程序后就不需要手工的编译每个程序文件。要使用文件。要使用 make,make, 首先要编写首先要编写 makefilemakefile 。。
MakefileMakefile 描述程序文件之间的依赖关系,描述程序文件之间的依赖关系,并提供更新文件的命令。在一个程序中,可执行并提供更新文件的命令。在一个程序中,可执行文件依赖于目标文件文件依赖于目标文件 ,, 而目标文件依赖于源文件。而目标文件依赖于源文件。如果如果 makefilemakefile 文件存在文件存在 ,, 每次修改完源程序后,每次修改完源程序后,用户通常所需要做的事情就是在命令行敲入“用户通常所需要做的事情就是在命令行敲入“ mmake”,ake”, 然后所有的事情都由然后所有的事情都由 makemake 来完成。来完成。
6. 6. 其他实用程序其他实用程序
目标文件格式转换工具目标文件格式转换工具 arm-elf-objarm-elf-objcopycopy 支持的文件格式有支持的文件格式有 H-recordH-record 、、 S-S-recordrecord 、、 ABSABS 、、 BINBIN 、、 COFFCOFF 、、 ELFELF 。。例如,它能够将例如,它能够将 ELFELF 格式文件转换为其格式文件转换为其它格式的文件,如它格式的文件,如 intel H-recordintel H-record 格式、格式、Motorola S-recordMotorola S-record 等。等。 arm-elf-nmarm-elf-nm 程程序用于显示文件中的符号信息。 序用于显示文件中的符号信息。
3.1.2 GCC3.1.2 GCC 编译程序的基本过程编译程序的基本过程GCCGCC 编译程序的编译程序的基本过程基本过程如下:如下:arm-elf-gccarm-elf-gcc 根据输入文件的后缀来确定文根据输入文件的后缀来确定文
件的类型,然后根据用户的编译选项(包括优件的类型,然后根据用户的编译选项(包括优化选项、调试信息选项等)将其编译成相应的化选项、调试信息选项等)将其编译成相应的汇编临时文件(后缀为汇编临时文件(后缀为 .s.s ););
arm-elf-asarm-elf-as 将该汇编文件编译成目标文件将该汇编文件编译成目标文件(后缀为(后缀为 .o.o ););
arm-elf-ld arm-elf-ld 根据用户的链接选项(包括指根据用户的链接选项(包括指定链接命令文件等)将目标文件和各种库链接定链接命令文件等)将目标文件和各种库链接起来生成可执行文件。起来生成可执行文件。
图图3-13-1展示了该编译过程:展示了该编译过程:
3.2 C/C++3.2 C/C++ 交叉编译器交叉编译器 arm-elf-gccarm-elf-gcc
3.2.1 3.2.1 概述概述arm-elf-gccarm-elf-gcc 是编译的前端程序,它通过调用其他程是编译的前端程序,它通过调用其他程
序来实现将程序源文件编译成目标文件。序来实现将程序源文件编译成目标文件。编译时它首先调用预处理程序编译时它首先调用预处理程序 (cpp)(cpp) 对输入的源程对输入的源程
序进行处理序进行处理 ;;然后调用然后调用 cc1cc1 将预处理后的程序编译成汇编代码将预处理后的程序编译成汇编代码 ;;最后由最后由 arm-elf-asarm-elf-as 将汇编代码编译成目标代码。将汇编代码编译成目标代码。
arm-elf-gccarm-elf-gcc 具有丰富的命令选项,控制编译的各个具有丰富的命令选项,控制编译的各个阶段,满足用户的各种编译需求阶段,满足用户的各种编译需求
1.1. 命令格式命令格式arm-elf-gcc [options] file…arm-elf-gcc [options] file…在命令在命令 arm-elf-gccarm-elf-gcc 后面跟一个或后面跟一个或
多个选项,选项间用空格隔开,然后跟多个选项,选项间用空格隔开,然后跟一个或多个目标文件。一个或多个目标文件。例如,将例如,将 test.c test.c 编译成目标文件编译成目标文件 tt
est.o est.o 并且生成调试信息:并且生成调试信息:arm-elf-gcc –g –c –o test.o test.arm-elf-gcc –g –c –o test.o test.
c c
2.2. 命令选项列表命令选项列表输出控制选项:输出控制选项:-c-c 将输入的源文件编译成目标文件将输入的源文件编译成目标文件-S-S 将将 C/C++C/C++ 文件生成汇编文件文件生成汇编文件-o file -o file 将输出内容存于文件将输出内容存于文件 filefile-pipe -pipe 在编译的不同阶段之间采用管道通讯方式在编译的不同阶段之间采用管道通讯方式-v-v 打印出编译过程中执行的命令打印出编译过程中执行的命令-x language-x language 说明文件的输入类型为说明文件的输入类型为 languagelanguage
CC 语言选项:语言选项:-ansi-ansi 支持所有支持所有 ANSI CANSI C 程序程序警告选项:警告选项:-w -w 关闭所有警告关闭所有警告-Wall -Wall 打开所有警告打开所有警告-Wimplicit -Wimplicit 如果有隐含申明,显示警告信如果有隐含申明,显示警告信
息息-Wno-implicit -Wno-implicit 不显示对隐含申明的警告不显示对隐含申明的警告调试选项:调试选项:-g -g 在文件中产生调试信息在文件中产生调试信息 (( 调试信息的文调试信息的文
件格式有件格式有 stabsstabs 、、 COFFCOFF 、、 XCOFFXCOFF 、、 DWARF)DWARF)
优化选项:优化选项:-O0 -O0 不优化不优化-O1 -O1 一级优化一级优化-O2 -O2 二级优化二级优化-O3 -O3 三级优化三级优化
预处理选项:预处理选项:-E -E 运行运行 CC 的预处理器的预处理器-C -C 在运用在运用 -E-E 进行预处理时不去掉注进行预处理时不去掉注
释释-D macro -D macro 定义宏定义宏 macromacro 为为 11-D macro=defn -D macro=defn 定义宏定义宏 macromacro 为为 defndefn
汇编选项:汇编选项:-Wa-Wa ,, optionoption 将选项将选项 optionoption传传
递 给汇编器递 给汇编器
搜索路径选项:搜索路径选项:-I dir -I dir 设置搜索路径为设置搜索路径为 dirdir-I- -I- 指定只对 指定只对 #include #include ""filefile"",,
有效的头文件搜索目录 有效的头文件搜索目录
3. 3. 源文件类型的识别源文件类型的识别arm-elf-gccarm-elf-gcc 能够自动根据文件名后缀识别文件类型能够自动根据文件名后缀识别文件类型 ..文件名后缀文件名后缀和和文件类型文件类型的对应关系如下:的对应关系如下:*.c *.c ——C——C 源文件源文件*.i *.i ————经过预处理后的经过预处理后的 CC 源文件源文件*.h *.h ——C——C 头文件头文件*.ii *.ii ————经过预处理后的经过预处理后的 C++C++ 源文件源文件*.cc *.cc ——C++——C++ 源文件源文件*.cxx *.cxx ——C++——C++ 源文件源文件*.cpp *.cpp ——C++——C++ 源文件源文件*.C*.C ——C++——C++ 源文件源文件*.s *.s ———— 不需要预处理的汇编文件不需要预处理的汇编文件*.S*.S ———— 需要预处理的汇编文件需要预处理的汇编文件
此外,用户可通过此外,用户可通过 -x language-x language说明文件的输入类型,说明文件的输入类型,此时可以不用以上的后缀规则。此时可以不用以上的后缀规则。
-x language-x language其中的其中的 languagelanguage 可为:可为:c c ——C——C 源文件源文件c++ c++ ——C++——C++ 源文件源文件c-header c-header ——C——C 头文件头文件cpp-output cpp-output ————经过预处理后的经过预处理后的 CC 源文件源文件c++-cpp-output ——c++-cpp-output ——经过预处理后的经过预处理后的 C++C++ 源文件源文件assembler assembler ———— 不需要预处理的汇编文件不需要预处理的汇编文件assembler-with-cpp ——assembler-with-cpp —— 需要预处理的汇编文件需要预处理的汇编文件例如,编译一个不需要预处理的例如,编译一个不需要预处理的 CC 程序:程序:arm-elf-gcc –c –g –xarm-elf-gcc –c –g –x cpp-output test.ccpp-output test.c-x none-x none如果如果 -x-x 后面未跟任何参数,则按照文件的后缀名做相后面未跟任何参数,则按照文件的后缀名做相
应处理。 应处理。
3.2.2 3.2.2 命令使用命令使用1.1. 输出文件名的指定输出文件名的指定-o file-o file将输出内容存于文件将输出内容存于文件 filefile ,仅适用于,仅适用于
只有一个输出文件时。只有一个输出文件时。
例如,将例如,将 test.ctest.c 编译成汇编程序并存编译成汇编程序并存放于文件放于文件 test.txttest.txt ::
arm-elf-gcc –S –o test.txt test.carm-elf-gcc –S –o test.txt test.c
2.2. 目标文件的生成目标文件的生成 -c-c将输入的源文件编译成目标文件。将输入的源文件编译成目标文件。例如,将例如,将 test.ctest.c 编译成编译成 test.otest.o ::arm-elf-gcc –c –o test.o test.carm-elf-gcc –c –o test.o test.c
33.将.将 C/C++C/C++ 文件生成汇编文件文件生成汇编文件 -S-S将将 C/C++C/C++ 文件生成汇编文件。文件生成汇编文件。例如例如 ::将将 test.ctest.c 编译生成汇编文件编译生成汇编文件 test.s:test.s:arm-elf-gcc –S –o test.s test.carm-elf-gcc –S –o test.s test.c
4.4. 预处理文件的生成预处理文件的生成-E-E
只对源文件进行预处理并且缺省输出到标准只对源文件进行预处理并且缺省输出到标准输出。输出。例如,对例如,对 test.ctest.c 进行预处理并将结果输出到进行预处理并将结果输出到
屏幕:屏幕:arm-elf-gcc –E test.carm-elf-gcc –E test.c
例如,对例如,对 test.ctest.c 进行预处理并将结果输出到进行预处理并将结果输出到文件文件 test.txttest.txt ::
arm-elf-gcc –E –o test.txt test.carm-elf-gcc –E –o test.txt test.c
55.设置头文件搜索路径.设置头文件搜索路径头文件的头文件的引用引用有两种形式:有两种形式:一种是一种是 # include“filename”# include“filename” ,,一种是一种是 # include <filename># include <filename> 。。
前一种形式的前一种形式的路径搜索顺序路径搜索顺序是:当前目录、是:当前目录、指定的搜索路径;指定的搜索路径;
后一种形式只搜索指定路径。 后一种形式只搜索指定路径。
-I dir-I dir
将目录将目录 dirdir 添加到头文件搜索目录列表的第添加到头文件搜索目录列表的第一项。一项。
通过此选项可以使用户头文件先于系统头通过此选项可以使用户头文件先于系统头文件被搜索到。文件被搜索到。
如果同时用如果同时用 -I-I 选项添加几个目录,目录被选项添加几个目录,目录被搜索时的优先级顺序为从左到右。搜索时的优先级顺序为从左到右。
例如,编译例如,编译 test.ctest.c ,在当前目录和,在当前目录和 /includ/includee 中搜索中搜索 test.ctest.c 所包含的头文件:所包含的头文件:
arm-elf-gcc –I ./ –I/include –c test.carm-elf-gcc –I ./ –I/include –c test.c
-I--I--I--I- 以前以前用用 -I-I指定的头文件搜索目录只指定的头文件搜索目录只
对 对 # include“file” # include“file” 有效,对 有效,对 # include<fi# include<fi
le> le> 无效;无效;-I--I- 以后以后指定的头文件搜索目录对以上指定的头文件搜索目录对以上
两种形式的头文件都有效。两种形式的头文件都有效。
此外,此外, -I--I- 会禁止对当前目录的隐含搜索,会禁止对当前目录的隐含搜索,不过用户可以通过使用“不过用户可以通过使用“ -I.”-I.” 使能对当前目使能对当前目录的搜索。 录的搜索。
例如例如 ::在需要编译的在需要编译的 test.ctest.c 文件对头文件的引用有:文件对头文件的引用有:# include<file1.h># include<file1.h># include“file2.h”# include“file2.h”# include“file3.h”# include“file3.h”其中,其中, file1.hfile1.h 在目录 在目录 /include/test/include/test下,下, file2.file2.
hh 在在 /include/include下,下, file3.hfile3.h 在当前目录下。在当前目录下。
在以下命令行中,只能搜索到在以下命令行中,只能搜索到 file2.hfile2.h ,而不 ,而不 能搜索到能搜索到 file1.hfile1.h ::arm-elf-gcc –I./include/test –I– –I./include –c test.arm-elf-gcc –I./include/test –I– –I./include –c test.
cc
而在以下命令行中而在以下命令行中 ,, 可以搜索到需要的两个头可以搜索到需要的两个头文件文件 file1.hfile1.h 和和 file2.h:file2.h: arm-elf-gcc –I– –I./include –I./include/test –c test.carm-elf-gcc –I– –I./include –I./include/test –c test.c
如果要搜索到如果要搜索到 file3.hfile3.h ,必须要添加对当前目录的搜索,必须要添加对当前目录的搜索 ::
arm-elf-gcc –I– –I. –I./include –I./include/test –c test.carm-elf-gcc –I– –I. –I./include –I./include/test –c test.c
实质上,上述编译命令等价于实质上,上述编译命令等价于 :: arm-elf-gcc –I. –I./include –I./include/test –c test.carm-elf-gcc –I. –I./include –I./include/test –c test.c
与与 arm-elf-gcc –I./include –I./include/test –c test.carm-elf-gcc –I./include –I./include/test –c test.c
66.控制警告产生.控制警告产生用户可以使用以用户可以使用以 -W-W 开头的不同选项对特定警告进行开头的不同选项对特定警告进行
设定。设定。对于每种警告类型都有相应以对于每种警告类型都有相应以 -Wno--Wno- 开始的选项关开始的选项关
闭警告。闭警告。例如例如 ::如果有隐含申明,显示警告信息:如果有隐含申明,显示警告信息:arm-elf-gcc –c –Wimplicit test.carm-elf-gcc –c –Wimplicit test.c不显示对隐含申明的警告:不显示对隐含申明的警告:arm-elf-gcc –c –Wno–implicit test.carm-elf-gcc –c –Wno–implicit test.c常用的警告选项有:常用的警告选项有:-w-w 关闭所有警告信息。关闭所有警告信息。-Wall-Wall 打开所有警告信息。打开所有警告信息。
77.实现优化.实现优化优化的优化的主要目的主要目的是使编译生成的代码的是使编译生成的代码的
尺寸更小、运行速度更快。尺寸更小、运行速度更快。但是在编译过程中随着优化级别的升高,但是在编译过程中随着优化级别的升高,
编译器会相应消耗更多时间和内存,而且优编译器会相应消耗更多时间和内存,而且优化生成代码的执行顺序和源代码有一定出入,化生成代码的执行顺序和源代码有一定出入,因此优化选项更多地用于生成固化代码,而因此优化选项更多地用于生成固化代码,而不用于生成调试代码。不用于生成调试代码。
arm-elf-gccarm-elf-gcc 支持多种优化选项,总体上划支持多种优化选项,总体上划分为分为三级优化三级优化::
1.1.-O1-O1 可以部分减小代码尺寸,对运行速可以部分减小代码尺寸,对运行速度有一定的提高。较多地使用了寄存器变量,度有一定的提高。较多地使用了寄存器变量,提高指令的并行度。提高指令的并行度。
2.2.-O2-O2 除了解循环、函数插装和静态变量除了解循环、函数插装和静态变量优化,几乎包含优化,几乎包含 arm-elf-gccarm-elf-gcc 所有优化选项。一所有优化选项。一般在生成固化代码时使用该选项较为适宜。般在生成固化代码时使用该选项较为适宜。
3.3.-O3-O3 包含包含 -O2-O2 的所有优化,并且还包含的所有优化,并且还包含了解循环、函数插装和静态变量优化。通常情了解循环、函数插装和静态变量优化。通常情况下,该级优化生成的代码执行速度最快,但况下,该级优化生成的代码执行速度最快,但是代码尺寸比是代码尺寸比 -O2-O2 大一些。大一些。
88.在命令行定义宏.在命令行定义宏
-D macro-D macro 定义宏定义宏 macromacro 为为 11 。。-D macro=defn-D macro=defn 定义宏定义宏 macromacro 为为 defndefn 。。
例如例如 ::编译编译 test.ctest.c 并且预定义宏 并且预定义宏 RUN_CACHE RUN_CACHE 值值
为为 11: arm-elf-gcc –c –D RUN_CACHE test.c: arm-elf-gcc –c –D RUN_CACHE test.c
编译编译 test.ctest.c 并且预定义宏 并且预定义宏 RUN_CACHE RUN_CACHE 值值为为 00: arm-elf-gcc –c –D RUN_CACHE=0 test.c: arm-elf-gcc –c –D RUN_CACHE=0 test.c
3.3 3.3 交叉连接器交叉连接器 arm-elf-ldarm-elf-ld
3.3.1 3.3.1 概述概述arm-elf-ldarm-elf-ld 根据链接定位文件根据链接定位文件 LinkcmdsLinkcmds
中代码段、数据段、中代码段、数据段、 BSSBSS 段和堆栈段等定位段和堆栈段等定位信息,将可重定位的目标模块链接成一个单信息,将可重定位的目标模块链接成一个单一的、绝对定位的目标程序,该目标程序是一的、绝对定位的目标程序,该目标程序是ELFELF 格式,并且可以包含调试信息。格式,并且可以包含调试信息。
arm-elf-ldarm-elf-ld 可以输出一个内存映象文件,可以输出一个内存映象文件,该文件显示所有目标模块、段和符号的绝对该文件显示所有目标模块、段和符号的绝对定位地址,它也产生目标模块对全局符号引定位地址,它也产生目标模块对全局符号引用的交叉参考列表。用的交叉参考列表。
arm-elf-ldarm-elf-ld 支持将多个目标模块支持将多个目标模块链接成一个单一的、绝对定位的目链接成一个单一的、绝对定位的目标程序,也能够依次对目标模块进标程序,也能够依次对目标模块进行链接,这个特性称为行链接,这个特性称为增量链接增量链接(( IIncremental Linkingncremental Linking )。)。
arm-elf-ldarm-elf-ld 会自动从库中装载被会自动从库中装载被调用函数所在的模块。调用函数所在的模块。
11.命令格式.命令格式
arm-elf-ld [option] file…arm-elf-ld [option] file…命令行后跟选项和可重定位的目标文件名。命令行后跟选项和可重定位的目标文件名。
例如例如 ::链接的输入文件为链接的输入文件为 demo.odemo.o ,输出文件为,输出文件为 dd
emo.elfemo.elf ,链接的库为,链接的库为 libxxx.alibxxx.a ,生成内存映象,生成内存映象文件文件 map.txtmap.txt ,链接定位文件为,链接定位文件为 linkcmdslinkcmds ,则,则命令如下:命令如下:
arm-elf-ld -Map map.txt -T linkcmds -L./liarm-elf-ld -Map map.txt -T linkcmds -L./lib –o demo.elf demo.o –lxxxb –o demo.elf demo.o –lxxx
22.命令选项列表.命令选项列表-e entry-e entry 指定程序入口指定程序入口-M-M 输出链接信息输出链接信息-lar-lar 指定链接库指定链接库-L dir-L dir 添加搜索路径添加搜索路径-o-o 设置输出文件名设置输出文件名-Tcommandfile-Tcommandfile 指定链接命令文件指定链接命令文件-v-v 显示版本信息显示版本信息-Map-Map 制定输出映像文件制定输出映像文件
3.3.2 3.3.2 命令使用命令使用11.程序入口地址.程序入口地址-e entry-e entry以符号以符号 entryentry作为程序执行的入口地址,而不从默作为程序执行的入口地址,而不从默
认的入口地址开始。默认入口地址的指定方式和其他指认的入口地址开始。默认入口地址的指定方式和其他指定方式的描述,参见定方式的描述,参见 3.3.33.3.3 节。节。
例如,链接的输入文件为例如,链接的输入文件为 demo.odemo.o ,输出文件为,输出文件为 dedemo.elfmo.elf ,链接定位文件为,链接定位文件为 linkcmdslinkcmds ,将入口地址设为,将入口地址设为_start_start ,命令如下:,命令如下:
arm-elf-ld –T linkcmds –e _start –o demo.elf dearm-elf-ld –T linkcmds –e _start –o demo.elf demo.omo.o
22.输出链接信息.输出链接信息-M-M
在标准端口打印出符号映象表和内存分布信在标准端口打印出符号映象表和内存分布信息。息。
例如:例如:链接的输入文件为链接的输入文件为 demo.o,demo.o, 输出文件为输出文件为 dede
mo.elf,mo.elf, 在标准端口打印出符号映象表和内存分在标准端口打印出符号映象表和内存分布信息,命令如下:布信息,命令如下:
arm-elf-ld –M –o demo.elf demo.oarm-elf-ld –M –o demo.elf demo.o
如果标准输出设置为显示器,运行命令后将如果标准输出设置为显示器,运行命令后将在显示器上显示内存映象信息和符号映象表。 在显示器上显示内存映象信息和符号映象表。
-Map mapfile-Map mapfile将链接的符号映象表和内存分布信将链接的符号映象表和内存分布信
息输出到文件息输出到文件 mapfilemapfile 里。里。
例如例如 ::链接的输入文件为链接的输入文件为 demo.odemo.o ,输出,输出
文件为文件为 demo.elfdemo.elf ,将链接的符号映象表,将链接的符号映象表和内存分布信息输出到文件和内存分布信息输出到文件 map.txtmap.txt 里,里,命令如下:命令如下:
arm-elf-ld –Map map.txt –o demo.elf demarm-elf-ld –Map map.txt –o demo.elf demo.oo.o
33.指定链接的库.指定链接的库-lar-lar指定库文件指定库文件 libar.alibar.a 为链接的库。为链接的库。可以重复使用可以重复使用 -l-l 来指定多个链接的库。来指定多个链接的库。
例如例如 ::
链接的输入文件为链接的输入文件为 demo.odemo.o ,指定,指定 libxxx.alibxxx.a为链接的库,输出文件为为链接的库,输出文件为 demo.elfdemo.elf ,命令如下:,命令如下:
arm-elf-ld –o demo.elf demo.o –lxxxarm-elf-ld –o demo.elf demo.o –lxxx
注意:库的命名规则为注意:库的命名规则为 libxxx.alibxxx.a ,在,在 -l-l 指定指定库名时使用的格式为库名时使用的格式为 -lxxx-lxxx 。。
44.添加库和脚本文件的搜索路径.添加库和脚本文件的搜索路径-Ldir-Ldir将将 dirdir 添加到搜索路径。添加到搜索路径。搜索顺序按照命令行中输入的顺序,并且搜索顺序按照命令行中输入的顺序,并且
优先于默认的搜索路径。优先于默认的搜索路径。所有在所有在 -L-L 添加的目录中找到的添加的目录中找到的 -l-l 指定的指定的
库都有效。库都有效。例如例如 :: 链接的输入文件为链接的输入文件为 demo.odemo.o ,输出文,输出文
件为件为 demo.elfdemo.elf ,将,将 /lib/lib 添加到库的搜索路径,添加到库的搜索路径,命令如下:命令如下:
arm-elf-ld -L./lib –o demo.elf demo.o arm-elf-ld -L./lib –o demo.elf demo.o
55.设置输出文件的名字.设置输出文件的名字-o output-o output
将输出文件名字设定为将输出文件名字设定为 outputoutput 。如。如果不指定输出文件名,果不指定输出文件名, arm-elf-ldarm-elf-ld 生成文生成文件名默认为件名默认为 a.outa.out 。。例如例如 ::
链接的输入文件为链接的输入文件为 demo.odemo.o ,输出文,输出文件为件为 demo.elfdemo.elf ,命令如下:,命令如下:
arm-elf-ld –o demo.elf demo.oarm-elf-ld –o demo.elf demo.o
3.3.3 linkcmds3.3.3 linkcmds 连接命令文件 连接命令文件
arm-elf-ldarm-elf-ld 的命令语言是一种描述性的脚本的命令语言是一种描述性的脚本语言,它主要应用于控制语言,它主要应用于控制 :: 有哪些输入文件、有哪些输入文件、文件的格式怎样、输出文件中的模块怎样布局、文件的格式怎样、输出文件中的模块怎样布局、分段的地址空间怎样分布、以及未初始化的数分段的地址空间怎样分布、以及未初始化的数据段怎样处理等。据段怎样处理等。
用命令语言写成的文件用命令语言写成的文件 (( 通常称为通常称为 linkcmlinkcmds)ds) 具有可重用性具有可重用性 ,, 不必每次在命令行输入一大不必每次在命令行输入一大堆命令选项堆命令选项 .. 并且对于不同的应用并且对于不同的应用 ,, 只需对只需对 linklinkcmdscmds 进行简单的修改就可以使用。进行简单的修改就可以使用。
11.调用.调用 linkcmdslinkcmds首先写一个链接命令文件首先写一个链接命令文件 linkcmdslinkcmds ,然后,然后在在 arm-elf-ldarm-elf-ld 的命令中使用的命令中使用 -T linkcmds-T linkcmds参数参数 ,, 就能在链接时自动调用就能在链接时自动调用 linkcmdslinkcmds 文文件.件.
例如例如 : : 链接的输入文件为链接的输入文件为 demo.odemo.o ,输出,输出文件为文件为 demo.elfdemo.elf ,链接定位文件为,链接定位文件为 linkcmlinkcmdsds ,则命令如下,则命令如下 ::arm-elf-ld –T linkcmds –o demo.elf demo.oarm-elf-ld –T linkcmds –o demo.elf demo.o
22.编写.编写 linkcmdslinkcmds(1)arm-elf-ld(1)arm-elf-ld 命令语言命令语言arm-elf-ldarm-elf-ld 命令语言是一系列语句的命令语言是一系列语句的
集合,包括用简单的关键字设定选项、描集合,包括用简单的关键字设定选项、描述输入文件及其格式、命名输出文件。述输入文件及其格式、命名输出文件。
其中有两种语句对于链接过程起重要其中有两种语句对于链接过程起重要作用:作用: SECTIONSSECTIONS 语句和语句和 MEMORYMEMORY 语句。语句。SECTIONSSECTIONS 语句用于描述输出文件中的模语句用于描述输出文件中的模块怎样布局,块怎样布局, MEMORYMEMORY 语句描述目标机语句描述目标机中可以用的存储单元。中可以用的存储单元。
(( 22 )表达式)表达式在在 linkcmdslinkcmds 中的表达式与中的表达式与 CC 语言中的表语言中的表
达式类似,它们具有如下的特征:达式类似,它们具有如下的特征:表达式的值都是“表达式的值都是“ unsigned long”unsigned long” 或者或者
““ long”long” 类型类型常数都是整数常数都是整数支持支持 CC 语言中的操作符语言中的操作符可以引用或者定义全局变量可以引用或者定义全局变量可以使用内建的函数可以使用内建的函数
① ① 整数整数八进制数以‘八进制数以‘ 0’0’ 开头,例如:开头,例如: 02340234;;十进制数以非十进制数以非 00 的数字开头,例如:的数字开头,例如: 567567;;十六进制数以‘十六进制数以‘ 0x’0x’ 或‘或‘ 0X’0X’ 开头,例如:开头,例如:
0x160x16;;负数以运算符‘负数以运算符‘ -’-’ 开头,例如:开头,例如: -102-102;;以以 KK ,, MM 为后缀分别表示以为后缀分别表示以 10241024 ,, 1024*1024*
10241024 为单位,例如:为单位,例如: var1 = 1Kvar1 = 1K 和和 var1 = 1024var1 = 1024相等,相等, var2 = 1Mvar2 = 1M 和和 var2 = 1024*1024var2 = 1024*1024 相等。 相等。
② ② 变量名变量名以字母、下划线和点开头,可以包含任何以字母、下划线和点开头,可以包含任何
字母、下划线、数字、点和连接符。字母、下划线、数字、点和连接符。变量名不能和关键字一样,如果变量名和变量名不能和关键字一样,如果变量名和
关键字一样,或者变量名中包含空格时,必须将关键字一样,或者变量名中包含空格时,必须将变量名包含在“”中变量名包含在“”中 ..例如:例如:““SECTION”SECTION” == 99;;““with a space”with a space” =“=“ also with a space” + also with a space” +
1010;;在在 arm-elf-ldarm-elf-ld 命令语言中,空格用于界定相命令语言中,空格用于界定相
邻符号,例如:邻符号,例如: A-BA-B 表示一个变量名,而表示一个变量名,而 A - BA - B表示一个减法的表达式。 表示一个减法的表达式。
③ ③ 地址记数器点号“地址记数器点号“ . ”. ”
““..”” 是一个包含当前输出地址的计数器。是一个包含当前输出地址的计数器。因为“因为“ ..””总是表示某个输出段的地址,总是表示某个输出段的地址,
所以总是和表达式一起在所以总是和表达式一起在 SECTIONSSECTIONS 命令中命令中出现。出现。
““..”” 可以在任何一般符号出现的地方出可以在任何一般符号出现的地方出现,对“现,对“ .”.” 的赋值将引起计数器所指位置的的赋值将引起计数器所指位置的移动,而计数器位置不能反向移动。移动,而计数器位置不能反向移动。
例如:例如:SECTIONSSECTIONS
{{outputoutput ::
{{file1(.text)file1(.text). = . + 1000. = . + 1000 ;;file2(.text)file2(.text). += 1000. += 1000 ;;file3(.text)file3(.text)
} = 0x1234} = 0x1234 ;;}}
在左面的例子中,在 file1 ( .text )与 file2 ( .text )之间被空出了 1000 个字节的空间, file2 ( .text )与 file3 ( .text )之间也被空出了 1000个字节的空间,而0x1234 为该分段的空间空隙的填充值。
可以将“可以将“ .”.” 赋给变量赋给变量 ;;
也可以对“也可以对“ .”.” 赋值。赋值。
例如:例如:data_start = . data_start = . ;;.= . + 2000.= . + 2000 ;;
(( 33 )) linkcmdslinkcmds 的结构的结构linkcmdslinkcmds 文件主要由四个部分文件主要由四个部分组成组成::1.1. 程序入口说明:用于指定程序运行时所需程序入口说明:用于指定程序运行时所需
要执行的第一条指令的地址。要执行的第一条指令的地址。2.2. 程序头说明:生成目标文件类型为程序头说明:生成目标文件类型为 ELFELF ,,
可以指定详细的程序头信息。可以指定详细的程序头信息。3.3. 内存布局的说明:用于规划内存的布局,内存布局的说明:用于规划内存的布局,
将内存空间划分为不同的部分。将内存空间划分为不同的部分。4.4. 分段的分步说明:详细指明各个分段的构分段的分步说明:详细指明各个分段的构
成以及分段的定位地址和装载地址。成以及分段的定位地址和装载地址。
其中①和④的部分不能被省略。 其中①和④的部分不能被省略。
(( 44 )对程序入口的说明)对程序入口的说明arm-elf-ldarm-elf-ld 命令语言有一条特定命令用于指定命令语言有一条特定命令用于指定
输出文件中第一条可执行指令输出文件中第一条可执行指令 ,, 即程序的入口点。即程序的入口点。该命令格式如下:该命令格式如下:ENTRYENTRY (( symbolsymbol ))
其中其中 ENTRYENTRY 是保留字,是保留字, symbolsymbol 表示程序的表示程序的入口地址,通常是用一个全局地址标号(入口地址,通常是用一个全局地址标号( labellabel ))来表示入口地址。来表示入口地址。例如,在程序中的开始地方有一标号:例如,在程序中的开始地方有一标号:.global demo_start.global demo_startdemo_start:demo_start:movl $_stack_topmovl $_stack_top ,, %esp%esp…………
那么在那么在 linkcmdslinkcmds 中可以用下面的方式说明中可以用下面的方式说明程序的入口:程序的入口:
ENTRY(demo_start);ENTRY(demo_start);该命令可以作为单独的一条命令放在该命令可以作为单独的一条命令放在 linkclinkc––
mdsmds 的任何位置,也可以放在的任何位置,也可以放在 SECTIONSSECTIONS 内关内关于段的定义部分,都对布局起作用。于段的定义部分,都对布局起作用。指定入口地址的方式还有很多,现在按优先指定入口地址的方式还有很多,现在按优先
级递减的顺序描述如下:级递减的顺序描述如下:用‘用‘ -e’-e’ 选项指定入口地址选项指定入口地址在在 linkcmdslinkcmds里用里用 ENTRYENTRY (( symbolsymbol )指)指
令令变量变量 startstart 的值,如果有变量的值,如果有变量 startstart.text.text 段第一字节的地址,如果存在段第一字节的地址,如果存在00 地址地址
(( 55 )对程序头的说明)对程序头的说明ELFELF 格式文件需要使用程序头,它用格式文件需要使用程序头,它用
于描述程序应该怎么被装入内存。于描述程序应该怎么被装入内存。在默认情况下,在默认情况下, arm-elf-ldarm-elf-ld 可以自己可以自己
生成一个程序头,用户也可以用生成一个程序头,用户也可以用 PHDRSPHDRS自己编写程序头,当运用该命令时,自己编写程序头,当运用该命令时, ararm-elf-ldm-elf-ld 不会生成默认的程序头。不会生成默认的程序头。
注意:如果没有特殊要求,建议用户注意:如果没有特殊要求,建议用户不要不要自己写程序头。自己写程序头。
PHDRSPHDRS{{name type [ FILEHDR ] [ PHDRS ] [ AT ( address )name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ] ] [ FLAGS [ FLAGS (( flagsflags )) ] ] ;;}}其中其中 PHDRSPHDRS 、、 FILEHDRFILEHDR 、、 ATAT 、、 FLAGSFLAGS 都是关键都是关键字。字。 namename 表示段(表示段( SegmentSegment )的名字,而该段()的名字,而该段( SSegmentegment )装入的内容由)装入的内容由 SECTIONSSECTIONS 中对分段(中对分段( SectSectionion )的描述决定,例如:)的描述决定,例如:SECTIONS{SECTIONS{……secname start BLOCK(align) (NOLOAD) : AT ( ldasecname start BLOCK(align) (NOLOAD) : AT ( ldadr )dr ){ contents } >region :phdr =fill{ contents } >region :phdr =fill……}}
因此因此 PHDRSPHDRS 中的中的 namename 应该和应该和 SECTIONSSECTIONS 中的中的 pphdrhdr保持一致。保持一致。 typetype 表示段的类型,可以为下面描述的表示段的类型,可以为下面描述的任意一种类型(括号内表示关键字的值):任意一种类型(括号内表示关键字的值):
PT_NULL (0) ——PT_NULL (0) —— 空程序头空程序头PT_LOAD (1) ——PT_LOAD (1) —— 描述一个可装入的段描述一个可装入的段PT_DYNAMIC (2)——PT_DYNAMIC (2)—— 表示包含动态链接信息的表示包含动态链接信息的
段段PT_INTERP (3) ——PT_INTERP (3) —— 表示该段包含程序解释器表示该段包含程序解释器
的名字的名字PT_NOTE (4) ——PT_NOTE (4) —— 表示包含注释信息的段表示包含注释信息的段PT_SHLIB (5) ——PT_SHLIB (5) —— 一个保留的程序头一个保留的程序头PT_PHDR (6) ——PT_PHDR (6) —— 表示该段可能包含程序头的描表示该段可能包含程序头的描
述信息述信息expression ——expression —— 用数值表示一个程序头的类型,用数值表示一个程序头的类型,
该类型没有对应的关键字该类型没有对应的关键字
FILEHDRFILEHDR 表示在段中包含表示在段中包含 ELFELF 文件头的信息。文件头的信息。 PHDPHDRSRS 表示在段中还要包含程序头本身的信息。表示在段中还要包含程序头本身的信息。[AT (address)][AT (address)] 表示该段的起始地址,若在表示该段的起始地址,若在 SECTIOSECTIONSNS 中也有中也有 ATAT 时,程序头中定义的时,程序头中定义的 ATAT 优先。优先。例如:例如:
PHDRSPHDRS
{{
headers PT_PHDR PHDRS ;headers PT_PHDR PHDRS ;
interp PT_INTERP ;interp PT_INTERP ;
text PT_LOAD FILEHDR PHDRS ;text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;data PT_LOAD ;
dynamic PT_DYNAMIC ;dynamic PT_DYNAMIC ;
}}
SECTIONSSECTIONS
{{
. = SIZEOF_HEADERS;. = SIZEOF_HEADERS;
.interp : { *(.interp) } :text :interp.interp : { *(.interp) } :text :interp
.text : { *(.text) } :text.text : { *(.text) } :text
.rodata : { *(.rodata) } /* defaults to :text */.rodata : { *(.rodata) } /* defaults to :text */
......
. = . + 0x1000; /* move to a new page in memory */. = . + 0x1000; /* move to a new page in memory */
.data : { *(.data) } :data.data : { *(.data) } :data
.dynamic : { *(.dynamic) } :data :dynamic.dynamic : { *(.dynamic) } :data :dynamic
......
}}
在上面的例子中,定义了在上面的例子中,定义了 55 个段:个段:headersheaders申明一个程序头段;申明一个程序头段;interpinterp 申明一个段,段中包含了申明一个段,段中包含了
程序解释器的名字;程序解释器的名字;texttext申明一个可被下载的段,并申明一个可被下载的段,并
且包含了文件的头信息和各段的信息;且包含了文件的头信息和各段的信息;datadata申明一个可被下载的段;申明一个可被下载的段;dynamicdynamic申明一个包含动态链接申明一个包含动态链接
信息的段; 信息的段;
在在 SECTIONSSECTIONS 中可以看到,有的分中可以看到,有的分段同时属于两个段,实质上是这两段同时属于两个段,实质上是这两个段占用同一空间。个段占用同一空间。
.rodata.rodata 也属于也属于 .text.text 段是由于它的段是由于它的上一个分段属于上一个分段属于 .text.text 段,而段,而 .rodat.rodataa又没有指明归属段。又没有指明归属段。
注意:如果没有特殊要求,建议用注意:如果没有特殊要求,建议用户不要自己写程序头。 户不要自己写程序头。
(( 66 )对内存布局的说明)对内存布局的说明arm-elf-ldarm-elf-ld 的默认配置允许将输出的默认配置允许将输出
程序定位到任何可用内存。程序定位到任何可用内存。用户也可以用用户也可以用 MEMORYMEMORY 命令对内命令对内
存进行配置。存进行配置。
MEMORYMEMORY 命令可以定义目标机内命令可以定义目标机内存段的位置和大小,当装载的程序块大存段的位置和大小,当装载的程序块大小超出指定的内存段大小时,小超出指定的内存段大小时, arm-elf-larm-elf-ldd 会提示出错,而不会自动寻找可用的会提示出错,而不会自动寻找可用的内存段,这样可以避免内存分配错误。 内存段,这样可以避免内存分配错误。
定义内存段的方式:定义内存段的方式:MEMORYMEMORY{{name (attr) :ORIGIN = origin, LENGTH = lenname (attr) :ORIGIN = origin, LENGTH = len……}}namename 表示内存段的名字,可以使用任何变量名,表示内存段的名字,可以使用任何变量名,但是不能和已有变量名、文件名和分段名(但是不能和已有变量名、文件名和分段名( sectiosection namen name )冲突。)冲突。attrattr没有实际的用途,可省略。没有实际的用途,可省略。originorigin (可简写为:(可简写为: orgorg 或者或者 oo )表示内存段的)表示内存段的起始位置。起始位置。LengthLength(( 可简写为可简写为 :len:len 或者或者 l)l) 表示内存段的长度表示内存段的长度 . .
MEMORYMEMORY{{rom : ORIGIN = 0x3f80000, LENGTH = 5rom : ORIGIN = 0x3f80000, LENGTH = 512K12Kram : org = 0, l = 1Mram : org = 0, l = 1M}}
表示定义了两个内存段:表示定义了两个内存段:romrom 内存段,起始地址为内存段,起始地址为 0x3f800000x3f80000 ,,长度为长度为 512K512K ;;ramram 内存段,起始地址为内存段,起始地址为 00 ,长度为,长度为 1M1M 。。
(( 77 )对分段的说明)对分段的说明SECTIONSSECTIONS 命令控制如何正确地命令控制如何正确地将输将输
入分段定位到输出分段入分段定位到输出分段 ,, 包括在输出文件中包括在输出文件中的顺序的顺序 ,, 和输入分段在输出分段中的定位。和输入分段在输出分段中的定位。
如果不用如果不用 SECTIONSSECTIONS 命令,命令, arm-elf-larm-elf-ldd 将对每个输入分段生成相同名字的输出将对每个输入分段生成相同名字的输出分段,分段的顺序由输入文件中遇到的分分段,分段的顺序由输入文件中遇到的分段的先后顺序决定。 段的先后顺序决定。
SECTIONSSECTIONS 命令的格式:命令的格式:SECTIONS{SECTIONS{......secname start BLOCK(align) (NOLOAD) : AT ( ldadr )secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill{ contents } >region :phdr =fill......}}其中其中 secnamesecname 和和 contentscontents 都是必须有的。都是必须有的。 secnasecnameme 表示输出分段的名字,受输出格式的限制,一表示输出分段的名字,受输出格式的限制,一些输出格式对段名有限制,些输出格式对段名有限制,例如例如 a.outa.out 只允许只允许 .text,.data.text,.data 或或 .bss.bss 的分段名存在的分段名存在 ..另外另外 arm-elf-ldarm-elf-ld 不输出空的分段。不输出空的分段。
startstart ,, BLOCKBLOCK (( alignalign )) ,, (( NOLOADNOLOAD )) ,AT,AT(( ldadrldadr )) , >region, >region ,, :phdr:phdr ,, =fill=fill 都是可选项:都是可选项:
StartStart 表示分段的起始地址表示分段的起始地址 ,, 该地址被称为重定位该地址被称为重定位地址地址 ;;
BLOCKBLOCK (( alignalign ))表示分段以表示分段以 alignalign 对齐;对齐;
(( NOLOADNOLOAD ))表示该段不能被装载;表示该段不能被装载;
AT (ldadr)AT (ldadr) 表示该分段装入的起始地址为表示该分段装入的起始地址为 ldadrldadr ,,当没有该参数时分段的装入地址和重定位地址相同;当没有该参数时分段的装入地址和重定位地址相同;
regionregion 表示该分段地址空间在表示该分段地址空间在 regiregionon 所定义的范围内所定义的范围内 ,region,region 就是在就是在 MEMOMEMORYRY 命令中定义的内存段名字;命令中定义的内存段名字;
:phdr:phdr 表示该分段的装载地址空间在表示该分段的装载地址空间在phdrphdr 定义的范围内,定义的范围内, phdrphdr 就是在程序头中就是在程序头中定义的段名字;定义的段名字;
=fill=fill 表示在该分段的空间空隙的填充表示在该分段的空间空隙的填充值。 值。
contentscontents 表示具体的分段内容,主要表示具体的分段内容,主要描述该输出分段中包含有输入文件中的哪些描述该输出分段中包含有输入文件中的哪些分段。分段。
常见的分段名如下:常见的分段名如下:.text .text ———— 表示代码段表示代码段.data .data ————初始化了的数据段初始化了的数据段.bss .bss ————未初始化的数据段未初始化的数据段.rodata .rodata ———— 不可写的数据段不可写的数据段COMMON COMMON ————未初始化的数据段未初始化的数据段
用户在汇编语言程序中可以自定义分用户在汇编语言程序中可以自定义分段名,如:段名,如: mycodemycode 、、 mydatamydata 之类。 之类。
在在 CC 语言文件编译成目标文件后语言文件编译成目标文件后 ,, 通通常包含有常包含有 .text.text 、、 .data.data 、、 COMMONCOMMON 、、 .r.rodataodata 段。段。
其中其中 .rodata.rodata 表示不可写的数据段,表示不可写的数据段,通常包含在通常包含在 CC 语言程序中定义的一些常量,语言程序中定义的一些常量,如如 const char version_string[ ]=“Lambconst char version_string[ ]=“Lambda x86/fpm”da x86/fpm” 之类。 之类。
contentscontents 可用格式:可用格式: filenamefilename filenamefilename (( sectionsection )) filenamefilename (( sectionsection ,, sectionsection ,…),…) filenamefilename (( section section …section section … ))
和针对所有文件的:和针对所有文件的: ** (( sectionsection )) ** (( sectionsection ,, sectionsection ,…),…) ** (( section section …section section … ))
如果在用“如果在用“ *”*”指定所有文件时,以指定所有文件时,以前已经使用前已经使用 filenamefilename 指定过一些文件,指定过一些文件,那么“那么“ *”*” 表示剩下的文件:表示剩下的文件:
filenamefilename (( COMMONCOMMON ))** (( COMMONCOMMON ))指定输入文件中名为指定输入文件中名为 COMMONCOMMON 的的
分段里未初始化的数据在输出分段中的分段里未初始化的数据在输出分段中的位置。位置。下面举例说明下面举例说明 contentscontents 中的具体中的具体
内容及编写方法。 内容及编写方法。
下面举例说明下面举例说明 contentscontents 中的具体内容及编写方法。中的具体内容及编写方法。例如:例如:
.text 0 :.text 0 :{ file1.o file2.o file3.o } { file1.o file2.o file3.o } 表示将表示将 file1.ofile1.o 、、 file2.ofile2.o 、、 file3.ofile3.o 中的所有中的所有
分段都放在输出文件的分段都放在输出文件的 .text.text 段中。段中。
例如:例如:.text 0 :.text 0 :
{{*(.text);*(.text);
}}表示将所有输入文件中的表示将所有输入文件中的 .text.text 分段都放在输分段都放在输
出文件的出文件的 .text.text 段中。 段中。
例如:例如:.text 0 :.text 0 :
{{file1.o (.text);file1.o (.text);file2.o (.text);file2.o (.text);file3.o (.text);file3.o (.text);
}}
表示将表示将 file1.ofile1.o 、、 file2.ofile2.o 、、 file3.ofile3.o 中的中的 .te.textxt 段都放在输出文件的段都放在输出文件的 .text.text 段中。 段中。
例如:例如:text1 :text1 :
{{file1.o (.text);file1.o (.text);
}}text2 :text2 :
{{*(.text);*(.text);
}}表示将表示将 file1.ofile1.o 中的中的 .text.text 段放在输出文段放在输出文
件的件的 text1text1 段中,而其他输入文件的段中,而其他输入文件的 .text.text 段段都放在输出文件的都放在输出文件的 text2text2 段。 段。
(( 88 )注释)注释
arm-elf-ldarm-elf-ld 语言中的注释和语言中的注释和 CC 语语言一样。言一样。
例如:例如: /* comments *//* comments */
3.4 3.4 工程管理器 工程管理器 MAKEMAKE
3.4.1 3.4.1 概述概述makemake 是用于自动编译、链接是用于自动编译、链接
程序的实用工具。程序的实用工具。使用使用 makemake 后就不需要手工编后就不需要手工编
译每个程序文件译每个程序文件。。
要使用要使用 makemake ,首先要编写,首先要编写 makefile makefile ,,makefilemakefile 描述程序文件之间的依赖关系以描述程序文件之间的依赖关系以及提供更新文件的命令。及提供更新文件的命令。典型地,在一个程序中,可执行文件依典型地,在一个程序中,可执行文件依
赖于目标文件,而目标文件依赖于源文件。赖于目标文件,而目标文件依赖于源文件。
如果如果 makefilemakefile 文件存在,每次修改完文件存在,每次修改完源程序后,用户通常所需要做的事情就是在源程序后,用户通常所需要做的事情就是在命令行敲入“命令行敲入“ make”make” ,然后所有的事情都,然后所有的事情都由由 makemake 来完成。来完成。
11.命令格式.命令格式make [-f makefile] [option] [target]…make [-f makefile] [option] [target]…
makemake 命令后跟命令后跟 -f-f 选项,指定选项,指定 makefilemakefile的名字为的名字为 makefilemakefile ;;
optionoption 表示表示 makemake 的一些选项的一些选项;; targettarget 是是 makemake指定的目标,在指定的目标,在 3.4.33.4.3
将详细说明。将详细说明。
例如例如 :makefile:makefile 的名字是的名字是my_hello_makemy_hello_make ::make –f my_hello_makemake –f my_hello_make
22.命令选项列表.命令选项列表 -f-f 指定指定 makefilemakefile -e-e 使环境变量优先于使环境变量优先于 makefilemakefile 的变量的变量 -I dir-I dir 设定搜索目录设定搜索目录 -i-i 忽略忽略makemake 过程中所有错误过程中所有错误 -n-n 只显示执行过程,而不真正执行只显示执行过程,而不真正执行 -r-r 使隐含规则无效使隐含规则无效 -w-w 显示工作目录显示工作目录 -C dir-C dir 读取读取makefilemakefile 设置的工作目录设置的工作目录 -s-s 不显示执行的命令 不显示执行的命令
3.4.23.4.2 命令使用命令使用 makefilemakefile 文件用来告诉文件用来告诉 makemake 需要做的事需要做的事
情,通常指怎样编译、怎样链接一个程序。情,通常指怎样编译、怎样链接一个程序。以以 CC 语言程序为例语言程序为例 ::在用在用 makemake 重新编译的时候,如果一个头重新编译的时候,如果一个头
文件已被修改,则包含这个头文件的所有文件已被修改,则包含这个头文件的所有 CC 源源代码文件都必须被重新编译。代码文件都必须被重新编译。
而每个目标文件都与而每个目标文件都与 CC 的源代码文件有关,的源代码文件有关,如果有源代码文件被修改过,则所有目标文件如果有源代码文件被修改过,则所有目标文件都必须被重新链接生成最后的结果。都必须被重新链接生成最后的结果。
编写一个编写一个 makefilemakefile 将在将在 3.4.33.4.3节详细介绍。节详细介绍。
11.指定.指定 makefilemakefile-f makefile-f makefile用该选项指定用该选项指定 makefilemakefile 的名字为的名字为 makemake
filefile 。。如果如果 makemake 中多次使用中多次使用 -f-f指定多个指定多个 makmak
efileefile ,则所有,则所有 makefilemakefile 将链接起来作为最后将链接起来作为最后的的 makefilemakefile 。。
如果不指定如果不指定 makefilemakefile ,, makemake默认的默认的makefilemakefile 依次为“依次为“ makefile”makefile” 、“、“ Makefile”Makefile” 。。例如:例如:make –f my_hello_makemake –f my_hello_make
22.使环境变量优先于.使环境变量优先于 makefilemakefile文件中的变量文件中的变量
-e-e使环境变量优先于使环境变量优先于 makefilemakefile
文件中的变量。文件中的变量。
例如:例如: make –emake –e
33.指定包含文件的搜索路径.指定包含文件的搜索路径-I dir-I dir指定在解析指定在解析 makefilemakefile 文件中文件中
的的 .include.include 时的搜索路径为时的搜索路径为 dirdir 。。如果有多个路径,将按输入顺如果有多个路径,将按输入顺
序依次查找。序依次查找。例如:例如:make –I/include/mkmake –I/include/mk
44.忽略错误.忽略错误
-i-i忽略忽略 makemake 执行过程中的所有错误。执行过程中的所有错误。例如:例如:
make –i make –i
55.显示命令的执行过程.显示命令的执行过程
-n-n只显示命令的执行过程而不真正执行。只显示命令的执行过程而不真正执行。例如:例如:
make –n make –n
66.使隐含规则无效.使隐含规则无效-r-r使使 makemake 的隐含规则无效,清除后缀名的隐含规则无效,清除后缀名
规则中默认的后缀清单。规则中默认的后缀清单。例如:例如:make –rmake –r
77.显示执行过程中的工作目录.显示执行过程中的工作目录-w-w显示显示 makemake 执行过程中的工作目录。执行过程中的工作目录。例如:例如:make –wmake –w
88.读取.读取 makefilemakefile 文件前设置工作目录文件前设置工作目录-C dir-C dir在读取在读取 makefilemakefile 文件以前将工作目录改文件以前将工作目录改
变为变为 dirdir ,完成,完成 makemake 后改回原来的目录。后改回原来的目录。如果在一次如果在一次 makemake 中使用多个中使用多个 -C-C 选项,选项,
每个选项都和前面一个有关系。每个选项都和前面一个有关系。““-C dir0 / -C dir1-C dir0 / -C dir1 ” ” 与“与“ -C dir0 / dir1-C dir0 / dir1”” 等价等价 ..
例如:例如:make –C bspmake –C bsp
99.不显示所执行的命令.不显示所执行的命令-s-s运行运行 makemake 时用选项时用选项 -s-s 可以不显示可以不显示
执行的命令,只显示生成的结果文件。执行的命令,只显示生成的结果文件。
例如:例如:make –smake –s
3.4.3 3.4.3 编写一个编写一个 makefilemakefile
1. makefile1. makefile 的结构的结构makefilemakefile 文件包含:文件包含:显式规则显式规则隐含规则隐含规则变量定义变量定义指令指令注释注释
2. 2. 编写编写 makefilemakefile 中的规则中的规则makefilemakefile 中规则的格式如下:中规则的格式如下:
targets targets :: dependenciesdependenciescommandcommand……或者或者targets targets :: dependencies dependencies ;; commandcommandcommandcommand……
targets targets 指定目标名,通常是一个程序产生的目标文件名,指定目标名,通常是一个程序产生的目标文件名,也可能是执行一个动作的名字,名字之间用空格隔开。也可能是执行一个动作的名字,名字之间用空格隔开。
dependencydependency 描述产生描述产生 targettarget 所需的文件,一个所需的文件,一个 targettarget通常依赖于多个通常依赖于多个 dependencydependency 。。
command command 用于指定该规则的命令。用于指定该规则的命令。
注意:注意: commandcommand必须以必须以 TABTAB键开头。如果某一行过长可键开头。如果某一行过长可以分作两行,用‘以分作两行,用‘ \’\’ 连接。连接。
例如:例如:smcinitsmcinit :: smc.o config.osmc.o config.oarm-elf-ar –ruvs –o smcinit.a smc.o config.oarm-elf-ar –ruvs –o smcinit.a smc.o config.osmc.osmc.o :: smc.c include.hsmc.c include.harm-elf-gcc –c –o smc.o smc.carm-elf-gcc –c –o smc.o smc.cconfig.oconfig.o :: config.c include.hconfig.c include.harm-elf-gcc –c –o config.o config.carm-elf-gcc –c –o config.o config.ccleanclean ::rm *.orm *.o表示目标名的有表示目标名的有 smcinitsmcinit 、、 smc.osmc.o 、、 config.oconfig.o 。。
smcinitsmcinit 依赖于依赖于 smc.osmc.o 和和 config.oconfig.o ,而,而 smc.osmc.o又依赖于又依赖于smc.csmc.c 和和 include.h,config.oinclude.h,config.o 依赖于依赖于 config.oconfig.o 和和 includinclude.h.e.h.
各目标分别由命令各目标分别由命令 arm-elf-ar –ruvs –o smcinit.a arm-elf-ar –ruvs –o smcinit.a smc.o config.osmc.o config.o ;; arm-elf-gcc –c –o smc.o smc.carm-elf-gcc –c –o smc.o smc.c ;;arm-elf-gcc –c –o config.o config.carm-elf-gcc –c –o config.o config.c 来生成。来生成。
cleanclean 为一动作名,删除所有后缀为为一动作名,删除所有后缀为 .o.o 的文件。 的文件。
3. make3. make 调用调用 makefilemakefile 中的规中的规则则在默认情况下,在默认情况下, makemake运行不是以“运行不是以“ .”.” 开头的第开头的第
一条规则。在上面的例子中,一条规则。在上面的例子中, makemake默认执行的是规则默认执行的是规则smcinitsmcinit ,此时只需要输入命令: ,此时只需要输入命令:
makemakemakemake 将读入将读入 makefilemakefile ,然后执行第一条规则,,然后执行第一条规则,
例子中该规则是链接目标文件生成库,因此必须执行规例子中该规则是链接目标文件生成库,因此必须执行规则则 smcinitsmcinit 依赖的规则依赖的规则 smc.osmc.o 和和 config.oconfig.o 。在执行过。在执行过程中将自动更新他们所依赖的文件。程中将自动更新他们所依赖的文件。
有些规则不是被依赖的规则,需要有些规则不是被依赖的规则,需要 makemake指定才能指定才能被运行,如上面的例子中的被运行,如上面的例子中的 cleanclean规则可以这样执行:规则可以这样执行:
make cleanmake clean这两种方式的结果一样。只是第一种方式没指明目这两种方式的结果一样。只是第一种方式没指明目
标名,第二种方式指明了目标名。标名,第二种方式指明了目标名。
4.4. 设置设置 makefilemakefile 中文件的搜索路径中文件的搜索路径在在 makefilemakefile 中,可以通过给中,可以通过给 VPATHVPATH赋值来设置规则中目标赋值来设置规则中目标
文件和依赖文件的搜索目录。文件和依赖文件的搜索目录。 makemake 首先搜索当前目录,如果未首先搜索当前目录,如果未找到依赖的文件,找到依赖的文件, makemake 将按照将按照 VPATHVPATH 中给的目录依次搜索。中给的目录依次搜索。
VPATHVPATH 对对 makefilemakefile 中所有文件都有效。中所有文件都有效。例如:例如:demo.odemo.o :: demo.c demo.hdemo.c demo.hdemo.cdemo.c 在目录在目录 //c/demo///c/demo/ 中,中, demo.hdemo.h 在目录在目录 //c/demo/he//c/demo/he
ad/ad/ 中,则可以给中,则可以给 VPATHVPATH变量赋值:变量赋值:VPATH VPATH :: = //c/demo //c/demo/head= //c/demo //c/demo/head或者或者VPATH VPATH :: = //c/demo= //c/demo :: //c/demo/head //c/demo/head 也可以使用指令也可以使用指令 vpathvpath ,与,与 VAPTHVAPTH 在使用上的在使用上的区别区别是是 :vpa:vpa
thth 可以给不同类文件指定不同的搜索目录。可以给不同类文件指定不同的搜索目录。 %.o%.o 表示所有以 表示所有以 .o.o结尾的子串。 结尾的子串。
vpath %.c //c/demovpath %.c //c/demo
vpath %.h //c/demo/headvpath %.h //c/demo/head
vpath %.c ——vpath %.c —— 表示清除所有表示清除所有 vpathvpath对对 %.c%.c 设置的搜索目录设置的搜索目录
vpath ——vpath —— 表示清除所有以前用表示清除所有以前用 vpatvpathh 设置的搜索目录设置的搜索目录
这两种方式的效果是一样的,但是后一这两种方式的效果是一样的,但是后一种要明确一些。这样种要明确一些。这样 makemake 就会根据就会根据 VPATVPATHH 或者或者 vpathvpath 来搜索相应的依赖文件。 来搜索相应的依赖文件。
5. 5. 如何定义变量如何定义变量为了简化为了简化 makefilemakefile 以及减少不必要的以及减少不必要的
错误,可以用变量的形式来代表目标文件名错误,可以用变量的形式来代表目标文件名或字符串,在需要使用时直接调用变量。或字符串,在需要使用时直接调用变量。
在在 makefilemakefile 中变量可以被这样定义:中变量可以被这样定义:CC = arm-elf-gccCC = arm-elf-gccAS AS :: = arm-elf -as= arm-elf -asAR = arm-elf -arAR = arm-elf -arLIBPATH LIBPATH :: = ./lib= ./lib
从上面的定义中可以看出,有从上面的定义中可以看出,有两种定义变量的形式:两种定义变量的形式:
1.1.变量名 变量名 = = 值值2.2.变量名:变量名: = = 值值
两者的两者的不同点不同点在于,前者定义在于,前者定义的变量是在被用到时才取它的值,的变量是在被用到时才取它的值,而后者则是在定义变量或者给它赋而后者则是在定义变量或者给它赋值时就确定了它的值。值时就确定了它的值。
例如:例如:var1 = hello firstvar1 = hello firstvar2 var2 == ${var1} ${var1}var1 = hello secondvar1 = hello secondtest_echotest_echo ::echo ${var2}echo ${var2}
执行的结果是显示:执行的结果是显示: hello secondhello second
var1 = hello firstvar1 = hello firstvar2 var2 :=:= ${var1} ${var1}var1 = hello secondvar1 = hello secondtest_echotest_echo ::echo ${var2}echo ${var2}
执行的结果是显示:执行的结果是显示: hello firsthello first
例如:例如:var1 var1 == hello first hello firstvar1 var1 == ${var1} and second ${var1} and secondecho_testecho_test ::echo ${var1}echo ${var1}会陷入死循环中。会陷入死循环中。
var1 var1 :: == hello first hello firstvar1 var1 :: == ${var1} and second ${var1} and secondecho_testecho_test ::echo ${var1}echo ${var1}会显示:会显示: hello first and secondhello first and second
6. 6. 引用变量引用变量有两种方式:有两种方式:1.1.${VarName}${VarName}
2.2.$(VarName)$(VarName)
两种方式的效果一样。两种方式的效果一样。VarNameVarName 表示变量名。表示变量名。
7. make7. make 提供的常用变量提供的常用变量$@——$@—— 表示目标名表示目标名$^ ——$^ —— 表示所有的依赖文件表示所有的依赖文件$< ——$< —— 第一个依赖文件第一个依赖文件例如:例如:demo.o : demo.c demo.hdemo.o : demo.c demo.h${CC} ${CFLAGS} $< -o $@${CC} ${CFLAGS} $< -o $@$<$< 的值为的值为 demo.cdemo.c ,,$@$@ 的值为的值为 demo.odemo.o ,,而而 $^$^ 的值为的值为 demo.c demo.hdemo.c demo.h 。 。
8. make8. make里的常用函数里的常用函数函数的使用方式有两种:函数的使用方式有两种:1.1.$(function arguments) $(function arguments) 2.2.${function arguments} ${function arguments} 常用的函数有:常用的函数有:(( 11 )) $$ (( subst from,to,textsubst from,to,text )将)将
字字 texttext 中的中的 fromfrom 子串替换为子串替换为 toto子串。子串。例如:例如:STR STR :: = $(subst I am,He is,I am an = $(subst I am,He is,I am an
engneer)engneer)与 与 STRSTR :: = He is an engneer = He is an engneer 相同。相同。
(( 22 )) $(patsubst pattern,replacement,text)$(patsubst pattern,replacement,text) 按按模式模式 patternpattern 替换替换 texttext 中的字串。中的字串。例如:例如:
OBJS = init.o main.o string.oOBJS = init.o main.o string.oSTR STR :: = $(patsubst %.o,%.c,${OBJS})= $(patsubst %.o,%.c,${OBJS})STRSTR 的值为:的值为: init.c main.c string.cinit.c main.c string.c
%.o%.o 表示所有以 表示所有以 .o.o 结尾的子串。结尾的子串。$(wildcard pattern...)$(wildcard pattern...) 表示与表示与 patternpattern 相匹配的所相匹配的所有文件。有文件。例如例如 :: 在当前目录中有文件在当前目录中有文件 init.cinit.c 、、 main.cmain.c 和和 strstring.cing.c ::
SRCS SRCS :: = $(wildcard *.c)= $(wildcard *.c)则则 SRCSSRCS 的值为的值为 init.c main.c string.cinit.c main.c string.c
9. 9. 隐含规则隐含规则隐含规则是指由隐含规则是指由 makemake 自定义的规则自定义的规则 ,,常用的有:常用的有:由由 *.c*.c 的文件生成的文件生成 *.o*.o 的文件的文件由由 *.s*.s 的文件生成的文件生成 *.o*.o 的文件 的文件
例如,下面是某例如,下面是某 makefilemakefile 的一部分: 的一部分: CC= arm-elf -gccCC= arm-elf -gccAS= arm-elf -asAS= arm-elf -asLD= arm-elf -ldLD= arm-elf -ldCFLAGS=-c -ansi -nostdinc -I- -I./CFLAGS=-c -ansi -nostdinc -I- -I./ASFLAGS=ASFLAGS=LDFLAGS=-Map map.txt -N -T linkcmds -LDFLAGS=-Map map.txt -N -T linkcmds -
L./lib L./lib
OBJS=i386ex-start.o i386ex-get-put-char.o iOBJS=i386ex-start.o i386ex-get-put-char.o i386ex-io.o386ex-io.o
OBJCOPY= arm-elf -objcopyOBJCOPY= arm-elf -objcopyOBJCOPYFLAG=-O ihexOBJCOPYFLAG=-O ihexAllAll :: monitor.elfmonitor.elf${OBJCOPY} ${OBJCOPYFLAG} monitor.elf ${OBJCOPY} ${OBJCOPYFLAG} monitor.elf
monitor.hexmonitor.hexmonitor.elfmonitor.elf :: ${OBJS}${OBJS}${LD} ${LDFLAGS} -o monitor.elf ${OBJS} -l${LD} ${LDFLAGS} -o monitor.elf ${OBJS} -l
monitormonitorcleanclean ::rm -rf *.o *.elfrm -rf *.o *.elf在该在该 makefilemakefile 中的中的 i386ex-start.oi386ex-start.o 、、 i386ex-gei386ex-ge
t-put-char.ot-put-char.o 、、 i386ex-io.oi386ex-io.o 都是由隐含规则生成的。 都是由隐含规则生成的。
实际上使用的实际上使用的隐含规则隐含规则如下所示:如下所示:对对 *.c-->*.o*.c-->*.o 的的隐含规则隐含规则为:为:%.o%.o :: %.c%.c
${CC} ${CFLAGS} $< -o $@${CC} ${CFLAGS} $< -o $@对于对于 *.s-->*.o*.s-->*.o 的的隐含规则隐含规则为: 为: %.o%.o :: %.s%.s
${AS} ${ASFLAGS} $< -o $@${AS} ${ASFLAGS} $< -o $@
3.5 3.5 交叉汇编器 交叉汇编器 arm-elf-asarm-elf-as
3.5.1 3.5.1 概述概述arm-elf-as arm-elf-as 将汇编语言程序转换为将汇编语言程序转换为 ELFELF
(( Executable and Linking FormatExecutable and Linking Format 执行时执行时链接文件格式)格式的可重定位目标代码,链接文件格式)格式的可重定位目标代码,这些目标代码同其它目标模块或库易于定位这些目标代码同其它目标模块或库易于定位和链接。和链接。
arm-elf-as arm-elf-as 产生一个交叉参考表和一个产生一个交叉参考表和一个标准的符号表,产生的代码和数据能够放在标准的符号表,产生的代码和数据能够放在多个段(多个段( SectionSection )中。)中。
11.命令格式.命令格式arm-elf-as [option…] [asmfile…]arm-elf-as [option…] [asmfile…]
在命令在命令 arm-elf-asarm-elf-as 后面跟一个或多个后面跟一个或多个选项,以及该选项的子选项,选项间用空选项,以及该选项的子选项,选项间用空格隔开,然后跟汇编源文件名。格隔开,然后跟汇编源文件名。
例如,将例如,将 demo.sdemo.s 编译成目标文件,编译成目标文件,并且设置头文件的搜索目录为并且设置头文件的搜索目录为 C:\demo\inclC:\demo\includeude ::
arm-elf-as –I//c/demo/include demo.sarm-elf-as –I//c/demo/include demo.s
22.命令选项列表.命令选项列表-a[dhlns]-a[dhlns] 显示显示 arm-elf-asarm-elf-as 信息信息-f-f 不进行预处理不进行预处理-I path-I path 设置头文件搜索路径设置头文件搜索路径-o-o 设定输出文件名设定输出文件名-v-v 显示版本信息显示版本信息-W-W 不显示警告提示不显示警告提示-Z-Z 不显示错误提示不显示错误提示
3.5.2 3.5.2 命令使用命令使用11.生成目标文件.生成目标文件每次运行每次运行 arm-elf-asarm-elf-as 只输出一个目只输出一个目
标文件,默认状态下名字为标文件,默认状态下名字为 a.outa.out 。。可以通过可以通过 -o-o 选项指定输出文件名字,选项指定输出文件名字,
通常都以通常都以 .o.o 为后缀。为后缀。例如例如 ::
编译编译 demo.sdemo.s 输出目标文件输出目标文件 demo.odemo.o ::arm-elf-as –o demo.o demo.sarm-elf-as –o demo.o demo.s
22.设置头文件搜索路径.设置头文件搜索路径-I path-I path添加路径添加路径 pathpath 到到 arm-elf-asarm-elf-as 的搜索路径,的搜索路径,
搜索搜索 .include ”file” .include ”file” 指示的文件。指示的文件。-I-I 可以被使用多次以添加多个目录,当前工可以被使用多次以添加多个目录,当前工
作目录将最先被搜索,然后从左到右依次搜索作目录将最先被搜索,然后从左到右依次搜索 -I-I指定的目录。指定的目录。
例如例如 :: 编译编译 demo.sdemo.s 时指定两个搜索目录,当时指定两个搜索目录,当前目录和前目录和 C:\demo\includeC:\demo\include ::
arm-elf-as –I../ –I//c/demo/include demo.s arm-elf-as –I../ –I//c/demo/include demo.s
33.显示.显示 arm-elf-asarm-elf-as 信息内容信息内容-a[dhlns]-a[dhlns]打开打开 arm-elf-asarm-elf-as 信息显示。信息显示。dhlnsdhlns 为其子选项,分别表示:为其子选项,分别表示:d d ———— 不显示调试信息不显示调试信息h h ———— 显示源码信息显示源码信息l l ———— 显示汇编列表显示汇编列表nn ———— 不进行格式处理不进行格式处理s s ———— 显示符号列表显示符号列表
在不添加子选项时,在不添加子选项时, -a-a 表示显示源码信息,表示显示源码信息,显示汇编列表,显示符号列表。显示汇编列表,显示符号列表。添加子选项时将选项直接加在添加子选项时将选项直接加在 -a-a 以后可以以后可以添加一个或多个。添加一个或多个。缺省时显示的信息输出到屏幕,也可用重缺省时显示的信息输出到屏幕,也可用重定向输出到文件。定向输出到文件。
例如例如 :: 编译编译 demo.sdemo.s 生成不进行格式处理的生成不进行格式处理的汇编列表,输出到文件汇编列表,输出到文件 a.txta.txt ::arm-elf-as –aln –o demo.o demo.s>a.txtarm-elf-as –aln –o demo.o demo.s>a.txt
44.设置目标文件名字.设置目标文件名字-o filename-o filename每次运行每次运行 arm-elf-asarm-elf-as 只输出一个目标文件,只输出一个目标文件,
默认输出文件为默认输出文件为 a.outa.out 。。可以通过可以通过 -o-o 选项指定输出文件名字,通常选项指定输出文件名字,通常
都以都以 .o.o 为后缀。为后缀。如果指定输出文件的名字和现有某个文件重如果指定输出文件的名字和现有某个文件重
名,生成的文件将直接覆盖已有的文件。名,生成的文件将直接覆盖已有的文件。
例如例如 :: 编译编译 demo.sdemo.s 输出目标文件输出目标文件 demo.odemo.o ::arm-elf-as –I/include –o demo.o demo.s arm-elf-as –I/include –o demo.o demo.s
55.如何取消警告信息.如何取消警告信息-W-W
加选项加选项 -W-W 以后,运行以后,运行 arm-elf-asarm-elf-as 就就不输出警告信息。不输出警告信息。
例如例如 ::
编译编译 demo.sdemo.s 输出目标文件输出目标文件 demo.odemo.o ,,不输出警告信息:不输出警告信息:
arm-elf-as –W –o demo.o demo.s arm-elf-as –W –o demo.o demo.s
66.设置是否进行预处理.设置是否进行预处理arm-elf-asarm-elf-as 内部的预处理程序,完成以内部的预处理程序,完成以下工作:调整并删除多余空格,删除注下工作:调整并删除多余空格,删除注释,将字符常量改成对应的数值。释,将字符常量改成对应的数值。arm-elf-asarm-elf-as 不执行不执行 arm-elf-gccarm-elf-gcc 预处理预处理程序能完成的部分,如宏预处理和包含程序能完成的部分,如宏预处理和包含文件预处理。文件预处理。可以通过可以通过 .include “file”.include “file” 对指定文件进对指定文件进行预处理。行预处理。arm-elf-gccarm-elf-gcc 可以对后缀为可以对后缀为 .S.S 汇编程序汇编程序进行其他形式的预处理。 进行其他形式的预处理。
如果源文件第一行是#如果源文件第一行是# NO_APPNO_APP 或者或者编译时使用选项编译时使用选项 -f-f 将不进行预处理。将不进行预处理。如果要保留空格或注释,可以在需要如果要保留空格或注释,可以在需要保留部分开始加入#保留部分开始加入# APPAPP ,结束的地方,结束的地方加#加# NO_APPNO_APP 。。
例如例如 :: 编译编译 demo.sdemo.s 输出目标文件输出目标文件 demdemo.oo.o ,并且编译时不进行预处理,则命,并且编译时不进行预处理,则命令如下:令如下:arm-elf-as –f –o demo.o demo.s arm-elf-as –f –o demo.o demo.s
3.6 3.6 汇编语言编程汇编语言编程在在 ARMARM 汇编语言程序里汇编语言程序里 ,, 有一些特殊指令助有一些特殊指令助
记符记符 ,, 这些助记符与指令系统的助记符不同这些助记符与指令系统的助记符不同 ,,没有没有相对应的操作码相对应的操作码 ,, 通常称这些特殊指令助记符为通常称这些特殊指令助记符为伪伪指令指令 ,, 它们所完成的操作称为它们所完成的操作称为伪操作伪操作。。伪指令在源程序中的作用是为完成汇编程序作伪指令在源程序中的作用是为完成汇编程序作
各种准备工作的,这些伪指令仅在汇编过程中起作各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就完成了。用,一旦汇编结束,伪指令的使命就完成了。
在在 ARMARM 的汇编程序中,有如下几种伪指令:的汇编程序中,有如下几种伪指令:符号定义伪指令、数据定义伪指令、汇编控制伪指符号定义伪指令、数据定义伪指令、汇编控制伪指令、宏指令以及其他伪指令。令、宏指令以及其他伪指令。
3.6.1 3.6.1 汇编语言汇编语言1. 1. 基本元素基本元素(1) (1) 字符集字符集
汇编中使用下列字符组成源程序汇编中使用下列字符组成源程序的各种语法元素:大写字母 的各种语法元素:大写字母 A A ~ ~ ZZ;;小写字母 小写字母 a a ~ ~ zz ;数字 ;数字 0 0 ~ ~ 99;;符号 符号 + - * / = [ ] ( ) ; , . : ‘ @ $ & # < + - * / = [ ] ( ) ; , . : ‘ @ $ & # < > { } % _ “ \ - | ^ ? !> { } % _ “ \ - | ^ ? ! 。。 其中大小写字母作用不同。其中大小写字母作用不同。
(2) (2) 约定的名字约定的名字包括寄存器名、指令名字和伪包括寄存器名、指令名字和伪
操作符。每一个伪操作符表示一定操作符。每一个伪操作符表示一定功能的操作。功能的操作。伪操作的功能由汇编系统实现伪操作的功能由汇编系统实现 ,,
没有目标代码对应。这一点是伪操没有目标代码对应。这一点是伪操作符与操作符的作符与操作符的不同不同之处。伪操作之处。伪操作符是由汇编系统约定的名字,不用符是由汇编系统约定的名字,不用定义就能实现。定义就能实现。
伪操作符可以分为六类: 伪操作符可以分为六类: 1.1. 数据定义伪操作符数据定义伪操作符2.2. 符号定义伪操作符符号定义伪操作符3.3. 程序结构伪操作符程序结构伪操作符4.4. 条件汇编伪操作符条件汇编伪操作符5.5. 宏伪操作符宏伪操作符6.6. 其他伪操作符其他伪操作符
(3) (3) 定义的名字定义的名字汇编程序中的标号、分段名、汇编程序中的标号、分段名、
宏定义名都是用户可以定义的名字。宏定义名都是用户可以定义的名字。
① ① 标号标号标号只能由标号只能由 a a ~ ~ z z 、、 A A ~ ~ ZZ 、、
0 0 ~ ~ 99 、.、、.、 __ 等字符组成,标号等字符组成,标号的长度不受限制的长度不受限制 ,, 大小写字母有区大小写字母有区别。别。
当标号为当标号为 0 0 ~ ~ 99 的单个数字时表示该的单个数字时表示该标号为局部标号标号为局部标号 , , 局部标号可以多次重复出局部标号可以多次重复出现。现。
在引用时,使用方法如下(在引用时,使用方法如下( NN 代表代表 00 ~ ~ 99 的数字):的数字):
Nf ——Nf —— 在引用处的地方向前(程序地在引用处的地方向前(程序地址增长的方向)的址增长的方向)的 NN 标号。标号。
Nb ——Nb —— 在引用处的地方向后(程序地在引用处的地方向后(程序地址增长的方向)的址增长的方向)的 NN 标号。标号。
标号在最终的绝对定位的代码中表示所标号在最终的绝对定位的代码中表示所在处的地址,因此在汇编中的标号可以在在处的地址,因此在汇编中的标号可以在 CC/C++/C++ 程序中当作变量或者函数来使用。程序中当作变量或者函数来使用。
② ② 分段名分段名汇编系统中预定义的分段名有汇编系统中预定义的分段名有 : .text : .text
.bss .data .sdata .sbss .bss .data .sdata .sbss 等,但是用户可等,但是用户可以自己定义段名,语法如下:以自己定义段名,语法如下:
.section section_name attribute.section section_name attribute
例如:定义一个可以执行的代码段 例如:定义一个可以执行的代码段 .mytext.mytext
.section ".mytext","ax".section ".mytext","ax"
mycodemycode
……
③ ③ 宏定义名宏定义名宏定义的语法如下:宏定义的语法如下:
.macro.macro macro_name parm1 … parmN macro_name parm1 … parmN
macro bodymacro body
.endm.endm
(4) (4) 常数常数二进制数由二进制数由 0b0b 或者或者 0B0B 开头,如:开头,如: 0b10000b1000
101101 、、 0B10011100B1001110;;十六进制数以十六进制数以 0x0x 或者或者 0X0X 开头,如:开头,如: 0x450x45
6767 、、 0X100890X10089;;八进制数由八进制数由 00 开头,如:开头,如: 03450345 、、 0987009870;;十进制数以非零数开头,如:十进制数以非零数开头,如: 345345 、、 1298012980
例如:例如:.byte 74, 0112, 092, 0x4A, 0X4a, 'J, '\J .byte 74, 0112, 092, 0x4A, 0X4a, 'J, '\J ..
ascii "Ring the bell\7"ascii "Ring the bell\7"
(5) (5) 当前地址数当前地址数
当前的地址数用点号“当前的地址数用点号“ .”.”表示,在汇编程序中可以直接表示,在汇编程序中可以直接使用该符号。使用该符号。
(6) (6) 表达式表达式在汇编程序中可以使用表达式,在在汇编程序中可以使用表达式,在
表达式中可以使用常数和数值。表达式中可以使用常数和数值。可以使用的运算符有:可以使用的运算符有:① ① 前缀运算符号前缀运算符号- —— - —— 取负数取负数~ —— ~ —— 取补数取补数② ② 中缀运算符号中缀运算符号* / % < << > >> | & ^ * / % < << > >> | & ^ !! + -+ -
(7) (7) 注释符号注释符号
不同芯片的汇编程序中,注释不同芯片的汇编程序中,注释的符号有所不同,ARM以“的符号有所不同,ARM以“@”@”开头的程序行是注释行。开头的程序行是注释行。
2. 2. 语句语句(1) (1) 语句类型语句类型汇编语句按其作用和编译的情况分为两汇编语句按其作用和编译的情况分为两
大类:大类:执行性语句执行性语句和和说明性语句说明性语句。。执行性语句执行性语句是在编译后有目标程序与之是在编译后有目标程序与之
对应,按其编译后目标程序的对应情况又可对应,按其编译后目标程序的对应情况又可以分为:以分为:一般执行性语句一般执行性语句和和宏语句宏语句。。
一般执行性语句一般执行性语句与目标程序是一一对应与目标程序是一一对应的,即一个一般执行语句只产生一条目标代的,即一个一般执行语句只产生一条目标代码指令。码指令。
宏语句宏语句由伪操作符定义,包括宏定义、由伪操作符定义,包括宏定义、宏调用及宏扩展语句。一个宏语句对应了一宏调用及宏扩展语句。一个宏语句对应了一组目标代码程序,可以看成是一般执行性语组目标代码程序,可以看成是一般执行性语句的扩展。 句的扩展。
说明性语句说明性语句由伪操作符定义,它用于用户以源由伪操作符定义,它用于用户以源程序方式和汇编程序通信。程序方式和汇编程序通信。
用户使用说明性语句表示源程序的终止说明、用户使用说明性语句表示源程序的终止说明、分段定义、数据定义、内存结构等信息。分段定义、数据定义、内存结构等信息。
数据定义语句用于描述数据和给数据赋初值数据定义语句用于描述数据和给数据赋初值 ..列表控制语句用于说明源程序的格式要求。列表控制语句用于说明源程序的格式要求。程序结构语句用于说明源程序的结构和目标程序结构语句用于说明源程序的结构和目标
程序的结构。程序的结构。条件汇编语句用于说明汇编某部分语句时的条件汇编语句用于说明汇编某部分语句时的
条件,满足条件则编译条件,满足条件则编译 ,,否则跳过这部分不予编译否则跳过这部分不予编译 ..
(2) (2) 数据语句数据语句 一字节数据定义语句一字节数据定义语句语法:语法: .byte expressions.byte expressions
例子:例子: .byte 0x89 ,0x45, 56, ‘K , ‘M , 023, 0B101011.byte 0x89 ,0x45, 56, ‘K , ‘M , 023, 0B101011 两字节数据定义语句两字节数据定义语句语法: 语法: .short expressions.short expressions
例子: 例子: .short 0x6789 ,0b101110111 .short 0x6789 ,0b101110111 四字节数据定义语句四字节数据定义语句语法:语法: .long expressions.long expressions
例子:例子: .long 0x78896676 , 02356243563456 .long 0x78896676 , 02356243563456 八字节数据定义八字节数据定义语法: 语法: .quad expressions.quad expressions
例子: 例子: .quad 0x1122334455667788.quad 0x1122334455667788
单个字串定义单个字串定义语法:语法: .string “ string”.string “ string”例子:例子: .string “this is an example”.string “this is an example”
多个字串多个字串 11语法:语法: .ascii “string”…...ascii “string”…..例子:例子: .ascii “string1” ,“string2”,“string3”.ascii “string1” ,“string2”,“string3”string1,string2,string3string1,string2,string3 字串间是连续的。字串间是连续的。
多个字串多个字串 22语法:语法: .asciz “string”…...asciz “string”…..例子:例子: .asciz “string1” ,“string2”,“string3”.asciz “string1” ,“string2”,“string3”
⑧ ⑧ 重复数据定义重复数据定义语法:语法: .rept count.rept count
数据定义数据定义 .endr.endr
例子:例子:.rept .rept 2 等价于: 2 等价于: .long 0x788.long 0x788 .byte 0x99.byte 0x99.long 0x788 .long 0x788.long 0x788 .long 0x788
.byte 0x99 .byte 0x99.byte 0x99 .byte 0x99
.endr.endr
各种数据在内存空间的对齐边界示按各种数据在内存空间的对齐边界示按数据本身的大小对齐的,数据本身的大小对齐的, bytebyte 以1字节以1字节对齐,对齐, shortshort 以2字节对齐,以2字节对齐, longlong 以4以4字节对齐,字节对齐,
例如例如 ::
.byte .byte 0x780x78
.short.short 0x660x66
.long.long 0x7890x789
一共占用8个字节而不是7个字节。一共占用8个字节而不是7个字节。
(3) (3) 列表控制语句列表控制语句①①.title “heading”.title “heading”
在汇编列表中将“在汇编列表中将“ heading“heading“ 作作为标题。为标题。
②②.list.list
系统遇此语句就输出列表文件。系统遇此语句就输出列表文件。
(( 44 )一般执行语句)一般执行语句
不同的芯片有不同指令集,不同的芯片有不同指令集,见相关的指令手册。 见相关的指令手册。
3. 3. 程序结构程序结构(( 11 )程序结构语句)程序结构语句程序结构语句程序结构语句是伪操作符定义的说明是伪操作符定义的说明
语句,用于说明程序段的开始、结束以及语句,用于说明程序段的开始、结束以及源程序的结束等。源程序的结束等。
在汇编系统中有预定义的程序结构语在汇编系统中有预定义的程序结构语句,用户也可以自己定义一些程序结构段句,用户也可以自己定义一些程序结构段(详见(详见 3.3.33.3.3 中关于中关于 .section.section 的说明)。的说明)。
预定义的程序结构语句如下(以下用预定义的程序结构语句如下(以下用ARMARM 的汇编指令举例说明):的汇编指令举例说明):
② ② 数据段的开始数据段的开始.data .data 表示一个数据段的开始,其它段的结束:表示一个数据段的开始,其它段的结束:.data.data.long .long 0xFFFFFC04,0x0F0CFC04,0x0FFFF804,0x01BF70040xFFFFFC04,0x0F0CFC04,0x0FFFF804,0x01BF7004
.long .long 0x0FFDD000,0x1FFFF447,0x0FFFFC04,0x1FFFFC070x0FFDD000,0x1FFFF447,0x0FFFFC04,0x1FFFFC07
.bss.bss 表示未初始化数据段的开始,其它段的结束:表示未初始化数据段的开始,其它段的结束:
.bss.bss
.long 0,0,0.long 0,0,0
.long 0,0,0 .long 0,0,0
③ ③ 源程序的结束源程序的结束
.end .end 表示该源程序的结束,表示该源程序的结束,在在 .end.end 后面的程序不会被编译。 后面的程序不会被编译。
(2) (2) 过程(函数)的定义过程(函数)的定义过程的结构如下:过程的结构如下:
过程名:过程名:过程体过程体返回语句返回语句
例如(用例如(用 ARMARM 的汇编指令举例的汇编指令举例说明):说明):
.align 2.align 2
.globl uart1_sendch.globl uart1_sendch
.type uart1_sendch,@function.type uart1_sendch,@functionuart1_sendch:uart1_sendch:ldrldr r2,=SYSFLGr2,=SYSFLG
1:1:ldrldr r1,[r2]r1,[r2]tsttst r1,#UTXFF1r1,#UTXFF1bnebne 1b1bldrldr r2,=UARTDR1r2,=UARTDR1strbstrb r0,[r2]r0,[r2]movmov pc,lrpc,lr一般情况下,一般情况下, .type .type 和和 .align.align声明可以缺省。声明可以缺省。
3.6.2 3.6.2 宏语句与条件汇编宏语句与条件汇编
1. 1. 等价语句等价语句(1) .equ(1) .equ 语句语句
语法: 语法: .equ symbol.equ symbol ,, expressionexpression例子: 例子: .equ PPC_PC.equ PPC_PC ,, 32*432*4应用: 应用: stwstw r4r4 ,, PPC_PC(r1) PPC_PC(r1)
(2) .set(2) .set 语句语句与与 .equ.equ 的功能相同的功能相同。。
2. 2. 宏定义与宏调用宏定义与宏调用
宏定义:宏定义:.macro macro_name param1 .macro macro_name param1 ,, parapara
m2m2 ,…,… ..paramN..paramN
.macro body.macro body
.endm.endm
例如:(使用例如:(使用 ARMARM 的汇编指令集说明)的汇编指令集说明).macro ROMSEC_patova.macro ROMSEC_patova TTPATTPA ,, pp
a_starta_start ,, va_startva_start ,, tmptmp ,, icic
ldrldr \tmp\tmp ,, =APFIELD_ROM=APFIELD_ROMaddadd \TTPA,\TTPA,\va_start,LSR #18\TTPA,\TTPA,\va_start,LSR #18addadd \tmp\tmp ,, \tmp\tmp ,, \pa_start\pa_start
20:20:strstr \tmp\tmp ,, [\TTPA][\TTPA] ,, #4#4addadd \tmp\tmp ,, \tmp\tmp ,, #0x10000#0x10000subssubs \ic\ic ,, \ic\ic ,, #1#1bne bne 20b20b
.endm.endm
例如:例如:.macro rept3.macro rept3.long 0x9000000.long 0x9000000.long 0x9000000.long 0x9000000.long 0x9000000.long 0x9000000.endm.endm应用:应用:.data.datarept3rept3等价于等价于.data.data.long 0x9000000.long 0x9000000.long 0x9000000.long 0x9000000.long 0x9000000 .long 0x9000000
当宏定义有参数时,在参数前面添加前当宏定义有参数时,在参数前面添加前缀“缀“ \” \” 。如果要提前退出宏可以使用。如果要提前退出宏可以使用 .exitm .exitm 。宏定义中的参数还可以有缺省值,如:。宏定义中的参数还可以有缺省值,如:
.macro test p1=0x100 p2 .macro test p1=0x100 p2
.long \p1.long \p1
.long \p2.long \p2
.endm.endm应用:应用:
test test , , 2 2 等价于等价于
.long 0x100.long 0x100
.long 2.long 2
3. 3. 重复块和源文件的嵌入重复块和源文件的嵌入(1) (1) 重复块重复块定义:定义:
.rept count.rept countcontentscontents.endr.endr
例如:例如:.rept 2.rept 2.long 0x12908.long 0x12908.endr.endr
等价于:等价于:.long 0x12908.long 0x12908.long 0x12908.long 0x12908
(2) (2) 源文件的嵌入源文件的嵌入
在一个汇编文件中可以嵌入其在一个汇编文件中可以嵌入其它汇编文件,例如汇编头文件等。它汇编文件,例如汇编头文件等。
方法如下:方法如下:.include “filename”.include “filename”
4 4 条件汇编条件汇编.if expression .if expression 表达式为非零则编译后面的语句,否则后面的语句表达式为非零则编译后面的语句,否则后面的语句
被忽略。被忽略。.ifdef symbol.ifdef symbol如果符号被定义则编译后面的语句,否则后面的语如果符号被定义则编译后面的语句,否则后面的语
句被忽略。句被忽略。.ifndef symbol.ifndef symbol如果符号未被定义则编译后面的语句,否则后面的如果符号未被定义则编译后面的语句,否则后面的
语句被忽略。语句被忽略。.else.else表示与前面的表示与前面的 ifif 语句的条件相反。语句的条件相反。.endif.endif表示条件判断结束。表示条件判断结束。
例如:例如:.macro sum from=0, to=5.macro sum from=0, to=5 .long \from.long \from .if \to - \from.if \to - \from sum “(\from+1)”,\tosum “(\from+1)”,\to .endif.endif.endm.endm
应用:应用:sumsum ,, 55
等价于等价于.long 0.long 0………… .long 5.long 5条件判断可以嵌套使用,条件判断可以嵌套使用, if-else-endifif-else-endif 遵循遵循
最近匹配的原则。最近匹配的原则。
3.6.3 3.6.3 模块化程序设计模块化程序设计
模块化程序设计汇编语言程序可模块化程序设计汇编语言程序可以先按模块独立汇编,然后和应用以先按模块独立汇编,然后和应用的其他模块链接形成一个可执行的的其他模块链接形成一个可执行的程序 程序
11.全局符号.全局符号在模块中定义的、要被别的模块使在模块中定义的、要被别的模块使
用的符号(包括变量名和函数名)都必用的符号(包括变量名和函数名)都必须被声明为全局符号。须被声明为全局符号。
方法如下:方法如下:.global symbol.global symbol
在本模块中要使用其他模块中的全在本模块中要使用其他模块中的全局符号,可以用局符号,可以用 .extern symbol.extern symbol 的方式的方式声明,但也可以不用声明在汇编时自动声明,但也可以不用声明在汇编时自动认为它是其它模块中的全局符号。认为它是其它模块中的全局符号。
22.模块间的符号互用.模块间的符号互用(( 11 )汇编模块与汇编模块间的调用)汇编模块与汇编模块间的调用
只要是全局符号在汇编模块间就只要是全局符号在汇编模块间就可以直接使用。可以直接使用。(( 22 )汇编模块调用)汇编模块调用 CC 语言模块中语言模块中的函数的函数
汇编模块调用汇编模块调用 CC 语言模块时,不语言模块时,不同芯片传递参数的方式有差别,详细同芯片传递参数的方式有差别,详细见见 3.6.53.6.5节。节。
(( 33 )汇编模块使用)汇编模块使用 CC 语言模块语言模块中的变量中的变量
首先保证该变量在首先保证该变量在 CC 语言中是全局语言中是全局变量,然后在汇编中直接使用变量名。变量,然后在汇编中直接使用变量名。
注意:注意: CC 语言中的变量名在汇编中语言中的变量名在汇编中不用加下划线。另外该变量名不能用不用加下划线。另外该变量名不能用 ststaticatic修饰,否则该变量只局限于所在的修饰,否则该变量只局限于所在的模块有效。 模块有效。
(( 44 )) CC 语言模块调用汇编模块中的函数语言模块调用汇编模块中的函数该函数名在汇编程序中必须是全局的该函数名在汇编程序中必须是全局的
符号,即必须用符号,即必须用 .global.global声明,然后在声明,然后在 CC语言中申明该函数的原型,最后在使用时语言中申明该函数的原型,最后在使用时与一般的与一般的 CC 函数一样。函数一样。
(( 55 )) CC 语言模块使用汇编模块中的变量语言模块使用汇编模块中的变量该变量在汇编程序中必须是全局的符该变量在汇编程序中必须是全局的符
号,即必须用号,即必须用 .global.global声明,然后在声明,然后在 CC 语语言中申明该变量的原型,最后在使用时与言中申明该变量的原型,最后在使用时与一般的一般的 CC变量一样。 变量一样。
3.6.4 3.6.4 内存模式内存模式
在在 uClinuxuClinux 环境下,内存模式为平环境下,内存模式为平模式,即整个内存空间最大为模式,即整个内存空间最大为 4GB4GB 。。
所有任务共享这所有任务共享这 4GB4GB 的空间,而不的空间,而不是每个任务有单独的是每个任务有单独的 4G4G虚拟空间。虚拟空间。
所有的寻址都是所有的寻址都是 3232 位地址的方式,位地址的方式,因此程序模块间可以很容易的共享变量因此程序模块间可以很容易的共享变量和数据。和数据。
3.6.5 3.6.5 StrongARM & ARM7 StrongARM & ARM7
1.1.寄存器名字寄存器名字寄存器名字如表寄存器名字如表 3-13-1 所示。 所示。
类型 说 明R0~ r14 通用寄存器F0~ f7 浮点寄存器Pc 指令指针Ps 机器状态寄存器fps 浮点状态寄存器
2. 2. 如何在汇编模块中调用如何在汇编模块中调用 CC 语言模语言模块中的函数块中的函数
在调用在调用 CC 函数之前,必须在当前栈中空出函数之前,必须在当前栈中空出至少至少 88 个字节的空间,然后才调用个字节的空间,然后才调用 CC 函数。函数。
CC 函数的第一个参数(最左边的参数)用函数的第一个参数(最左边的参数)用 r0r0传递,后面的参数依次用传递,后面的参数依次用 r1r1 、、 r2r2 等来传递。等来传递。例如:例如:假定假定 CC 函数为函数为 int get_sum (int var1,int vaint get_sum (int var1,int va
r2),r2), 则在汇编程序中首先将参数送到则在汇编程序中首先将参数送到 r0r0 、、 r1r1 中,中,然后将栈指针减然后将栈指针减 88 ,最后调用,最后调用 get_sumget_sum 。。注意注意 :C:C 函数名在汇编中使用时不用加下划函数名在汇编中使用时不用加下划
线线
33.注释符号.注释符号以“以“@”@” 开头的程序行是注释行。开头的程序行是注释行。
44.一段程序.一段程序在下面的程序中有在下面的程序中有 .data .bss .text.data .bss .text 等三个预定等三个预定义的段,在程序的后面定义了一个用户自己的段义的段,在程序的后面定义了一个用户自己的段 ..mytextmytext ,属性为可执行段。,属性为可执行段。在程序中还用在程序中还用 .global .global 声明了几个本文件中的符声明了几个本文件中的符号为全局符号号为全局符号 ,, 在其他模块中可以使用这些符号在其他模块中可以使用这些符号(( var1var1作为变量作为变量 ,u1b_set,u1b_set 作为函数使用)作为函数使用)
.title “example”.title “example”
.data.data
..globalglobal var1 var1
var1:var1:.long 0x897678 ,0x2378789.long 0x897678 ,0x2378789.byte 89 ,56, 23.byte 89 ,56, 23.string “ hello”.string “ hello”.bss.bss.global zero_var.global zero_varzero_var:zero_var:.short 0,0,0.short 0,0,0.long 0,0,0,0.long 0,0,0,0
.text.text
UART1INIT_TEST:UART1INIT_TEST:
ldr r3,=SYSCON1ldr r3,=SYSCON1
ldr r0,[r3]ldr r0,[r3]
tst r0,#UART1ENtst r0,#UART1EN
beq 2fbeq 2f
1:1:
ldr r1,[r3]ldr r1,[r3]
tst r1,#UTXFF1tst r1,#UTXFF1
bne 1bbne 1b
2:2:bic r0,r0,#UART1ENbic r0,r0,#UART1ENstr r0,[r3]str r0,[r3]bic r0,r0,#SIRENbic r0,r0,#SIRENstr r0,[r3]str r0,[r3]orr r0,r0,#UART1ENorr r0,r0,#UART1ENstr r0,[r3]str r0,[r3]ldr r3,=SYSFLG2ldr r3,=SYSFLG2ldr r0,[r3]ldr r0,[r3]and r0,r0,#0x40and r0,r0,#0x40mov pc,lrmov pc,lr
.section “.mytext” , “ax”.section “.mytext” , “ax”
.global u1b_set.global u1b_set
u1b_set:u1b_set:
ldr r3,=UBLCR1ldr r3,=UBLCR1
str r0,[r3]str r0,[r3]
mov pc,lrmov pc,lr
.end.end
3.7 3.7 简单程序设计简单程序设计3.7.1 3.7.1 顺序程序设计顺序程序设计
例例 3-1 3-1 用用 ARMARM 指令实现的指令实现的 CC 赋值语句:赋值语句:x=(a+b)-cx=(a+b)-c
可以用可以用 r0r0 表示表示 aa 、、 rlrl 表示表示 bb 、、 r2r2 表示表示 cc和和 r3r3 表示表示 xx ,用,用 r4r4作为间接寻址寄存器。作为间接寻址寄存器。
在进行算术运算之前,代码必须先把在进行算术运算之前,代码必须先把 aa 、、bb 、、 cc 的值装入到寄存器,运算结束后,还的值装入到寄存器,运算结束后,还要把要把 xx 的值存回存储器中。的值存回存储器中。
这段代码执行下面这些必须的步骤这段代码执行下面这些必须的步骤::
ADR r4,aADR r4,a ;读取变量 ;读取变量 a a 的地址的地址LDR r0,[r4]LDR r0,[r4] ;读;读 aa 的内容到 的内容到 r0r0ADR r4,bADR r4,b ;读取变量;读取变量 bb 的地址的地址LDR rl,[r4] LDR rl,[r4] ;读;读 bb 内容到 内容到 r1r1ADD r3,r0,rlADD r3,r0,rl ;; a+b a+b 的结果保存在的结果保存在 r3r3ADR r4,cADR r4,c ;读取变量;读取变量 cc 的地址的地址LDR r2,[r4]LDR r2,[r4] ;读;读 cc 的内容到的内容到 r2r2SUB r3,r3,r2SUB r3,r3,r2 ;; (a+b)-c(a+b)-c 结果保存到结果保存到 r3r3ADR r4,xADR r4,x ;读;读 xx 的地址的地址STR r3,[r4] STR r3,[r4] ;保存变量;保存变量 xx
例例 3-2 3-2 用用 ARMARM指令实现的指令实现的 CC赋值语句:赋值语句:z=(a<<2)|(b&15)z=(a<<2)|(b&15)
可以使用可以使用 r0r0 表示表示 aa 和和 zz ,, r1r1 表示表示 bb ,, r4r4 表示地址表示地址进行编码,代码如下:进行编码,代码如下: ADR r4,a ADR r4,a ;读取变量;读取变量 aa 的地址到的地址到 r4r4
LDR r0,[r4] LDR r0,[r4] ;读;读 aa 的内容到的内容到 r0r0
MOV r0,r0,LSL 2 MOV r0,r0,LSL 2 ;实现;实现 a<<2 a<<2 操作,结果保存在操作,结果保存在 r0r0
ADR r4,b ADR r4,b ;读取变量;读取变量 bb 的地址到的地址到 r4r4
LDR rl,[r4] LDR rl,[r4] ;读;读 bb 的内容到的内容到 r1r1
AND r1,r1,#15 AND r1,r1,#15 ;实现;实现 b&15 b&15 操作,结果保存在操作,结果保存在 r1r1 中中 ORR rl,r0,rl ORR rl,r0,rl ;计算;计算 zz 的结果的结果 ADR r4,z ADR r4,z ;读取变量;读取变量 zz 的地址到的地址到 r4r4
STR rl,[r4] STR rl,[r4] ;保存变量;保存变量 z z
3.7.2 3.7.2 分支程序设计分支程序设计我们用示例我们用示例 3-33-3作为探讨条件执行的用法的方法。作为探讨条件执行的用法的方法。
例例 3-3 3-3 在在 ARMARM 中实现下面中实现下面 ifif 语句:语句: if(a<b){if(a<b){
x=5x=5;; y=c+dy=c+d :: }}
else x=c-delse x=c-d;;实现上述指令的第一种方法比较传统并且和其实现上述指令的第一种方法比较传统并且和其
他微处理器相似。下列指令使用条件分支和无条件数他微处理器相似。下列指令使用条件分支和无条件数据操作:据操作:
ADR r4,a ADR r4,a ;读取变量;读取变量 aa 的地址到的地址到 r4 r4
LDR r0,[r4] LDR r0,[r4] ;读;读 aa 的内容到的内容到 r0r0
ADR r4,b ADR r4,b ;读取变量;读取变量 bb 的地址到的地址到 r4 r4
LDR rl,[r4] LDR rl,[r4] ;读;读 bb 的内容到的内容到 r1r1
CMP r0CMP r0 ,, rl rl ;比较 ;比较 a,ba,b
BGE fblock BGE fblock ;如果 ;如果 a>=b,a>=b,跳转到 跳转到 fblockfblock子程序执子程序执行行
MOV r0,#5 MOV r0,#5 ;令;令 x = 5x = 5
ADR r4,x ADR r4,x ;读取;读取 xx 的地址到的地址到 r4r4
STR r0,[r4] STR r0,[r4] ;保存变量;保存变量 xx
ADR r4,c ADR r4,c ;读取变量;读取变量 cc 的地址到的地址到 r4r4
LDR r0,[r4] LDR r0,[r4] ;读;读 c c 的内容到的内容到 r0r0
ADR r4,d ADR r4,d ;读取变量;读取变量 dd 的地址到的地址到 r4 r4
LDR rl,[r4] LDR rl,[r4] ;读取;读取 dd 的内容到的内容到 r1r1 ADD r0,r0,rl ADD r0,r0,rl ;计算;计算 a+ba+b ,结果保存在,结果保存在 r0r0 ADR r4,y ADR r4,y ;读取变量;读取变量 yy 的地址的地址 STR r0,[r4] STR r0,[r4] ;结果保存在;结果保存在 yy 中中 B after B after ;程序跳转到;程序跳转到 after after 子块子块fblock: ADR r4,c fblock: ADR r4,c ;读取变量;读取变量 cc 的地址的地址 LDR r0,[r4] LDR r0,[r4] ;读;读 cc 的内容到的内容到 r0 r0 ADR r4,d ADR r4,d ;读取变量;读取变量 dd 的地址到的地址到 r4r4 LDR rl,[r4] LDR rl,[r4] ;读变量;读变量 dd 的内容到的内容到 r1r1 SUB r0,r0,rl SUB r0,r0,rl ;计算;计算 a – b a – b 结果保存在结果保存在 r0r0 ADR r4,x ADR r4,x ;读取变量;读取变量 xx 的地址的地址 STR r0,[r4] STR r0,[r4] ;结果保存在 ;结果保存在 xx 中中after: …after: …
例例 3-4 3-4 在在 ARMARM 中实现中实现 CC 的的 switchswitch 语句 语句 CC 中的中的 switchswitch 语句采用下列形式:语句采用下列形式: switch(test){switch(test){
case 0case 0 :...:... breakbreak;; case 1case 1 :...:... breakbreak;; }}
上述语句也可以像上述语句也可以像 ifif 语句那样编码,首语句那样编码,首先测试先测试 test=Atest=A ,然后测试,然后测试 test=Btest=B ,依此类推,依此类推 ..
用基址加偏移量寻址并建立分支表的方法用基址加偏移量寻址并建立分支表的方法执行起来会更有效地实现: 执行起来会更有效地实现:
ADR r2,test ;ADR r2,test ; 读取变量读取变量 testtest 的地址的地址 LDR r0LDR r0 ,, [r2] ;[r2] ; 读读 testtest 的内容到的内容到 r0r0
ADR rl,switchtab ;ADR rl,switchtab ; 读取读取 switchtab switchtab 的地址到的地址到 rr11
LDR rl5,[rl,r0,LSL #2] LDR rl5,[rl,r0,LSL #2]
switchtab:switchtab:
.word case 0 .word case 0
.word case l.word case l
……case0case0 : … : … @code for case 0@code for case 0
caselcasel : … : … @code for case 1@code for case 1
这种实现方法使用这种实现方法使用 testtest值作为一个表的偏移量,其值作为一个表的偏移量,其中该表保存了实现各种情况的代码段的地址。中该表保存了实现各种情况的代码段的地址。
这段代码的核心就是这段代码的核心就是 LDRLDR指令,它把多种功能集中指令,它把多种功能集中在一个简单的指令里:在一个简单的指令里:
它将它将 r0r0 的值左移两位,把偏移量转化为字地址。的值左移两位,把偏移量转化为字地址。它用基址加偏移量寻址的方法把左移了的它用基址加偏移量寻址的方法把左移了的 testtest 的值的值
(( 存放在存放在 r0r0 中中 ))加到保存在加到保存在 r1r1 中的表的基址中。中的表的基址中。它将该指令计算出的新地址置为程序计数器它将该指令计算出的新地址置为程序计数器 (r15)(r15)
的值。的值。每一个每一个 casecase 都由存放在存储器某处的一段代码实现。都由存放在存储器某处的一段代码实现。
分支表从名为分支表从名为 switchtabswitchtab 的单元开始。的单元开始。wordword 语句是在该处装入一个语句是在该处装入一个 3232 位地址到存储器的位地址到存储器的
一种方法,因此分支表包含了对应于各个一种方法,因此分支表包含了对应于各个 casecase 的代码段的代码段起点的地址。起点的地址。
3.7.33.7.3 循环程序设计 循环程序设计 循环是非常通用的循环是非常通用的 CC 语句。语句。循环能用条件分支自然地实现,因为循环总是循环能用条件分支自然地实现,因为循环总是
对存在数组中的值进行操作,循环也是对基址加偏对存在数组中的值进行操作,循环也是对基址加偏移量寻址模式的另一种用法较好的说明。移量寻址模式的另一种用法较好的说明。
例例 3-4 3-4 用用 ARMARM指令实现指令实现 FIRFIR 过滤器过滤器FIR(finite impulser response)FIR(finite impulser response) 过滤器是一种过滤器是一种
处理信号的常用方法;处理信号的常用方法;FIRFIR 过滤器是简单的对积求和:过滤器是简单的对积求和: ∑ ∑ cixicixi 1≤i≤n1≤i≤n作为过滤器使用时,作为过滤器使用时, xixi 假定为周期性采集的数假定为周期性采集的数
据样品,据样品, cici 是系数。是系数。这个计算总是按如下方式进行: 这个计算总是按如下方式进行:
这种表示假定样品是周期性采集而来的,每次一个新的样品到来都要重新计算一次 FIR 过滤器的输出。
△方框表示存储刚刚到来的样品产生 xi 时延元素。
延迟的样品分别单独与 c 相乘 , 然后求和得到过滤器的输出
Δ Δ Δ Δ
Σ f
c1c2 c3 c4
x1 x2 x3 x4
FIRFIR 过滤器的过滤器的 CC 语言代码如下:语言代码如下: for(i=0for(i=0 ,, f=0;i<n;i++)f=0;i<n;i++)
f=f+c[i]*x[i];f=f+c[i]*x[i];
我们可以根据基址加偏移量寻址我们可以根据基址加偏移量寻址法对数组法对数组 cc 和和 xx 进行编址。将每个进行编址。将每个数组的第零元素的地址数组的第零元素的地址 ,, 装入一个寄装入一个寄存器存器 ,, 存放存放 ii 的寄存器则用作偏移量。的寄存器则用作偏移量。
下面就是该循环的代码:下面就是该循环的代码: MOV r0,#0 MOV r0,#0 ;使用;使用 r0r0作为计数器作为计数器 ii ,置初,置初值 值 ;为;为 00 MOV r8,#0 MOV r8,#0 ;使用;使用 r8r8作为字节偏移量,作为字节偏移量,置置 ;初值为;初值为 00 ADR r2,n ADR r2,n ;读取;读取 nn 的地址到的地址到 r2r2 LDR rlLDR rl ,, [r2][r2] ;读;读 nn 的值到的值到 r1r1 MOV r2,#0MOV r2,#0 ;使用;使用 r2r2作为 作为 ff ,置初值为 ,置初值为 00 ADR r3,cADR r3,c ;读取;读取 cc 的地址到的地址到 r3 r3 作为作为 c[i]c[i]数数 ;组的首地址;组的首地址 ADR r5,xADR r5,x ;读取;读取 xx 的地址到的地址到 r5r5 ,作为,作为 xx[i][i] ;数组的首地址 ;数组的首地址
l oopl oop : : LDR r4LDR r4 ,, [r3,r8] [r3,r8] ;读取 ;读取 c[i] c[i] 的值到的值到 r4 r4
LDR r6,[r5,r8]LDR r6,[r5,r8] ;读取;读取 x[i] x[i] 的值到的值到 r6r6
MUL r4,r4,r6MUL r4,r4,r6 ;计算 ;计算 c[i]*s[i]c[i]*s[i] ,,;结果保存到;结果保存到 r4r4
ADD r2,r2,r4ADD r2,r2,r4 ;求和送给;求和送给 ff
;修改循环计数器和数组下标;修改循环计数器和数组下标 ADD r8,r8,#4ADD r8,r8,#4 ;偏移量增加;偏移量增加 3232 位位 ADD r0,r0,#1ADD r0,r0,#1 ;; i++i++
;测试推出循环条件 ;测试推出循环条件 CMP r0,rlCMP r0,rl
BLT LoopBLT Loop ;; if i<Nif i<N ,继续循环 ,继续循环 looploop
loop end…loop end…
不论该代码是用不论该代码是用 CC 语言还是用汇编语言语言还是用汇编语言编写,我们都要注意代码中的数值精确度。编写,我们都要注意代码中的数值精确度。
3232 位位 X 32X 32 位的乘法得到位的乘法得到 6464 位的结果。位的结果。ARMARM 的的 MULMUL 指令把结果的低指令把结果的低 3232 位保存到目位保存到目的寄存器中。只要结果不超过的寄存器中。只要结果不超过 3232 位,就能得位,就能得到所需结果。到所需结果。
如果输入值正是可能有时超过如果输入值正是可能有时超过 3232 位,我位,我们就要重新设计代码来计算高分辨率的值。们就要重新设计代码来计算高分辨率的值。
3.7.43.7.4 子程序设计子程序设计 CC 语言的另一重要类是函数。语言的另一重要类是函数。每个每个 CC 函数返回一个值函数返回一个值 ((除非它的返回类型是除非它的返回类型是
void)void);一般把不返回值的结构称为;一般把不返回值的结构称为子例程子例程或或过程过程。。考虑下面这个考虑下面这个 CC 函数的简单用法:函数的简单用法: x=a+bx=a+b;; foo(x)foo(x);; y=c-dy=c-d;;当函数被调用后马上返回到调用代码中,在上当函数被调用后马上返回到调用代码中,在上
例中就是返回到对例中就是返回到对 yy赋值的语句。一个简单的分支赋值的语句。一个简单的分支是不够的,因为我们不知要返回到哪儿。要想正确是不够的,因为我们不知要返回到哪儿。要想正确返回,就要在调用函数或过程时保存返回,就要在调用函数或过程时保存 PCPC 的值,当的值,当调用过程结束时,将调用过程结束时,将 PCPC 设置到下条指令的地址。设置到下条指令的地址。
分支链接指令在分支链接指令在 ARMARM 中用于过程调用。中用于过程调用。这样,例如:这样,例如: BL fooBL foo将执行一个分支并链接到从定位点将执行一个分支并链接到从定位点 foofoo 开始的代码开始的代码
(( 用相对用相对 PCPC寻址的方式寻址的方式 )) 。分支链接其实和分支很相。分支链接其实和分支很相似,只不过是在分支前将当前似,只不过是在分支前将当前 PCPC 的值存在的值存在 r14r14 中。过中。过程返回时,将程返回时,将 r14r14 中的值移入中的值移入 r15r15 中即可:中即可:
MOV r15MOV r15 ,, r14r14当然在调用过程中不能覆盖保存在当然在调用过程中不能覆盖保存在 r14r14 中的中的 PCPC值。值。
但是这种机制只能调用一层过程。但是这种机制只能调用一层过程。例如例如 ::如果我们在另一个如果我们在另一个 CC 函数中调用一个函数中调用一个 CC 函数,第函数,第
二个函数调用将会覆盖二个函数调用将会覆盖 r14r14 ,破坏第一个调用函数的返,破坏第一个调用函数的返回地址。回地址。
允许嵌套过程调用允许嵌套过程调用 (( 包括递归调用包括递归调用 )) 的标准过程将的标准过程将建立一个栈完成。 建立一个栈完成。
例例 3-5 ARM3-5 ARM 中的过程调用。中的过程调用。CC 语言描述如下:语言描述如下:void f1(int a){void f1(int a){f2(a)f2(a) ;;}}
ARMARM 的的 CC 编译程序常规是用编译程序常规是用 r13r13指向栈顶。假定参数指向栈顶。假定参数 aa已经传人栈中的已经传人栈中的f1()f1() ,并且假设我们必须在调用,并且假设我们必须在调用 f2()f2() 前前将将 f2f2 的参数的参数 ((碰巧是同一个值碰巧是同一个值 )) 入栈。入栈。
下面是包含对下面是包含对 f2()f2() 调用的调用的 f1()f1() 的手写代码:的手写代码: f1f1 :: LDR r0LDR r0 ,, [r13][r13];读取栈顶单元的内容到;读取栈顶单元的内容到 r0r0
;调用;调用 f2()f2()
STMFA rl3!STMFA rl3! ,, {rl4} {rl4} ;将;将 f1f1返回地址存储到栈中返回地址存储到栈中 STMFA rl3!STMFA rl3! ,, {r0} {r0} ;将 ;将 f2 f2 的参数保存到栈顶的参数保存到栈顶 BL f2BL f2 ;跳转到 ;跳转到 f2f2 执行执行 ;返回到;返回到 f1()f1()
SUB r13SUB r13 ,, #4 #4 ; ; f2f2 的参数出栈的参数出栈 LDR r13!LDR r13! ,, r15 r15 ;返回到;返回到 R15R15
我们用基址加偏移量寻址法将传入我们用基址加偏移量寻址法将传入 f1()f1()的参数值装入的参数值装入 r0r0 中。调用中。调用 f2()f2() 时,先将时,先将 f1f1()() 的返回地址入栈,该地址在执行进入的返回地址入栈,该地址在执行进入 f1()f1()的分支链接指令时保存在的分支链接指令时保存在 r14r14 中,然后将中,然后将 ff2()2() 的参数入栈。的参数入栈。
这两种情况下,我们都是使用自动增这两种情况下,我们都是使用自动增长的地址来入栈和调节栈指针的。长的地址来入栈和调节栈指针的。
要返回,我们首先要调整栈,将掩盖要返回,我们首先要调整栈,将掩盖了了 f1()f1()返回地址的返回地址的 f2()f2() 的参数去掉;然后的参数去掉;然后用自动增长寻址弹出用自动增长寻址弹出 f1()f1() 的返回地址,放的返回地址,放入入 PC(r15)PC(r15) 中。 中。
3.8 3.8 混合语言编程混合语言编程在应用系统的程序设计中,若所有在应用系统的程序设计中,若所有
的编程任务均用汇编语言来完成,其工的编程任务均用汇编语言来完成,其工作量是可想而知的,同时,不利于系统作量是可想而知的,同时,不利于系统升级或应用软件移植。升级或应用软件移植。事实上,事实上, ARMARM体系结构支持体系结构支持 C/C+C/C+
++ 以及与汇编语言的混合编程,在一个以及与汇编语言的混合编程,在一个完整的程序设计中,除了初始化部分用完整的程序设计中,除了初始化部分用汇编语言完成以外,其主要的编程任务汇编语言完成以外,其主要的编程任务一般都用一般都用 C/C++C/C++完成。完成。
汇编语言与汇编语言与 C/C++C/C++ 的混合编程通常的混合编程通常有以下几种方式:有以下几种方式:
1.1. 在在 C/C++C/C++ 代码中嵌入汇编指令;代码中嵌入汇编指令;2.2. 在汇编程序和在汇编程序和 C/C++C/C++ 的程序之间进的程序之间进
行变量的互访;行变量的互访;3.3. 汇编程序、汇编程序、 C/C++C/C++ 程序间的相互调程序间的相互调
用。用。
在实际的编程应用中,使用较多的方在实际的编程应用中,使用较多的方式是:式是:
程序的程序的初始化部分初始化部分用用汇编语言汇编语言完成,完成,然后用然后用 C/C++C/C++完成主要的完成主要的编程任务编程任务 ;;
程序在执行时首先完成初始化过程,程序在执行时首先完成初始化过程,然后跳转到然后跳转到 C/C++C/C++ 程序代码中,汇编程序程序代码中,汇编程序和和 C/C++C/C++ 程序之间一般没有参数的传递,程序之间一般没有参数的传递,也没有频繁的相互调用,因此,整个程序也没有频繁的相互调用,因此,整个程序的结构显得相对简单,容易理解,以下进的结构显得相对简单,容易理解,以下进行详细介绍。行详细介绍。
3.8.13.8.1 如何在如何在 CC 语言内嵌汇编语语言内嵌汇编语言言
在在 CC 程序中嵌入汇编程序,可以实现一些高级程序中嵌入汇编程序,可以实现一些高级语言所没有的功能,提高程序执行效率。语言所没有的功能,提高程序执行效率。
armccarmcc 编译器的内嵌汇编器支持编译器的内嵌汇编器支持 ARMARM指令集,指令集,tcctcc 编译器的内嵌汇编器支持编译器的内嵌汇编器支持 ThumbThumb指令集。指令集。
1. 1. 内嵌汇编的语法内嵌汇编的语法__asm __asm { { 指令指令 [;[;指令指令 ] ] // **注释注释 **//…………
[[指令指令 ]]} }
嵌入汇编程序如例嵌入汇编程序如例 3-63-6 所示,给出了所示,给出了 IRQIRQ 中断中断使能使能 // 关闭函数关闭函数 enable_IRQenable_IRQ 和 和 disable_IRQdisable_IRQ 。 。
例例 3-6 3-6 使能/禁能使能/禁能 IRQIRQ 中断中断 __inline void enable_IRQ(void)__inline void enable_IRQ(void) { { int tmpint tmp ;; __asm //__asm // 嵌入汇编代码嵌入汇编代码 { { MRS tmpMRS tmp ,, CPSR //CPSR // 读取读取 CPSRCPSR 的值的值 BIC tmp,tmp,#0x80BIC tmp,tmp,#0x80 MSR MSR CPSR_c,tmpCPSR_c,tmp }}}}
__inline void disable_IRQ(vold)__inline void disable_IRQ(vold)
int tmpint tmp ;;__asm__asm
{{
MRS tmpMRS tmp ,, CPSRCPSR
ORR tmpORR tmp ,, tmptmp ,, #0x80#0x80
MSR CPSR_cMSR CPSR_c ,, tmptmp
}}
}}
2. 2. 内嵌汇编的指令用法内嵌汇编的指令用法 (1) (1) 操作数操作数内嵌的汇编指令中作为操作数的寄存器和内嵌的汇编指令中作为操作数的寄存器和常量可以是常量可以是 CC 表达式。表达式。这些表达式可以是这些表达式可以是 charchar 、、 shortshort 或或 intint类类型,而且这些表达式都是作为无符号数进行型,而且这些表达式都是作为无符号数进行操作。操作。若需要有符号数,用户需要自己处理与符若需要有符号数,用户需要自己处理与符号有关的操作。号有关的操作。编译器将会计算这些表达式的值,并为其编译器将会计算这些表达式的值,并为其分配寄存器。 分配寄存器。
(2) (2) 物理寄存器物理寄存器内嵌汇编中使用物理寄存器有以下内嵌汇编中使用物理寄存器有以下限制限制::
1.1. 不能直接向不能直接向 PCPC寄存器赋值,程序跳转只能使用寄存器赋值,程序跳转只能使用 BB或或 BLBL指令实现。指令实现。
2.2. 使用物理寄存器的指令中,不要使用过于复杂的使用物理寄存器的指令中,不要使用过于复杂的 CC表达式。因为表达式过于复杂时,将会需要较多的表达式。因为表达式过于复杂时,将会需要较多的物理寄存器。这些寄存器可能与指令中的物理寄存物理寄存器。这些寄存器可能与指令中的物理寄存器在使用时发生冲突。器在使用时发生冲突。
3.3. 编译器可能会使用编译器可能会使用 R12R12 或或 R13R13 存放编译的中间结果。存放编译的中间结果。在计算表达式的值时可能会将寄存器在计算表达式的值时可能会将寄存器 R0~R3R0~R3 、、 R12R12和和 R14R14 用于子程序调用。因此,在内嵌的汇编指令用于子程序调用。因此,在内嵌的汇编指令中,不要将这些寄存器同时指定为指令中的物理存中,不要将这些寄存器同时指定为指令中的物理存储器。储器。
4.4. 通常内嵌的汇编指令中不要指定物理寄存器,因为通常内嵌的汇编指令中不要指定物理寄存器,因为这可能会影响编译器分配寄存器,进而影响代码的这可能会影响编译器分配寄存器,进而影响代码的效率。效率。
(3) (3) 常量。在内嵌汇编指令中,常量前面的常量。在内嵌汇编指令中,常量前面的““ #”#” 可以省略。可以省略。(4) (4) 指令展开。内嵌的汇编指令中,如果包含指令展开。内嵌的汇编指令中,如果包含常量操作数,则该指令有可能被内嵌汇编器常量操作数,则该指令有可能被内嵌汇编器展开成几条指令。展开成几条指令。(5) (5) 标号。标号。 CC 程序中的标号可以被内嵌的汇程序中的标号可以被内嵌的汇编指令使用。但是只有指令编指令使用。但是只有指令 BB 可以使用可以使用 CC 程程序中的标号,而指令序中的标号,而指令 BLBL 则不能使用。 则不能使用。 (6) (6) 内存单元的分配。所有的内存分配均由内存单元的分配。所有的内存分配均由 CC编译器完成,分配的内存单元通过变量供内编译器完成,分配的内存单元通过变量供内嵌汇编器使用。内嵌汇编器不支持内嵌汇编嵌汇编器使用。内嵌汇编器不支持内嵌汇编程序中用于内存分配的伪指令。 程序中用于内存分配的伪指令。
(7) SWI(7) SWI 和和 BLBL 指令。指令。在内嵌的在内嵌的 SWISWI 和和 BLBL 指令中,除了正常的操指令中,除了正常的操作数域外,还必须增加以下作数域外,还必须增加以下 33 个可选的寄存个可选的寄存器列表:器列表:
1.1.第第 11 个寄存器列表中的寄存器用于输入的参个寄存器列表中的寄存器用于输入的参数。数。2.2.第第 22 个寄存器列表中的寄存器用于存储返回个寄存器列表中的寄存器用于存储返回的结果。的结果。3.3.第第 33 个寄存器列表中的寄存器的内容可能被个寄存器列表中的寄存器的内容可能被被调用的子程序破坏,即这些寄存器是供被被调用的子程序破坏,即这些寄存器是供被调用的子程序作为工作寄存器。 调用的子程序作为工作寄存器。
3. 3. 内嵌汇编器内嵌汇编器与与 armasmarmasm 汇编器汇编器的差异的差异
内嵌汇编器不支持通过“内嵌汇编器不支持通过“ .”.”指示符或指示符或 PCPC 获取当前获取当前指令地址;指令地址;
不支持“不支持“ LDR RnLDR Rn ,=,= expr”expr”伪指令,而使用伪指令,而使用““ MOV RnMOV Rn ,, expr”expr”指令向寄存器赋值;指令向寄存器赋值;
不支持标号表达式;不支持不支持标号表达式;不支持 ADRADR 和和 ADRLADRL伪指令;伪指令;不支持不支持 BXBX指令;指令;不能向不能向 PCPC赋值。赋值。
使用使用 0x0x 前缀代替“前缀代替“ &”&” ,表示十六进制数。,表示十六进制数。当使用当使用 88 位移位常数导致位移位常数导致 CPSRCPSR 的的 ALUALU 标志更新标志更新
时,时, NN 、、 ZZ 、、 CC 和和 VV 标志中的标志中的 CC 不具有真实意义。 不具有真实意义。
4. 4. 内嵌汇编注意事项内嵌汇编注意事项(1)(1)必须小心使用物理寄存器必须小心使用物理寄存器 ..
如如 R0~R3R0~R3 、、 PCPC 、、 LRLR 和和 CPSRCPSR 中的中的 NN 、、 ZZ 、、 CC和和 VV 标志位标志位 ,,因为计算汇编代码中的因为计算汇编代码中的 CC 表达式时表达式时 ,, 可能会可能会使用这些物理寄存器使用这些物理寄存器 ,, 并会修改并会修改 NN 、、 ZZ 、、 CC 和和 V V 标志位。标志位。
例如:例如: __asm__asm
{ MOV var{ MOV var ,, xx
ADD yADD y ,, varvar ,, x/yx/y
}}
计算计算 x/yx/y 时时 R0R0 会被修改。内嵌汇编器探测到隐含会被修改。内嵌汇编器探测到隐含的寄存器冲突就会报错。的寄存器冲突就会报错。
(( 22 )不要使用寄存器代替变量。)不要使用寄存器代替变量。尽管有时寄尽管有时寄存器明显对应某个变量,但也不能直接使用寄存器明显对应某个变量,但也不能直接使用寄存器代替变量。存器代替变量。
例如:例如: int bad_f(int x) // xint bad_f(int x) // x 存放在存放在 R0R0 中中
{ __asm{ __asm { ADD R0{ ADD R0 ,, R0R0 ,, #1 //#1 // 发生寄存器发生寄存器冲突,实际上冲突,实际上 xx 的值没有变化的值没有变化 }} return(x)return(x) ; ; } }
尽管根据编译器的编译规则似乎可以确尽管根据编译器的编译规则似乎可以确定定 R0R0 对应对应 xx ,但这样的代码会使内嵌汇编,但这样的代码会使内嵌汇编器认为发生了寄存器冲突。器认为发生了寄存器冲突。
用其它寄存器代替用其它寄存器代替 R0R0 存放参数存放参数 xx ,使得,使得该函数将该函数将 xx原封不动地返回。原封不动地返回。
这段代码的正确写法如下:这段代码的正确写法如下: int bad_f(int x)int bad_f(int x) { __asm{ __asm { ADD x{ ADD x ,, xx ,, #1#1 }} return(x)return(x) ;; }}
(3) (3) 使用内嵌式汇编无需保存和恢复寄存器。使用内嵌式汇编无需保存和恢复寄存器。事实上,除了事实上,除了 CPSRCPSR 和和 SPSRSPSR寄存器,对物理寄存器,对物理
寄存器先读后写都会引起汇编器报错。寄存器先读后写都会引起汇编器报错。例如:例如: int f(int x)int f(int x) { __asrn{ __asrn { STMFD SP!{ STMFD SP! ,, {R0} //{R0} // 保存保存 R0R0 。。
先读后写,汇编出错先读后写,汇编出错 ADD R0ADD R0 ,, xx ,, ll EOR xEOR x ,, R0R0 ,, xx LDMFD SP!LDMFD SP! ,, {R0}{R0} } } return(x)return(x) ;;
} }
(4)LDM(4)LDM 和和 STMSTM指令的寄存器列表指令的寄存器列表中只允许使用物理寄存器。中只允许使用物理寄存器。
内嵌汇编可以修改处理器模式、协内嵌汇编可以修改处理器模式、协处理器模式以及处理器模式以及 FPFP 、、 SLSL 、、 SBSB 等等 APAPCSCS寄存器。但是编译器在编译时并不寄存器。但是编译器在编译时并不了解这些变化,因此必须保证在执行了解这些变化,因此必须保证在执行 CC代码前恢复相应被修改的处理器模式。代码前恢复相应被修改的处理器模式。
(5) (5) 汇编语言中的“,”号作为操作数分隔符。汇编语言中的“,”号作为操作数分隔符。如果有如果有 CC 表达式作为操作数,若表达式中包表达式作为操作数,若表达式中包
含有“,”,则必须使用符号“含有“,”,则必须使用符号“ (”(” 和“和“ )”)” 将其归将其归约为一个汇编操作数。约为一个汇编操作数。
例如:例如:__asm__asm
{ ADD x{ ADD x ,, yy ,, (f()(f() ,, z) //“f()z) //“f() ,, z”z” 为一为一个带有“,”的个带有“,”的 CC 表达式表达式
} }