Golang在ARM/Linux平台上函数参数的传递

2023-11-19

一.前言

    作为一名初级的嵌入式软件开发从业者,工作中大部分项目以C语言实现。使用C语言来编写代码,通常我们可以预测到编译生成的汇编/机器编码的大致情况,在不同的芯片架构上,有其相应的ABI标准。而近年来逐渐流行起来的Go语言编程,虽然同样语法上和C语言语法都有较为简单的特点,也都是编译型的静态语言,但我们对它在基本类型——函数参数的传递方式就了解很少了。另外,Go语言的函数可以有多个返回值,其底层机制是如何实现的,也需要分析探究一下。

    本文将记录Golang官方编译器生成的可执行文件在ARM/Linux平台上函数传递的分析过程。测试使用的设备是一部安卓手机,在其中安装了Bash、Git、Vim、GDB等开源软件(安装过程请参考https://pan.baidu.com/s/1i5o6Lwh中的《安装记录.docx》),并在C4DROID中找到了可在安卓中执行的GCC编译器,这样就可以在手机上编译Golang编译器了,其版本为1.4.3。之所以选择该版本的Golang编译器,是因为后续版本的Golang编译器的编译可能会依赖现有的Golang编译器,这就会产生一个鸡生蛋、蛋生鸡的问题了。使用TELNET登入手机后,可以查看到,现在已经可以正常工作了:


二.获取当前的栈指针

    我们知道Go语言的函数定义的关键字为func,其返回值可以为空,或者有多个返回值,于是我们猜测它是以栈来传递参数和返回值的,当然事实确实也是如此,那么在分析函数在调用瞬间、函数返回瞬间,其参数和返回值在栈空间上如何分配这个问题之前,我们需要获取当前函数执行的栈指针。但与C语言不同,Go语言并不支持内联汇编,那我们就需要使用其它的方法。

    这个方法也比较直接,使用汇编来实现,参照Golang代码库的一些汇编格式,我们创建了一个nonsafe包,提供了一个Fetchsp()函数:


    当这个函数被引用,生成的可执行文件中该汇编生成的指令为:


    可以看到,我们将栈指针存放在了栈指针的4字节偏移处,这样的就隐含了我们已知函数返回值的方法,后面我们会对其进一步验证。另外,RET伪指令被编译为add pc, lr, #0,可见Golang 1.4.3版本的ARM汇编器仍不完善。

三.简单函数的参数传递和返回值

    有了当前函数的栈指针后,我们就可以直接把函数在调用前后的栈上面的数据使用fmt包中的类printf函数输出查看了。相应的代码为:


    直接编译并执行,可以得到结果:


    虽然如此,我们不能确定Fetchsp()返回的栈针是正确的,这就需要对生成的可执行文件进一步查看,得到在调用Fetchsp()之前的指令地址:


    上图中高亮的部分是我们感兴趣的地方。使用gdb调试,让它在调用Fetchsp()之前停下来:


    这样,我们就可以确定,Fetchsp函数在Golang 1.4.3版本的正确性了。通过对执行结果的仔细分析,可以得出其下结论:

    函数在传递参数时,在当前函数栈指针4字节偏移处开始传参,[sp + 0x4]为函数第一个参数(0x7e0 = 2016),[sp + 0x8]为函数第二个参数(0x7d9 = 2009);当函数返回时,返回参数存放在最后一个参数之后的位置,即[sp+ 0xc](0x3ee = 2016 * 2009 / (2016 + 2009) = 1006)。

四.interface{}作为函数参数的传递

    Go语言中的函数也支持多个、不确定的参数传递,在《Programming In Go》一书的5.2.2.2一节中提到了类型检测,修改过后的函数如下:


    可以看到,函数classifier()除了第一个参数类型是确定的之外,其他参数都是类型和个数不确定的。在C语言中,也有类似的函数,如printf(const char *, …)等。但是我们知道,诸如printf()之类的函数,可变参数部分基本上都是压入栈的,而且没有类型信息,其类型是根据格式化字符串来推测的。而Go语言则支持类型检测,其实现的机制是怎样的呢?

    通过对生成的可执行文件分析,可以确定在调用classifier()函数之前,入栈了四个参数或者说,入栈了四个值:


    除了第一个指针函数外,那么可以说只有三个值用于interface{}的多个参数传递了。于是我们猜测这三个值类似于一个结构体,包含了所有的可变参数的类型信息和其相应的值。经过测试,我们使用下面的代码将其输出:


    这一段代码比较难看,并不是因为写的很复杂,而是因为使用了很多的“强制类型转换”。在Go语言中,指针不参与数学运算,只好将其转化为uintptr类型,再转化为指针并对其解引用。先来看一下执行的结果:


    从上图可以看到,上面解构造的解析可变参数的功能可以得到可变参数的信息,与类型检测得到的参数值很相近,这就说明了上面的解析是正确的。那么,可变参数列表的信息结构是怎样的呢?

    可以确认,一个可变参数列表在栈上传递会有三个值,其中后两个值很可能是相同的,表示为可变参数的个数(在例子中我们传入了6个可变参数),而第一个值指向了一段栈空间,其中会有两倍于可变参数的数据,每两个为一组,其中第一个值为一个指针或标识,用于确定该参数的类型;第二个值为该参数的指针,同样的,该指针指向栈空间,此处存放了该参数的值。其中有一个例外是nil,它的类型和指针都是0。为了进一步确认这一点,可以查看最后一个参数,float32(2016),使用MATLAB可以验证其十六进制的表示为0x44fc0000:


五.总结

    了解Golang语言编译出的可执行文件的函数传参机制,可能对学习Go语言的帮助并不大。另外,这些函数传参的机制在GCCGO编译生成的可执行文件中并不存在。尽管如此,了解一些Golang底层的实现细节,仍然是十分有趣的,也可以略微满足一下我们对Golang的"ABI"探究的心理。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang在ARM/Linux平台上函数参数的传递 的相关文章

  • springMVC基于Session实现动态国际化

    1 在spring配置文件中配置资源文件properties的位置及公共名 下列配置指定的properties文件处于src目录下的resources文件夹中 名字为message info properties

随机推荐

  • Unity 反射绑定UI

    ui的名称和定义的字段名要保持一致 using System using System Collections using System Collections Generic using System Linq using System
  • 计算机f g 盘找不到了,电脑E/F盘符突然不见了怎么办

    随着分区工具的普及 越来越多的人起初自己对硬盘重新界定分区 由于目前这些分区软件和平台不兼容造成再次分区的之后 分区会重叠 这会导致以后使用电脑的之后 会时常丢失一个或几个分区 1 首先开启磁盘管理 打开的步骤 右击桌面的计算机界面 管理
  • Compiler- volatile关键字

    为了直观的感受编译器为程序所做的编译优化 我们通过以下的C 程序来进行演示 只能体现编译优化的一小部分hh 请大家预测一下下面代码的输出结果 include
  • didChangeDependencies什么时候被调用

    参考 我先上一个Demo 这个Demo也就是网上面传的比较广的 我们就以这个来举例子说明网上的结论 父级结构中的层级发生变化时didChangeDependencies被调用 这个结论为什么是不完整 import package flutt
  • (2022 COLING)Context-Tuning情景化提示

    论文题目 Title Context Tuning Learning Contextualized Prompts for Natural Language Generation 研究问题 Question 自然语言生成 生成长文本 研究动
  • 5G+边缘计算,对于VR移动电竞游戏来说意味着什么?

    这是一个5G 边缘计算意义的问题 其实对VR游戏 特别是电竞游戏 这类大流量 低延迟的应用服务来说 大多数人第一时间想到的优点会是高达1Gbps s的数据传输速度 虽然事实确实如此 但并不是全部 从技术上讲 无线传输性能的进步能给我们带来更
  • element 可移动dialog

    import Vue from vue v dialogDrag 弹窗拖拽属性 Vue directive dialogDrag bind el binding vnode oldVnode const dialogHeaderEl el
  • ES6数组方法总结

    1 forEach let array 1 2 3 4 array forEach item index array gt console log item forEach会遍历数组 没有返回值 不允许在循环体内写return 不会改变原来
  • 小程序自定义导航栏返回主页

    小程序自定义导航栏返回主页 效果图 在app js中获取状态栏的高度statusBarHeight 自定义组件navbar wxml 自定义组件navbar wxss 自定义组件navbar json 自定义组件navbar js 调用组件
  • 睿智的目标检测60——Tensorflow2 Focal loss详解与在YoloV4当中的实现

    睿智的目标检测60 Tensorflow2 Focal loss详解与在YoloV4当中的实现 学习前言 什么是Focal Loss 一 控制正负样本的权重 二 控制容易分类和难分类样本的权重 三 两种权重控制方法合并 实现方式 学习前言
  • 如何用Stata完成(shui)一篇经济学论文(九):画线性图

    目录 普通线性图 多图并列 一图多线 什么 为什么只讲线形图 因为我只用过线形图 言归正传 我的确只用过线形图 说了跟没说一样 Stata画图给我的感觉一直都是很复杂 很多命令 我觉得好像也没有很多的地方要画图 一般就画个线形图看看趋势 如
  • 2023年,想要年赚百万必懂的道理?

    1 一个人只有经历过风雨沧桑 才会明白一个道理 这个世界最大的监狱就是人的思维 而越狱最好的方式就是人的觉醒 2 人活明白了就会知道 不要拿自己去跟别人比较 后果不是忘记了自己 就是让自己失落 3 如果一个人不向内求 总是拿自己的一点优势去
  • 机器学习可解释性

    20210508 随笔 后续有时间在对概念有了深入理解之后再进行整理 0 引言 今天不想写论文 就想起了之前关注的一个内容 机器学习的可解释性 在之前的时候 或多或少了解这个东西 发现他更多的是从特征的角度来解释 这个特征怎么影响了模型 但
  • python实现货币转换

    实现美元与人民币的转换 2022 4 16 1美元 6 37人民币 moneyStr input 请输入带有标志 RMB rmb USD usd 的钱数 if moneyStr 3 in RMB rmb dollar eval moneyS
  • [java]线程安全问题

    线程安全问题产生有五个产生原因 1 线程的随机调度和抢占式执行 就是这个机制使得线程安全问题产生 2 代码结构 多个线程对同一个变量进行修改 3 原子性 修改操作的是可拆分的 导致脏读问题 4 内存可见性问题 一个线程读一个线程写 5 指令
  • 自定义屏幕保护

    一 设计器页面及代码 Form2 Designer cs namespace 自定义屏保 partial class Form2
  • 直接执行:sudo su 就可以了。

    直接执行 sudo su 就可以了
  • GD32F405RGT6定时器固件库(所有定时器的配置(12个))

    GD32F405RGT6所有定时器的配置 GD32F4XXX系列拥有12个定时器 定时器的类型如下表 一般我们可以根据定时器的作用以及类型选取合适的定时器 在这次对GD的单片机而言我就将它所拥有的12个定时器撸了一遍 通用定时器以及高级定时
  • 虚拟ip、浮动ip

    虚拟ip 虚拟 IP 是一个虚拟的 软件定义的 IP 地址 它可以用来在网络中隐藏真实的 IP 地址 或者在多个物理服务器之间共享一个 IP 地址 虚拟 IP 通常用于网络负载均衡 高可用性和网络安全等方面 Docker 在Docker中
  • Golang在ARM/Linux平台上函数参数的传递

    一 前言 作为一名初级的嵌入式软件开发从业者 工作中大部分项目以C语言实现 使用C语言来编写代码 通常我们可以预测到编译生成的汇编 机器编码的大致情况 在不同的芯片架构上 有其相应的ABI标准 而近年来逐渐流行起来的Go语言编程 虽然同样语