汇编程序调用 C 程序详解

文章目录

1. ATPCS 规则

2. 汇编和C程序传递参数

汇编程序向 C 程序的函数传递参数

C 程序返回结果给汇编程序

代码示例

3. C 函数使用栈

4. C 语言中读写寄存器


在嵌入式开发中,经常需要在 C 程序和 ARM 汇编程序之间进行相互调用。为了保证这些调用的正确性和兼容性,ARM 提出了 ATPCS(ARM-Thumb Procedure Call Standard)规范。该规范定义了函数调用时的基本规则和寄存器使用约定。

1. ATPCS 规则

ATPCS 是 ARM 和 THUMB 指令集过程序调用的规范,它规定了函数调用时如何传递参数、如何获取参数以及如何返回值。

寄存器使用规则:

  • 在函数中,通过寄存器 R0-R3 传递参数,被调用的函数在返回前无须恢复寄存器 R0-R3 的内容。
  • 在函数中,通过寄存器 R4~R11 保存局部变量。
  • 寄存器 R12 用作函数间的 scratch 寄存器,即临时寄存器。
  • 寄存器 R13 用作堆栈指针,即 SP(Stack Pointer),在函数中寄存器 R13 不能用于其他用途。寄存器 SP 在进入函数时的值和退出函数时的值必须相等。
  • 寄存器 R14 用于存放返回地址,即 LR(Link Register),它用于存放调用函数的返回地址。函数返回时,CPU 会跳转到 LR 指向的地址继续执行调用函数。
  • 寄存器 R15 是程序计数器,即 PC(Program Counter),用于指向当前指令的地址,指令执行时自动递增。

示例代码

假设一个 C 函数 add,它接收两个整数参数并返回它们的和:

// add.c
int add(int a, int b) {return a + b;
}

汇编代码示例:

.global main.extern add  // 声明外部函数 addmain:MOV R0, #5       // 第一个参数 a = 5MOV R1, #10      // 第二个参数 b = 10BL add           // 调用 C 函数 addMOV R7, #1       // syscall: exitSWI 0            // 软件中断,退出程序
  • .global main:声明 main 函数为全局符号,以便链接器能够识别和连接它。
  • .extern add:声明外部符号 add,表明 add 函数是在外部文件中定义的。
  • MOV R0, #5MOV R1, #10:将值 5 和 10 分别加载到寄存器 R0 和 R1 中,这两个寄存器用于传递参数 a 和 b。
  • BL add:调用 C 函数 add。BL 指令(Branch with Link)会将当前 PC 值存储到 LR 中,并跳转到 add 函数的地址。
  • MOV R7, #1SWI 0:用于执行软件中断,退出程序。

函数调用前准备:

  • 在调用 add 函数之前,需要将参数准备好。根据 ATPCS 规则,前四个参数依次存放在 R0、R1、R2 和 R3 寄存器中。

函数调用:

  • 使用 BL add 指令调用 add 函数。BL 指令会保存返回地址到 LR 寄存器,并跳转到 add 函数的入口地址。

函数返回:

  • add 函数执行完毕后,返回值存放在 R0 寄存器中,CPU 会从 LR 寄存器中读取返回地址并跳转回调用函数继续执行。

2. 汇编和C程序传递参数

汇编程序向 C 程序的函数传递参数

在汇编程序中调用 C 函数时,通常需要传递参数给 C 函数。ATPCS 规范规定了如何传递参数的方法:

  • 参数少于等于 4 个时:
    • 使用寄存器 R0 至 R3 来进行参数传递。第一个参数传递到 R0,第二个参数传递到 R1,依此类推。如果只有一个参数,它将存储在 R0 中;如果有两个参数,第一个参数在 R0 中,第二个参数在 R1 中,以此类推,直到 R3。
  • 参数大于 4 个时:
    • 前四个参数按照上述方法传递。剩余的参数通过堆栈传递。调用函数时,调用者需要将这些多余参数压入堆栈。入栈的顺序是从最后一个参数开始,即最后一个参数先入栈。

C 程序返回结果给汇编程序

C 函数执行完毕后,需要将结果返回给调用它的汇编程序。返回值的传递方法也有一定的规范:

  • 结果为一个 32 位的整数时:

    • 通过寄存器 R0 返回。函数的返回值会存储在 R0 寄存器中。
  • 结果为一个 64 位整数时:

    • 通过寄存器 R0 和 R1 返回。64 位整数的低 32 位存储在 R0 中,高 32 位存储在 R1 中。
  • 结果为一个浮点数时:

    • 通过浮点寄存器 f0、d0 或 s0 返回。浮点数结果存储在浮点寄存器中。
  • 结果为一个复杂的浮点数时:

    • 通过寄存器 f0fN 或者 d0dN 返回。复杂浮点数的每一部分可能存储在不同的浮点寄存器中。
  • 对于传递更多位数的结果:

    • 通过调用者的不同寄存器来传递,具体依据实际情况和编译器的规定。

