www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Override assert handler

reply Manu <turkeyman gmail.com> writes:
So I've declared a local function:
  extern(C) void _d_assert_msg (string msg, string file, uint line)

Intent is to replace the druntime assert handler.
I declared this function and the program builds and links, but it still
calls the druntime function.

1. I'm not sure exactly why linking succeeds... I would have expected a
link error with multiple defined symbols...

should be marked with weak linkage such that a user-override will be
accepted when supplied, but that seems not to be the  case right now...?

Have people done this in their own programs? What is the best practice?

On a related note; _d_assert_msg is missing an argument; it supplies msg,
line, file, but it also needs to receive another argument which is the
stringified assert expression that failed.
Aug 17
next sibling parent reply ryuukk_ <ryuukk.dev gmail.com> writes:
```
import core.stdc.stdio;

void main()
{
     assert(false, "no");
}

extern(C) void _d_assert_msg (string msg, string file, uint line)
{
     printf("assert failed: %.*s:%u %.*s\n", cast(int)file.length, 
file.ptr, line, cast(int)msg.length, msg.ptr);
}
```

```
assert failed: onlineapp.d:4 no
```


This works

That's funny, because i was thinking about that just right now, 
and how the default message of druntime sucks ass

```
core.exception.AssertError onlineapp.d(5): no
----------------
??:? _d_assert_msg [0x5625e324e580]
./onlineapp.d:5 _Dmain [0x5625e324e4e4]

```


assert message is obfuscated and surrounded with unpleasant 
characters to read, it should be clear by default, i want to send 
a PR but i can't be bothered to read druntime codebase, a waste 
of time
Aug 17
parent reply Manu <turkeyman gmail.com> writes:
On Sat, 17 Aug 2024 at 18:42, ryuukk_ via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 ```
 import core.stdc.stdio;

 void main()
 {
      assert(false, "no");
 }

 extern(C) void _d_assert_msg (string msg, string file, uint line)
 {
      printf("assert failed: %.*s:%u %.*s\n", cast(int)file.length,
 file.ptr, line, cast(int)msg.length, msg.ptr);
 }
 ```

 ```
 assert failed: onlineapp.d:4 no
 ```


 This works

 That's funny, because i was thinking about that just right now,
 and how the default message of druntime sucks ass

 ```
 core.exception.AssertError onlineapp.d(5): no
 ----------------
 ??:? _d_assert_msg [0x5625e324e580]
 ./onlineapp.d:5 _Dmain [0x5625e324e4e4]

 ```


 assert message is obfuscated and surrounded with unpleasant
 characters to read, it should be clear by default, i want to send
 a PR but i can't be bothered to read druntime codebase, a waste
 of time
Orly? My project is rather more complex than this... I'll give it some more time to find where it goes sideways. What was your build cmdline? Did you link druntime? I mean, there's also the `assertHandler()` stuff, but I don't really see the point; I'd rather just replace the assert handler symbol directly. Using `assertHandler` is awkward because you need a static constructor to register it at runtime, and then anything you do in your assert handler almost inevitably leads to bootup issues with cyclic module dependencies because everything leads back to assert.
Aug 17
next sibling parent ryuukk_ <ryuukk.dev gmail.com> writes:
On Saturday, 17 August 2024 at 09:31:53 UTC, Manu wrote:
 On Sat, 17 Aug 2024 at 18:42, ryuukk_ via Digitalmars-d < 
 digitalmars-d puremagic.com> wrote:

 ```
 import core.stdc.stdio;

 void main()
 {
      assert(false, "no");
 }

 extern(C) void _d_assert_msg (string msg, string file, uint 
 line)
 {
      printf("assert failed: %.*s:%u %.*s\n", 
 cast(int)file.length,
 file.ptr, line, cast(int)msg.length, msg.ptr);
 }
 ```

 ```
 assert failed: onlineapp.d:4 no
 ```


 This works

 That's funny, because i was thinking about that just right 
 now, and how the default message of druntime sucks ass

 ```
 core.exception.AssertError onlineapp.d(5): no
 ----------------
 ??:? _d_assert_msg [0x5625e324e580]
 ./onlineapp.d:5 _Dmain [0x5625e324e4e4]

 ```


 assert message is obfuscated and surrounded with unpleasant 
 characters to read, it should be clear by default, i want to 
 send a PR but i can't be bothered to read druntime codebase, a 
 waste of time
Orly? My project is rather more complex than this... I'll give it some more time to find where it goes sideways. What was your build cmdline? Did you link druntime? I mean, there's also the `assertHandler()` stuff, but I don't really see the point; I'd rather just replace the assert handler symbol directly. Using `assertHandler` is awkward because you need a static constructor to register it at runtime, and then anything you do in your assert handler almost inevitably leads to bootup issues with cyclic module dependencies because everything leads back to assert.
I use custom runtime + `-betterC`, but this also works on run.dlang.io with no specific flags Do you use `-betterC`? Do you load a dll? it perhaps overwrite the symbol?
Aug 17
prev sibling next sibling parent reply kinke <noone nowhere.com> writes:
On Saturday, 17 August 2024 at 09:31:53 UTC, Manu wrote:
 I mean, there's also the `assertHandler()` stuff, but I don't 
 really see the point; I'd rather just replace the assert 
 handler symbol directly. Using `assertHandler` is awkward 
 because you need a static constructor to register it at 
 runtime, and then anything you do in your assert handler almost 
 inevitably leads to bootup issues with cyclic module 
 dependencies because everything leads back to assert.
