【问题标题】:Assembly/ABI: Caller Responsibility for Large Return Types组装/ABI:大返回类型的调用者责任
【发布时间】:2015-05-13 01:10:00
【问题描述】:

我的汇编技能相当差,但我一直在努力更好地理解汇编,以提高我对分析会话和优化编译器工作原理的理解。

在研究 Windows 的 64 位调用约定时,我从未停下来真正思考过的一件事是:

struct BigUdt
{
    double buf[16]; // exceeds 64 bits
};
struct BigUdt create_big_udt();

...如果我正确理解文档,create_big_udt 通常会为struct BigUdt 分配内存(我假设在堆栈上)并将地址返回到rax 中的内存。

所以,从汇编的角度来看,我的问题是:就职责而言,调用者在哪里?我假设有人有责任将sizeof(BidUdt) 添加回来在某个时候到rsp。对于大小不是 64 位的倍数的类型,64 位堆栈对齐是否会成为这里的一个问题,或者 struct BigUdt's 填充会覆盖吗?

唷,我希望这是一个合理的问题。我发现调用者和被调用者之间的这些 ABI 合同以及调用约定非常令人生畏,并且是迄今为止学习汇编中最难的部分之一。

【问题讨论】:

  • 一个简单的事情是构建简单的测试,组装和反汇编它们,看看符合该 abi 的编译器做了什么
  • 一直在做这种逆向工程,试图弄清楚这些调用约定!对于上述这种情况,我发现令人困惑的一件事是,如果我正确理解了反汇编,那么在我们从调用者返回时,调用这样的函数似乎会使 rsp 保持不变/恢复。因此,似乎不必将添加回rsp 的责任留给被调用者,但也许它确实需要在将其覆盖到堆栈中之前立即使用该数据。优化器如何处理这个问题让我的大脑想爆炸。
  • 啊,你说的是调用者而不是被调用者。只是一个猜测,我会假设被调用者将它设置在堆栈上并恢复堆栈,让它挂在风中,调用者如果它实际上想要该值将有一个变量,该变量将是本地或全局的,并且取决于将被分配某处。如果足够大,分配 ret=function() 将生成一个 memcpy()。但我还没有尝试过确认....
  • 非常感谢——当我检查编译器的反汇编时,“悬在风中”的比喻似乎与我所看到的相符。这也更令人欣慰——我认为如果调用者事后忘记对rsp 做某事,那里可能会发生灾难。

标签: windows assembly 64-bit calling-convention


【解决方案1】:

documentation 很清楚:

否则,调用者承担分配内存的责任 并将返回值的指针作为第一个参数传递。 随后的参数将向右移动一个参数。这 RAX 中的被调用者必须返回相同的指针。

所以基本上foo bar(...) 变成了foo* bar(foo* retval, ...)。指针可能指向调用者想要结果的任何地方(通常是调用者堆栈帧中的局部变量的地址),这当然不受调用中涉及的堆栈操作的影响,并且始终是有效的内存、堆栈或除此以外。它永远不会“悬在风中”(如果我正确理解了这个词的话),因为这确实会导致灾难。

【讨论】:

  • 哦,我错过了调用者分配内存并在RCX 中指向它的部分!谢谢,这让我在反汇编中感到困惑的一些事情清楚了很多。
猜你喜欢
  • 2021-07-21
  • 1970-01-01
  • 2013-02-19
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-05
相关资源
最近更新 更多