最新帖子 精华区 社区服务 会员列表 统计排行
主题 : Tiny4412裸机程序之常用gcc/makefile/arm汇编指令
admin 离线
级别: 管理员
UID: 1
精华: 1
发帖: 1000
金币: 526 个
银元: 488 个
铜钱: 7883 个
技术分: 601 个
在线时间: 743(时)
注册时间: 2010-04-21
最后登录: 2018-12-11
楼主  发表于: 2015-10-30   

Tiny4412裸机程序之常用gcc/makefile/arm汇编指令

管理提醒: 本帖被 admin 从 阶段1讨论区 移动到本区(2016-11-20)
常用gcc/makefile/arm汇编指令
这3个工具平时用的比较少,基本上是看了忘,忘了看,我们只记住几个常用的命令,不懂的可以用到再查。
推荐资料:<>,<<跟我一起写makefile>>,<<汇编语言程序设计+基于ARM体系结构>>
(1)arm-linux-gcc 选项
-c
预处理、编译和汇编源文件,但是不作连接,编译器根据源文件生成 OBJ 文件。缺省情
况下,GCC 通过用`.o'替换源文件名的后缀`.c',`.i',`.s'等,产生 OBJ 文件名。可以使
用-o 选项选择其他名字。GCC 忽略-c 选项后面任何无法识别的输入文件。
-S
编译后即停止,不进行汇编。对于每个输入的非汇编语言文件,输出结果是汇编语言文
件。缺省情况下,GCC 通过用`.s'替换源文件名后缀`.c',`.i'等等,产生汇编文件名。可
以使用-o 选项选择其他名字。GCC 忽略任何不需要汇编的输入文件。
-E
预处理后即停止,不进行编译。预处理后的代码送往标准输出。GCC 忽略任何不需要预
处理的输入文件。
-o file
指定输出文件为 file。无论是预处理、编译、汇编还是连接,这个选项都可以使用。如
果没有使用`-o'选项,默认的输出结果是:可执行文件为`a.out';修改输入文件的名称是
`source.suffix',则它的 OBJ 文件是`source.o',汇编文件是 `source.s',而预处理后的
C 源代码送往标准输出。
-v
显示制作 GCC 工具自身时的配置命令;同时显示编译器驱动程序、预处理器、编译器的
版本号
-Idir
在头文件的搜索路径列表中添加 dir 目录
举例如下:
$ gcc -c -o main.o main.c
$ gcc -c -o sub.o sub.c
$ gcc -o test main.o sub.o
(2)arm-linux-ld 选项
直接指定代码段、数据段、bss段的起始地址
-Ttext startaddr
-Tdata startaddr
-Tbss startaddr
举例如下:
arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf
使用连接脚本设置地址
arm-linux-ld -Ttimer.lds -o timer_elf head.o init.o interrupt.o main.o
timer.lds:
SECTIONS {
. = 0x30000000;   //设置“当前运行地址”为 0x30000000
.text : { *(.text) }
.rodata ALIGN(4) : {*(.rodata)}
.data ALIGN(4) : { *(.data) }
.bss ALIGN(4) : { *(.bss) *(COMMON) }
}
(3) arm-linux-objdump 选项
-b bfdname 或--target=bfdname
指定目标码格式
--disassemble 或-d
反汇编可执行段(executable sections)。
--disassemble-all 或-D
与-d 类似,反汇编所有段。
--architecture=machine 或-m machine
指定反汇编目标文件时使用的架构,
arm-linux-objdump -D elf_file > dis_file   //将ELF格式的文件转换为反汇编文件
arm-linux-objdump -D -b binary -m arm bin_file > dis_file   //将二进制文件转换为反汇编文件
(4) arm-linux-objcopy 选项
-O bfdname 或--output-target= bfdname
使用指定的格式来输出文件,bfdname 是 BFD 库中描述的标准格式名。
-R sectionname 或--remove-section= sectionname
从输出文件中删掉所有名为 sectionname 的段。这个选项可以多次使用。
注意:不恰当地使用这个选项可能会导致输出文件不可用。
-S 或--strip-all(strip,剥去、剥)
不从源文件中拷贝重定位信息和符号信息到目标文件中去。
-g 或--strip-debug
不从源文件中拷贝调试符号到目标文件中去。
arm-linux-objcopy -O binary -S elf_file bin_file //将 ELF 格式的生成结果转换为二进制文件