You can set the handler in a CRT constructor, which avoids any cycles and makes sure it is installed before initializing druntime (`rt_init()`), so should really cover all asserts: ``` pragma(crt_constructor) void setupAssertHandler() { import core.exception : assertHandler; assertHandler = &myAssertHandler; } void myAssertHandler(string file, size_t line, string msg) nothrow { // print... import core.stdc.stdlib; abort(); // or something like that } ``` ` core.attribute.weak` isn't implemented by DMD, plus needs emulation on Windows (done by LDC) with according limitations. The extra indirection via that custom assert handler in druntime works e.g. for a pre-linked druntime DLL too.
Aug 17
parent Manu <turkeyman gmail.com> writes:
On Sat, 17 Aug 2024 at 22:46, kinke via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Saturday, 17 August 2024 at 09:31:53 UTC, Manu wrote:
 I mean, there's also the `assertHandler()` stuff, but I don't
 really see the point; I'd rather just replace the assert
 handler symbol directly. Using `assertHandler` is awkward
 because you need a static constructor to register it at
 runtime, and then anything you do in your assert handler almost
 inevitably leads to bootup issues with cyclic module
 dependencies because everything leads back to assert.
You can set the handler in a CRT constructor, which avoids any cycles and makes sure it is installed before initializing druntime (`rt_init()`), so should really cover all asserts: ``` pragma(crt_constructor) void setupAssertHandler() { import core.exception : assertHandler; assertHandler = &myAssertHandler; } void myAssertHandler(string file, size_t line, string msg) nothrow { // print... import core.stdc.stdlib; abort(); // or something like that } ``` ` core.attribute.weak` isn't implemented by DMD, plus needs emulation on Windows (done by LDC) with according limitations. The extra indirection via that custom assert handler in druntime works e.g. for a pre-linked druntime DLL too.
That's a reasonable solution. Thanks for the tip! I'd still prefer to just replace a weak library call though, so that calls to assert are direct and don't add a bunch of extra layers to the callstack. I'd also really like it if the condition were stringified and handed to the assert handler... that seems like a weird oversight?
Aug 17
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/17/2024 2:31 AM, Manu wrote:
 I mean, there's also the `assertHandler()` stuff, but I don't really see the 
 point; I'd rather just replace the assert handler symbol directly.
 Using `assertHandler` is awkward because you need a static constructor to 
 register it at runtime, and then anything you do in your assert handler almost 
 inevitably leads to bootup issues with cyclic module dependencies because 
 everything leads back to assert.
Yes, that is a botched design that makes things much more complicated than necessary.
Aug 17
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Allow me to explain how a library works.

The linker resolves all the symbols in the object files given to it. When there 
are references to symbols, but no symbols, then (and only then) does the linker 
consult the library. The linker will pull in modules from the library that 
export those missing symbols.

Therefore, the easiest way to override a function that's in the library is to 
write your own and put it in the list of object files for the linker to
incorporate.

Unfortunately, some early work done on druntime was not aware of this, and so 
the assert handler is set up as a pointer to a function, with another function 
to call to set that pointer to your own function.

By the time I noticed that, it was too late to fix it.

I have spent nearly the entire time I've been working on compilers trying and 
failing to explain how linkers work.
Aug 17
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 18/08/2024 1:53 PM, Walter Bright wrote:
 Allow me to explain how a library works.
 
 The linker resolves all the symbols in the object files given to it. 
 When there are references to symbols, but no symbols, then (and only 
 then) does the linker consult the library. The linker will pull in 
 modules from the library that export those missing symbols.
 
 Therefore, the easiest way to override a function that's in the library 
 is to write your own and put it in the list of object files for the 
 linker to incorporate.
This needs some context. It only applies to object files and executables, the moment shared libraries enter the picture (such as druntime) this no longer is the full story. Once shared libraries enter, you have to consult the image loader as well. If you have a symbol that overrides another it is first come first served and loading is dependency aware. If the process has been up for a month already, a replacement symbol will not replace the existing one. It isn't safe. The loader would effectively have the ability to unmap a currently executing function. Needless to say that is a bad thing to do on loading of a binary.
 Unfortunately, some early work done on druntime was not aware of this, 
 and so the assert handler is set up as a pointer to a function, with 
 another function to call to set that pointer to your own function.
Unfortunately there is a broader context, and it is my belief that this was the correct solution. It works in more situations and still allows you via ldc's `` weak`` (see Martin's reply) to replace the initializer during initial link.
 By the time I noticed that, it was too late to fix it.
 
 I have spent nearly the entire time I've been working on compilers 
 trying and failing to explain how linkers work.
