热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

i386amd64架构下获取可变参数的差异

偶然看到正面这篇文章:https:blog.nelhage.com201010amd64-and-va_arg发现使用可变参数的时候会有个坑:i386和

偶然看到正面这篇文章:https://blog.nelhage.com/2010/10/amd64-and-va_arg/

发现使用可变参数的时候会有个坑:i386和amd64架构 它们获取可变参数的方式是不同的。

Amd64 and Va_arg

OCT 3RD, 2010

A while back, I was poking around LLVM bugs, and discovered, to my surprise, that LLVM doesn't supportthe va_arg intrinsic, used by functions to accept multiple arguments, at all on amd64. It turns out that clang and llvm-gcc, the compilers that backend to LLVM, have their own implementations in the frontend, so this isn't as big a deal as it might sound, but it was still a surprise to me.

Figuring that this might just be something no one got around to, and couldn't actually be that hard, I pulled out my copy of the amd64 ABI specification, figuring that maybe I could throw together a patch and fix this issue.

Maybe half an hour of reading later, I stopped in terror and gave up, repenting of my foolish ways to go work on something else. va_arg on amd64 is a hairy, hairy beast, and probably not something I was going to hack together in an evening. And so instead I decided to blog about it.

The problem: Argument passing on amd64

On i386, because of the dearth of general-purpose registers, the calling convention passes all arguments on the stack. This makes the va_arg implementation easy – A va_list is simply a pointer into the stack, and va_arg just adds the size of the type to be retrieved to the va_list, and returns the old value. In fact, the i386 ABI reference simply specifies va_arg in terms of a single line of code:

#define va_arg(list, mode) ((mode *)(list = (char *)list + sizeof(mode)))[-1]

On amd64, the problem is much more complicated. To start, amd64 specifies that up to 6 integer arguments and up to 8 floating-point arguments are passed to functions in registers, to take advantage of amd64's larger number of registers. So, for a start, va_arg will have to deal with the fact that some arguments may have been passed in registers, and some on the stack.

(One could imagine simplifying the problem by stipulating a different calling convention for variadic functions, but unfortunately, for historical reasons and otherwise, C requires that code be able to call functions even if their prototype is not visible, which means the compiler doesn't necessarily know if it's calling a variadic function at any given call site. [edited to add: caf points out in the comments that C99 actually explicitly does not require this property. But I speculate that the ABI designers wanted to preserve this property from i386 because it has historically worked, and so existing code depended on it]).

That's not all, however. Not only can integer arguments be passed by registers, but small structs (16 bytes or fewer) can also be passed in registers. A sufficiently small struct, for the purposes of the calling convention, is essentially broken up into its component members, which are passed as though they were separate arguments – unless only some of them would fit into registers, in which case the whole struct is passed on the stack.

So va_arg, given a struct as an argument, has to be able to figure out whether it was passed in registers or on the stack, and possibly even re-assemble it into temporary space.

The implementation

Given all those constraints, the required implementation is fairly straightforward, but incredibly complex compared to any other platform I know of.

To start, any function that is known to use va_start is required to, at the start of the function, save all registers that may have been used to pass arguments onto the stack, into the "register save area", for future access by va_start and va_arg. This is an obvious step, and I believe pretty standard on any platform with a register calling convention. The registers are saved as integer registers followed by floating point registers. As an optimization, during a function call, %rax is required to hold the number of SSE registers used to hold arguments, to allow a varargs caller to avoid touching the FPU at all if there are no floating point arguments.

va_list, instead of being a pointer, is a structure that keeps track of four different things:

typedef struct {unsigned int gp_offset;unsigned int fp_offset;void *overflow_arg_area;void *reg_save_area;
} va_list[1];

reg_save_area points at the base of the register save area initialized at the start of the function. fp_offsetand gp_offset are offsets into that register save area, indicating the next unused floating point and general-purpose register, respectively. Finally, overflow_arg_area points at the next stack-passed argument to the function, for arguments that didn't fit into registers.

