S3C2440④ | ARM-THUMB子程序调用规则ATPCS

2020-07-16 10:57:19 浏览数 (1)

在GPIO的实验中,我们首先编写汇编程序操作寄存器点亮LED,奈何汇编语言可读性和可移植性太差,所以编写启动代码,设置栈顶指针SP,然后调用C语言中的main函数,转入C语言的世界,由C语言访问控制寄存器,点亮LED,程序的可读性和可移植性大大提高,那么,我们可曾想过,在汇编语言中是如何来调用C语言入口函数main呢?

其实,对于ARM处理器,在ARM指令集汇编程序和THUMB指令集汇编程序中制定了子程序调用的规则 —— ATPCS规则,这个规则包括:

  • 寄存器使用规则
  • 数据栈使用规则
  • 参数传递规则

1.寄存器使用规则

ARM处理器中有R0-R15这16个寄存器,每个寄存器都由ATPCS规则规定了用途,并根据其用途规定了别名,如下表所示:

寄存器

别名

用途

R15

pc

程序计数器

R14

lr

链接寄存器(用于保存子程序返回地址)

R13

sp

数据栈指针(指向栈顶)

R12

ip

子程序内部调用的scratch寄存器

R11

v8

ARM状态局部变量寄存器8

R10

v7

ARM状态局部变量寄存器7

R9

v6

ARM状态局部变量寄存器6

R8

v5

ARM状态局部变量寄存器5

R7

v4

ARM状态局部变量寄存器4

R6

v3

ARM状态局部变量寄存器3

R5

v2

ARM状态局部变量寄存器2

R4

v1

ARM状态局部变量寄存器1

R3

a4

参数/结果/scratch寄存器4

R2

a3

参数/结果/scratch寄存器3

R1

a2

参数/结果/scratch寄存器2

R0

a1

参数/结果/scratch寄存器1

总结如下:

  • 子程序间通过寄存器R0-R3传递参数和返回结果
  • 子程序中通过寄存器R4-R11保存局部变量
  • 寄存器R12用作子程序间scratch寄存器;
  • 寄存器R13用作数据栈指针,指向栈顶;
  • 寄存器R14用作链接寄存器,保存子程序的返回地址
  • 寄存器R15用作程序计数器;

2. 数据栈使用规则

ATPCS规定数据栈为FD类型Full Descending,满递减),即栈指针指向栈顶元素,并且向内存地址减小的方向增长,操作的时候对数据栈的操作是8字节对齐的,使用stmdb/ldmia批量内存访问指令来操作FD数据栈。

FD类型的数据栈具体是这样操作的:

  • 保存内容时先递减SP指针,再保存数据
  • 恢复数据时先获得数据,再递增SP指针

3.参数传递规则

  • 函数调用传递参数时,如果不超过4个,使用R0-R3依次传递,如果超过4个,剩余的参数通过数据栈传递
  • 函数返回传递结果时,使用R0-R3依次传递

实验 —— 汇编调用函数时传递参数实验

1. 实验目的

在汇编语言中调用函数并且传递参数。

2. 实验内容

main函数定义参数,如果传入参数是1,点亮第一个LED,如果传入参数是2,点亮第二个LED。

3. 实验代码

3.1.启动代码

代码语言:javascript复制
@ brief:    S3C2440启动文件
@ author:    mculover666

.text
.global    _start

_start:
    @ 关闭看门狗
    LDR R0,=0x53000000
    MOV R1,#0
    STR R1,[R0]

    @ 设置栈顶指针SP(从Nand启动)
    LDR SP,=4096

    @ 传递参数1调用led_on,点亮第一个LED
    LDR R0,=1
    BL led_on

    @ 传递参数100000,调用delay,延时
    LDR R0,=100000
    BL delay

    @ 传递参数2调用led_on,点亮第二个LED
    LDR R0,=2
    BL led_on

    @ 程序暂停
halt:
    B   halt

3.2.C代码

代码语言:javascript复制
void delay(volatile int xms)
{
    while(xms--);
}

int led_on(int led)
{

    if(led == 1)
    {
        /* 设置GPFCON寄存器,配置GPF4引脚为输出模式 */
        *(unsigned int *)0x56000050 &= ~(3<<(2*4));
        *(unsigned int *)0x56000050 |= 1<<(2*4);

        /* 设置GPFDAT寄存器,GPF4输出低电平,点亮LED */
        *(unsigned int *)0x56000054 &= ~(1<<4);     
    }
    else if(led == 2)
    {
        /* 设置GPFCON寄存器,配置GPF4引脚为输出模式 */
        *(unsigned int *)0x56000050 &= ~(3<<(2*5));
        *(unsigned int *)0x56000050 |= 1<<(2*5);

        /* 设置GPFDAT寄存器,GPF4输出低电平,点亮LED */
        *(unsigned int *)0x56000054 &= ~(1<<5);     
    }

    return 0;
}

3.3.编译

代码语言:javascript复制
TARGET = led_blink

CFLAGS = -Wall    #输出所有warning

$(TARGET).bin:$(TARGET).elf
    arm-linux-objcopy -O binary -S $(TARGET).elf $(TARGET).bin

#注意:启动文件必须第一个链接
$(TARGET).elf:start.o $(TARGET).o
    arm-linux-ld -Ttext 0 start.o $(TARGET).o -o $(TARGET).elf

$(TARGET).o:$(TARGET).c
    arm-linux-gcc -c $(TARGET).c $(CFLAGS) -o $(TARGET).o
start.o:start.s
    arm-linux-gcc -c start.s $(CFLAGS) -o start.o

clean:
    rm -rf *.o *.elf *.bin

download_to_nand:
    #下载到nand flash
    oflash 0 1 0 0 0 $(TARGET).bin 

4.下载运行

程序开始运行,第一个LED点亮:

延时1s左右,第二个LED也被点亮:

5.实验总结

通过本实验掌握了ATPCS规则在实际开发中的使用,在调用main函数时使用R0寄存器传递参数,总结如下:

  • ARM处理器中子程序调用规则由ATPCS制定,包括寄存器使用规则,数据栈使用规则,参数传递规则;
  • R0-R3可以传递参数/结果,R4-R11可以保存局部变量,R13是数据栈指针SP,R14保存子程序返回地址;
  • ATPCS中数据栈是FD类型,即满递减类型;
  • ATPCS中参数传递使用R0-R3依次传递,如果大于4个参数,剩余的使用数据栈传递。

0 人点赞