Jump to page: 1 2
Thread overview
Override assert handler
Aug 17
Manu
Aug 17
ryuukk_
Aug 17
Manu
Aug 17
ryuukk_
Aug 17
kinke
Aug 18
Manu
Aug 18
Johan
Aug 19
Ogi
August 17
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...
2. That said, #1 shouldn't actually happen because the druntime symbol
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.


August 17
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

August 17
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.


August 17

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?

August 17

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.

August 17
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.
August 17
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.
August 18
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?


August 19
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 ;)

August 18
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

« First   ‹ Prev
1 2