Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

mgrzeg.net - Admin on Rails :)

SQL-CLR - debuggowanie z użyciem WinDbg

W poprzedniej notce opisałem technikę pozwalającą na debuggowanie dynamic assemblies przy wykorzystaniu najnowszej wersji biblioteki sosex w ramach WinDbg. Tym razem sprawdzimy skuteczność tej metody w odniesieniu do SQL-CLR.

Nieco przydługawy, ale konieczny wstęp

CLR pojawił się w SQL Serverze w wersji 2005 i pozostał w nim na dobre. Dzięki jego obecności można pisać triggery, procedury składowane, funkcje, agregaty oraz własne typy danych definiowane przez użytkownika i korzystać z nich w podobny sposób, jak to jest w przypadku rozwiązań klasycznych, opartych o T-SQL, a także tych pisanych w C i ładowanych dynamicznie. Wystarczy wrzucić do bazy danych własną bibliotekę i zadeklarować co w niej jest, a SQL Server zrobi za nas resztę.
Do wrzucenia naszego assembly do bazy danych korzystamy z polecenia CREATE ASSEMBLY, podając jako parametr ścieżkę do pliku .dll zawierającego nasz skompilowany kod. SQL Server przechowuje całą bibliotekę żywcem w bazie danych, a możemy to sprawdzić zaglądając do tabel sys.assemblies oraz sys.assembly_files (a dokładniej pola content, w którym przechowywana jest żywcem biblioteka). Więcej na ten temat można poczytać w dokumentacji, a także na stronie Database Administrator's Guide to SQL Server Database Engine .NET CLR Environment.

W momencie, w którym SQL Server zostanie przez nas poproszony o użycie naszej biblioteki, zawartość assembly zostaje pobrana z tabeli i korzystając z Assembly.Load załadowana do pamięci.
Co ciekawe, obok pliku .dll można załadować do bazy danych także inne pliki, w tym również odpowiedający bibliotece plik .pdb. Wystarczy użyć polecenia ALTER ASSEMBLY ze ścieżką wskazującą na odpowiedni plik zawierający symbole i gotowe! :)

Przykład: funkcja definiowana przez użytkownika

W ramach Visual Studio mamy do dyspozycji odpowiednie szablony projektowe, dzięki którym tworzenie, wdrażanie oraz debuggowanie odpowiednich bibliotek dla SQL-CLR jest łatwe, proste i przyjemne :)
Ponieważ ten tekst nie jest o tworzeniu kodu SQL-CLR, więc pozwolę sobie pójść na skróty i skorzystać z gotowca, czyli projektu SqlRegularExpressions, dostępnego na stronie codeproject.com i nieco go zmodyfikować na nasze potrzeby.
Zachęcam do pobrania, skompilowania oraz wrzucenia (deploy) projektu do którejś ze swoich baz testowych oraz przygotowania jakichkolwiek danych, na których będzie można przedstawione tam funkcje użytkownika przetestować. Dla ułatwienia można użyć wersji 2 projektu, który jest pełnoprawnym projektem SQL Server ze wszelkimi udogodnieniami.

Do tej pory w przykładowych projektach używałem przerywników w postaci Console.ReadLine(), tudzież metod klasy System.Diagnostics.Debugger do tego, aby wskorzyć w odpowiednim momencie do debuggera i popatrzeć na to, co w trawie piszczy. SQL Server nie pozwala jednak na interektywną pracę z kodem CLR, więc dla odmiany skorzystam z metody Sleep, którą umieszczę w metodzie RegexMatch, np. w bloku try:

try
{
  if (((string)input).Contains("zine"))
  {
    System.Threading.Thread.Sleep(10000);
  }
  Match match = Regex.Match((string)input, (string)pattern, (RegexOptions)(int)options);
  if (match.Value != String.Empty)
  {
    return SqlBoolean.True;
  }
}

Po skompilowaniu i wrzuceniu do bazy danych sprawdzamy działanie funkcji w Management Studio:

select dbo.RegexMatch('Michal', 'Michal', 0)

i w wyniku dostajemy

1

Dla

select dbo.RegexMatch('Asia', 'Michal', 0)

dostajemy

0

A więc wszystko gra.
Teraz dla odmiany wpisujemy

select dbo.RegexMatch('zine', 'Michal', 0)

