linux 汇编 `.eabi_attribute',简单的GBA/NDS汇编程序讲解(ARM汇编)(2008-09-06)

批注:博文标题中的日期标记此文为我在当时发表在csai上的博客cgao.csai.cn,今搬至此处.

这几天,空余花了点时间看了一下NDS/GBA的ARM汇编,小发一篇文章,充饥一下我的博客.

下面的示例是GBA程序,非常简单,:

先在PN2(Programmer’s Notepad 2)里建个工程,主C文件内容:

//AsmTest.c

extern int Add_Func(int a,int b);

extern void memset16_func(unsigned short* dst,int ,int

hwcount);

int main()

{

*(unsigned int*)0×04000000 = 0×0403;//0×04000000

是控制寄存器的内存映射地址,这条语句类似于当年DOS 中的 INT 13H 的作用.

((unsigned short*)0×06000000)[136+80*240] = 0x03E0;

//0×06000000是VRAM,即显存的起始地址

while(1);

return 0;

}

程序很简单,main函数上面有两个外部函数的申明,这两个函数都是从单独的ARM汇编程序导出的,等会介绍,先来看看这个main函数的工作内容,就两

条语句,第一条设置GBA的显示模式为mode3并开启BG2背景层,准备往屏幕画点,第二条语句就是向VRAM,即显存里写入一个点,对应屏幕位置为

(136,80),颜色值为0x03E0(16位值,实为RGB为555的颜色).这篇文章是要讲ARM汇编的一点小知识,下面先来看看这个超简单的c文

件用GCC转成ARM汇编程序是什么样的,工具以DevkitARM为例说明,执行:

arm-eabi-gcc -mthumb-interwork -mthumb -O2 -Wall

-fno-strict-aliasing AsmTest.c -S

生成此c文件对应的汇编文件,因为这个C文件本身非

常简单,所以生成的汇编也不会复杂到哪里去,下面一起来看一下这个生成的汇编文件的内容,我在每行后面加了注释:

@AsmTest.s文 件内容

.cpu arm7tdmi

.fpu softvfp

.eabi_attribute 20, 1

.eabi_attribute 21, 1

.eabi_attribute 23, 3

.eabi_attribute 24, 1

.eabi_attribute 25, 1

.eabi_attribute 26, 1

.eabi_attribute 30, 2

.eabi_attribute 18, 4

.code 16

.file “AsmTest.c”

@以上几行我们不太关心,下面的才是重点

.text @说明这是一个代码段(code

section),其内容会被放在GBA的ROM或EWRAM内,视链接器说明而定

.align 2 @以2^2字节对齐

.global main

@全局符号 main

.code 16

@下面的代码是16位指令,即thumb指令

.thumb_func @说明这是一个thumb指令集写成的函数

.type main, %

@函数名

main: @main函数的入口

push {lr}

@先将link register即r14压栈

ldr r2, .L5 @

r2=1027=0×0403,读入.L5标示符所指的第一个位置的数到r2,看下面.L5入口第一个数是用.word前缀,说明是一个字(32bit)

mov r3, #128

@ r3=128

lsl r3, r3,

#19 @

r3=r3<<19=128*2^19=67108864=0×04000000,即GBA的控制寄存器的地址

str r2, [r3]

@ *r3=r2,即对应C文件中的*(unsigned int*)0×04000000 = 0×0403;

@第一条语句完成

mov r3, #248

@ r3=248

ldr r2, .L5+4

@

r2=100701968,注意,存储器地址都是以字节为单位编址的,这里.L5+4就是.L5的地址跳到下一个字的位置,即跳过4个字节,就是第二字,

也就是100701968

lsl r3, r3,

#2 @ r3=r3<<2=248*2^2=992=0x03E0

strh r3, [r2]

@

*r2=r3,将0x03E0赋给地址100701968,这个100701968怎么来的看后面解释,就是C文件里的第二条语句((unsigned

short*)0×06000000)[136+80*240] = 0x03E0;

.L2:

b .L2 @

死循环,对应while(1);

.L6:

.align 2 @

字对齐 32bit

.L5:

.word 1027 @

1027=0×0403

.word 100701968 @ =((unsigned short*)0×06000000)[136+80*240]的地址

.size main,

.-main

.ident “GCC:

(devkitARM release 23b) 4.3.0″

果然不难吧,只要知道ARM的那几条指令,就很容易理解.

这里先说说100701968这个数是怎么来的,其实100701968就是((unsigned

short*)0×06000000)[136+80*240]的地址,我们来算一下:

起始地址是0×06000000=100663296,偏移量是136+80*240=19336,但由于基址指向的是unsigned

short*类型地址,即为2Byte为单位,所以偏移的地址数为19336*2=38672,再加上基址,即

100663296+38672=100701968,这就对了.

再说一下,生成的汇编程序是thumb指令,其指令字长为16bit,而不是ARM的32bit,为什么要生成thumb而不生成32bit的

ARM指令

呢?因为GBA的CPU访问ROM(烧录卡)的总线是16bit,生成thumb指令的程序可使CPU每一个周期取一条指令,如果是ARM指令则得花两个

周期,其取指周期就会慢一倍,当然总体速度不一定会慢那么多,这还和数据量,所执行指令的复杂性有关。总之,如果ROM总线是32位的话,用ARM指令也

许就更合适些。

OK,C/C++文件转汇编文件的简要分析到这里,下面来看一下C和汇编的混合编程.

C/C++和汇编的混合编程主要有这几种方式: 1.C/C++内嵌汇编 2.C/C++调用汇编程序(函数)

3.汇编调用C函数

下面针对第二种情况进行简要介绍,刚好以前我的跨平台3D引擎需要一个高速的memset的16位版,即以16bit(半字)为单位进行memset.我

随便写了一个,ARM指令的,非thumb,如下:

@memset_16.s

.code 32 @ ARM指令,即32位指令字长

.align 2 @ 32位,即4字节对齐

.text @ 说明下面是代码段(不是数据段)

.global memset16_func @ 要想被C调用,一定要定义为global标识符,即全局可见

memset16_func: @ 函数入口

@ r0=dst r1= r2=hwcount,即r0为目标地址,r1为要set的值,r2为要set多少次

cmp r2,#0

bxeq lr @ 如果要set的个数为0,则不执行任何操作,直接返回

sub r0,#2 @ r0-=2

LoopR:

strh r1,[r0,#2]! @ 相当于C语言的:*((unsigned signed*)r0++)=r1

subs r2,#1 @ r2-=1,计数器减1

bne LoopR @ 如果计数器还没减到0,则继续循环

bx lr @ 函数返回

这样,这个单独的ARM汇编函数就完成了,接下来就要将它汇编成二进制.o文件,执行:

arm-eabi-gcc -c memset_16.s -mthumb-interwork -mthumb -O2 -Wall

-fno-strict-aliasing -o memset_16.o

这样就生成了.o文件,后面的工作和其它正常情况一下,就是将.o文件链接成一个elf文件,再用objcopy去掉elf里的debug信息,生

成.gba或.bin文件.

现在简单说一下如何在C/C++调用这个汇编文件里的函数.其实,最上面的C文件里main()上面的函数声明就是最关键的一步,这个函数声明要注意几

点:

1.如果是C调用,则前面一定要加上extern,如果是C++调用,则前面加extern “C”

2.函数返回值视实际函数而定,没有就写void

4.函数名一定要和汇编文件的.global标识定义的那个一样.

3.这是最重要一点,函数的参数,一般都用AAPCS规范来处理,简单地说就是,如果函数参数不超过三个的话,第一,二,三个参数传到汇编函数里时,分别

被赋给了r0,r1,r2寄存器,如果参数超过三个,就用栈,具体就看AAPCS吧.

好了,写个超简单的C函数测试一下:

extern void memset16_func(unsigned short* dst,int ,int

hwcount);

int main()

{

*(unsigned int*)0×04000000 = 0×0403;

//((unsigned short*)0×06000000)[136+80*240] = 0x03E0;

memset16_func((unsigned

short*)0×06000000+(240<<5),0x001f,240);//在屏幕的第2^5行,即第32行画一条颜色值为

0x001f的线.

//((unsigned short*)0×06000000)[120+96*240] = 0x7C00;

while(1);

return 0;

}

编译并运行,下面是VBA模拟器上的运行效果:

a4c26d1e5885305701be709a3d33442f.png

几点说明:

1.写命令行来编译文件是件头痛的事,可以用make,附件内有本例的工程文件,下载:Demo.rar

2.这里的memset_16,是粗糙版,如果每次调用memset_16中的,hwcout比较大(比如大于8),就可以考虑用这种方法处理来提高效

率:将r3-r10均赋为要set的值,并用strmia指令处理,然后再处理剩下的尾巴.这个留到有空再写了.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值