How does clang-cl decide not to emitting SEH __try/__except block?

In this example, clang-cl.exe doesn’t generate exception handling code and the program just crashes unless I uncomment the printfs. But cl.exe doesn’t have the same issue. I wonder which passes are responsible for modeling the usage of __try/__except blocks, and if there is a way to make it more consistent with cl.exe.

The command lines I used were clang-cl.exe Main.cpp and cl.exe Main.cpp.

#include <stdio.h>
#include <windows.h>

int testFunc(const int* addr, int retVal) {
  int i = 0;
  __try {
    // printf("");
    v = *addr;
    // printf("");
  }
  __except(EXCEPTION_EXECUTE_HANDLER) {
    i = retVal;
  }
  return v;
}
int main() {
	testFunc(0, 1);
	printf("done");
}

clang-cl doesn’t support asynchronous exceptions.

From MSVC compatibility — Clang 18.0.0git documentation

Asynchronous Exceptions (SEH): Partial. Structured exceptions (__try / __except / __finally) mostly work on x86 and x64. LLVM does not model asynchronous exceptions, so it is currently impossible to catch an asynchronous exception generated in the same frame as the catching __try.

What’s the version you are using? I cannot duplicate the crash on LLVM 17.0.1 release.

Although it’s too early to say it fully supported, the asynchronous exceptions support has been improved a lot by Ten Tzen in recent releases. Note, some features may need /EHa added explicitly in command line option.

Okay, that explains why exception code is generated when I uncomment the printfs.

I built clang based off main branch. I tested 17.0.1 clang, program compiled with & 'C:\Program Files\LLVM\bin\clang-cl.exe' .\Main.cpp doesn’t print out “done”.

Here is a little background of what I’m trying to achieve:

At work we are currently using clang 12. I want to build our project with clang 17

What happened

Clang 17 hangs while compiling one of our cpp file. I swtiched to an assert enabled clang 17 and it fails at an assertion inside a snippet that adds support for /EHa. Which prompts the question of what has changed for /EHa in clang 17.

Difference between clang 12 and clang 17 on ​/EHa​

Clang 12 driver translates both -EHs and -EHa into -fexceptions -fcxx-exceptions. Clang 17 adds -fasync-exceptions on top of -fexcetipns -fcxx-exceptions for -EHa. And the new feature for /EHa should be guarded behind -fasync-exceptions.

In order to exercise the same code path on both clang 12 and 17 for comparison, I decided to just pass -fexcceptions -fcxx-exceptions directly to the clang front-end. This works fine with clang 12 but clang 17 would generate exception code for some __try/__except blocks but not all of them (verified by checking scoped exception data in the final binary against pdb code file/lines and their corresponing binary virtual address). I feel like the new EHa patch shouldn’t affect what I’m trying to do here so I 'm looking into how clang decideds to emit __try/__except code without looking at the new EHa patch.

See clang-cl incorrect SEH exception handling · Issue #62606 · llvm/llvm-project · GitHub

Thank you for the link. I think it’s related but doesn’t really answer my questions. I’m currently looking at SEHTryStmt::Create and CodeGenFunction::GenerateCode hoping to figure out how catchpad related instructions are generated.

I solved my problem. What happened was clang 17 inlined functions called inside the __try block so there was no exception handling code generated. And my aforementioned example was flawed as well. After uncommenting the printfs, unwind lables catch.dispatch were added for the function invokes, and that somehow made the null pointer derefencing exception caught? The program still crashes if I only uncomment one printf. So yes, as the documentation says: it is currently impossible to catch an asynchronous exception generated in the same frame as the catching __try.

As for /EHa, I swapped all our usage of /EHa with /EHs since that matches what we were getting from clang 12 which was in turn appending -fexceptions and -fcxx-exceptions. I get many clang assertion failures using /EHa with clang 17, maybe I can work out the reasons behind it once I successfully migrating from clang 12 to clang 17.

Thank you all for commenting.

I think it would be reasonable to treat SEH functions (those using __try) as always supporting non-call exceptions, meaning we would compile them as we do today with /EHa. My understanding of the semantics of /EHa is that it mostly affects how regular C++ exceptions are handled. It makes C++ destructors run when foreign, non-call exceptions (like an access violation) are thrown through a frame, and it makes catch (...) catch foreign exceptions (if memory serves…).

It’s just a coincidence that /EHa happens to make clang-cl’s implementation of __try work more faithfully to the MSVC version.

However, it sounds like from the crashes, that there are more bugs in /EHa to work out. It’s not surprising there are bugs here, this is an extremely challenging feature to implement. There was a reason we limited scope and avoided modelling non-call exceptions when we implemented __try in the first place.

1 Like