Although today’s post is loosely related to the esoterica of multiple virtual inheritance I unearthed in part 3, it is far more straightforward and goes no further than single inheritance. Spoiler alert: people come up with clever stuff.
Part 3 was a riff on the theme of finding the address of an object, given the address of an interface (vftable) within that object. This was made possible by a combination of member offsets known at compile time and offsets explicitly stored within the object. Now we move on to discover how the address of one object can be derived from the address of another, purely by convention and without relying on magic numbers known only to the compiler or stored in runtime structures. Sounds interesting?
I have often walked down this deck before
But the water always stayed beneath my neck before
It’s the first time I’ve had to scuba dive
When I’m down on the deck where you live.
Come dive with me
In Part 2, the last example was a function which optionally called commondelete as object disposal after the class destructor:
sqllang!CSecContextToken::`vector deleting destructor': ======================================================= mov qword ptr [rsp+8],rbx push rdi sub rsp,20h mov ebx,edx mov rdi,rcx call sqllang!CSecContextToken::~CSecContextToken test bl,1 +-+- je LabelX | | | | { if low bit of original edx was set | -> mov rcx,rdi | call sqllang!commondelete | } | LabelX: +--> mov rax,rdi mov rbx,qword ptr [rsp+30h] add rsp,20h pop rdi ret
So what does commondelete() do? Well, quite simply it is a global function that releases a previously allocated block of memory starting at the supplied address. Prior to the construction of an object, we need to allocate a chunk of memory to store it in, and after its destruction, this memory must be given back to the pool of available memory. Forgetting to do the latter leads to memory leaks, not to mention getting grounded for a week.
Notionally we can imagine a global portfolio of active memory allocations, each chunk uniquely identified by its starting address. When we want memory, we ask the global memory manager to lend us some from the unused pool, and when we’re done with it, we hand it back to that memory manager, who carefully locks its internal structures during such operations, because we should only access mutable global state in a single-threaded manner, and…. Oops. No, no, double no. That is not how SQL Server does things, right?
Okay, we know that there are actually a variety of memory allocators out there. If nothing else, this avoid the single bottleneck problem. But now the question becomes one of knowing which allocator to return a chunk of memory to after we’re done with it.
One possible solution would be for the object which constructs a child object to store its own reference to the allocator, and this parent object is then the only object who is allowed to destroy its child object. However, this yields a system where all objects have to exist in a strict parent-child hierarchy, which is very restrictive.
As it turns out, many interesting SQL Server objects control their lifetime through reference counting. If object A wants to interact with object B, it first announces that interest, which increases the reference count on B. This serves as a signal that B can’t be disposed, i.e. the pointer held by A remains valid. Once done, A releases the reference to B, which decreases B’s reference count again. Ultimately, B can only be destroyed after its reference count has reached zero. One upshot of this is that objects can live independent lives, without forever being tied to the apron strings of a parent object.
From that given, the problem becomes this: an object playing in this ecosystem must carry around a reference to its memory allocator. Doing it explicitly is conceptually simple. However, it would be onerous, because this pointer would take up an eight-byte chunk of memory within the object itself, which can be a significant tax when you have a large number of small objects. Perhaps more significantly, it raises the complexity bar to playing along, because now all objects must be born with a perfectly-developed plan for their own eventual death, and they must implement standardised semantics for postmortem disposal of their bodies. That’s harsh, not to mention bug-prone.
Far better then would be a way for an outside agent to look at an object and know exactly where to press the Recycle button, although the location of that button is neither global nor stored within the target object.
Pleasant little kingdom
Imagine a perfectly rational urban design; American grid layout taken to the extreme. Say you live at #1137 Main Street and want to find the nearest dentist, dentistry being of particular importance in this kingdom. Well, the rule is simple: every house number divisible by 100 contains a dentist, and to find your local one you just replace the last two digits of your house number with zeros.
That is the relationship that objects have with their memory allocators under SQLOS, except that we are dealing with 8192-byte pages. Take the address of any object in memory, round that address down to the next lower page boundary, and you will find a page header that leads you directly or indirectly to the memory object (allocator) that owns the object’s memory; this object will happily receive the notification that it can put your chunk of memory back into the bank.
I’m skipping some detail around conditionals and an indirect path, but the below is the guts of how commondelete() takes an address and crafts a call to the owning memory object to deallocate that target object and recycle the corpse:
mov rdx, rcx and rcx, 0FFFFFFFFFFFFE000h ; now rdx contain the address we want to free ; and rcx is the start of the containing page mov rcx, qword ptr [rcx+8] ; now rcx contains the 2nd qword on the page. ; This is the address of the memory object, ; aka pmo (pointer to memory object) mov rax, qword ptr [rcx] ; 1st qword of object = pointer to vftable ; Now rax points to the vftable call qword ptr [rax+28h] ; vftable is an array of (8-byte) function ; pointers. This calls the 6th one. Param 1 ; (rcx) is the pmo. Param 2 (rdx) is the ; address we want to free.
See, assembler isn’t really that bad once you put comments in.
Show me
Let’s find ourselves some SQLOS objects. SOS_Schedulers are sitting ducks, because you can easily find their addresses from a DMV query, and they don’t suddenly fly away. And of course I have explored them before. Below from a sad little VM:
Got myself a few candidate addresses, so it’s time to break into song the debugger. I’m going to dump a section of memory in 8-byte chunks, and to make it more interesting, I’ll do so with the dps command, which assumes all the memory contents are pointers, and will call out any that it recognises from public symbols. Never mind that this regimented 8-byte view may be at odds with the underlying data’s shape – it’s just a convenient way to slice it.
Because I know where I’m heading, I’m rounding the starting address down to the start of that 8Kb page:
0:031> dps 40130000 00000000`40130000 00010002`00000000 00000000`40130008 00000000`4001e040 00000000`40130010 00000001`00000008 00000000`40130018 00000000`00000000 00000000`40130020 00000000`40188000 00000000`40130028 00000000`00000000 00000000`40130030 00000000`00000003 00000000`40130038 00000000`000010a0 00000000`40130040 00007ffb`edbe0ff8 sqldk!ThreadScheduler::`vftable' 00000000`40130048 00000000`40120048 00000000`40130050 00000000`40080170 00000000`40130058 00000000`00000003 00000000`40130060 00000000`40080170 00000000`40130068 00000000`399d3858 00000000`40130070 00000000`400204f8 00000000`40130078 00000014`00000000
Firstly, we have a beautiful piece of confirmation that the object starting at 0040 is indeed a scheduler, because its very first quadword contains a pointer to the ThreadScheduler vftable. But we knew that already š
What I’d like to know is what the second quadword on the page, at 0008, is pointing to. Interesting, its address also ends in 0040…
Same thing then, let’s dump a bit of that, starting from the top of that page:
0:057> dps 4001e000 00000000`4001e000 00010002`00000000 00000000`4001e008 00000000`4001e040 00000000`4001e010 00000045`00000001 00000000`4001e018 00000000`00000000 00000000`4001e020 00000000`3a6e8000 00000000`4001e028 00000000`40188000 00000000`4001e030 00000000`00000001 00000000`4001e038 00000000`000001b0 00000000`4001e040 00007ffb`edbdc640 sqldk!CMemThread::`vftable' 00000000`4001e048 00000000`00000001 00000000`4001e050 00000000`00002000 00000000`4001e058 00000000`00000004 00000000`4001e060 00000000`40022200 00000000`4001e068 00000000`00000010 00000000`4001e070 00000000`40000280 00000000`4001e078 00000000`4001e040
Aha! On a freaking platter. The vftable symbol name confirms that the scheduler’s parent memory object is in fact a CMemThread<CMemObj>. Not only that, but if you look at the second slot, it seems that a memory object is its own owner!
But speaking of the vftable, since we’re now familiar with these creatures as simple arrays of function pointers, let’s dump a bit of it. The full thing is rather long; this is just to give you the idea:
0:057> dps 7ffb`edbdc640 00007ffb`edbdc640 00007ffb`edadc140 sqldk!IMemObj::QueryInterface 00007ffb`edbdc648 00007ffb`eda75f00 sqldk!IMemObj::AddRef 00007ffb`edbdc650 00007ffb`eda84d50 sqldk!IMemObj::Release 00007ffb`edbdc658 00007ffb`edb0ee40 sqldk!CMemThread::Alloc 00007ffb`edbdc660 00007ffb`edb0f0b0 sqldk!CMemThread ::Realloc 00007ffb`edbdc668 00007ffb`eda75410 sqldk!CMemThread ::Free 00007ffb`edbdc670 00007ffb`edb0f1d0 sqldk!CMemThread ::GetSize 00007ffb`edbdc678 00007ffb`edb0f440 sqldk!CMemThread ::DidAlloc
Recall that commondelete() calls the sixth entry, located 0x28 bytes from the start. Unsurprisingly, this is the Free() function. And look, the first three tell us that COM is spoken here.
Takeaway
This is not the time to get into the details of how memory objects actually work, but we sure got a nice little glimpse of what SQLOS does with the memory space. Also, the charming side of vftables was laid bare.
Although the assembler view might look daunting, we saw how a virtual method, in this case Free(), is called, using nothing more than the address of the vftable and the knowledge that the object residing there is (or is derived from) the base type exposing that method.
But let’s not lose sight of the “Context in perspective” goal. Here the context is the address of a single object, and the perspective is seeing it framed by convention within a memory page. By its placement in memory, the object gains the all-important implicit attribute of an owning memory object.
Further reading
Bob Dorr’s classic How it works: CMEMTHREAD and debugging them is definitely going to feature when I visit memory objects again.
One thought on “Context in perspective 4: On the street where you live”