Makefile 规则
目标(target)„: 依赖(prerequiries)„
命令(command)

Makefile 常用函数
(1)$(subst from,to,text)
在文本`text’中使用`to’替换每一处`from’。
比如:
$(subst ee,EE,feet on the street)
结果为‘fEEt on the strEEt’
(2)$(patsubst pattern,replacement,text)
寻找`text’中符合格式`pattern’的字,用`replacement’替换它们。`pattern’和
`replacement’中可以使用通配符。
比如:
$(patsubst %.c,%.o,x.c.c bar.c)
结果为:`x.c.o bar.o’。
(3)$(strip string)
去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
比如:
$(strip a  b c )
结果为`a b c’。
(4)$(findstring find,in)
在字符串`in’中搜寻`find’,如果找到,则返回值是`find’,否则返回值为空。
比如:
$(findstring a,a b c)
$(findstring a,b c)
将分别产生值`a’和`’(空字符串)。
(5)$(filter pattern...,text)
返回在`text’中由空格隔开且匹配格式`pattern...’的字 , 去除不符合格式`pattern...’的字。
比如:
$(filter %.c %.s,foo.c bar.c baz.s ugh.h)
结果为`foo.c bar.c baz.s’。
(6)$(filter-out pattern...,text)
返回在`text’中由空格隔开且不匹配格式`pattern...’的字,去除符合格式`pattern...’的字。它是函数 filter 的反函数。
比如:
$(filter %.c %.s,foo.c bar.c baz.s ugh.h)
结果为`ugh.h’。
(7)$(sort list)
将‘list’中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。
比如:
$(sort foo bar lose)
返回值是‘bar foo lose’。

(1)$(dir names...)
抽取‘names...’中每一个文件名的路径部分,文件名的路径部分包括从文件名的首字
符到最后一个斜杠(含斜杠)之前的一切字符。
比如:
$(dir src/foo.c hacks)
结果为‘src/ ./’。
(2)$(notdir names...)
抽取‘names...’中每一个文件名中除路径部分外一切字符(真正的文件名)
比如:
$(notdir src/foo.c hacks)
结果为‘foo.c hacks’。
(3)$(suffix names...)
抽取‘names...’中每一个文件名的后缀。
比如:
$(suffix src/foo.c src-1.0/bar.c hacks)
结果为‘.c .c’。
(4)$(basename names...)
抽取‘names...’中每一个文件名中除后缀外一切字符。
比如:
$(basename src/foo.c src-1.0/bar hacks)
结果为‘src/foo src-1.0/bar hacks’。
(5)$(addsuffix suffix,names...)
参数‘names...’是一系列的文件名,文件名之间用空格隔开;suffix 是一个后缀名。
将 suffix(后缀)的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间
用单个空格隔开。
比如:
$(addsuffix .c,foo bar)
结果为‘foo.c bar.c’。
(6)$(addprefix prefix,names...)
参数‘names’是一系列的文件名,文件名之间用空格隔开;prefix 是一个前缀名。将preffix(前缀)的值附加在每一个独立文件名的前面,
完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
$(addprefix src/,foo bar)
结果为‘src/foo src/bar’。
(7)$(wildcard pattern)
参数‘pattern’是一个文件名格式,包含有通配符(通配符和 shell 中的用法一样)。函
数 wildcard 的结果是一列和格式匹配的且真实存在的文件的名称,
文件名之间用一个空格隔开。比如若当前目录下有文件 1.c、2.c、1.h、2.h,则:
c_src := $(wildcard *.c)
结果为‘1.c 2.c’。
3.
其他函数
(1)$(foreach var,list,text)
前两个参数,‘var’和‘list’将首先扩展,注意最后一个参数‘text’此时不扩展;
接着,‘list’扩展所得的每个字,都赋给‘var’变量;然后‘text’引用该变量进行扩展,
因此‘text’每次扩展都不相同。函数的结果是由空格隔开的‘text’ 在‘list’中多次扩展后,得到的新‘list’
,就是说:‘text’多次扩展的字串联起来,字与字之间由空格隔开,如此就产生了函数 foreach的返回值。
下面是一个简单的例子,将变量‘files’的值设置为 ‘dirs’中的所有目录下的所有
文件的列表:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
这里‘text’是‘$(wildcard $(dir)/*)’,它的扩展过程如下:
1 第一个赋给变量 dir 的值是`a’,扩展结果为‘$(wildcard a/*)’;
2 第二个赋给变量 dir 的值是`b’,扩展结果为‘$(wildcard b/*)’;
3 第三个赋给变量 dir 的值是`c’,扩展结果为‘$(wildcard c/*)’;
4 如此继续扩展。
这个例子和下面的例有共同的结果:
files := $(wildcard a/* b/* c/* d/*)
(2)$(if condition,then-part[,else-part])
首先把第一个参数‘condition’的前导空格、结尾空格去掉,然后扩展。如果扩展为非
空字符串,则条件‘condition’
为 ‘真’ 如果扩展为空字符串,则条件‘condition’ ‘为假’

如果条件‘condition’为‘真’,那么计算第二个参数‘then-part’的值,并将该值作为整个函数 if 的值。
如果条件‘condition’ ‘假’,并且第三个参数存在,
则计算第三个参数‘else-part’的值,并将该值作为整个函数 if 的值;如果第三个参数不存在,函数 if 将什么也不计算,
返回空值。注意:仅能计算‘then-part’和‘else-part’二者之一,不能同时计算。这样有可能产生副作用(例如函数 shell 的调用)

(3)$(origin variable)
变量‘variable’是一个查询变量的名称,不是对该变量的引用。所以,不能采用‘$’
和圆括号的格式书写该变量,当然,如果需要使用非常量的文件名,可以在文件名中使用变
量引用。
函数 origin 的结果是一个字符串,该字符串变量是这样定义的:
‘undefined':如果变量‘variable’从没有定义;
‘default':变量‘variable’是缺省定义;
‘environment':变量‘variable’作为环境变量定义,选项‘-e’没有打开;
‘environment override' :变量‘variable’作为环境变量定义,选项‘-e’已打开;
‘file':变量‘variable’在 Makefile 中定义;
‘command line':变量‘variable’在命令行中定义;
‘override':变量‘variable’在 Makefile 中用 override 指令定义;
‘automatic':变量‘variable’是自动变量
(4)$(shell command arguments)
函数 shell 是 make 与外部环境的通讯工具。函数 shell 的执行结果和在控制台上执行
‘command arguments’的结果相似。不过如果‘command arguments’的结果含有换行符(和
回车符) 则在函数 shell 的返回结果中将把它们处理为单个空格,
,
若返回结果最后是换行符
(和回车符)则被去掉。
比如当前目录下有文件 1.c、2.c、1.h、2.h,则:
c_src := $(shell ls *.c)
结果为‘1.c 2.c’。
举例如下:
src := $(shell ls *.c)
objs := $(patsubst %.c,%.o,$(src))

test: $(objs)
    gcc -o $@ $^
%.o:%.c
    gcc -c -o $@ $<
clean:
    rm -f test *.o
上述 Makefile 中$@、$^、$<称为自动变量。$@表示规则的目标文件名;$^表示所有依赖的名字,名字之间用空格隔开;$<表示第一个依赖的文件名。
‘%’是通配符,它和一个字符串中任意个数的字符相匹配

常用ARM汇编指令:
1.  相对跳转指令:b、bl
这两条指令的不同之处在于 bl 指令除了跳转之外,还将返回地址(bl 的下一条指令的地
址)保存在 lr 寄存器中。
这两条指令的可跳转范围是当前指令的前后 32M:-32M~+32M。
它们是位置无关的指令。
使用示例:
    b fun1
„„
fun1:
    bl fun2
„„
fun2:
„„
 
2.  数据传送指令 mov,地址读取伪指令 ldr
mov 指令可以把一个寄存器的值赋给另一个寄存器,或者把一个常数赋给寄存器。例子
如下:
mov r1, r2            /* r1=r2   */
mov r1, #4096         /* r1=4096 */
mov 指令传送的常数必须能用“立即数”来表示,立即数的格式请参考 4.1.5 节。
 
当不知道一个数能否用“立即数”来表示时,可以使用 ldr 命令来赋值。ldr 是伪指令,
它不是真实存在的指令,编译器会把它扩展成真正的指令:如果该常数能用“立即数”来表
示,则使用 mov 指令;否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。
例子如下:
ldr r1, =4097         /* r1=4097 */
 
ldr 本意为“大范围的地址读取伪指令”,上面的例子使用它来将常数赋给寄存器 r1。下
面的例子是获得代码的绝对地址:
    ldr r1, =label
label:
„„
 
3.  内存访问指令:ldr、str、ldm、stm
注意: “ldr”指令既可能是前面所述的“大范围的地址读取伪指令”, 也可能是内存访问指令。
当它的第二个参数前面有“=”号时,表示伪指令,否则表示内存访问指令。
ldr 指令从内存中读取数据到寄存器,str 指令把寄存器的值存储到内存中,它们操作的
数据都是 32位的。示例如下:
ldr r1, [r2, #4]     /* 将地址为 r2+4 的内存单元数据读取到 r1 中 */
ldr r1, [r2]         /* 将地址为 r2 的内存单元位数据读取到 r1 中 */
ldr r1, [r2], #4     /* 将地址为 r2 的内存单元数据读取到 r1 中,然后 r2=r2+4 */
 
str r1, [r2, #4]     /* 将r1 的数据保存到地址为 r2+4 的内存单元中 */
str r1, [r2]         /* 将r1 的数据保存到地址为 r2 的内存单元中 */
str r1, [r2], #4     /* 将r1 的数据保存到地址为 r2 的内存单元中,然后 r2=r2+4 */
 
 
ldm 和 stm 属于批量内存访问指令,只用一条指令就可以读写多个数据。它们的格式如
下:
ldm{cond} {!} {^}
stm{cond} {!} {^}
其中{cond}表示指令的执行条件,请参考后面表 4.2。
表示地址变化模式,有以下 4 种方式:
①  ia(Increment After)   事后递增方式
②  ib(Increment Before)  事先递增方式
③  da(Decrement After)   事后递减方式
④  db(Decrement Before)  事先递减方式
 
中保存内存的地址,如果后面加上了感叹号,指令执行后,rn 的值会更新:等于下
一个内存单元的地址。
表示寄存器列表,对于 ldm 指令,从所对应的内存块中取出数据,
写入这些寄存器;对于 stm 指令,把这些寄存器的值,写入所对应的内存块中。
{^}有两种含义:如果中有 pc 寄存器,它表示指令执行后,spsr 寄存
器的值将自动复制到 cpsr 寄存器中──这常用于从中断处理函数中返回;如果list>中没有pc 寄存器, {^}表示操作的是用户模式下的寄存器,而不是当前特权模式的寄存
器。
指令中寄存器列表和内存单元的对应关系为:编号低的寄存器对应内存中的低地址单元,
编号高的寄存器对应内存中的高地址单元。
 
ldm 和 stm 指令示例如下:
01 HandleIRQ:                          @ 中断入口函数
02     sub lr, lr, #4                  @ 计算返回地址
03     stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器
04                                     @ r0-r12,lr 被保存在 sp 表示的内存中,
05                                     @ “!”使得指令执行后,sp=sp-14*4
06     
07     ldr lr, =int_return             @ 设置调用 IRQ_Handle 函数后的返回地址  
08     ldr pc, =IRQ_Handle             @ 调用中断分发函数
09 int_return:
10     ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将 spsr 的值复制到 cpsr
11                                     @ 于是从 irq 模式返回被中断的工作模式
4.  加减指令:add、sub
例子如下:
add r1, r2, #1      /* 表示 r1=r2+1,即寄存器 r1 的值等于寄存器 r2 的值加上 1 */
sub r1, r2, #1      /* 表示 r1=r2-1 */
 
 
5.  程序状态寄存器的访问指令:msr、mrs
ARM 处理器有一个程序状态寄存器(cpsr),它用来控制处理器的工作模式、设置中断的
总开关。示例如下:
msr cpsr, r0        /* 复制 r0 到 cpsr 中 */
mrs r0, cpsr        /* 复制 cpsr到 r0 中 */
 
在第 9 章会描述 cpsr 寄存器的格式。
 
6.  其他伪指令
在本书的汇编程序中,常常见到如下语句:
.extern     main
.text 
.global _start 
_start:
“.extern”定义一个外部符号(可以是变量也可以是函数), 上面的代码表示本文件中引
用的 main 是一个外部函数。
 
“.text”表示下面的语句都属于代码段。
 
“.global”将本文件中的某个程序标号定义为全局的,比如上面的代码表示_start 个
全局函数。
 
描述
快速回复

如果您在写长篇帖子又不马上发表,建议存为草稿
认证码:

验证问题:
printf("%d", 43)
按"Ctrl+Enter"直接提交