从0学ARM-汇编伪指令、LDS详解

前端 2023-07-05 17:29:38
495阅读

 一、MDK和/GNU伪指令差别

我们在学习培训汇编代码的情况下历经会见到下列二种设计风格的编码:

gnu编码开头是:

 
  1. .global _start 
  2. _start:      @选编通道 
  3.  ldr sp,=0x41000000 
  4. .end         @汇编程序完毕 

MDK编码开头是:

 
  1.  AREA Example,CODE,READONLY    ;申明代码段Example 
  2.  ENTRY ;程序流程通道 
  3. Start              
  4.  MOV R0,#0      
  5. OVER 
  6.  END 

这二种设计风格的编码是要应用不一样的c语言编译器,大家以前的案例编码全是MDK设计风格的。

那么多针对大家新手而言要学习培训哪样设计风格呢?回答是毫无疑问的,学习培训GNU设计风格的汇编代码,由于做Linux驱动开发务必把握的linux核心、uboot,而这两个手机软件便是GNU设计风格的。

为了更好地大伙儿不必把过少活力消耗在临时不起作用的专业知识上,下边大家只讲GNU设计风格选编。

二、GNU选编写法:

1. 编码行中的注释符号:

‘@’ 整行注释符号: ‘#’ 句子分离出来标记:

立即操作数作为前缀: ‘#’ 或 ‘$’

2. 全局性型号:

型号只有由a~z,A~Z,0~9,“.”,_等(由点、英文字母、数据、下横线等构成,除部分型号外,不可以以数据开始)标识符构成,型号的后边加“:”。

段内型号的详细地址值在选编时明确;段外型号的详细地址值在联接时明确。

3. 部分型号:

部分型号关键在部分范畴内应用并且部分型号能够反复出現。它由两台构成开头是一个0-99立即的数据部分型号 后边加“:”

 
  1. F:标示c语言编译器只向前搜索,编码个数提升的方位 / 编码的下一句 
  2. B:标示c语言编译器只向后检索,编码个数减少的方位 

留意部分型号的自动跳转,就远原则「举例说明:」

 
  1. 文档部位 
  2. arch/arm/kernel/entry-armv.S 

 


三、伪实际操作:

1. 标记界定伪指令

2. 数据信息界定(Data Definition)伪实际操作

数据信息界定伪实际操作一般用以为特殊的数据信息分派数据存储器,另外可进行已分派数据存储器的复位。普遍的数据信息界定伪实际操作有以下几类:

【举例说明】

.word

 
  1. val:   .word  0x11223344 
  2. mov r1,#val  ;将值0x11223344设定到存储器r1中 

.space

 
  1. label: .space size,expr     ;expr能够是4字节之内的浮点数  
  2.  a:  space 8, 0x1 

.rept

 
  1. .rept cnt   ;cnt是反复频次 
  2. .endr 

留意:

  1. 变量的定义放到,stop后,.end前
  2. 型号是详细地址的助记符,型号不占储存空间。部位在end前就可以,相对性随便。

3. if挑选

句法结构

 
  1. .if  logical-expressing  
  2.   ……                                    
  3. .else 
  4.   …… 
  5. .endif     

相近c语言里的条件编译 。

【举例说明】

 
  1. .if  val2==1 
  2.  mov r1,#val2 
  3. .endif 

4. macro宏定义.

macro,.endm 宏定义相近c语言里的宏涵数 。

macro伪实际操作能够将一段编码界定为一个总体,称之为宏命令。随后就可以在程序流程中根据宏命令数次启用此段编码。

英语的语法文件格式:

 
  1. .macro    {$label} 姓名{$parameter{,$parameter}…} 
  2.  ……..code 
  3. .endm 

在其中,$型号在宏命令被进行时,型号会被更换为客户界定的标记。

宏实际操作能够应用一个或好几个主要参数,当宏实际操作被进行时,这种主要参数被相对的值更换。

「留意」:先界定后应用

举例说明:

「【例1】:沒有主要参数的宏完成子函数回到」

 
  1. .macro MOV_PC_LR 
  2.    MOV PC,LR 
  3. .endm 

 
  1. 启用方法以下: 
  2.     MOV_PC_LR 

