关于c#:如何通过堆栈跟踪访问本地?

How to access locals through stack trace? (Mimicking dynamic scope)

背景

即使可以在运行时编译C代码,也不可能在当前范围内包含和运行生成的代码。相反,所有变量都必须作为显式参数传递。

与Python等动态编程语言相比,我们永远无法真正复制eval的完整行为(如本例中所示)。

1
2
x = 42
print(eval("x + 1")) # Prints 43

问题

所以我的问题是(不管它是否真的有用;)是否可以通过反射来模拟.NET中的动态范围。

由于.NET为我们提供了允许我们检查调用方法的Diagnostics.StackTrace类,所以这个问题可以归结为:(如何)能够可靠地访问调用方法的局部变量?

堆栈跟踪是否为我们提供足够的信息来计算内存偏移量,或者在托管代码中是否仍然禁止这样的操作?

这样的代码有可能吗?

1
2
3
4
5
6
7
8
void Foo() {
   int x = 42;
   Console.WriteLine(Bar());
}

int Bar() {
   return (int)(DynamicScope.Resolve("x")); // Will access Foo's x = 42
}

号


So my question is whether it's possible to mimic dynamic scope in .NET through the use of reflection.

号

反射允许对程序集元数据中表示的项进行运行时操作。

局部变量不是程序集元数据中表示的项。

因此,你的问题的答案是"不"。

(How) is it possible to reliably access the locals of calling methods?

号

访问调用方法的局部变量的设备称为"调试器"。所以问题的答案是"给自己写一个调试器"。

注意,在代码优化的世界中,调试器不能可靠地访问局部变量。局部变量可以被优化,抖动可以生成代码,对两个不同的局部变量使用相同的寄存器,堆栈帧可以在尾部调用期间重复使用,等等。当然,最好有一个PDB文件告诉调试器与每个堆栈帧位置相关联的名称是什么。

方法需要做的是将自定义调试器作为新进程启动,然后等待调试器向其发送消息。然后,调试器将挂起原始进程的线程,询问堆栈帧,然后恢复进程的线程。然后它会将消息发送到包含您发现的信息的调试对象。由于调试对象处于等待状态,等待消息,因此它将恢复其业务。

如果您想要的是一种进程内的"eval"功能,那么可以考虑jscript.net,一种专门为这种功能设计的语言。


这是不可能的。在编译的.NET代码(中间语言)中,变量简单地表示为堆栈上的索引。例如,加载变量值的ldloc指令只取unsigned int16值作为参数。对于以调试模式编译的应用程序,可能有某种方法可以做到这一点(毕竟,在调试应用程序时,Visual Studio会做到这一点),但一般来说,它不能工作。

在phalanger(我部分参与的php编译器for.net)中,必须以某种方式解决这个问题,因为php语言有eval(它不需要提供动态范围,但需要按名称访问变量)。因此,phalanger检测一个函数是否包含对eval的任何使用,如果包含,它将把所有变量存储在Dictionary中,然后将这些变量传递给eval函数(以便它可以按变量的名称读取变量)。恐怕这是唯一的办法…