代码示例

假设一个 C 函数 multiply,它接收两个整数参数并返回它们的乘积:

// multiply.c
int multiply(int x, int y) {return x * y;
}

在汇编程序中调用这个 C 函数并传递参数 67

.global main
.extern multiply  // 声明外部函数 multiplymain:MOV R0, #6       // 第一个参数 x = 6MOV R1, #7       // 第二个参数 y = 7BL multiply      // 调用 C 函数 multiplyMOV R7, #1       // syscall: exitSWI 0            // 软件中断,退出程序
  • MOV R0, #6MOV R1, #7:将值 6 和 7 分别加载到寄存器 R0 和 R1 中。这两个寄存器用于传递参数 x 和 y。
  • BL multiply:调用 C 函数 multiply。BL 指令会将当前 PC 值存储到 LR 中,并跳转到 multiply 函数的地址。
  • MOV R7, #1SWI 0:用于执行软件中断,退出程序。

调用完 multiply 函数后,乘积结果会存储在 R0 中。通过这种方式,可以在汇编程序和 C 程序之间进行参数传递和结果返回。

3. C 函数使用栈

在 C 程序和 ARM 汇编程序的函数调用过程中,使用栈(stack)是非常重要的一部分。栈的主要作用有两个:保存现场/上下文,传递参数。

保存现场/上下文

  1. 保存现场: 在程序执行过程中,当发生函数调用时,需要暂时保存当前的执行现场,以便函数执行完毕后可以恢复到调用前的状态。这个保存的过程通常称为“保存现场”。

    例如,当 CPU 运行到某些寄存器时(如 R0~R3,LR 等),这些寄存器中可能存有重要的数据。如果直接跳转到函数去执行,而不保存这些寄存器的数据,函数执行过程中对这些寄存器的操作就会破坏原有的数据。因此,需要先将这些寄存器的数据暂时存放到栈中。

  2. 保存上下文: 保存上下文的过程和保存现场类似。上下文指的是当前程序执行的状态,包括寄存器内容、程序计数器等信息。当函数调用发生时,需要将这些上下文信息保存到栈中,以便函数执行完毕后能够准确地恢复到调用前的状态。

    因此,在函数调用之前,应该将这些寄存器的数据临时保存到栈中,等待函数执行完毕返回后,再恢复现场。这样 CPU 就可以正确地继续执行后续的指令。

传递参数

  1. 传递参数: 当参数数量大于 4 个时(不包括第 4 个参数),第 4 个参数后的参数就保存在栈中。

    传递参数的过程通常如下:

    • 当被调用函数的参数超过 4 个时,前四个参数通过寄存器 R0~R3 传递,剩余的参数依次压入栈中。
    • 在函数调用过程中,调用者会将这些参数按顺序压入栈中,函数被调用时通过从栈中读取这些参数来进行处理。

代码示例

假设一个 C 函数 func,它接收五个整数参数并返回它们的和:

// func.c
int func(int a, int b, int c, int d, int e) {return a + b + c + d + e;
}

在汇编程序中调用这个 C 函数并传递参数 1, 2, 3, 4, 5

.global main
.extern func  // 声明外部函数 funcmain:MOV R0, #1       // 第一个参数 a = 1MOV R1, #2       // 第二个参数 b = 2MOV R2, #3       // 第三个参数 c = 3MOV R3, #4       // 第四个参数 d = 4PUSH {R4}        // 保存现场,R4 用于传递第 5 个参数MOV R4, #5       // 第五个参数 e = 5BL func          // 调用 C 函数 funcPOP {R4}         // 恢复现场MOV R7, #1       // syscall: exitSWI 0            // 软件中断,退出程序
  • MOV R0, #1MOV R3, #4:将前四个参数加载到寄存器 R0~R3 中。
  • PUSH {R4}:将寄存器 R4 的内容压入栈中,以便在使用 R4 传递第 5 个参数时,不破坏原有的内容。
  • MOV R4, #5:将第 5 个参数加载到寄存器 R4 中。
  • BL func:调用 C 函数 func。BL 指令会将当前 PC 值存储到 LR 中,并跳转到 func 函数的地址。
  • POP {R4}:从栈中恢复寄存器 R4 的内容,恢复现场。
  • MOV R7, #1SWI 0:用于执行软件中断,退出程序。