F5, i... czekamy. Po 10 sekundach pojawia się w wyniku '0'. Wystarczająco dużo czasu, aby przerwać działanie i wskoczyć do debuggera.
Uruchamiamy WinDbg, podłączamy się do SqlServer (dla wersji 64-bit - używamy wersji 64-bit!) i w SSMS wykonujemy nasze

select dbo.RegexMatch('zine', 'Michal', 0)

Tym jednak razem, w momencie, w którym Management Studio pokazuje nam, że czeka na wynik, my bez zwłoki przerywamy działanie SQL Servera (ctrl+break) w WinDbg i zaczynamy zabawę.

Czas na zabawę!

Na początek ładujemy psscor2 (SQL Server 2008 R2 wspiera .NET Framework do wersji 3.5), sosex, listujemy zarządzane wątki i ustawiamy się na właściwym:

0:085> .load psscor2
0:085> .load sosex
0:085> !threads
ThreadCount: 5
UnstartedThread: 0
BackgroundThread: 4
PendingThread: 0
DeadThread: 1
Hosted Runtime: yes (Memory, Task, Sync, Assembly, GC, Security)
                                              PreEmptive                                                Lock
       ID OSID        ThreadOBJ     State   GC     GC Alloc Context                  Domain           Count APT  Exception
XXXX    1    0 00000000820f39c0     18820 Enabled  0000000000000000:0000000000000000 0000000082142080     0 Ukn 0000000000000000
  41    2  b18 00000000820bcf70      b220 Enabled  0000000000000000:0000000000000000 0000000082142080     0 MTA 0000000000000000 (Finalizer)
  66    3 1260 000000008208f480      1220 Enabled  0000000000000000:0000000000000000 0000000082142080     0 Ukn 0000000000000000
<<<<    4    0 0000000081f873b0     40220 Enabled  0000000000000000:0000000000000000 0000000082142080     0 Ukn 0000000000000000
  75    5  b64 0000000081f8c240   2000220 Enabled  000000048045ca30:000000048045cfd0 000000008878a080     0 Ukn 0000000000000000