And now I get to explain how loaders work ;)
Aug 18
next sibling parent reply Johan <j j.nl> writes:
On Sunday, 18 August 2024 at 17:22:44 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 On 18/08/2024 1:53 PM, Walter Bright wrote:
 Allow me to explain how a library works.
 
 The linker resolves all the symbols in the object files given 
 to it. When there are references to symbols, but no symbols, 
 then (and only then) does the linker consult the library. The 
 linker will pull in modules from the library that export those 
 missing symbols.
 
 Therefore, the easiest way to override a function that's in 
 the library is to write your own and put it in the list of 
 object files for the linker to incorporate.
This needs some context. It only applies to object files and executables, the moment shared libraries enter the picture (such as druntime) this no longer is the full story.
And there is of course inlining that needs to be disabled for an "overridable" function. weak does that too. -Johan
Aug 18
parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/18/2024 1:29 PM, Johan wrote:
 And there is of course inlining that needs to be disabled for an "overridable" 
 function.  weak does that too.
pragma(inline, false)
Aug 19
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
I actually do understand how shared libraries work.

What happens by default when an assert failure happens is the function:

```
core.exception._d_assertp(immutable(char*) file, uint line);
```
is called. That forwards the call to:

```
core.exception.onAssertError(string file, size_t line);
```
which then forwards the call to:
```
(*_assertHandler)(file,line,null);
```
and core.exception._assertHandler is the pointer to the function.

The default behavior of _assertHandler is:
```
throw staticError!AssertError(file, line);
```

Therefore, if you write your own _d_assertp function in the executable, it will 
override the library version in your executable. For code in the shared
library, 
the shared library _d_assertp will be called.


------

To understand assert error handling, it's necessary to understand:

```
AssertHandler
assertHandler
assertHandler  (yes, two of them!)
_assertHandler
onAssertError
onAssertErrorMsg
AssertError
_d_assertp
_d_assert_msg
_d_assert
```

which is overly complex.
Aug 19
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 20/08/2024 9:03 AM, Walter Bright wrote:
 I actually do understand how shared libraries work.
 
 What happens by default when an assert failure happens is the function:
 
 ```
 core.exception._d_assertp(immutable(char*) file, uint line);
 ```
 is called. That forwards the call to:
 
 ```
 core.exception.onAssertError(string file, size_t line);
 ```
 which then forwards the call to:
 ```
 (*_assertHandler)(file,line,null);
 ```
 and core.exception._assertHandler is the pointer to the function.
 
 The default behavior of _assertHandler is:
 ```
 throw staticError!AssertError(file, line);
 ```
 
 Therefore, if you write your own _d_assertp function in the executable, 
 it will override the library version in your executable. For code in the 
 shared library, the shared library _d_assertp will be called.
Its not quite as simple as that. For Windows yes that's how it'll work without compiler & linker assistance. "Symbols so introduced may duplicate symbols already defined by the program or previous dlopen() operations. To resolve the ambiguities such a situation might present, the resolution of a symbol reference to symbol definition is based on a symbol resolution order. Two such resolution orders are defined: load or dependency ordering. Load order establishes an ordering among symbol definitions, such that the definition first loaded (including definitions from the image file and any dependent objects loaded with it) has priority over objects added later (via dlopen()). Load ordering is used in relocation processing. Dependency ordering uses a breadth-first order starting with a given object, then all of its dependencies, then any dependents of those, iterating until all dependencies are satisfied. With the exception of the global symbol object obtained via a dlopen() operation on a file of 0, dependency ordering is used by the dlsym() function. Load ordering is used in dlsym() operations upon the global symbol object." https://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html If your ``_d_assertp`` was first seen it would apply to the entire process if the symbol was exported. If the shared library symbol was first seen, its the one that gets used no matter what image is being discussed as long as everything is exported. Of course, the Posix behavior of setting the assert handler for the entire process is what you want. Not the Windows one where it is localized to the binary. After all it doesn't matter who errors out, you want to act upon it the same way consistently. Which brings us back to what I already said, the function pointer design works best and doesn't limit you into a subset of desirable situations. It also covers the use case where you want to swap it out in a process that has been running for a month. Not to mention it's fully portable and won't change behavior based upon platform.
 ------
 
 To understand assert error handling, it's necessary to understand:
 
 ```
 AssertHandler
 assertHandler
 assertHandler  (yes, two of them!)
 _assertHandler
 onAssertError
 onAssertErrorMsg
 AssertError
 _d_assertp
 _d_assert_msg
 _d_assert
 ```
 
 which is overly complex.
Agreed, there may be some simplification possible.
Aug 19
prev sibling parent Ogi <ogion.art gmail.com> writes:
On Saturday, 17 August 2024 at 08:10:26 UTC, Manu wrote:
 Have people done this in their own programs? What is the best 
 practice?
As an option, you can just catch it: ```D void main() { import core.exception : AssertError; try { assert(true == false); } catch (AssertError e) { writeln("Oh no! ", e.msg); } } ```
Aug 19