通过使用栈,可以确保在函数调用过程中,参数能够正确传递,并且在函数调用完毕后,能够正确恢复程序的执行现场,从而保证程序的正常运行。

4. C 语言中读写寄存器

在嵌入式系统开发中,经常需要访问芯片上某些特定模块的寄存器。这些寄存器并不位于 CPU 内部,而是在芯片的外部模块中。我们通过指针访问这些寄存器的方式与访问普通内存一样。

寄存器地址与指针

每个寄存器都有一个固定的地址。通过定义一个指向这个地址的指针,可以对寄存器进行读写操作。

例如,假设有一个寄存器 CCM_CCGR1,它的地址为 0x20C406C,我们可以定义一个指向该地址的指针,并使用 volatile 关键字告知编译器该寄存器的值可能会被外部硬件或其他程序修改,不应进行优化。

示例代码

volatile unsigned int *CCM_CCGR1 = (volatile unsigned int *)(0x20C406C);

这里的 volatile unsigned int * 表示一个指向 unsigned int 类型的指针,并且这个指针指向的数据是 volatile 类型,即数据可能会被外部因素修改。

读写寄存器

通过这个指针,我们可以进行读写操作:

  • 读寄存器
val = *CCM_CCGR1;

这行代码将读取 CCM_CCGR1 寄存器的值,并存储到变量 val 中。

  • 写寄存器
*CCM_CCGR1 |= (3 << 30);
  • *CCM_CCGR1 通过指针访问寄存器 CCM_CCGR1
  • |= (3 << 30) 是一个位操作,将 3 左移 30 位,得到一个值,这个值在二进制中表示为 11(即第 31 位和第 30 位都为 1),然后将这个值与 CCM_CCGR1 的当前值进行按位或操作,结果将这两位设置为 1。

通过上述方法可以在 C 语言中方便地对寄存器进行读写操作,确保对硬件寄存器的正确访问和修改。这种方法广泛应用于嵌入式系统开发中,特别是在处理器需要与外部设备进行通信或控制时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/46059.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合Java Mail实现发送邮件

SpringBoot整合Java Mail实现发送邮件 实现 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>发送邮件配置 这里使用qq邮箱发送邮件&#xff0c;需要…

Leetcode3200. 三角形的最大高度

Every day a Leetcode 题目来源&#xff1a;3200. 三角形的最大高度 解法1&#xff1a;模拟 枚举第一行是红色还是蓝色&#xff0c;再按题意模拟即可。 代码&#xff1a; /** lc appleetcode.cn id3200 langcpp** [3200] 三角形的最大高度*/// lc codestart class Solutio…

java.sql.SQLException: Before start of result set

情况描述&#xff0c;在通过JDBC连接数据库时&#xff0c;想直接判断获取的值是否存在&#xff0c;运行时报错。 翻译&#xff1a; 在开始结果集之前 报错截图 解决问题的方法&#xff1a;对结果集ResultSet进行操作之前&#xff0c;一定要先用ResultSet.next()将指针移动至…

RAG 效果提升的最后一步—— 微调LLM

如果说&#xff0c;rerank能够让RAG的效果实现百尺竿头更进一步&#xff0c;那么LLM微调应该是RAG效果提升的最后一步。 把召回的数据&#xff0c;经过粗排&#xff0c;重排序后&#xff0c;送给模型&#xff0c;由模型最后总结答案。LLM的确已经是RAG的最后一步了。 这里还是会…

C#可空类型与数组

文章目录 可空类型NULL合并运算符&#xff08;??&#xff09;数组数组声明数组初始化数组赋值数组访问多维数组交错数组数组类数组类的常用属性数组类的常用方法 可空类型 C#提供了一种特殊的数据类型&#xff0c;nullable类型&#xff08;可空类型&#xff09;&#xff0c;可…

<数据集>夜间车辆识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;5000张 标注数量(xml文件个数)&#xff1a;5000 标注数量(txt文件个数)&#xff1a;5000 标注类别数&#xff1a;8 标注类别名称&#xff1a;[car, pedestrian, traffic light, traffic sign, bicycle, bus, truck…

vue学习day08-v-model详解、sync修饰符、ref和$refs获取dom组件、Vue异步更新和$nextTick

25、v-model详解 &#xff08;1&#xff09;v-model原理 1&#xff09;原理: v-model本质上是一个语法糖&#xff0c;比如&#xff1a;在应用于输入框时&#xff0c;就是value属性与input事件的合写。 2&#xff09;作用 ①数据变&#xff0c;视图变 ②视图变&#xff0c…