Here's an ASCII art diagram of the stack frame during the execution of a varargs function, after the register save area has been established. Note that the spec allows functions to put the register save area anywhere in its frame it wants, so I've shown potential storage both above and below it.

| ... | [high addresses]
+----------------+
| argument |
| passed |
| on stack (2) |
&#43;----------------&#43; <---- overflow_arg_area
| argument |
| passed |
| on stack (1) |
&#43;----------------&#43;
| return address |
&#43;----------------&#43;
| ... | (possible local storage for func)
&#43;----------------&#43;
| %xmm15 | \
&#43;----------------&#43; |
| %xmm14 | | ___
&#43;----------------&#43; | |
| ... | \ register
&#43;----------------&#43; }save|
| %xmm0 | / area|
&#43;----------------&#43; | |
| %r9 | | |
&#43;----------------&#43; | | fp_offset
| %r8 | | ___ |
&#43;----------------&#43; | | |
| ... | | | |
&#43;----------------&#43; | | gp_offset
| %rsi | | | |
&#43;----------------&#43; | | |
| %rdi | / | |
&#43;----------------&#43; <----&#43;--&#43;--- reg_save_area
| ... | (potentially more storage)
&#43;----------------&#43; <----------- %esp
| ... | [low addresses]

Because va_arg must tell determine whether the requested type was passed in registers, it needs compiler support, and can&#39;t be implemented as a simple macro like on i386. The amd64 ABI reference specifies va_arg using a list of eleven different steps that the macro must perform. I&#39;ll try to summarize them here.

First off, va_arg determines whether the requested type could be passed in registers. If not, va_argbehaves much like it does on i386, using the overflow_arg_area member of the va_list (Plus some complexity to deal with alignment values).

Next, assuming the argument can be passed in registers, va_arg determines how many floating-point and general-purpose registers would be used to pass the requested type. It compares those values with the gp_offset and fp_offset fields in the va_list. If the additional registers would cause either value to overflow the number of registers used for parameter-passing for that type, then the argument was passed on the stack, and va_arg bails out and uses overflow_arg_area.

If we&#39;ve made it this far, the argument was passed in registers. va_arg fetches the argument using reg_save_area and the appropriate offsets, and then updates gp_offset and fp_offset as appropriate.

Note that if the argument was passed in a mix of floating-point and general-purpose registers, or requires a large alignment, this means that va_arg must copy it out of the register save area onto temporary space in order to assemble the value.

So, in the worst case, va_arg on a type that embeds both a floating-point and an integer type must do two comparisons, a conditional branch, and then update two fields in the va_list and copy multiple values out of the register save area into a temporary object to return. That&#39;s quite a lot more work than the i386 version does. Note that I don&#39;t mean to suggest this is a performance concern – I don&#39;t have any benchmarks to back this up, but I would be shocked if this is measurable in any reasonable code. But I was surprised by how complex this operation is.

 

转:https://www.cnblogs.com/karimlee/p/5106769.html



推荐阅读
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 本文介绍了Python函数的定义与调用的方法,以及函数的作用,包括增强代码的可读性和重用性。文章详细解释了函数的定义与调用的语法和规则,以及函数的参数和返回值的用法。同时,还介绍了函数返回值的多种情况和多个值的返回方式。通过学习本文,读者可以更好地理解和使用Python函数,提高代码的可读性和重用性。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 本文介绍了Windows Vista操作系统中的用户账户保护功能,该功能是为了增强系统的安全性而设计的。通过对Vista测试版的体验,可以看到系统在安全性方面的进步。该功能的引入,为用户的账户安全提供了更好的保障。 ... [详细]
  • 如何使用Python从工程图图像中提取底部的方法?
    本文介绍了使用Python从工程图图像中提取底部的方法。首先将输入图片转换为灰度图像,并进行高斯模糊和阈值处理。然后通过填充潜在的轮廓以及使用轮廓逼近和矩形核进行过滤,去除非矩形轮廓。最后通过查找轮廓并使用轮廓近似、宽高比和轮廓区域进行过滤,隔离所需的底部轮廓,并使用Numpy切片提取底部模板部分。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
author-avatar
长大的夜夜
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有