This seemed like a fun project for a Sunday afternoon, so I had a go at it. To get straight to the point, here's the call stack for a function in SQL server that parses and then executes the query (addresses and offsets taken from SQL Server 2008 R2 running on Windows 7 SP1 32-bit):
0x7814500a msvcr80.i386!memcpy+0x5a
0x013aa370 sqlservr!CWCharStream::CwchGetWChars+0x5c
0x013a9db5 sqlservr!CSQLStrings::CbGetChars+0x35
0x012ffa50 sqlservr!CParser::FillBuffer+0x3d
0x0138bbfd sqlservr!CParser::CParser+0x3c8
0x01352e96 sqlservr!sqlpars+0x7b
0x013530f2 sqlservr!CSQLSource::FParse+0x16d
0x013531ed sqlservr!CSQLSource::FParse+0x268
0x012ff9e8 sqlservr!`string'+0x3c
0x015894b8 sqlservr!CSQLSource::Execute+0x2c8
0x0158ad31 sqlservr!process_request+0x2ac
0x0158a328 sqlservr!process_commands+0x15f
0x015cf8b4 sqlservr!SOS_Task::Param::Execute+0xdd
0x015cf9ea sqlservr!SOS_Scheduler::RunTask+0xb4
0x015cf575 sqlservr!SOS_Scheduler::IsShrinkWorkersNecessary+0x48
0x77f06854 ntdll!ZwSignalAndWaitForSingleObject+0xc
0x77e479e2 kernel32!SignalObjectAndWait+0x82
Based on this, you probably want to take a close look at the CSQLSource
class, and particularly its Execute
method.
Armed with this information, I was also able to dig up a couple blog posts by someone at Microsoft on how to extract the query string from a memory dump of SQL Server. That post seems to confirm that we're on the right track, and gives you a place to interpose and a way to extract the query string.
Methodology
I felt like this would be most easily tackled using some form of Dynamic Binary Instrumentation (DBI); since we suspect the query string will be processed somewhere in the SQL Server process, we can look at memory reads and writes made by the process, searching for a point that reads or writes the query string. We can then dump the callstack at that point and see what interesting addresses show up, and map them back to symbols (since, as Rolf points out, SQL Server has debug symbols available). It really was basically as simple as that!
Of course, the trick is having something around that lets you easily instrument a process. I solved this using a (hopefully soon-to-be-released) whole-system dynamic analysis framework based on QEMU; this let me avoid any unpleasantness involved in getting SQL Server to run under, e.g., PIN. Because the framework includes record and replay support, I also didn't have to worry about slowing down the server process with my instrumentation. Once I had the callstack, I used PDBParse to get the function names.