「【例2】:带主要参数宏完成子函数回到」

 
  1. .macro MOV_PC_LR ,param 
  2.    mov r1,\param 
  3.    MOV PC,LR 
  4. .endm 

启用方式以下:

 
  1. MOV_PC_LR  #12 

四、杂类伪实际操作

举例说明:.set

 
  1. .set start, 0x40 
  2. mov r1, #start      ;r1里边是0x40 

举例说明 .equ

 
  1. .equ   start,  0x40                                       
  2. mov r1, #start      ;r1里边是0x40      
 
 
  1. #define  PI  3.1415 

等额的于

 
  1. .equ   PI, 31415 

五、GNU伪指令

关键环节:伪指令在编译程序的时候会转换为相匹配的ARM命令

1.ADR伪指令 :该命令把标识所属的详细地址载入到存储器中。ADR伪指令为小范畴详细地址载入伪指令,应用的相对性偏位范畴:当详细地址值是字节对齐 (8位) 时,取值范围为-255~255,当详细地址值是字两端对齐 (32位系统) 时,取值范围为-1020~1020。英语的语法文件格式:

 
  1. ADR{cond}   register,label 
  2. R      R0,  lable 

2.ADRL伪指令:将中等水平范畴详细地址载入到存储器中

ADRL伪指令为中等水平范畴详细地址载入伪指令。应用相对性偏位范畴:当详细地址值是字节对齐时,取值范围为-64~64KB;当详细地址值是字两端对齐时,取值范围为-256~256KB

英语的语法文件格式:

 
  1. ADRL{cond}   register,label 
  2. ADRL        R0,lable 

3.LDR伪指令: LDR伪指令装车一个32位系统的参量和一个详细地址到存储器。英语的语法文件格式:

 
  1. LDR{cond}  register,=[expr|label-expr] 
  2. LDR    R0,=0XFFFF0000      ;mov r1,#0x12   比照一下 

留意:(1)ldr伪指令和ldr命令区别 下边是ldr伪指令:

 
  1. ldr r1,=val  @ r1 = val   是伪指令,将val型号详细地址赋给r1     
  2. 【与MDK不一样,MDK只适用ldr r1,=val】 

下边是ldr命令:

 
  1. ldr r2,val   @ r1 = *val    是arm命令,将型号val详细地址里的內容给r2 
  2. val: .word 0x11223344 

(2)怎样运用ldr伪指令完成长自动跳转

 
  1. ldr  pc,=32位系统详细地址 

(3)编号中处理非立即数的难题 用arm伪指令ldr

 
  1. ldr r0,=0x999   ;0x999  并不是立即数, 

六、GNU选编的编译程序

1. 没有lds文档的编译程序

假定大家有下列编码,包含一个main.c文档,一个start.s文件:start.s

 
  1. .global _start 
  2. _start:      @选编通道 
  3.  ldr sp,=0x41000000 
  4.  b main 
  5. .global mystrcopy 
  6. .text 
  7. mystrcopy: //主要参数dest->r0,src->r2 
  8.   LDRB r2, [r1], #1 
  9.   STRB r2, [r0], #1 
  10.   CMP r2, #0 //分辨是否字符串数组尾 
  11.   BNE mystrcopy 
  12.   MOV pc, lr 
  13. stop: 
  14.  b stop   @无限循环,避免 跑飞 等额的于while(1) 
  15. .end         @汇编程序完毕 

main.c

 
  1. extern void mystrcopy(char *d,const char *s); 
  2. int main(void) 
  3.  const char *src ="yikoulinux"
  4.  char dest[20]={}; 
  5.  mystrcopy(dest,src);//启用选编完成的mystrcopy涵数 
  6.  while(1); 
  7.     return 0; 

Makefile撰写方式以下:

 
  1. 1. TARGET=start    
  2. 2. TARGETC=main 
  3. 3. all
  4. 4.   arm-none-linux-gnueabi-gcc -O0 -g -c -o $(TARGETC).o  $(TARGETC).c 
  5. 5.    arm-none-linux-gnueabi-gcc -O0 -g -c -o $(TARGET).o $(TARGET).s 
  6. 6.    #arm-none-linux-gnueabi-gcc -O0 -g -S -o $(TARGETC).s  $(TARGETC).c   
  7. 7.    arm-none-linux-gnueabi-ld $(TARGETC).o $(TARGET).o -Ttext 0x40008000 -o $(TARGET).elf 
  8. 8.    arm-none-linux-gnueabi-objcopy   -O binary -S  $(TARGET).elf  $(TARGET).bin 
  9. 9. clean: 
  10. 10.  rm -rf *.o *.elf *.dis *.bin 

