Compile and debug this code (using Mago):
-----------------------------------
C++:
----
#include <stdio.h>
class Something
{
public:
virtual void x();
int data = 10;
};
void Something::x()
{
printf("X");
}
void dfunc(Something*);
void main()
{
Something s;
dfunc(&s);
}
-----------------------------------
D:
--
extern(C++) class Something
{
abstract void x();
int data;
}
extern(C++) void dfunc(Something s)
{
s.x();
}
-----------------------------------
Put a breakpoint in dfunc().
If you inspect 's' in C++, you will see "__vfptr ..." and "data = 10"
If you inspect 's' from D, you will only see "data = 10"
In C++, __vfptr is an array of "void*" that you can open and see a list of all the virtual functions.
I really want to be able to see __vfptr from D's debuginfo too. At very least, it will help to debug mis-alignments between C++ and extern(C++) vtables.
It's very easy to create a vtable mismatch from D.
If the debuginfo doesn't want to emit this member (it should), then I think it might also be possible to fabricate its appearance with the natvis?
Right, I've noticed that our pointers don't seem to show symbol names like C++ does... why is that? Is that possible to fix?
The experience should match C++.
Comment #3 by turkeyman — 2018-05-20T01:00:53Z
I just installed that build.
I can see an item in place of the vtable:
-tclass 0x02D20000 Source.Base
+ [Source.Derived] 0x02D20000 Source.Derived
object.Object D0006: Error: Type resolve failed
x 10 int
y 20 int
It seems there's an object.Object item there, which fails to evaluate correctly.
It should be void** (or rather, void*[N] to inspect properly) right?
Comment #4 by r.sagitario — 2018-05-20T06:47:56Z
It seems I've messed up the defaults between Visual D and mago: you have to enable it in Tools->Options->Debugger->Mago. I think that being disabled is actually the better default as the vtable yields no additional information for most people.
Unfortunately that build is also broken, as it tries to show [0] multiple times and fails.
Comment #5 by turkeyman — 2018-05-20T06:57:23Z
Even if we can't see the symbol names, seeing the pointers might help matching values and confirm function order against C++ code that you can also see in the debugger.
Comment #6 by r.sagitario — 2018-05-20T15:41:07Z
The build was ok, I just looked at the class instance before it was initialized.
I've now added symbol names (no demangling yet). One gotcha: if you are linking with /INCREMENTAL, the linker relocates symbols through a series of jumps and that confuses the symbol display.
Comment #7 by turkeyman — 2018-05-20T18:24:50Z
Haha, oh man. Everything is always so hard! ;)
I turned it off and everything works great!
Sadly, /INCREMENTAL is overwhelmingly common, and also the default... basically nobody will ever turn that off :/
Comment #8 by turkeyman — 2018-05-20T18:51:30Z
I just noticed a minor issue...
Mago is displaying hex with A-F in CAPS.
VS debugger displays hex with a-f in lower case.
I'm finding it surprisingly jarring when calling in/out of D, because a pointer that I was just looking at *looks* different, and I think the value has changed.
Can we make Mago match VS with lower case hex?
Comment #9 by turkeyman — 2018-05-20T20:06:29Z
Also, another super-minor detail.
In C++, it shows the derived type member at the top, then __vfptr (ie, first member), then the normal member listing.
In Mago, it's showing __vfptr at the top, then the derived type member, then the normal members.
Can we swap those top 2 items to match C++? __vfptr is really just the first member, and it feels strange when it's disconnected from the other members (separated by the derived accessor) that way.
Comment #10 by r.sagitario — 2018-05-21T07:55:39Z
Ok, I changed hex values to lower letters.
The order of the class view is
[derived type]
__vfptr
base-types
The base types (multiple with interfaces) are part of the "field list" in the debug info, so a bit harder to mock fields.
Unfortunately I noticed two more cases where the display fails:
- with interfaces, the vtable contains pointers to thunks, but these have no symbols attached.
- LDC does not emit any vtable information at all, so nothing to be shown there.
Comment #11 by turkeyman — 2018-05-21T08:45:02Z
Well this is all great progress.
This experience is starting to feel pretty tight!
Comment #12 by r.sagitario — 2018-05-24T07:07:23Z
Next build also works with /INCREMENTAL.
Comment #13 by turkeyman — 2018-05-25T02:39:43Z
Wow! Works on all my tests!
Thanks again!
I wonder if this feature should be turned on by default now :P
Comment #14 by turkeyman — 2018-05-25T02:50:07Z
I'm noticing C++ symbols in the vtable don't demangle (ie, DMD).
Is there a way to call the C++ demangler that VS uses (to make sure the demangle is identical)?
Comment #15 by r.sagitario — 2018-05-25T05:59:31Z
demangling C++ is easy, helper functions everywhere... ;-)
Comment #16 by turkeyman — 2018-05-25T06:21:58Z
Sure, except that it needs to demangle EXACTLY like the MSVC tools, so you don't see 2 errors from C++ and from D next to eachother and get confused as if they're different symbols, and then spend minutes figuring out that they're actually the same symbol, just piped through 2 different demanglers! ;)
Comment #17 by turkeyman — 2018-05-25T06:23:55Z
Or if you're looking at a symbol in the watch window let's say, and you step from D <-> C, and the symbol appears to suddenly change name in the crossing :P
I'm doing a lot of watching vtables as I step back and fourth.
Comment #18 by r.sagitario — 2018-05-25T06:45:58Z
I use the demangler supplied by the debugger with flags as they seem to be used in C++. That doesn't prepend the module name, though.
But looking at the vtable isn't too common and is probably rather special to your current work. I also do it sometimes to figure the dynamic type if the debugger fails to do it.
Comment #19 by turkeyman — 2018-05-25T06:50:40Z
extern(C++) in D is fraught with perils. When working on a multi-language program, you really need those symbols and the vtable is super useful to know everything's in good state.