wordpress制作主题步骤

WordPress主题的制作是一个涉及多个步骤的过程&#xff0c;旨在控制网站的外观、布局和功能。以下是一个详细的WordPress主题制作指南&#xff1a; 一、准备工作 安装WordPress&#xff1a;首先&#xff0c;确保你已经在你的服务器上安装了WordPress。WordPress可以从其官方网…

短链接服务Octopus-搭建实战

[WARNING] The POM for cn.throwx:octopus-contract:jar:1.0-SNAPSHOT is missing, no dependency information available 解决方案&#xff1a; cd octopus-contract/ mvn install -------------- ➜ octopus-server git:(master) ✗ mkdir -p /data/log-center/octopus/s…

DockerCompose介绍,安装,使用

DockerCompose 1、Compose介绍 将单机服务-通过Dockerfile 构建为镜像 -docker run 成为一个服务 user 8080 net 7000 pay 8181 admin 5000 监控 .... docker run 单机版、一个个容器启动和停止问题&#xff1a; 前面我们使用Docker的时候&#xff0c;定义 Dockerfil…

Lottery 分布式抽奖(个人向记录总结)

1.搭建&#xff08;DDDRPC&#xff09;架构 DDD——微服务架构&#xff08;微服务是对系统拆分的方式&#xff09; &#xff08;Domain-Driven Design 领域驱动设计&#xff09; DDD与MVC同属微服务架构 是由Eric Evans最先提出&#xff0c;目的是对软件所涉及到的领域进行建…

mysql笔记(表导出文件,文件导入表)

遇见权限问题1: cat /etc/my.cnf加入[mysqld] secure_file_priv ""遇见目录错误2:因为 MySQL 服务器没有权限在根目录下创建文件。你可以尝试将文件导出到一个 MySQL 服务器有权限写入的目录下&#xff0c;例如 MySQL 数据目录或 /tmp目录。sudo chmod 755 /path/to…

开源科学工程技术软件

目录 0 参考链接 1 Silx 2 Klampt 3 参数化三维3D软件Dune 3D 4 GPS日志文件查看器GPXSee 5 三维3D软件Chili3D 6 集成电路设计软件XicTools 7 天文学软件Cosmonium 8 计算流体力学软件FluidX3D 9 点云处理软件CloudCompare 10 野外火灾建模软件WindNinja 11 电子设…

.NET MAUI开源架构_2.什么是 .NET MAUI?

1.什么是.NET MAUI&#xff1f; .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架&#xff0c;用于使用 C# 和 XAML 创建本机移动和桌面应用。使用 .NET MAUI&#xff0c;可从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。 .NET MAUI 是一款…

使机器人在执行任务时更加稳定

为了使机器人在执行任务时更加稳定&#xff0c;调整参数时需要考虑多个因素&#xff0c;如步态、速度、角度等。这些参数的调整需要基于实际环境、任务需求和机器人自身的物理特性。以下是一些具体的调整建议&#xff1a; 1. 调整步态和步高 gait_type3; step_height0.03;步态…

iOS热门面试题(四)

问题一&#xff1a;请详细解释iOS中的Core Data框架&#xff0c;包括它的工作原理、优势、以及在实际项目中的应用场景。 Core Data框架概述&#xff1a; Core Data是iOS和macOS开发中一个强大的数据持久化框架&#xff0c;它允许开发者以面向对象的方式管理应用数据。Core D…

pytorch中一些最基本函数和类

1.Tensor操作 Tensor是PyTorch中最基本的数据结构&#xff0c;类似于NumPy的数组&#xff0c;但可以在GPU上运行加速计算。 示例&#xff1a;创建和操作Tensor import torch# 创建一个零填充的Tensor x torch.zeros(3, 3) print(x)# 加法操作 y torch.ones(3, 3) z x y pr…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(三)-机上无线电接入节点无人机

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

BGP笔记的基本概要

技术背景&#xff1a; 在只有IGP&#xff08;诸如OSPF、IS-IS、RIP等协议&#xff0c;因为最初是被设计在一个单域中进行一个路由操纵&#xff0c;因此被统一称为Interior Gateway Protocol&#xff0c;内部网关协议&#xff09;的时代&#xff0c;域间路由无法实现一个全局路由…

Hadolint提升Dockerfile的质量和安全性 —— 筑梦之路

https://github.com/hadolint/hadolint hadolint 在线版本&#xff1a;https://hadolint.github.io/hadolint/ FROM debian RUN export node_version"0.10" \ && apt-get update && apt-get -y install nodejs"$node_verion" COPY packa…