我在x86程序集中编写一个函数,应该可以从c代码中调用,我想知道在返回调用者之前我必须恢复哪些寄存器.
目前我只恢复esp
和ebp
,而返回值是eax
.
还有其他我应该关注的寄存器,还是我可以留下任何令我高兴的东西?
在Visual Studio 2013中,存在一个新的调用约定_vectorcall.它适用于可以在SSE寄存器中传递的SSE数据类型.
您可以指定成员函数的调用约定,如此.
struct Vector{//a 16 byte aligned type
_m128i _vectorcall operator *(Vector a);
};
Run Code Online (Sandbox Code Playgroud)
这是有效的,它可以编译,并且尽管有16个对齐要求,类型也可以通过值传递.
另一方面,如果我尝试将它附加到任何构造函数(这似乎完全合乎逻辑),它就会失败.
struct Vector
_vectorcall Vector(SomeOtherTypeWith16Alignment a);
};
Run Code Online (Sandbox Code Playgroud)
编译器发出警告消息(我有警告错误):
警告C4166:构造函数/析构函数的非法调用约定.
强迫我将代码更改为:
struct Vector{
Vector(SomeOtherTypeWith16Alignment a); //fails to compile
};
Run Code Online (Sandbox Code Playgroud)
这也无法编译,因为现在SomeOtherTypeWith16Alignment不能通过值传递,因为在构造函数上未启用_vectorcall.
所以我被迫改变它.
struct Vector{
Vector(const SomeOtherTypeWith16Alignment& a);
};
Run Code Online (Sandbox Code Playgroud)
哪个编译.但它不再使用_vectorcall,可能不会传递SSE寄存器中的数据,因为我更喜欢......
所以基本上,为什么我不能指定构造函数使用的调用约定?
这可能是Visual C++特定的(_vectorcall当然是).我没有在其他编译器上试过这个 -
考虑以下示例:
struct vector {
int size() const;
bool empty() const;
};
bool vector::empty() const
{
return size() == 0;
}
Run Code Online (Sandbox Code Playgroud)
生成的汇编代码vector::empty
(通过 clang,经过优化):
push rax
call vector::size() const
test eax, eax
sete al
pop rcx
ret
Run Code Online (Sandbox Code Playgroud)
为什么要分配堆栈空间?它根本没有被使用。该push
和pop
可以省略。MSVC 和 gcc 的优化构建也为此功能使用堆栈空间(请参阅有关Godbolt 的内容),因此必须有一个原因。
我试图了解旧的经典Mac应用程序的切入点.我已经反汇编了第一个CODE资源(不是CODE#0,它是跳转表).代码引用堆栈外的一些变量:0004(A7)处的一个字,一个从000C开始的长字数组(A7),其长度是0004(A7)处的值,以及超出该数组的最后一个长字似乎是一个指向字符串的指针.
长字的数组看起来像字符串乍一看,所以它看起来像我们处理(int argc,char**argv)情况,除了"argv"数组在堆栈框架中内联.
当程序首次被Mac OS调用时,程序应该在其堆栈/寄存器上期待什么?
我试图理解GCC(4.4.3)为在Ubuntu Linux下运行的x86_64机器生成的可执行代码.特别是,我不明白代码如何跟踪堆栈帧.在过去,在32位代码中,我习惯于在几乎每个函数中看到这个"序幕":
push %ebp
movl %esp, %ebp
Run Code Online (Sandbox Code Playgroud)
然后,在功能结束时,也会出现"结局"
sub $xx, %esp # Where xx is a number based on GCC's accounting.
pop %ebp
ret
Run Code Online (Sandbox Code Playgroud)
或者干脆
leave
ret
Run Code Online (Sandbox Code Playgroud)
这完成了同样的事情:
在64位代码中,正如我通过objdump反汇编看到的那样,许多函数不遵循这个约定 - 它们不会推送%rbp然后将%rsp保存到%rbp,像GDB这样的调试器如何构建回溯?
我的真正目标是尝试找出一个合理的地址,当执行到达程序中的任意函数的开始时,可以将堆栈指针向下移动时作为用户堆栈的顶部(最高地址).例如,对于"top",argv的原始地址是理想的 - 但是我无法从主要调用的任意函数访问它.我一开始以为我可以使用旧的回溯方法:追踪保存的帧指针值,直到保存的值为0 - 然后,下一个可以算作最高实际值.(这与获取argv的地址不同,但它会 - 比如说,找出_start或任何_start调用的堆栈指针值[例如__libc_start_main].)现在,我不知道如何获得64位代码中的等效地址.
谢谢.
我们已经知道,当我们在Java中调用方法时,参数和局部变量将存储在堆栈中.
例如,以下代码:
public class Test
{
int x = 10;
int y = 20;
void test(int y)
{
int z = y;
this.x = y; // How JVM knows where is our current object?
}
public static void main(String [] args)
{
Test obj = new Test();
obj.test(3);
}
}
Run Code Online (Sandbox Code Playgroud)
当我们调用时,会产生如下所示的调用堆栈obj.test()
:
| |
+-------------+
| z |
| y | obj.test()
+-------------+
| obj | main()
+-------------+
Run Code Online (Sandbox Code Playgroud)
但我想知道存储this
在哪里引用method
?它是否也存储在堆栈中,如下所示:
| |
+-------------+
| this | …
Run Code Online (Sandbox Code Playgroud) 除了链接相关的东西,我没有在标准中看到任何评论.
虽然标准没有说调用约定,但在现实世界中C和C++之间的调用约定可能不同,所以我期望C函数和C++函数的类型不同.但似乎没有,特别是在海湾合作委员会.
#include <type_traits>
extern "C" {
int c_func(int);
}
int cpp_func(int);
static_assert(!std::is_same<decltype(c_func), decltype(cpp_func)>::value,
"It should not be the same type");
Run Code Online (Sandbox Code Playgroud)
static_assert
由于GCC认为这些功能具有相同的类型,因此失败.
extern "C"
函数类型的一部分吗?我尝试了解System V AMD64 的含义-ABI的调用约定并查看以下示例:
struct Vec3{
double x, y, z;
};
struct Vec3 do_something(void);
void use(struct Vec3 * out){
*out = do_something();
}
Run Code Online (Sandbox Code Playgroud)
甲Vec3
-variable是型存储器的,因此调用者(use
)必须返回的变量分配空间并把它传递为隐藏指针到被叫方(即,do_something
)。这是我们在生成的汇编器中看到的(在godbolt上,使用编译-O2
):
use:
pushq %rbx
movq %rdi, %rbx ;remember out
subq $32, %rsp ;memory for returned object
movq %rsp, %rdi ;hidden pointer to %rdi
call do_something
movdqu (%rsp), %xmm0 ;copy memory to out
movq 16(%rsp), %rax
movups %xmm0, (%rbx)
movq %rax, 16(%rbx)
addq $32, …
Run Code Online (Sandbox Code Playgroud) 我试图了解System V AMD64-ABI对于从函数按值返回的含义。
对于以下数据类型
struct Vec3{
double x, y, z;
};
Run Code Online (Sandbox Code Playgroud)
该类型Vec3
属于MEMORY类,因此ABI就“返回值”指定了以下内容:
如果类型具有MEMORY类,则调用方将为返回值提供空间,并以%rdi形式传递此存储的地址,就好像它是该函数的第一个参数一样。实际上,此地址成为“隐藏”的第一个参数。该存储不得与通过此参数以外的其他名称对被调用方可见的任何数据重叠。
返回时,%rax将包含调用者在%rdi中传递的地址。
考虑到这一点,以下(傻)功能:
struct Vec3 create(void);
struct Vec3 use(){
return create();
}
Run Code Online (Sandbox Code Playgroud)
可以编译为:
use_v2:
jmp create
Run Code Online (Sandbox Code Playgroud)
我认为,正如ABI所保证的,可以执行尾调用优化,这create
会将%rdi
传递的值放入%rax
寄存器中。
但是,似乎没有一个编译器(gcc,clang和icc)正在执行此优化(此处为godbolt)。生成的汇编代码%rdi
仅能将其值移动到%rax
,因此保存在堆栈上,例如gcc:
use:
pushq %r12
movq %rdi, %r12
call create
movq %r12, %rax
popq %r12
ret
Run Code Online (Sandbox Code Playgroud)
无论是对于这种最小的,愚蠢的功能,还是对于现实生活中更复杂的功能,都不会执行尾调用优化。这使我相信,我必须丢失某些东西,这是禁止的。
毋庸置疑,对于SSE类的类型(例如,仅2而不是3的两倍),将执行尾调用优化(至少由gcc和clang进行,仅靠戈德螺栓)
use_v2:
jmp create
Run Code Online (Sandbox Code Playgroud)
结果是
use:
jmp create
Run Code Online (Sandbox Code Playgroud) 我正在将一些 AArch64/ARM64/Apple Silicon 汇编代码从 Linux 移植到 macOS。
\n此代码使用所有 31 个可用寄存器(堆栈指针不算在内)来避免几乎所有溢出情况;Linux 调用约定允许我使用那么多寄存器。
\n如果迫不得已,我承认溢出一个额外的寄存器(从而将其减少到使用 30 个寄存器)是可行的,因为性能受到的影响最小,但如果限制为 29 个或更少的可用寄存器,性能将受到更大的影响。因此,我真的希望至少有 30 个可用寄存器,最好是 31 个。
\n我刚刚从 Apple 官方文档中了解到,保留了两个额外的寄存器,超出了 Linux 调用约定的要求:
\n\n\n尊重特定 CPU 寄存器的用途
\nARM 标准将某些决策委托给平台设计者。Apple 平台遵循以下选择:
\n\n
\n- 该平台保留寄存器x18。不要\xe2\x80\x99 使用该寄存器。
\n- 帧指针寄存器 (x29) 必须始终寻址有效的帧记录。某些函数 \xe2\x80\x94 例如叶函数或尾部调用 \xe2\x80\x94 可能会选择不在该列表中创建条目。因此,即使没有调试信息,堆栈跟踪也始终是有意义的。
\n
尽管有这些说法,我的代码在没有它的情况下似乎运行良好。
\n现在,我完全明白忽略此类 ABI 要求是一件非常糟糕的事情 (TM)。但是,我想确切地了解代码如何因使用 x18 和 x29 而中断。
\n例如,通过阅读上述文档,我的理解是 x29 是为了支持调试或故障转储。假设我不关心调试这个函数(实际上我不关心),或者任何生成的故障转储是否有意义。那么,使用x29有什么坏处吗?
\n至于x18,知道它有什么用吗?我假设(零支持证据)如果在该代码运行时执行中断或上下文切换,则不会保存 x18,因此一旦返回就会破坏我的函数的结果。这将是一个更严重的情况,我会听取建议,在这种情况下不要使用 x18。
\n另请注意,所讨论的代码是叶函数,因此破坏从其中调用的任何函数都没有问题。
\n