1.MDK和/GNU伪指令的区别我们在学习汇编代码的时候会看到以下两种风格的代码:gnu代码以:.global_start_start开头:@汇编入口ldrsp,=0x41000000.end@assemblerendMDKcodestartswith:AREAExample,CODE,READONLY;声明代码段ExampleENTRY;程序入口StartMOVR0,#0OVEREND这两种风格的代码是为了使用不同的编译器,我们以前的示例代码都是MDK风格的。说了这么多我们初学者,到底应该学哪种风格呢?答案是肯定的,学习GNU风格的汇编代码,因为Linux驱动开发必须掌握Linux内核和uboot,而这两个软件都是GNU风格的。为了不把太多精力浪费在一时无用的知识上,下面只讲GNU风格的编译。二、GNU汇编书写格式:1、代码行中的注释符号:'@'整行注释符号:'#'语句分隔符:直接操作数前缀:'#'或'$'2、全局标号:仅标号可以由a~z、A~Z、0~9、"."、_等组成(由点、字母、数字、下划线等组成,部分标签除外,不能以数字开头)字符和“:”。段中标号的地址值是在汇编时确定的;段外标签的地址值在链接时确定。3.局部标签:局部标签主要用于局部区域,局部标签可以重复出现。它由两部分组成。开头是一个0-99直接数字局部标号后面跟着“:”F:指示编译器只向前查找,代码行数增加的方向/下一句代码B:指示编译器toonlybackwardSearch,减少代码行数的方向,注意局部标签的跳转,就近原则“Example:”文件位置arch/arm/kernel/entry-armv.S3.伪-operations:1.Symboldefinitionpseudo-instruction2.Datadefinition(DataDefinition)伪操作数据定义伪操作一般用于为特定数据分配存储单元,同时可以完成分配存储单元的初始化.常用数据定义伪操作如下:[例子].wordval:.word0x11223344movr1,#val;设置值0x11223344注册r1variable放在stop之后,.end之前的标号是地址的助记符,标号不占用存储空间。位置可以在结束前,比较随意。3.如果选择语法结构。iflogical-expressing....else....endif类似于c语言中的条件编译。【例】.ifval2==1movr1,#val2.endif4.macro宏定义.macro,.endm宏定义类似于C语言中的宏函数。宏伪操作可以将一段代码定义为一个整体,称为宏指令。然后在程序中就可以通过宏指令多次调用这段代码。语法格式:.macro{$label}name{$parameter{,$parameter}……}…….code.endm其中,宏指令展开时,$label会被用户自定义的符号代替。一个宏动作可以带一个或多个参数,当宏动作展开时,这些参数被相应的值替换。《注意》:先定义再使用例子:《【例1】:无参宏实现子函数返回》。macroMOV_PC_LRMOVPC,LR.endm的调用方法如下:MOV_PC_LR《【例2】:带参数的宏实现子函数返回》.macroMOV_PC_LR,parammovr1,\paramMOVPC,LR.endm调用如下:MOV_PC_LR#124.杂项伪操作示例:.set.setstart,0x40movr1,#start;r1中的0x40例如.equ.equstart,0x40movr1,#start;r1里面是0x40#definePI3.1415相当于.equPI,314155.GNU伪指令的要点:伪指令在编译时会转换成对应的ARM指令1.ADR伪指令:该指令加载地址标签位于寄存器中。ADR伪指令是小范围地址读伪指令,使用的相对偏移量范围:当地址值字节对齐(8位)时,取值范围为-255到255;当地址值为字对齐(32位)时,取值范围为-1020~1020。语法格式:ADR{cond}register,labelRR0,lable2。ADRL伪指令:读中程地址到寄存器ADRL伪指令是中程地址读伪指令。使用相对偏移范围:地址值字节对齐时,取值范围为-64~64KB;当地址值是字对齐时,取值范围是-256~256KB语法格式:ADRL{cond}register,labelADRLR0,lable3.LDR指令:LDR指令加载一个32位常量和一个地址到一个寄存器中。语法格式:LDR{cond}register,=[expr|label-expr]LDRR0,=0XFFFF0000;movr1,#0x12比较注意:(1)ldr指令和ldr指令的区别下面是ldr指令:ldrr1,=val@r1=val是伪指令,将val标签地址赋值给r1【区别于MDK,MDK只支持ldr1,=val]下面是ldr指令:ldr2,val@r1=*val是arm指令,并赋标号val地址r2val:.word0x11223344中的内容(2)如何使用ldr实现长跳转的伪指令ldrpc,=32位地址(3)解决编码中的非立即数问题使用arm伪指令ldrldrr0,=0x999;0x999不是立即数6.GNU程序集的编译1.不带lds文件的编译假设我们有如下代码,包括1个main.c文件和1个start.s文件:start.s.global_start_start:@assemblyentryldrsp,=0x41000000bmain.globalmystrcopy.textmystrcopy://parameterdest->r0,src->r2LDRBr2,[r1],#1STRBr2,[r0],#1CMPr2,#0//判断是否为字符串结尾BNEmystrcopyMOVpc,lrstop:bstop@无限循环,防止跑掉相当于while(1).end@assemblerendmain.cexternvoidmystrcopy(char*d,constchar*s);intmain(void){constchar*src="yikoulinux";chardest[20]={};mystrcopy(dest,src);//调用汇编实现的mystrcopy函数while(1);return0;}Makefile中写法如下:1.TARGET=start2.TARGETC=main3.all:4.arm-none-linux-gnueabi-gcc-O0-g-c-o$(TARGETC).o$(TARGETC).c5.arm-none-linux-gnueabi-gcc-O0-g-c-o$(TARGET).o$(TARGET).s6.#arm-none-linux-gnueabi-gcc-O0-g-S-o$(TARGETC).s$(TARGETC).c7.arm-none-linux-gnueabi-ld$(TARGETC).o$(TARGET).o-Ttext0x40008000-o$(TARGET).elf8.arm-none-linux-gnueabi-objcopy-Obinary-S$(TARGET).elf$(TARGET).bin9.clean:10.rm-rf*.o*.elf*.dis*.binMakefile含义如下:定义环境变量TARGET=start,start是定义环境变量TARGETC=main的汇编文件的文件名,main是c语言文件target:all,第4~8行是编译main.c的指令语句intomain.o,$(TARGETC)会被main替??换编译start.s生成start.o,$(TARGET)会被start替换4-5也可以用这一行的一条指令链接main。o和start.o通过ld命令生成start.elf,-Ttext0x40008000表示设置代码段起始地址为0x40008000通过objcopy,-Obinary(或--out-target=binary)输出为原始二进制文件,-S(或--strip-all)不重定位输出文件中的formation和symbol信息,减小文件大小,清除cleantarget的执行语句,删除编译产生的临时文件【补充】gcc的代码优化级别在makefile文件中4中的编译命令levelsO0--O3数字越大,优化程度越高。O3最大化优化volatile修饰的volatile变量,编译器不再优化,每次实际访问内存地址空间。2.依赖lds文件编译实际工程文件,段的复杂度比我们的复杂很多,尤其是linux内核有几万个文件,段的分布极其复杂,所以我们需要使用lds文件定义内存分布。文件列表main.c和start.s与上一节一致。map.ldsOUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")/*OUTPUT_FORMAT("elf32-arm","elf32-arm","elf32-arm")*/OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS{.=0x40008000;.=ALIGN(4);.text:{.start.o(.text)*(.text)}.=ALIGN(4);.rodata:{*(.rodata)}.=ALIGN(4);.data:{*(.data)}.=ALIGN(4);.bss:{*(.bss)}}解释上面的例子:OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm")指定输出目标文件的默认二进制文件格式。您可以使用objdump-i列出支持的二进制文件格式;OUTPUT_ARCH(arm)指定输出平台为arm,可以通过objdump-i查询支持的平台;ENTRY(_start):设置符号_start的值为入口地址;.=0x40008000:设置定位器符号为0x40008000(若不指定,符号初始值为0);.text:{.start.o(.text)*(.text)}:前者表示开始。o放在textsection的第一个位置,后面的意思是将输入文件的所有(*符号代表任意输入文件).textsection合并为一个.textsection;.rodata:{*(.data)}:将输入文件的所有Merge.rodata部分合并为一个.rodata部分;.data:{*(.data)}:将所有输入文件的.data段合并为一个.data段;.bss:{*(.bss)}:将所有输入文件的.bss段合并为一个.bss段;该部分通常存储全局未初始化的变量。=对齐(4);表示后面的section4字节对齐链接器每次读取一个section描述后都会定位寄存器符号的值增加section的大小。看看Makefile应该怎么写:#CORTEX-A9PERIDRIVERCODE#VERSION1.0#ATHUOR一口Linux#MODIFYDATE#2020.11.17Makefile#=======================================================#CROSS_COMPILE=arm-none-linux-gnueabi-NAME=startCFLAGS=-mfloat-abi=softfp-mfpu=vfpv3-mabi=apcs-gnu-fno-builtin-fno-builtin-function-g-O0-cLD=$(CROSS_COMPILE)ldCC=$(CROSS_COMPILE)gccOBJCOPY=$(CROSS_COMPILE)objcopyOBJDUMP=$(CROSS_COMPILE)objdumpOBJS=start.omain.o#===================================================#all:$(OBJS)$(LD)$(OBJS)-Tmap.lds-o$(NAME).elf$(OBJCOPY)-Obinary$(NAME)。elf$(NAME).bin$(OBJDUMP)-D$(NAME).elf>$(NAME).dis%%.o:%.S$(CC)$(CFLAGS)-c-o$@$<%.o:%.s$(CC)$(CFLAGS)-c-o$@$<%.o:%.c$(CC)$(CFLAGS)-c-o$@$
