【发布时间】:2017-04-04 19:42:34
【问题描述】:
考虑一个 C 函数(带有外部链接),如下所示:
void f(void **p)
{
/* do something with *p */
}
现在假设f 的调用方式使得p 指向堆栈上f 的返回地址,如以下代码所示(假设System V AMD64 ABI):
leaq -8(%rsp), %rdi
callq f
可能发生的情况是f的代码通过给*p赋值来修改栈上的返回地址。因此,编译器必须将堆栈上的返回地址视为易失性值。我如何告诉编译器,在我的例子中是 gcc,返回地址是 volatile 的?
否则,编译器至少在原则上可以为f生成以下代码:
pushq %rbp
movq 8(%rsp), %r10
pushq %r10
## do something with (%rdi)
popq %r10
popq %rbp
addq 8,%rsp
jmpq *%r10
诚然,编译器不太可能生成这样的代码,但如果没有任何进一步的函数属性,似乎也不会被禁止。并且这段代码不会注意到堆栈上的返回地址是否在函数中间被修改,因为原始返回地址已经在函数开始时被检索到。
P.S.:正如 Peter Cordes 所建议的,我应该更好地解释我的问题的目的:它是关于使用移动垃圾收集器动态生成机器代码的垃圾收集:函数 f 代表垃圾收集器。 f 的被调用者可能是一个函数,其代码在 f 运行时被移动,所以我想出了让 f 知道返回地址的想法,以便 f 可以根据是否修改它返回地址指向的内存区域是否已移动。
【问题讨论】:
-
所有这些都是未定义的行为
-
在真正编译的实际代码中遇到真正问题时回来。对依赖于实现的东西的理论讨论是乏味的。
-
@Marc:您需要使用最小的汇编函数来包装函数,该函数调用
f()并使用堆栈上的返回地址。如果f()的目的只是为了改变返回地址,你应该完全用汇编写。 -
你应该把为 JITed 代码编写 GC 的解释放入问题中。以它为动力更有意义!
-
@Marc:是的,包装器越来越有意义。您可以让包装函数(当然是在不移动的代码部分中,没有被 JIT 处理)提供一个额外的指针,指向包装函数堆栈帧中的返回地址。 SYSV AMD64 ABI 很简单,如果你说
f()有5 个参数,你可以设置第六个(%r9)指向堆栈上的返回地址;作为一个纯汇编函数,您甚至不需要适当的堆栈框架,并且包装器将是最小的。运行时成本只是额外的调用。