Why are all registers kept during .NET exception handling?

At least in accordance with the description of Mono Exception Handling Implementation , all x86 registers are saved when an exception is thrown. Besides the stack pointer (ESP), frame pointers (EBP), instruction pointers (EIP), and the register that contains the exception object, why are other registers also saved? Why keep the whole context when there is no mechanism in .NET to continue execution at a point immediately after a throw?

UPDATE:

EMCA states the following in Section 12.3.2.4 Overview of Exception Handling:

  • An exception object describing the exception is automatically created by the CLI and pushed onto the evaluation stack as the first element when entering a filter or catch clause.

  • Execution cannot be resumed at the exception, with the exception of the filter handler.

I do not know how to "exclude using a filter handler" can be done in the light of the previous statement. In addition, the description of the endfilter opcode has only two results: “continue to search for another exception handler” or execute a handler (which will execute another block of CIL opcodes). There is no option to resume. Therefore, I interpret this discrepancy in the specification basically means that .NET does not support continued execution at the point immediately after the throw. Perhaps this was considered at some point, but was not fully implemented and implemented.

UPDATE 2:

Someone noted that a “resume” can happen simply if the filter handler does not catch the exception. However, the specification for throw states is: "the throw command pushes an exception object onto the stack and frees the stack." Emptying the stack will make it impossible to execute the code immediately after the throw statement, as this code may require something on the stack.

+4
source share
1 answer

You look at the implementation details: they, as a rule, do not meet each other's ECMA specifications for many possible reasons (for example, ease of implementation, code sharing, providing additional semantics, future protection, optimization, etc.).

In this particular case, there are several considerations.

Mono handles exceptions based on a view of the state of the MonoContext processor: since this structure is used elsewhere, it contains all the appropriate registers, even if some of them may not be needed in some specific cases. In particular, exception handling can also be triggered from a processor exception, where all registers must be restored, so they are all collected.

Someone already noticed that context is also used for the debugger.

Consider also the case of a try / catch method with a catch being a simple local var setting. Now the extended register allocator can put a local var, which is used as before the throw (note that the throw can be implicit, like a null reference, this is handled by cpu exception) and after catch in the register, for example edx. If you do not save all the registers at the throw / exception point, you have corrupted the program state, since edx will be overwritten during exception handling.

As a final comment: are you worried that storing a few extra registers is a waste of processor cycles? If you actually count the cycles spent, they completely in noise accept everything that should happen during exception handling.

+3
source

Source: https://habr.com/ru/post/1386927/


All Articles