一:背景1。讲故事 前些天看到一个奇怪的Function函数,调用的是C链接库中的一个UserLogin方法,参考代码如下:CREATEFUNCTIONdbo。clrUserLogin(nameASNVARCHAR(100),passwordASNVARCHAR(100))RETURNSINTASEXTERNALNAMEasmXXX。〔xxx。CLRFunctions〕。UserLogin;GO 这就让我产生了很大的兴趣,众所周知SQLSERVER是C写的,那这里的C怎么和C打通呢?而且C是一门托管语言,需要JIT将其native化,这个JIT又在哪里呢?带着这些疑问一起研究下吧。二:互通原理研究1。一个简单的例子 首先写一段简单的C代码,然后把它编译成dll。namespaceAQMN。Bussiness{publicclassUserFunctions{publicstaticstringUserLogin(stringusername,stringpassword){varrandomnewRandom();varisSuccessrandom。Next()20;returnisSuccess?登录成功:登录失败;}}} 接下来需要做的就是数据库参数配置,开启CLR支持,并且指定某个数据库支持unsafe模式。EXECspconfigureclrenabled,1;RECONFIGURE;GOALTERDATABASEMyTestDBSETTRUSTWORTHYON;GO 为了能够调到C的UserLogin方法,需要SQLSERVER先导入这个程序集,然后再以Function映射其中方法即可,参考代码如下:CREATEASSEMBLYclrAQMNBussinessFROMD:et6SQLCrawlAQMN。BussinessbinDebugAQMN。Bussiness。dllWITHPERMISSIONSETUNSAFE;GOCREATEFUNCTIONdbo。clrUserLogin(usernameASNVARCHAR(100),passwordASNVARCHAR(100))RETURNSNVARCHAR(100)ASEXTERNALNAMEclrAQMNBussiness。〔AQMN。Bussiness。UserFunctions〕。UserLogin;GO 创建完了之后,可以观察assembly开头的几个系统视图。SELECTFROMsys。assembliesSELECTFROMsys。assemblyfiles;SELECTFROMsys。assemblymodules; 看起来没啥问题,接下来调用一下刚才创建的clrUserLogin函数。SELECTdbo。clrUserLogin(Njack,N123456)ASStateGO10 从图中看登录结果是随机的,说明C的Random函数起到了作用,非常有意思。2。WinDbg观察 从案例的运行结果看,推测在SQLSERVER中应该承载了一个CLR运行环境,那是不是这样呢?可以用WinDbg附加到sqlservr。exe进程,用lm观察下模块加载情况。0:092lmstartendmodulename。。。00007ff8d396000000007ff8d3aaf000clrjit(deferred)00007ff8de04000000007ff8deb02000clr(deferred)。。。0:092!eeversion4。8。4300。0freeServermodewith12gcheapsSOSVersion:4。8。4300。0retailbuild 从输出看果然加载了clr和clrjit动态链接库,当前还是gcserver模式,哈。 接下来再验证一个问题,既然clrUserLogin函数会显示登录成功登录失败,那必然会调用C的UserLogin方法,可以在WinDbg中对UserLogin方法下一个断点观察一下这个调用过程。0:090!name2eeAQMN。Bussiness!AQMN。Bussiness。UserFunctions。UserLoginModule:00007ff87ee37988Assembly:AQMN。Bussiness,Version1。0。0。0,Cultureneutral,PublicKeyTokennullToken:0000000006000001MethodDesc:00007ff87ee38020Name:AQMN。Bussiness。UserFunctions。UserLogin(System。String,System。String)JITTEDCodeAddress:00007ff87ec560d00:090bp00007ff87ec560d00:090g 从输出信息看UserLogin方法已经被JIT过了,用bp下完断点之后,继续g,然后在SSMS上再次执行查询就可以成功命中啦。0:090kChildSPRetAddrCallSite00000000df1557ae4800007ff87ee500b60x00007ff87ec560d001000000df1557ae5000007ff87ec55ef10x00007ff87ee500b602000000df1557aeb000007ff8de04222e0x00007ff87ec55ef103000000df1557af0000007ff8a2b79ff3clr!UMThunkStub0x6e04000000df1557af9000007ff8a2b741bdsqllang!CallProtectorImpl::CallWithSEHconst0x2305000000df1557afc000007ff8a2b6bfc4sqllang!CallProtectorImpl::CallExternalFullconst0x2dd06000000df1557b13000007ff8a2bda602sqllang!CAppDomain::InvokeClrFn0xd407000000df1557b1d000007ff8aef51ee7sqllang!UDFInvokeExternalImpl0xb7208000000df1557b7e000007ff89de52e24sqlTsEs!CEsExec::GeneralEval40xe709000000df1557b8b000007ff89de52d64sqlmin!CQScanProjectNew::EvalExprs0x18f0a000000df1557b92000007ff89ddd8759sqlmin!CQScanProjectNew::GetRow0x980b000000df1557b97000007ff89ddc73desqlmin!CQScanLightProfileNew::GetRow0x190c000000df1557b9a000007ff8a25e51d7sqlmin!CQueryScan::GetRow0x800d000000df1557b9d000007ff8a32a78b2sqllang!CXStmtQuery::ErsqExecuteQuery0x3d80e000000df1557bb4000007ff8a2bc2451sqllang!CXStmtSelect::XretDoExecute0x3420f000000df1557bc1000007ff8a2b733d3sqllang!UMLoopbackForStatementExecution0x19110000000df1557bd0000007ff8de48e940sqllang!AppDomainCallbackFunctionCallBinder5void,void(cdecl)(CXStmtQueryptr64,CCompExecCtxtStmtconstptr64,CMsqlExecContextptr64,unsignedlongptr64,enumESqlReturnCodeptr64),CXStmtQueryptr64,CCompExecCtxtStmtconstptr64,CMsqlExecContextptr64,unsignedlongptr64,enumESqlReturnCodeptr640x2311000000df1557bd4000007ff8de48e193clr!ExecuteInAppDomainHelper0x4012000000df1557bd8000007ff8a2b79f39clr!CorHost2::ExecuteInAppDomain0x3a013000000df1557c0a000007ff8a2b73a86sqllang!CallProtectorImpl::CallWithSEH0x2914000000df1557c0d000007ff8a2b6c2d0sqllang!CallProtectorImpl::CallExternalFull0x18615000000df1557c17000007ff8a32a72f4sqllang!CAppDomain::LoopbackForStatementExecution0x18016000000df1557c23000007ff8a32a79adsqllang!CXStmtQuery::XretCLRExecute0x10417000000df1557c2a000007ff8a25e4a65sqllang!CXStmtSelect::XretExecute0x4a18000000df1557c37000007ff8a25e44a8sqllang!CMsqlExecContext::ExecuteStmts1,10x8f219000000df1557cf1000007ff8a25e3a2csqllang!CMsqlExecContext::FExecute0x9361a000000df1557def000007ff8a25ee67bsqllang!CSQLSource::Execute0xc5c1b000000df1557e3d000007ff8a25ed815sqllang!processrequest0xca61c000000df1557ead000007ff8a25ed5efsqllang!processcommandsinternal0x4b71d000000df1557ec0000007ff8b1e46523sqllang!processmessages0x1d61e000000df1557ede000007ff8b1e46e6dsqldk!SOSTask::Param::Execute0x2321f000000df1557f3e000007ff8b1e46c75sqldk!SOSScheduler::RunTask0xa520000000df1557f45000007ff8b1e6b160sqldk!SOSScheduler::ProcessTasks0x39d21000000df1557f57000007ff8b1e6aa5bsqldk!SchedulerManager::WorkerEntryPoint0x2a122000000df1557f64000007ff8b1e6afa4sqldk!SystemThreadDispatcher::ProcessWorker0x3ed23000000df1557f94000007ff8f6d86fd4sqldk!SchedulerManager::ThreadEntryPoint0x3b524000000df1557fa3000007ff8f865cec1KERNEL32!BaseThreadInitThunk0x1425000000df1557fa600000000000000000ntdll!RtlUserThreadStart0x21 果然是一个request请求,然后达到了托管方法UserLogin,顶部的三行线程栈可以用!clrstack具意下。0:090!clrstackOSThreadId:0x6df4(90)ChildSPIPCallSite000000df1557ae4800007ff87ec560d0AQMN。Bussiness。UserFunctions。UserLogin(System。String,System。String)000000df1557ae5000007ff87ee500b6DynamicClass。SQLCLREval(IntPtr,IntPtr,IntPtr)000000df1557aeb000007ff87ec55ef1DomainBoundILStubClass。ILSTUBReversePInvoke(Int64,Int64,Int64)000000df1557bf1800007ff8de04222e〔ContextTransitionFrame:000000df1557bf18〕三:总结 SQLSERVER内嵌了CLR,让sqlservr进程成了一种托管和非托管的混合环境,不知道是好事还是坏事,在我的分析旅程中这种混合环境下看过太多的堆破坏问题,但不管怎么说,托管的C,VB,F可以助SQLSERVER更加强大。