0:085> ~75s
ntdll!ZwSignalAndWaitForSingleObject+0xa:
00000000`77402aaa c3              ret
0:075> !clrstack
OS Thread Id: 0xb64 (75)
Child-SP         RetAddr          Call Site
000000001903d9e0 000007ff006902c5 UserDefinedFunctions.RegexMatch(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlInt32)
000000001903db20 000007ff004db60c DynamicClass.SQLCLR_Eval(IntPtr, IntPtr, IntPtr)
000000001903dc40 000007fef8baf03a DomainBoundILStubClass.IL_STUB(Int64, Int64, Int64)

Wygląda na to, że jesteśmy we właściwym miejscu :) Sprawdźmy zatem stos, i przełączmy się na odpowiednią ramkę:

0:075> !mk
Thread 75:
     ESP              EIP
00:U 000000001903cdc8 0000000077402aaa ntdll!ZwSignalAndWaitForSingleObject+0xa
01:U 000000001903cdd0 00000000770f2da0 kernel32!SignalObjectAndWait+0x110
02:U 000000001903ce80 00000000003e4f97 sqlservr!SOS_Scheduler::Switch+0x181
03:U 000000001903d5d0 00000000003e2eba sqlservr!SOS_Scheduler::SuspendNonPreemptive+0xca
04:U 000000001903d610 00000000003e2628 sqlservr!SOS_Scheduler::Suspend+0x2d
05:U 000000001903d640 00000000003e671c sqlservr!SOS_Task::Sleep+0xec
06:U 000000001903d6a0 0000000002929e27 sqlservr!CHostTaskManager::Sleep+0xe3
07:U 000000001903d710 000000000292710a sqlservr!CHostTaskManagerWrapper::Sleep+0x96
08:U 000000001903d750 000007fef8e277f1 mscorwks!EESleepEx+0x91
09:U 000000001903d7d0 000007fef8afca85 mscorwks!Thread::UserSleep+0x71
0a:U 000000001903d830 000007fef8fdfc39 mscorwks!ThreadNative::Sleep+0xf9
0b:M 000000001903d9e0 000007ff004dc24a
"qlRegularExpressions," was not found in the image list.
Debugger will attempt to load "qlRegularExpressions," at given base 00000000`00000000.
Please provide the full image name, including the extension (i.e. kernel32.dll)
for more reliable results.Base address and size overrides can be given as
.reload <image.ext>=<base>,<size>.
Unable to add module at 00000000`00000000
"qlRegularExpressions," was not found in the image list.
Debugger will attempt to load "qlRegularExpressions," at given base 00000000`00000000.
Please provide the full image name, including the extension (i.e. kernel32.dll)
for more reliable results.Base address and size overrides can be given as
.reload <image.ext>=<base>,<size>.
Unable to add module at 00000000`00000000
UserDefinedFunctions.RegexMatch(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlInt32)(+0x72 IL)(+0x23a Native)
0c:M 000000001903db20 000007ff006902c5 DynamicClass.SQLCLR_Eval(IntPtr, IntPtr, IntPtr)(+0x0 IL)(+0x0 Native)
0d:M 000000001903dc40 000007ff004db60c DomainBoundILStubClass.IL_STUB(Int64, Int64, Int64)(+0x0 IL)(+0x0 Native)
0e:U 000000001903dc70 000007fef8baf03a mscorwks!UMThunkStubAMD64+0x7a
0f:U 000000001903dd00 00000000011742a1 sqlservr!CallProtectorImpl::CallWithSEH<AppDomainUserCallTraits,void,FunctionCallBinder_3<void,void (__cdecl*)(CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64),CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64> const >+0x25
10:U 000000001903dd30 00000000011656a8 sqlservr!CallProtectorImpl::CallExternalFull<AppDomainUserCallTraits,void,FunctionCallBinder_3<void,void (__cdecl*)(CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64),CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64> const >+0x2a8
11:U 000000001903de20 0000000001156296 sqlservr!CAppDomain::InvokeClrFn+0x9a
12:U 000000001903deb0 000000000145e165 sqlservr!UDFInvokeExternal+0xbb6
13:U 000000001903e2e0 0000000000418fde sqlservr!CEs::GeneralEval4+0x11d
14:U 000000001903e390 00000000004798c0 sqlservr!CXStmtQuery::ErsqExecuteQuery+0x700
15:U 000000001903e520 00000000018fd6ec sqlservr!CXStmtSelect::XretDoExecute+0x2b4
16:U 000000001903e5f0 00000000011ad2f1 sqlservr!UM_LoopbackForStatementExecution+0x1b1
17:U 000000001903e6f0 0000000001163c53 sqlservr!AppDomainCallback<FunctionCallBinder_5<void,void (__cdecl*)(CXStmtQuery * __ptr64,CCompExecCtxtStmt const * __ptr64,CMsqlExecContext * __ptr64,unsigned long * __ptr64,enum ESqlReturnCode * __ptr64),CXStmtQuery * __ptr64,CCompExecCtxtStmt const * __ptr64,CMsqlExecContext * __ptr64,unsigned long * __ptr64,enum ESqlReturnCode * __ptr64> >+0x23
18:U 000000001903e730 000007fef8fb5744 mscorwks!ExecuteInAppDomainHelper+0x94
19:U 000000001903e7b0 000007fef903bc46 mscorwks!CorHost2::ExecuteInAppDomain+0x226
1a:U 000000001903e9a0 000000000117422b sqlservr!CallProtectorImpl::CallWithSEH<AppDomainCallTraits,long,MethodCallBinder_3<long,ICLRRuntimeHost,long (__cdecl ICLRRuntimeHost::*)(unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64) __ptr64,unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64> >+0x2b
1b:U 000000001903e9d0 0000000001164fbc sqlservr!CallProtectorImpl::CallExternalFull<AppDomainCallTraits,long,MethodCallBinder_3<long,ICLRRuntimeHost,long (__cdecl ICLRRuntimeHost::*)(unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64) __ptr64,unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64> >+0x19c
1c:U 000000001903ea70 0000000001156529 sqlservr!CAppDomain::LoopbackForStatementExecution+0x145
1d:U 000000001903eb20 00000000018fd063 sqlservr!CXStmtQuery::XretCLRExecute+0xb3
1e:U 000000001903eb70 0000000000a53548 sqlservr!CXStmtSelect::XretExecute+0x4b
1f:U 000000001903ec40 0000000000439d59 sqlservr!CMsqlExecContext::ExecuteStmts<1,1>+0x377
20:U 000000001903ed50 000000000043a9b8 sqlservr!CMsqlExecContext::FExecute+0x983
21:U 000000001903eed0 000000000043b30c sqlservr!CSQLSource::Execute+0x7b2
22:U 000000001903f000 000000000043c1a6 sqlservr!process_request+0x64b
23:U 000000001903f660 0000000000485342 sqlservr!process_commands+0x4e5
24:U 000000001903f870 00000000003ebbd8 sqlservr!SOS_Task::Param::Execute+0x12a
25:U 000000001903f980 00000000003eb8ba sqlservr!SOS_Scheduler::RunTask+0x96
26:U 000000001903f9e0 00000000003eb6ff sqlservr!SOS_Scheduler::ProcessTasks+0x128
27:U 000000001903fa50 0000000000908fb6 sqlservr!SchedulerManager::WorkerEntryPoint+0x2b6
28:U 000000001903fb30 0000000000909175 sqlservr!SystemThread::RunWorker+0xcc
29:U 000000001903fb70 0000000000909839 sqlservr!SystemThreadDispatcher::ProcessWorker+0x2db
2a:U 000000001903fc20 0000000000909502 sqlservr!SchedulerManager::ThreadEntryPoint+0x173
2b:U 000000001903fcc0 0000000073d137d7 MSVCR80!endthreadex+0x47
2c:U 000000001903fcf0 0000000073d13894 MSVCR80!endthreadex+0x104
2d:U 000000001903fd20 000000007709652d kernel32!BaseThreadInitThunk+0xd
2e:U 000000001903fd50 00000000773dc521 ntdll!RtlUserThreadStart+0x1d

Ech, znowu to samo, co poprzednio. Sprawdzamy zatem listę modułów oraz dynamic assemblies:

0:075> lm m *regular*
start             end                 module name
0:075> !dda
Domain: DefaultDomain
-------------------
Domain: mssqlsystemresource.sys[runtime].1
-------------------
Domain: Michal.dbo[runtime].2
-------------------
Assembly: 0x000000008983f120 [SqlRegularExpressions, Version=2.0.4304.25114, Culture=neutral, PublicKeyToken=null] Dynamic Module: 0x000007ff00640198 loaded at: 0x0000000015440000 Size: 0x0000000000001600(5,632)
Domain: Michal.dbo[ddl].3
-------------------
Assembly: 0x0000000088a7da50 [] Dynamic Module: 0x000007ff00653030 loaded at: 0x0000000000000000 Size: 0x0000000000000000(0)
Assembly: 0x0000000089173bf0 [] Dynamic Module: 0x000007ff00655a58 loaded at: 0x0000000000000000 Size: 0x0000000000000000(0)
Assembly: 0x0000000089172c40 [] Dynamic Module: 0x000007ff007135a8 loaded at: 0x0000000000000000 Size: 0x0000000000000000(0)
Assembly: 0x0000000089173060 [] Dynamic Module: 0x000007ff007a0a38 loaded at: 0x0000000000000000 Size: 0x0000000000000000(0)
Assembly: 0x000000008917ba60 [] Dynamic Module: 0x000007ff007a22d8 loaded at: 0x0000000000000000 Size: 0x0000000000000000(0)
Assembly: 0x0000000088adbc20 [] Dynamic Module: 0x000007ff007d65a8 loaded at: 0x0000000000000000 Size: 0x0000000000000000(0)
--------------------------------------
Total 7 Dynamic Assemblies, Total size: 0x0(0) bytes.
=======================================

Jak widać, SQL Server utworzył dla mnie osobną domenę aplikacji (Michal.dbo[runtime].2), do której załadował bibliotekę, co oczywiście sprawia, iż kod jest bezpiecznie odseparowany i w razie potrzeby może zostać z aplikacji rozładowany. Dla wyjaśnienia - 'Michal' to nazwa bazy danych w której rzecz się dzieje.

Jawne wskazanie modułu

Dopisujemy w takim razie ścieżkę do biblioteki i przeładowujemy odpowiednie assembly

0:075> .sympath+ C:\PATH_TO_PDBS
Symbol search path is: srv*C:\websymbols*http://msdl.microsoft.com/downloads/symbols;C:\PATH_TO_PDBS
Expanded Symbol search path is: srv*c:\websymbols*http://msdl.microsoft.com/downloads/symbols;c:\PATH_TO_PDBS
0:075> .reload /f SqlRegularExpressions.dll=0x0000000015440000,0x0000000000001600
*** WARNING: Unable to verify checksum for SqlRegularExpressions.dll
0:075> lm m *regular*
start             end                 module name
00000000`15440000 00000000`15441600   SqlRegularExpressions M (private pdb symbols)  c:\PATH_TO_PDBS\SqlRegularExpressions.pdb

Oczywiście moduł się znalazł, więc możemy wrócić do naszych zabaw.

0:075> !mk
Thread 75:
     ESP              EIP
00:U 000000001903cdc8 0000000077402aaa ntdll!ZwSignalAndWaitForSingleObject+0xa
01:U 000000001903cdd0 00000000770f2da0 kernel32!SignalObjectAndWait+0x110
02:U 000000001903ce80 00000000003e4f97 sqlservr!SOS_Scheduler::Switch+0x181
03:U 000000001903d5d0 00000000003e2eba sqlservr!SOS_Scheduler::SuspendNonPreemptive+0xca
04:U 000000001903d610 00000000003e2628 sqlservr!SOS_Scheduler::Suspend+0x2d
05:U 000000001903d640 00000000003e671c sqlservr!SOS_Task::Sleep+0xec
06:U 000000001903d6a0 0000000002929e27 sqlservr!CHostTaskManager::Sleep+0xe3
07:U 000000001903d710 000000000292710a sqlservr!CHostTaskManagerWrapper::Sleep+0x96
08:U 000000001903d750 000007fef8e277f1 mscorwks!EESleepEx+0x91
09:U 000000001903d7d0 000007fef8afca85 mscorwks!Thread::UserSleep+0x71
0a:U 000000001903d830 000007fef8fdfc39 mscorwks!ThreadNative::Sleep+0xf9
0b:M 000000001903d9e0 000007ff004dc24a UserDefinedFunctions.RegexMatch(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlInt32)(+0x72 IL)(+0x23a Native) [C:\PATH_TO_SRC\RegexMatch.cs @ 69,9]
0c:M 000000001903db20 000007ff006902c5 DynamicClass.SQLCLR_Eval(IntPtr, IntPtr, IntPtr)(+0x0 IL)(+0x0 Native)
0d:M 000000001903dc40 000007ff004db60c DomainBoundILStubClass.IL_STUB(Int64, Int64, Int64)(+0x0 IL)(+0x0 Native)
0e:U 000000001903dc70 000007fef8baf03a mscorwks!UMThunkStubAMD64+0x7a
0f:U 000000001903dd00 00000000011742a1 sqlservr!CallProtectorImpl::CallWithSEH<AppDomainUserCallTraits,void,FunctionCallBinder_3<void,void (__cdecl*)(CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64),CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64> const >+0x25
10:U 000000001903dd30 00000000011656a8 sqlservr!CallProtectorImpl::CallExternalFull<AppDomainUserCallTraits,void,FunctionCallBinder_3<void,void (__cdecl*)(CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64),CXVariant * __ptr64,CXVariant * __ptr64,CClrLobContext * __ptr64> const >+0x2a8
11:U 000000001903de20 0000000001156296 sqlservr!CAppDomain::InvokeClrFn+0x9a
12:U 000000001903deb0 000000000145e165 sqlservr!UDFInvokeExternal+0xbb6
13:U 000000001903e2e0 0000000000418fde sqlservr!CEs::GeneralEval4+0x11d
14:U 000000001903e390 00000000004798c0 sqlservr!CXStmtQuery::ErsqExecuteQuery+0x700
15:U 000000001903e520 00000000018fd6ec sqlservr!CXStmtSelect::XretDoExecute+0x2b4
16:U 000000001903e5f0 00000000011ad2f1 sqlservr!UM_LoopbackForStatementExecution+0x1b1
17:U 000000001903e6f0 0000000001163c53 sqlservr!AppDomainCallback<FunctionCallBinder_5<void,void (__cdecl*)(CXStmtQuery * __ptr64,CCompExecCtxtStmt const * __ptr64,CMsqlExecContext * __ptr64,unsigned long * __ptr64,enum ESqlReturnCode * __ptr64),CXStmtQuery * __ptr64,CCompExecCtxtStmt const * __ptr64,CMsqlExecContext * __ptr64,unsigned long * __ptr64,enum ESqlReturnCode * __ptr64> >+0x23
18:U 000000001903e730 000007fef8fb5744 mscorwks!ExecuteInAppDomainHelper+0x94
19:U 000000001903e7b0 000007fef903bc46 mscorwks!CorHost2::ExecuteInAppDomain+0x226
1a:U 000000001903e9a0 000000000117422b sqlservr!CallProtectorImpl::CallWithSEH<AppDomainCallTraits,long,MethodCallBinder_3<long,ICLRRuntimeHost,long (__cdecl ICLRRuntimeHost::*)(unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64) __ptr64,unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64> >+0x2b
1b:U 000000001903e9d0 0000000001164fbc sqlservr!CallProtectorImpl::CallExternalFull<AppDomainCallTraits,long,MethodCallBinder_3<long,ICLRRuntimeHost,long (__cdecl ICLRRuntimeHost::*)(unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64) __ptr64,unsigned long,long (__cdecl*)(void * __ptr64),void * __ptr64> >+0x19c
1c:U 000000001903ea70 0000000001156529 sqlservr!CAppDomain::LoopbackForStatementExecution+0x145
1d:U 000000001903eb20 00000000018fd063 sqlservr!CXStmtQuery::XretCLRExecute+0xb3
1e:U 000000001903eb70 0000000000a53548 sqlservr!CXStmtSelect::XretExecute+0x4b
1f:U 000000001903ec40 0000000000439d59 sqlservr!CMsqlExecContext::ExecuteStmts<1,1>+0x377
20:U 000000001903ed50 000000000043a9b8 sqlservr!CMsqlExecContext::FExecute+0x983
21:U 000000001903eed0 000000000043b30c sqlservr!CSQLSource::Execute+0x7b2
22:U 000000001903f000 000000000043c1a6 sqlservr!process_request+0x64b
23:U 000000001903f660 0000000000485342 sqlservr!process_commands+0x4e5
24:U 000000001903f870 00000000003ebbd8 sqlservr!SOS_Task::Param::Execute+0x12a
25:U 000000001903f980 00000000003eb8ba sqlservr!SOS_Scheduler::RunTask+0x96
26:U 000000001903f9e0 00000000003eb6ff sqlservr!SOS_Scheduler::ProcessTasks+0x128
27:U 000000001903fa50 0000000000908fb6 sqlservr!SchedulerManager::WorkerEntryPoint+0x2b6
28:U 000000001903fb30 0000000000909175 sqlservr!SystemThread::RunWorker+0xcc
29:U 000000001903fb70 0000000000909839 sqlservr!SystemThreadDispatcher::ProcessWorker+0x2db
2a:U 000000001903fc20 0000000000909502 sqlservr!SchedulerManager::ThreadEntryPoint+0x173
2b:U 000000001903fcc0 0000000073d137d7 MSVCR80!endthreadex+0x47
2c:U 000000001903fcf0 0000000073d13894 MSVCR80!endthreadex+0x104
2d:U 000000001903fd20 000000007709652d kernel32!BaseThreadInitThunk+0xd
2e:U 000000001903fd50 00000000773dc521 ntdll!RtlUserThreadStart+0x1d
0:75> !mframe 0b

Jest OK - ramka została poprawnie rozpoznana. Zrzućmy parametry funkcji i zmienne lokalne

0:75> !mdv
Frame 0xb: (UserDefinedFunctions.RegexMatch(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlInt32)):
[A0]:input:VALTYPE (MT=000007ff005fdb18, ADDR=0000000024cddac0) (System.Data.SqlTypes.SqlString)
[A1]:pattern:VALTYPE (MT=000007ff005fdb18, ADDR=0000000024cddaa0) (System.Data.SqlTypes.SqlString)
[A2]:options:VALTYPE (MT=000007ff005fa368, ADDR=0000000024cdda80) (System.Data.SqlTypes.SqlInt32)
[L0]:match:null (System.Text.RegularExpressions.Match)
[L1]:CS$1$0000:VALTYPE (MT=000007ff005f90c8, ADDR=0000000024cdd960) (System.Data.SqlTypes.SqlBoolean)
[L2]:CS$4$0001:0x0000000000000000 (System.Boolean)

Przyjrzyjmy się nieco bliżej wartości zmiennej pattern oraz input

0:75> !mdt input
(System.Data.SqlTypes.SqlString) VALTYPE (MT=000007ff005fdb18, ADDR=0000000024cddac0)
    m_value:000000048045cd50 (System.String: "zine")
    m_cmpInfo:NULL (System.Globalization.CompareInfo)
    m_lcid:0x415 (System.Int32)
    m_flag:0x19(IgnoreCase|IgnoreKanaType|IgnoreWidth) (System.Data.SqlTypes.SqlCompareOptions)
    m_fNotNull:true (System.Boolean)
0:75> !mdt pattern
(System.Data.SqlTypes.SqlString) VALTYPE (MT=000007ff005fdb18, ADDR=0000000024cddaa0)
    m_value:000000048045cd78 (System.String: "Michal")
    m_cmpInfo:NULL (System.Globalization.CompareInfo)
    m_lcid:0x415 (System.Int32)
    m_flag:0x19(IgnoreCase|IgnoreKanaType|IgnoreWidth) (System.Data.SqlTypes.SqlCompareOptions)
    m_fNotNull:true (System.Boolean)

i oczywiście zobaczmy w jakim miejscu kodu jesteśmy :)

0:075> !muf -il
UserDefinedFunctions.RegexMatch(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlInt32): System.Data.SqlTypes.SqlBoolean
    match:System.Text.RegularExpressions.Match
    CS$1$0000:System.Data.SqlTypes.SqlBoolean
    CS$4$0001:bool
    IL_0000: nop
    IL_0001: ldarga.s 0 (input)
    IL_0003: call System.Data.SqlTypes.SqlString::get_IsNull
    IL_0008: ldc.i4.0
    IL_0009: ceq
    IL_000b: stloc.2  (CS$4$0001)
    IL_000c: ldloc.2  (CS$4$0001)
    IL_000d: brtrue.s IL_001b
    IL_000f: nop
    IL_0010: ldsfld System.Data.SqlTypes.SqlBoolean::Null
    IL_0015: stloc.1  (CS$1$0000)
    IL_0016: br IL_00bc
    IL_001b: ldarga.s 1 (pattern)
    IL_001d: call System.Data.SqlTypes.SqlString::get_IsNull
    IL_0022: ldc.i4.0
    IL_0023: ceq
    IL_0025: stloc.2  (CS$4$0001)
    IL_0026: ldloc.2  (CS$4$0001)
    IL_0027: brtrue.s IL_0037
    IL_0029: nop
    IL_002a: ldsfld System.String::Empty
    IL_002f: call System.Data.SqlTypes.SqlString::op_Implicit
    IL_0034: starg.s 1 (pattern)
    IL_0036: nop
    IL_0037: ldarga.s 2 (options)
    IL_0039: call System.Data.SqlTypes.SqlInt32::get_IsNull
    IL_003e: ldc.i4.0
    IL_003f: ceq
    IL_0041: stloc.2  (CS$4$0001)
    IL_0042: ldloc.2  (CS$4$0001)
    IL_0043: brtrue.s IL_004f
    IL_0045: nop
    IL_0046: ldc.i4.0
    IL_0047: call System.Data.SqlTypes.SqlInt32::op_Implicit
    IL_004c: starg.s 2 (options)
    IL_004e: nop
.try {
    IL_004f: nop
    IL_0050: ldarg.0  (input)
    IL_0051: call System.Data.SqlTypes.SqlString::op_Explicit
    IL_0056: ldstr "zine"
    IL_005b: callvirt System.String::Contains
    IL_0060: ldc.i4.0
    IL_0061: ceq
    IL_0063: stloc.2  (CS$4$0001)
    IL_0064: ldloc.2  (CS$4$0001)
    IL_0065: brtrue.s IL_0074
    IL_0067: nop
    IL_0068: ldc.i4 10000(0x2710)
    IL_006d: call System.Threading.Thread::Sleep
>>>>IL_0072: nop
    IL_0073: nop
    IL_0074: ldarg.0  (input)
    IL_0075: call System.Data.SqlTypes.SqlString::op_Explicit
    IL_007a: ldarg.1  (pattern)
    IL_007b: call System.Data.SqlTypes.SqlString::op_Explicit
    IL_0080: ldarg.2  (options)
    IL_0081: call System.Data.SqlTypes.SqlInt32::op_Explicit
    IL_0086: call System.Text.RegularExpressions.Regex::Match
    IL_008b: stloc.0  (match)
    IL_008c: ldloc.0  (match)
    IL_008d: callvirt System.Text.RegularExpressions.Capture::get_Value
    IL_0092: ldsfld System.String::Empty
    IL_0097: call System.String::op_Inequality
    IL_009c: ldc.i4.0
    IL_009d: ceq
    IL_009f: stloc.2  (CS$4$0001)
    IL_00a0: ldloc.2  (CS$4$0001)
    IL_00a1: brtrue.s IL_00ac
    IL_00a3: nop
    IL_00a4: ldsfld System.Data.SqlTypes.SqlBoolean::True
    IL_00a9: stloc.1  (CS$1$0000)
    IL_00aa: leave.s IL_00bc
    IL_00ac: nop
    IL_00ad: leave.s IL_00b3
} // END TRY (IL_004f)
.catch {
    IL_00af: pop
    IL_00b0: nop
    IL_00b1: rethrow
} // END CATCH (IL_00af)
    IL_00b3: nop
    IL_00b4: ldsfld System.Data.SqlTypes.SqlBoolean::False
    IL_00b9: stloc.1  (CS$1$0000)
    IL_00ba: br.s IL_00bc
    IL_00bc: nop
    IL_00bd: ldloc.1  (CS$1$0000)
    IL_00be: ret
} // END TRY (IL_004f)
.catch {
    IL_00af: pop
    IL_00b0: nop
    IL_00b1: rethrow
} // END CATCH (IL_00af)
    IL_00b3: nop
    IL_00b4: ldsfld System.Data.SqlTypes.SqlBoolean::False
    IL_00b9: stloc.1  (CS$1$0000)
    IL_00ba: br.s IL_00bc
    IL_00bc: nop
    IL_00bd: ldloc.1  (CS$1$0000)
    IL_00be: ret

i po ostatnim zrzucie widzimy dokładnie w którym miejscu kodu się znajdujemy (zaznaczyłem na czerwono).

0:075> !muf -s
UserDefinedFunctions.RegexMatch(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlInt32): System.Data.SqlTypes.SqlBoolean
 {
  if (input.IsNull)
  {
   return SqlBoolean.Null;
  if (pattern.IsNull)
  {
   pattern = String.Empty;  //""
  }
  if (options.IsNull)
  {
   options = 0;  //RegexOptions.None
  }
  {
      if (((string)input).Contains("zine"))
    {
        System.Threading.Thread.Sleep(100000);
      }
   Match match = Regex.Match((string)input, (string)pattern, (RegexOptions)(int)options);
   if (match.Value != String.Empty)
   {
    return SqlBoolean.True;
  }
  catch
  {
   throw;
  return SqlBoolean.False;
 }

Jak widać, mamy pełen przegląd tego, co się aktualnie dzieje w ramach funkcji definiowanej przez użytkownika, załadowanej przez SQL Server CLR. I to bez użycia Visual Studio, a wyłącznie w ramach WinDbg+psscor+sosex.

Krótkie zakończenie

Oczywiście, Visual Studio ma bardzo rozbudowany debugger i pełne wsparcie do analizy kodu SQL-CLR. Ba, powiem więcej - SQL Server jest na tyle uprzejmy, że w sytuacji, gdy dostarczamy mu plik .pdb (alter assembly... add file), to on grzecznie ładuje go razem z biblioteką, a więc korzysta z metody Assembly.Load dwuparametrowej. Visual Studio oczywiście korzysta z tej uprzejmości i widząc plik .pdb dołączony do biblioteki, sięga po niego. Widać to wyraźnie, gdy dołączamy debugger VS do SQL Server bez załadowanego (a wręcz bez obecnego na dysku) projektu biblioteki SQL-CLR i przeglądamy listę modułów wraz z rozpoznanymi symbolami. Oczywiście, przy próbie zrobienia czegokolwiek VS poprosi nas o pliki z kodem źródłowym i chyba jest to jedyna 'niedoróbka' (bo nawet, gdy plik .cs jest załadowany do bazy (alter assembly), to VS nie ma z niego żadnego pożytku).

Opublikowane 16 października 2011 00:32 przez mgrzeg

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

 

dotnetomaniak.pl said:

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

października 16, 2011 09:01
 

Paweł said:

Wow. Michał jak zwykle w formie. Świetny wpis.

Paweł

października 16, 2011 16:57

Co o tym myślisz?

(wymagane) 
(opcjonalne)
(wymagane) 

  
Wprowadź kod: (wymagane)
Wyślij

Subskrypcje

W oparciu o Community Server (Personal Edition), Telligent Systems