Makefile含意以下:

  1. 界定系统变量TARGET=start,start为选编文档的文件夹名称
  2. 界定系统变量TARGETC=main,main为c语言文件
  3. 总体目标:all,4~8行是该命令的命令句子
  4. 将main.c编译程序形成main.o,$(TARGETC)会被换成main
  5. 将start.s编译程序形成start.o,$(TARGET)会被换成start
  6. 4-5还可以用这家银行1条命令完成
  7. 根据ld指令将main.o、start.o连接形成start.elf,-Ttext 0x40008000表明设定代码段起止详细地址为0x40008000
  8. 根据objcopy将start.elf转化成start.bin文档,-O binary (或--out-target=binary) 輸出为初始的二进制文件,-S (或 --strip-all)輸出文档中不必重精准定位信息内容和符号信息内容,变小了文件尺寸,
  9. clean总体目标
  10. clean总体目标的实行句子,删掉编译程序造成的临时文件夹

【填补】

  1. gcc的代码设计等级,在 makefile 文档中的编译程序指令 四级 O0 -- O3 数据越大,提升水平越高。O3较大 提升
  2. volatile功效 volatile装饰的自变量,c语言编译器已不开展提升,每一次都真实浏览内存地址室内空间。

2. 依靠lds文档编译程序

具体的工程文件,段复杂性远比大家这一要繁杂的多,特别是在Linux核心有上万个文档,段的遍布以及繁杂,因此 这就必须大家依靠lds文档来界定运行内存的遍布。

文档目录

main.c和start.s和上一节一致。

map.lds

 
  1. OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm"
  2. /*OUTPUT_FORMAT("elf32-arm""elf32-arm""elf32-arm")*/ 
  3. OUTPUT_ARCH(arm) 
  4. ENTRY(_start) 
  5. SECTIONS 
  6.  . = 0x40008000; 
  7.  . = ALIGN(4); 
  8.  .text      : 
  9.  { 
  10.   .start.o(.text) 
  11.   *(.text) 
  12.  } 
  13.  . = ALIGN(4); 
  14.     .rodata :  
  15.  { *(.rodata) } 
  16.     . = ALIGN(4); 
  17.     .data :  
  18.  { *(.data) } 
  19.     . = ALIGN(4); 
  20.     .bss : 
  21.      { *(.bss) } 

解释一下所述的事例:

  1. OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 特定輸出object档案预置的binary 格式文件。能够应用objdump -i列举适用的binary 格式文件;
  2. OUTPUT_ARCH(arm) 特定輸出的服务平台为arm,能够通过objdump -i查看适用服务平台;
  3. ENTRY(_start) :将标记_start的值设成通道详细地址;
  4. . = 0x40008000: 把定位仪标记置为0x40008000(若不特定, 则该标记的初值为0);
  5. .text : { .start.o(.text) *(.text) } :前面一种表明将start.o放进text段的第一个部位,后面一种表明将全部(*符号代表随意键入文档)键入文档的.text section合拼成一个.text section;
  6. .rodata : { *(.data) } : 将全部键入文档的.rodata section合拼成一个.rodata section;
  7. .data : { *(.data) } : 将全部键入文档的.data section合拼成一个.data section;
  8. .bss : { *(.bss) } : 将全部键入文档的.bss section合拼成一个.bss section;此段一般储放全局性未复位自变量
  9. . = ALIGN(4);表明下边的段4字节两端对齐

射频连接器每念完一个section叙述后, 将定位仪标记的值提升该section的尺寸。

