链接脚本
什么是链接脚本?
- 程序链接时的参考文件,目的是描述输入文件中各段应该怎样被映射到输出文件,以及程序运行时的内存布局等
为什么需要链接脚本?
- 程序运行在OS之上时,不需要显式指定链接脚本,默认使用OS相关链接命令内置的脚本,可避免出错
- 程序运行在OS之下时,或者本身就是OS时,链接脚本就很重要。要根据实际环境去编写链接脚本才不容易出错
示例代码
ENTRY(helloworld) SECTIONS { . = 0x00000000; .text :{ *(.text) } . = ALIGN(32); .data :{ *(.data) } . = ALIGN(32); .bss: { *(.bss) }
.haha: {
*(.haha)
} }
- 点号(.):位置计数器,代表当前位置。可以赋值。会自动动态增加
- text, data, bss分别代表代码段、数据段和bss段,其实名字可随意命名,就像haha段
- *(.text)的意思是所有目标文件的代码段都被链接到这个区域,也可以特别地指定某个目标文件出现在代码段的最前面
- ALIGN(N):数据和代码段对齐。例如 . = ALIGN(4)就是使位置计数器向高地址取最近4字节的整数倍。其实你也可以不使用,像上面那样,造成haha段地址紧跟着bss段,这时可能就会造成字节不对齐
- ENTRY()命令是指定整个程序的入口地址,例如ENTRY(helloworld)就是指定hello world函数为整个程序入口地址。也就是说,helloworld就是出现在0x00000000地址上的那个函数
一些说明
- 在链接操作中,参数-Ttext的作用是指定该程序的运行基地址。也就是说,链接工具将目标文件链接到某地址上,并从此处执行
- -verbose参数可以用来查看链接脚本
ARM汇编语言
注意事项
- ARM的通用寄存器R0 ~ R12随你怎么用
- R13负责保存堆栈地址,SP
- R14负责保存程序返回地址,LR
- R15负责记录程序地址,PC
- CPSR程序状态寄存器,判断程序是否溢出、负数,也包含当前处理器模式
示例代码
.arch armv7 # 选择目标体系结构,这里指定编译后生成armv7体系结构的代码 .global helloworld # 若想在外部文件里引用函数或变量,就要用.global声明,相当于C语言extern .equ REG_FIFI, 0x50000020 # 相当于C语言宏定义,这里是指串口地址 .text # 从当前位置开始的内容被归并到代码段中 .align 2 # 在ARM下,.align后面的数是以幂出现的,这里指按4字节对齐 helloworld: ldr r1, =REG_FIFO # 将常量写入寄存器中,即将串口地址存储到r1中 adr r0, .L0 # 将相对地址写入寄存器中,这里使r0指向helloworld字符串的首地址 .L2: ldrb r2, [r0], #0x1 # b的意思是ldr一个字节的数据,这里意思是把r0的内容的一个字节写到r2,然后r0值加1。即使r2指向'h',r0指向'e' str r2, [r1] # 将寄存器值存储到另一个寄存器所指向的内存地址中。这里把r2的值存到r1中,即把'h'通过串口打印到屏幕上 cmp r2, #0x0 # 将两个数相减,影响CPSR的零标志位,从而影响判断条件的执行 bne .L2 # 如果r2 != 0,跳到.L2,即执行循环。 不等于0说明还没有到达字符串结尾,因为字符串最后是'\0' .L1: b .L1 .align 2 .L0: .ascii "helloworld\n\0" # 用于在内存中定义字符串,这里定义“helloworld\n”字符串
- 伪指令不一定以.开头,例如ldr, adr
- 伪指令与编译器相关,不同编译器的伪指令集可能不同,这里是gcc编译器,换了一种编译器不一定能完全兼容
混合编程
说明
- 汇编语言和C语言混合编程要遵守约定的标准,叫过程调用标准(Procedure Call Standard),简称PCS
- ARM过程调用标准叫APCS
示例代码
文件start.s:
.arch armv7 .global _start .equ REG_FIFO, 0x50000020 .text .align 2 _start: ldr r0, =REG_FIFO # r0, r1这两个值就是传给helloworld的参数 adr r1, .L0 bl helloworld # bl的作用和b医院,只是多了保存程序返回地址的功能 .L1: b .L1 .align 2 .L0: .ascii "helloworld\n\0"
文件helloworld.c:
int helloworld(unsigned int *addr, const char *p) { while(*p) { *addr = *p++; }; return 0; }
- AAPCS中规定,发生函数调用时,函数的参数保存在ARM核心寄存器的R0 ~ R3中,如果参数多余3个,则剩下的参数保存在堆栈中
- 在代码中,调用helloworld之前,给R0和R1赋的值就是传给helloworld的参数
- 因此,helloworld返回int型值,这个返回值会保存到R0中,如果返回的是64位的值,将由R0,R1同时保存
- 最后编译的时候使用命令
arm-elf-ld -e _start -Ttext 0x0 start.o hello world.o -o helloworld
运行成功,这里-e命令是说以_start函数为程序入口函数,也可以在链接脚本里用ENTRY(_start)指定。