看来下,Makefile应当要怎么写:

 
  1. # CORTEX-A9 PERI DRIVER CODE 
  2. # VERSION 1.0 
  3. # ATHUOR 一口Linux 
  4. MODIFY DATE 
  5. # 2020.11.17  Makefile 
  6. #=================================================# 
  7. CROSS_COMPILE = arm-none-linux-gnueabi- 
  8. NAME =start 
  9. CFLAGS=-mfloat-abi=softfp -mfpu=vfpv3 -mabi=apcs-gnu -fno-builtin  -fno-builtin-function -g -O0 -c                                    
  10. LD = $(CROSS_COMPILE)ld 
  11. CC = $(CROSS_COMPILE)gcc 
  12. OBJCOPY = $(CROSS_COMPILE)objcopy 
  13. OBJDUMP = $(CROSS_COMPILE)objdump 
  14. OBJS=start.o  main.o 
  15. #================================================# 
  16. all:  $(OBJS) 
  17.  $(LD)  $(OBJS) -T map.lds -o $(NAME).elf 
  18.  $(OBJCOPY)  -O binary  $(NAME).elf $(NAME).bin  
  19.  $(OBJDUMP) -D $(NAME).elf > $(NAME).dis  
  20. %.o: %.S  
  21.  $(CC) $(CFLAGS) -c -o  $@ $< 
  22. %.o: %.s  
  23.  $(CC) $(CFLAGS) -c -o  $@ $< 
  24. %.o: %.c 
  25.  $(CC) $(CFLAGS) -c -o  $@ $< 
  26. clean: 
  27.  rm -rf $(OBJS) *.elf *.bin *.dis *.o 

编译程序結果以下:


编译程序結果

最后形成start.bin,改文档能够烧录到单片机开发板检测,由于本例沒有形象化状况,事后文章内容大家添加其他作用再检测。

【留意】

  1. 在其中交叉编译专用工具链「arm-none-linux-gnueabi-」 要依据自身具体的服务平台来挑选,本例是根据三星的exynos-4412专用工具链完成的。
  2. 详细地址0x40008000也不是随意挑选的,

exynos4412 详细地址遍布

阅读者能够依据自身手上的单片机开发板相匹配的soc指南搜索该详细地址。

linux核心的出现异常空间向量表

linux核心的运行内存遍布也是依靠lds文档界定的,linux核心的编译程序大家暂不探讨,编译程序好以后会再下列部位形成相匹配的lds文档:

 
  1. arch/arm/kernel/vmlinux.lds 

大家看下该文件的一部分內容:


vmlinux.lds

OUTPUT_ARCH(arm)制订相匹配的CPU;

ENTRY(stext)表明程序流程的通道是stext。

另外大家还可以见到linux运行内存的区划更为的繁杂,事后大家探讨linux核心,再再次剖析该文件。

3. elf文件和bin文档差别:

1) ELF

ELF格式文件是一个对外开放规范,各种各样UNIX系统软件的可执行程序都选用ELF文件格式,它有三种不一样的种类:

  • 可多次精准定位的总体目标文档(Relocatable,或是Object File)
  • 可执行程序(Executable)
  • 共享资源库(Shared Object,或是Shared Library)

ELF文件格式出示了二种不一样的角度,连接器把ELF文档当做是Section的结合,而加载器把ELF文档当做是Segment的结合。

2) bin

BIN文件是立即的二进制文件,內部沒有详细地址标识。bin文档內部数据信息依照代码段或是数据信息段的物理学室内空间详细地址来排序。一般用开发板烧录时从00刚开始,而假如免费下载运作,则免费下载到编译程序时的详细地址就可以。

在Linux OS上,为了更好地运作可执行程序,她们是遵照ELF文件格式的,一般gcc -o test test.c,形成的test文档便是ELF文件格式的,那样就可以运作了,实行elf文件,则核心会应用加载器来分析elf文件并实行。

在Embedded中,假如通电运行,沒有OS系统软件,假如将ELF文件格式的文档烧写进去,包括一些ELF文档的符号表标识符表这类的section,运作遇到这种,便会造成 不成功,假如用objcopy形成纯碎的二进制文件,去祛除符号表这类的section,只将代码段数据信息段保存出来,程序流程就可以一步一步运作。

elf文件里边包括了符号表等。BIN文件是将elf文件中的代码段,数据信息段,也有一些自定的段提取出去制成的一个运行内存的镜像系统。

而且elf文件中代码段数据信息段的部位并并不是它具体的物理学部位。他具体物理学部位是在表格中标识出去的。

the end
免责声明:本文不代表本站的观点和立场,如有侵权请联系本站删除!本站仅提供信息存储空间服务。