Palestra Windows Internals

Enquanto não sai o próximo post da série Inside the Machine, fiz uma apresentação sobre fundamentos de Windows Internals para alguns DBAs SQL Server.

Nessa palestra falei, entre outras coisas, sobre um pequeno segredo para gerar dumps “full” do SQL Server sem que se tenha que suspender o processo original do mesmo. Isso é bastante interessante em cenários onde você tem uma instância com um Buffer Pool relativamente grande (30 GB+) e não pode manter o processo parado enquanto gera um dump.

Pretendo (um dia haha) postar sobre isso, mas por enquanto veja nos slides para maiores informações. Vale ressaltar que essa funcionalidade não é documentada, não é suportada pela Microsoft e pode destruir o processo da sua instância SQL, corromper os bancos de dados, explodir o seu data center e ainda por cima matar alguns gatinhos! Não use em produção.

Segue o link para download do pdf: http://bit.ly/1KgXElG

Se você quer se aprofundar mais nos assuntos, recomendo dar uma olhadinha nas referências ao fim da apresentação e também o meu treinamento on-demand de Windows Internals na Sr. Nimbus.

No mais, fique à vontade para deixar seus comentários sobre o material!

Pin It

Windows Internals

MCTS Microsoft Certified Technology Specialist Windows Internals Logo

Microsoft Windows Internals Specialist

Não costumo fazer alarde em relação à certificações, mas acredito que essa vale um blog post. :)

Nesta semana realizei a prova de certificação Windows Internals e, com muito gosto, posso dizer que fui aprovado!

Quando soube a respeito dessa prova, ainda em 2011, fiquei bastante interessado. Segue a descrição do exame:

“This exam validates deep technical skills in the area of Windows Internals. Including troubleshooting operating systems that are not performing as expected or applications that are not working correctly, identifying code defects, and developing and debugging applications that run unmanaged code or that are tightly integrated with the operating system, such as Microsoft SQL Server, third party applications, antivirus software, and device drivers.”

Há alguns meses lançaram no Defrag Tools do Channel 9 um episódio falando do exame, e decidi que realmente era hora de tentar.

Esse exame marcou um fim de um ciclo para mim. Hoje estou me dedicando bastante a outras áreas de TI, relacionados ou não diretamente a Windows, mas tenho certeza que o conhecimento adquirido e a certificação ainda vão me auxiliar muito ao longo da minha carreira, além de dar mais pique para continuar gravando os meus treinamentos de Windows Internals pela Sr. Nimbus.

Os skills mensurados durante o exame foram basicamente:

  • Identifying Architectural Components (16%)
  • Designing Solutions (15%)
  • Monitoring Windows (14%)
  • Analyzing User Mode (18%)
  • Analyzing Kernel Mode (19%)
  • Debugging Windows (18%)

Para informações à respeito do que cai em cada tópico, esse post do blog MSDN NtDebugging lista o material e ferramentas que foram cobrados, e no episódio do Defrag Tools já mencionado acima algumas dessas ferramentas são demonstradas.

Pontos gerais da prova

A prova em si é bem prática, com muito debugging tanto em user mode quanto kernel mode. Cobrou bastante comandos do Windbg, e alguns parâmetros. Também caíram questões de desenvolvimento de soluções – drivers e apps Win32 – com direito a código-fonte em C e perguntas sobre parâmetros de APIs em algumas questões. Outro ponto que caiu bastante foi troubleshooting de drivers e aplicativos mal comportados com ferramentas Sysinternals e do WDK.

No geral, eu gostei da prova. Bastante prática e objetiva. As questões e cenários eram bem mais simples do que encontramos no dia a dia mas o suficiente, na minha opinião, para aferir se o profissional realmente conhece os conceitos do sistema operacional, kernel e API Win32, e se tem experiência real realizando trobleshooting, tuning e desenvolvimento nativo e baixo-nível.

EOL da versão 2008

O lado ruim, entretanto, é que a prova não estará mais disponível. Foi descontinuada no dia 31/07, um dia após o meu exame.

Infelizmente a Microsoft ainda não disponibilizou uma versão atualizada da prova para as novas versões do sistema operacional. Levando em conta que os livros Windows Internals do Server 2008 R2 (Client 7) acabaram de ser lançados e que os de 2012 ainda nem têm previsão, não acredito que teremos outra prova tão cedo, o que é uma pena.

O pessoal do Defrag Tools até tentou intervir junto ao time de certificações da Microsoft para que ela mantivesse o exame disponível por mais algum tempo, mas sem sucesso.

Na minha opinião, deveriam ter mantido essa prova, pelo menos até introduzir a versão atualizada. De qualquer maneira, isso me forçou a finalmente criar coragem e realizá-la depois de enrolar por muito tempo, caso contrário teria que esperar uma próxima versão em um futuro distante…

Que venham os próximos desafios!

Pin It

SQL Server – XEvents e ETW

Esse post é fruto de uma profunda investigação a respeito do funcionamento interno/implementação dos Extended Events no SQL Server.

O meu entendimento inicial dos XEvents era de que a funcionalidade havia sido implementada através do ETW.

ETW, ou Event Trace for Windows é um framework de tracing de eventos implementado no kernel do Windows para a análise de problemas e desempenho entre diversos mecanismos, subsistemas e aplicações do sistema operacional. O ETW permite a captura e correlação destes eventos de forma integrada e de altíssimo desempenho, permitindo a sua utilização mesmo em ambientes de produção.

Apesar de existir desde a versão 2000 do SO, uma das funcionalidades mais interessantes do ETW surgiu com a introdução do Windows 7/2008 R2, que permite a captura de stack traces do código em execução no momento em que foi gerado o evento, uma funcionalidade disponibilizada também nos XEvents do SQL Server.

Através dessas investigações descobri que minha impressão inicial a respeito da implementação dos XEvents estava incorreta.

A implementação dos XEvents é independente do ETW até que você utilize o target do mesmo, chamado etw_classic_sync_target. Ao informar o uso desse target na sua sessão de XEvents, você está informando ao SQL Server que gostaria de possibilitar a correlação de eventos da Database Engine com aqueles do sistema operacional.

Para entendermos a implementação dos ETW target, vamos primeiro revisar alguns conceitos dos XEvents.

Conceitos de Extended Events

A peça mais fundamental na arquitetura dos XEvents são os pacotes. Através dos pacotes é que são implementados os eventos em si, os alvos (targets), ações, tipos de dados e o mapeamento dos valores dos eventos com os valores do SQL Server.

Os pacotes são registrados na instância de SQL Server e estão disponíveis para consulta através da DMV sys.dm_xe_packages:

select * from sys.dm_xe_packages;
go
XEvents Packages

Pacotes do XEvents

Repare na coluna guid, pois voltaremos a falar dela mais tarde.

Nesse momento, o que nos é interessante é a coluna module_address, que informa qual o módulo executável (PE) é o responsável pelo registro desse pacote. Podemos conferir os módulos através do Windbg ou através da DMV sys.dm_os_loaded_modules:

select p.*, m.name
from sys.dm_xe_packages as p
join sys.dm_os_loaded_modules as m
 on p.module_address = m.base_address;
go

Módulos onde estão implementados os pacotes do XEvents

Todos os pacotes (atualmente) estão implementados nos módulos sqllang.dll, sqldk.dll e sqlmin.dll.

Podemos verificar quais os eventos de cada pacote através do seu guid:

select top 10 *
from sys.dm_xe_objects
where package_guid = '03FDA7D0-91BA-45F8-9875-8B6DD0B8E9F2';
go

Uma visão completa dos Extended Events está além do escopo desse artigo, e existem diversos materiais sobre o assunto disponíveis on-line. Eu mesmo já falei sobre isso anteriormente, inclusive.

Sessão de teste

Estarei utilizando a seguinte sessão de XEvents para teste ao longo do post:

CREATE EVENT SESSION test0
ON SERVER
ADD EVENT sqlserver.checkpoint_begin (ACTION (package0.callstack)),
ADD EVENT sqlserver.checkpoint_end (ACTION (package0.callstack))
WITH (MAX_DISPATCH_LATENCY = 1 SECONDS);
GO

Com um ring_buffer como target inicial:

ALTER EVENT SESSION test0
ON SERVER
ADD TARGET package0.ring_buffer;
GO

Targets do SQL Server

Alguns targets são implementados nos pacotes SecAudit, mas esses targets são específicos para aa implementação da solução de auditoria do SQL Server. Os targets que nos interessam são implementados em sqlserver e package0:

select p.name, o.*
from sys.dm_xe_objects as o
join sys.dm_xe_packages as p
 on p.guid = o.package_guid
where p.name <> 'SecAudit'
 and object_type = 'target';
go

Ao realizarmos a criação de uma nova sessão de XEvents com qualquer target do próprio SQL Server (isto é, qualquer target menos o etw_classic_sync_target), a sessão é completamente interna ao SQL Server, não existe qualquer tipo de interação entre o SQL Server e o framework de ETW do sistema operacional. A implementação dos XEvents não é baseada em ETW. Ela apenas o suporta.

Uma das features dos XEvents que me intrigou foi a captura dos stacktraces, que eu acreditava depender do framework de ETW, que também possui a funcionalidade, como dito anteriormente.

Utilizando a sessão descrita acima em conjunto com o Windbg podemos visualizar e entender como funciona o mecanismo para a coleta das stacktraces no SQL Server.

Stacktraces

A captura dos stacktraces de execução do SQL Server são realizados pela própria instância, através de chamadas à função RtlCaptureStackBackTrace em ntdll e passando como parâmetro FramesToSkip o número de frames correspondente ao processamento do evento (nesse caso 3) para que estes não apareçam na lista de eventos ocorridos, no target. Portanto no Windbg temos:

ntdll!RtlCaptureStackBackTrace
sqldk!XEPackage0::CallStackAction::Invoke
sqlmin!XeSqlPkg::checkpoint_begin::Publish
sqlmin!HardenAndLogCheckpoint
sqlmin!CheckpointRU
sqlmin!AsynchronousDiskAction::DoFlushCache
sqlmin!AsynchronousDiskAction::ExecuteDeferredAction
sqlmin!AsynchronousDiskWorker::ThreadRoutine
sqlmin!SubprocEntrypoint
...

Enquanto no ring_buffer da nossa sessão com a action callstack de XEvents nós temos:

sqlmin!HardenAndLogCheckpoint
sqlmin!CheckpointRU
sqlmin!AsynchronousDiskAction::DoFlushCache
sqlmin!AsynchronousDiskAction::ExecuteDeferredAction
sqlmin!AsynchronousDiskWorker::ThreadRoutine
sqlmin!SubprocEntrypoint
...

Portanto sem os 3 primeiros frames.

Até o momento o SQL Server realizou zero chamadas às APIs de ETW do sistema operacional.

Se não precisamos do ETW até agora, pra que ele serve? Antes de respondermos essa pergunta vamos investigar um pouco a implementação (peculiar) dos eventos de ETW no SQL Server.

ETW Target

A implementação de ETW no SQL Server é realizada através do objeto XEPackage0::XE_ETWTarget. Esse objeto realiza o registro dos pacotes XEvents (aqueles que vimos na DMV sys.dm_xe_packages) no sistema operacional através de chamadas a função não documentada EtwRegisterTraceGuidsW em ntdll.

Note que existe uma API documentada para realizar o mesmo procedimento em advapi32 chamada RegisterTraceGuidsW mas o SQL Server não a utiliza. Essa API documentada acabará chamando a mesma função que o SQL Server chama diretamente, portanto efetivamente o resultado será o mesmo.

Ao adicionarmos o target etw_classic_sync_target à nossa sessão, o SQL Server irá chamá-la:

ALTER EVENT SESSION test0
ON SERVER
ADD TARGET package0.etw_classic_sync_target;
GO

E aqui nós temos o stacktrace da chamada no Windbg:

Breakpoint 5 hit
sqldk!XEPackage0::XE_ETWTarget::Create:
000007fe`dcd09540 fff5            push    rbp
0:056> kc
Call Site
sqldk!XEPackage0::XE_ETWTarget::Create
sqldk!XE_PackageManager::InitTarget
sqldk!XE_ActualTargetEntry::Create
sqldk!XE_SingletonTargetManager::GetOrCreate
sqldk!XE_Session::AddTarget
sqldk!XE_Engine::AddTarget
sqllang!CXE_Target::AddTarget
sqllang!XEventController::AlterEventSession
...

Repare no topo da nossa stack onde temos o método Create do objeto XE_ETWTarget do “pacote” XEPackage0 (package0 nas DMVs).

A implementação do target etw_classic_sync_target é o que chamamos de singleton, isto é, só pode haver uma por instância. Mais à frente enderemos o porquê.

select name, object_type, capabilities_desc
from sys.dm_xe_objects
where name = 'etw_classic_sync_target';
go
Singleton XEvent ETW Target

Target singleton dos XEvent para ETW

Na arquitetura do ETW, o SQL Server é o que chamamos de controller e provider ao mesmo tempo. Ou seja, o SQL Server fornece eventos e controla quais as sessões que consomem esses eventos.

ETW Architecture

ETW Architecture (MSDN)

O SQL Server só registra os seus ETW providers no momento em que o usuário (DBA) configura o target etw_classic_sync_target na sessão de XEvents, se a mesma já estiver em execução, ou caso a sessão esteja parada os providers somente serão registrados no momento em que a sessão for iniciada. Ou seja, o SQL Server não mantém os providers registrados no Windows durante a sua execução, impossibilitando a sua ativação pelos controllers padrão do sistema: perfmon, logman, xperf, etc.

O único controller capaz de ativar e desativar os eventos ETW do SQL Server é ele mesmo.

Voltando nas guids que falei anteriormente, no momento em que a sessão é inicializada e o SQL Server registra os pacotes dos XEvents como ETW providers para que seja possível começar a produzir eventos através destes providers, os guids utilizados para registrá-los no ETW são os mesmos encontrados na DMV. Os pacotes são:

C:tempSQL Server Tracing>xperf -providers R | findstr /I "SQL XE"
sqlserver
sqlos
XEvent Package 0
SQL2012 Trace
C:tempSQL Server Tracing>

E podemos ver através do logman que o processo do SQL Server registrou esses providers através dos comandos abaixo:

C:tempSQL Server Tracing>tasklist /FI "SERVICES eq MSSQL$SQL2012"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
sqlservr.exe                 12268 Services                   0     60,628 K

C:tempSQL Server Tracing>logman query providers -pid 12268

Provider                                 GUID
-------------------------------------------------------------------------------
...
Microsoft-Windows-User Profiles General  {DB00DFB6-29F9-4A9C-9B3B-1F4F9E7D9770}
Security: Kerberos Authentication        {6B510852-3583-4E2D-AFFE-A67F9F223438}
Security: NTLM Authentication            {5BBB6C18-AA45-49B1-A15F-085F7ED0AA90}
Security: SChannel                       {37D2C3CD-C5D4-4587-8531-4696C44244C8}
SQL2012 Trace                            {26773F6F-E7B5-4F58-9347-0347C998BA7D}
sqlos                                    {BD97CC63-3F38-4922-AA93-607BD12E78B2}
sqlserver                                {655FD93F-3364-40D5-B2BA-330F7FFB6491}
XEvent Package 0                         {60AA9FBF-673B-4553-B7ED-71DCA7F5E972}
{03FDA7D0-91BA-45F8-9875-8B6DD0B8E9F2}   {03FDA7D0-91BA-45F8-9875-8B6DD0B8E9F2}
...

The command completed successfully.

C:tempSQL Server Tracing>

Podemos conferir os GUIDs acima com os retornados pelo SQL Server na DMV sys.dm_xe_packages:

name        guid
package0    60AA9FBF-673B-4553-B7ED-71DCA7F5E972
sqlos       BD97CC63-3F38-4922-AA93-607BD12E78B2
XeDkPkg     52FC232C-03D5-4E1F-A6BF-BBC66FE20E6A
sqlserver   655FD93F-3364-40D5-B2BA-330F7FFB6491

Uma vez que os pacotes tenham sido registrados, o SQL Server utiliza a sessão XE_DEFAULT_ETW_SESSION para capturar os eventos.

ALTER EVENT SESSION test0
ON SERVER
STATE = start;
GO

C:tempSQL Server Tracing>xperf -loggers XE_DEFAULT_ETW_SESSION
Logger Name           : XE_DEFAULT_ETW_SESSION
Logger Id             : c
Logger Thread Id      : 000000000000291C
Buffer Size           : 128
Maximum Buffers       : 30
Minimum Buffers       : 8
Number of Buffers     : 8
Free Buffers          : 8
Buffers Written       : 1
Events Lost           : 0
Log Buffers Lost      : 0
Real Time Buffers Lost: 0
Flush Timer           : 0
Age Limit             : 0
Log File Mode         : Circular PagedMemory
Maximum File Size     : 20
Log Filename          : C:UsersMSSQL$~1AppDataLocalTempXEEtw.etl
Trace Flags           : 03fda7d0-91ba-45f8-9875-8b6dd0b8e9f2+b086c2f3-2738-4389-b119-d80b5362b5ca+1e99fe90-a4fe-45
e6-9dfd-a45041f02314+c0ab75c5-b1ea-445b-b7df-f897686f94e7+f235752a-d5c0-4c9a-a735-9c3b6f6e43b1+"sqlserver"+"sqlos"
+"XEvent Package 0"

C:tempSQL Server Tracing>

No campo Trace Flags podemos observar as guids e os nomes dos pacotes do SQL Server (providers, do ponto de vista do ETW).

Outro ponto importante é que, ao chamar a função para registrar os ETW providers um dos parâmetros passados pelo SQL Server é uma callback que será chamada sempre que algum controller tentar utilizar algum destes providers. O SQL Server irá negar o uso de controllers externos, impossibilitando a captura dos eventos por outra sessão além da sessão criada pelo próprio XEvents.

Podemos observar o mecanismo em ação:

Callback dos XEvents para o ETW

Portanto, mesmo conhecendo os nomes e a forma como o SQL Server realiza o registro das sessões de XEvents no ETW, não é possível capturar tais eventos através do sistema operacional.

Callback causado pela tentiva de ativação do provider sqlos externamente

Podemos verificar que durante o processamento da callback (em azul na imagem abaixo), o SQL Server está realizando uma escrita em um Ring Buffer através da chamada SOS_RingBuffer::StoreRecordInternalWithoutEvent (em verde). Presumidamente esta chamada não gera eventos pois a própria engine dos XEvents quem está realizando a escrita, caso contrário entraria em loop infinito ou deadlock.

Ring Buffer do SQL Server no Windbg

Logando o problema durante o processamento do callback

Através do valor do registrador rcx (em vermelho), podemos encontrar informações a respeito deste Ring Buffer e a mensagem armazenada, nas DMVs:

Ring Buffers no SQL Server (SSMS)

Is this cool or what?! =)

O fato do SQL Server não permitir que controllers externos tenham acesso aos eventos gerados por ele pode parecer ruim a princípio, mas na verdade é uma boa escolha. Se o mecanismo não fosse implementado dessa forma poderíamos ter um cenário onde o DBA inicia uma sessão de XEvents com o target de ETW e posteriormente alguém (um sysadmin, por exemplo) inicia uma sessão de ETW (de fora do SQL Server) e consegue “roubar” os eventos da sessão de XEvents do DBA, sem deixar vestígios.

O SQL Server utiliza o que chamamos de classic providers, que podem emitir eventos para uma sessão ETW apenas. Por isso temos a implementação singleton do target de ETW dos XEvents. Os providers baseados em manifestos, introduzidos no Windows Vista/2008 permitem que várias sessões capturem os eventos de um mesmo provider, mas por questões de compatibilidade o SQL Server não os utiliza.

Uma vez que a sessão de XEvents tenha sido interrompida, o SQL Server não irá remover a sessão ETW do sistema:

ALTER EVENT SESSION test0
ON SERVER
STATE = stop;
GO

Se não vamos mais utilizá-la, nós mesmos podemos realizar o flush da sessão e removê-la:

C:tempSQL Server Tracing>logman update XE_DEFAULT_ETW_SESSION
 -fd -ets
The command completed successfully.

C:tempSQL Server Tracing>logman stop XE_DEFAULT_ETW_SESSION -ets
The command completed successfully.

E para visualizar o conteúdo do arquivo de trace, no meu caso “C:UsersMSSQL$~1AppDataLocalTempXEEtw.etl” como retornado pelo comando xperf -loggers XE_DEFAULT_ETW_SESSION acima, utilizamos o xperfview:

C:tempSQL Server Tracing>xperfview C:UsersMSSQL$~1
 AppDataLocalTempXEEtw.etl
Checkpoints no XPerf

Bastante amigável, não?

Os pontos em azul representam os eventos sqlserver.checkpoint_begin da nossa sessão XEvents, e os pontos em vermelho representam sqlserver.checkpoint_end.

O Xperf e o seu sucessor Windows Performance Analyzer não são conhecidos pela sua facilidade de uso, mas permitem análises de desempenho extremamente precisas.

Um ponto em comum entre os artigos sobre XEvents que encontrei são o fato de todos comentarem a respeito do possível correlacionamento de eventos dos XEvents e do ETW, mas não encontrei nenhuma documentação de como podemos realizá-lo (muito menos de uma forma útil).

Apesar de não ser exatamente óbvio ou amigável, é sim possível fazê-lo. Nos próximos posts vamos entender como podemos correlacionar as informações dos XEvents e do sistema operacional e através dessas informações tentar visualizar o que realmente está acontecendo no processamento de nossas queries, pela visão do SO.

Se você estiver interessado em saber mais sobre o uso do ETW com os XEvents, aconselho que dê uma olhada também no post An XEvent a Day (10 of 31) – Targets Week – etw_classic_sync_target do Jonathan Kehayias.

Além disso, ainda tenho diversos testes e investigações a fazer com os XEvents e o ETW, que vou continuar postando aqui. :)

Pin It

SQL Server – Identificando o uso de trace flags via Windbg

Estava há pouco em uma call com a equipe de SQL Server da Sr. Nimbus onde entre outras coisas, discutíamos um cenário de Lock Escalation e crescimento do Memory Clerk de Locks (OBJECTSTORE_LOCK_MANAGER) e o Luti (Twitter | Blog) comentou o método utilizado pela Engine no SQL Server: sqlmin!IsEscalationPossible.

Decidi aproveitar o gancho e conferir mais de perto o funcionamento desse método, e notei que logo de início o método já realiza a verificação de Trace Flags ativas na instância, que são inclusive documentadas, 1211 e 1224.

Mas como é realizada a verificação das trace flags? Podemos ver essa verificação? Podemos sim e não tem nenhum segredo.

Como o Luti já sabia até o nome do método (acho que ele já falou sobre algum cenário semelhante em algum post), já matamos metade do trabalho – que seria de encontrar a função de interesse para análise – e partimos direto para o que interessa.

Vamos realizar o disassembly do método em questão, pelo Windbg, e observar como é realizado a ativação de trace flags na nossa instância.

Realizamos o disassembly através do comando uf:

0:051> uf sqlmin!IsEscalationPossible

sqlmin!IsEscalationPossible:
000007fe`f197e0d0 fff5 push rbp
000007fe`f197e0d2 56 push rsi
000007fe`f197e0d3 57 push rdi
000007fe`f197e0d4 4154 push r12
000007fe`f197e0d6 4155 push r13
000007fe`f197e0d8 4156 push r14
000007fe`f197e0da 4157 push r15
...
sqlmin!IsEscalationPossible+0x96:
000007fe`f197e166 babb040000      mov     edx,4BBh
000007fe`f197e16b ff158f5432ff    call    qword ptr [sqlmin!_imp_?CheckSessionTraceInternalCSessionTraceFlagsCAHPEAV1KZ (000007fe`f0ca3600)]
000007fe`f197e171 85c0            test    eax,eax
000007fe`f197e173 0f8563070000    jne     sqlmin!IsEscalationPossible+0x80c (000007fe`f197e8dc)
...

E logo no início da implementação da função que estamos analisando podemos ver o SQL Server realizando uma chamada à função CheckSessionTraceInternalCSessionTraceFlagsCAHPEAV1KZ passando como parâmetro o valor hexadecimal 4BBh (falei sobre passagem de parâmetros em x64 em um post anterior).

Transformando esse valor hexadecimal para decimal nós temos o valor da nossa trace flag: 1211.

Já temos o primeiro traceflag. E o outro?

Para facilitar ainda mais o trabalho, realizei uma busca no disassembly dessa função através do comando # do Windbg, pelo padrão call, que como podemos inferir pelo nome é a instrução utilizada para realizar chamadas a funções:

0:051> # call sqlmin!IsEscalationPossible L240

sqlmin!IsEscalationPossible+0x9b:
000007fe`f197e16b ff158f5432ff    call    qword ptr [sqlmin!_imp_?CheckSessionTraceInternalCSessionTraceFlagsCAHPEAV1KZ (000007fe`f0ca3600)]
sqlmin!IsEscalationPossible+0x111:
000007fe`f197e1e1 ff15a95532ff    call    qword ptr [sqlmin!_imp_?MakeMiniSOSThreadSystemThreadSAXXZ (000007fe`f0ca3790)]
sqlmin!IsEscalationPossible+0x19c:
000007fe`f197e26c e87ffe38ff      call    sqlmin!HoBtAccess::IsWorkTable (000007fe`f0d0e0f0)
sqlmin!IsEscalationPossible+0x214:
000007fe`f197e2e4 ff15165332ff    call    qword ptr [sqlmin!_imp_?CheckSessionTraceInternalCSessionTraceFlagsCAHPEAV1KZ (000007fe`f0ca3600)]
sqlmin!IsEscalationPossible+0x22e:
000007fe`f197e2fe e83d5b37ff      call    sqlmin!HoBtAccess::GetLockResourceId (000007fe`f0cf3e40)
...
sqlmin!EscalateLocks+0x56:
000007fe`f197e966 e8d55437ff      call    sqlmin!HoBtAccess::GetLockResourceId (000007fe`f0cf3e40)
sqlmin!EscalateLocks+0x6d:
000007fe`f197e97d e88ec74c00      call    sqlmin!XactLockInfo::EscalateObjectLocks (000007fe`f1e4b110)

Podemos observar que existem duas chamadas à função CheckSessionTraceInternalCSessionTraceFlagsCAHPEAV1KZ logo no início da execução da função IsEscalationPossible.

Como já sabemos o valor da primeira chamada, vamos conferir a segunda, utilizando o endereço da chamada, retornado acima (000007fe´f197e2e4):

0:051> ub 000007fe`f197e2e4

sqlmin!IsEscalationPossible+0x1fa:
000007fe`f197e2ca 488b0408        mov     rax,qword ptr [rax+rcx]
000007fe`f197e2ce 4885c0          test    rax,rax
000007fe`f197e2d1 7428            je      sqlmin!IsEscalationPossible+0x22b (000007fe`f197e2fb)
000007fe`f197e2d3 488b4008        mov     rax,qword ptr [rax+8]
000007fe`f197e2d7 488b08          mov     rcx,qword ptr [rax]
000007fe`f197e2da 4885c9          test    rcx,rcx
000007fe`f197e2dd 741c            je      sqlmin!IsEscalationPossible+0x22b (000007fe`f197e2fb)
000007fe`f197e2df bac8040000      mov     edx,4C8h

O valor nessa chamada é o hexadecimal 4C8h (na última linha). Podemos conferir que é realmente este o valor utilizado na chamada através do endereço acima:

0:051> u 000007fe`f197e2df

sqlmin!IsEscalationPossible+0x20f:
000007fe`f197e2df bac8040000      mov     edx,4C8h
000007fe`f197e2e4 ff15165332ff    call    qword ptr [sqlmin!_imp_?
   CheckSessionTraceInternalCSessionTraceFlagsCAHPEAV1KZ (000007fe`f0ca3600)]
000007fe`f197e2ea 85c0            test    eax,eax
...

E por fim convertemos o valor 4C8h para o valor da trace flag 1224, que corresponde exatamente com o que está documentado. :)

Essa técnica pode ser utilizada inclusive para encontrar o uso de trace flags não documentados no SQL Server sem muitas dificuldades, mas isso fica pra próxima…

Happy debugging! ;)

Pin It

Introdução ao WinDbg

WinDbg

O WinDbg é uma interface de ferramenta de depuração (debugger ou depurador) sofisticado desenvolvido pela Microsoft distribuído gratuitamente com o SDK (e o DDK) do Windows. Ele pode ser utilizado tanto para depuração em user-mode ou kernel-mode. Apesar de não ser um depurador por si só – por baixo da interface gráfica o WinDbg utiliza os depurados de linha de comando cdb (user-mode) e kd (kernel-mode) – para os nossos propósitos vamos tratá-lo como tal no restante desse post.

Símbolos

Durante a compilação de um software com as ferramentas do Visual Studio, através de parâmetros ao compilador é possível gerar arquivos .pdb que são bancos de símbolos do módulo que está sendo compilado. Sendo menos abstrato, o arquivo .pdb mapeia endereços de memória do módulo – ininteligíveis aos seres humanos – aos seus nomes originais, no código-fonte. Dessa forma é possível observar os nomes das funções, classes, métodos, parâmetros, tipos, entre diversas outras informações à respeito do módulo executável mesmo sem possuirmos acesso ao código-fonte original, desde que o software tenha sido compilado com os parâmetros para a geração do pdb e que tenhamos acesso aos símbolos.

A Microsoft, por sua vez, compila (quase) todos os seus módulos (inclusive os de SQL Server!) com símbolos e os distribui gratuitamente na internet para que os profissionais que tiverem necessidade (ou interesse) pela utilização desses símbolos tenham acesso a eles e possam utilizá-los nas suas sessões de depuração (entre outras coisas que são possíveis através dos símbolos, mas falarei sobre isso em outro post :)).

Símbolos privados e públicos

Nem tudo são flores, infelizmente.

Existem dois tipos de símbolos, os chamados de privados, que podem ser utilizados juntamente do código-fonte do software para debugging por parte do desenvolvedor do mesmo e contém todas as informações à respeito do módulo, e os símbolos públicos, que são símbolos que contém um número bem menor de informações e que são distribuídos ao público geral.

Os símbolos públicos não contém informações bastante relevantes como tipagem de dados, parâmetros, entre outras coisas, mas já fornecem informação o suficiente para possibilitar o uso do WinDbg na depuração de problemas ou para ganhar um insight no funcionamento interno (interno mesmo!) do software.

Servidor de símbolos

Qualquer empresa pode disponibilizar os símbolos de seus módulos da maneira que preferir. A Microsoft disponibiliza os símbolos através de um servidor através da URL http://msdl.microsoft.com/download/symbols. Apesar de parecer uma URL comum, essa URL não é utilizada para navegação e sim na configuração do WinDbg para que ele mesmo baixe os símbolos de forma preguiçosa (por default, apesar de eu preferir carga imediata para evitar delays durante a depuração.)

Configuração dos símbolos

Quando o WinDbg precisa de algum símbolo que não está carregado, ele primeiramente irá procurar no diretório padrão de símbolos e, caso não encontre neste diretório irá procurá-los no servidor de símbolos.

Para configurar o WinDbg de forma a carregar os símbolos através do servidor oferecido pela Microsoft, temos diversas opções:

  1. Configuração à nível de sistema e/ou usuário através da variável de ambiente _NT_SYMBOL_PATH.
  2. Configuração à nível de sessão no Command Prompt, também através da variável de ambiente _NT_SYMBOL_PATH.
  3. Na sessão de debugging dentro do WinDbg através do comando .sympath (ou ainda automaticamente através de .symfix.)
  4. Ou ainda através do parâmetro -y na linha de comando do WinDbg.

Particularmente, eu prefiro deixar o ambiente configurado por facilidade na hora da depuração e por haver o compartilhamento da configuração com outras ferramentas que façam uso dos bancos de símbolos, como Xperf por exemplo.

Setting environment variable using the System Properties Window

Variável de ambiente - System Properties

No WinDbg:

Setting symbols path using the .sympath WinDbg command

Configuração de símbolos no WinDbg

Como já mencionado, apesar dos símbolos públicos não oferecerem informações bastante relevantes para o processo de debugging como os símbolos privados, com assinaturas completas de funções e métodos, e exigir um pouco mais de esforço, ainda é possível identificar variáveis e parâmetros na memória do processo. A brincadeira fica muito mais fácil quando estamos falando de APIs documentadas, como a Windows API, com as assinaturas facilmente encontradas no MSDN, bastando apenas um pouquinho de conhecimento da convenção de chamadas da arquitetura em uso, que hoje em dia é na maioria dos casos o x64:

Breakpoint at VirtualAlloc

Breakpoint em VirtualAlloc

Windows API e x64 call convention

A VirtualAlloc é uma API utilizada para reservar ou confirmar (commit) endereço de memória virtual (VAS) do processo. Como é possível ver acima, não temos nenhuma informação à respeito dos parâmetros que estão sendo utilizados na chamada dessa API, mas através da sua documentação podemos observar quais parâmetros são esperados por ela:

LPVOID WINAPI VirtualAlloc(
   _In_opt_  LPVOID lpAddress,
   _In_      SIZE_T dwSize,
   _In_      DWORD flAllocationType,
   _In_      DWORD flProtect
 );

A convenção de chamadas x64 da Microsoft determina que os 4 primeiros parâmetros serão colocados nos registradores 64-bit rcx, rdx, r8 e r9, nesta ordem. À partir dos 4 primeiros parâmetros a brincadeira fica um pouco menos direta, mas como nesse caso temos exatamente 4 parâmetros, não vamos nos preocupar com isso agora.

Parameters for VirtualAlloc in Windbg

Registradores com os parâmetros de VirtualAlloc

Vamos primeiro verificar os valores dos parâmetros e depois tentar entendê-los:

  • (LPVOID) lpAddres: Podemos ver que o primeiro parâmetro, lpAddress, recebeu o valor 0 ou NULL em C/C++.
  • (SIZE_T) dwSize: O segundo parâmetro recebeu o valor 80000.
  • (DWORD) flAllocationType: O terceiro parâmetro recebeu o valor 101000.
  • (DWORD) flProtect: O quarto e último parâmetro recebeu o valor 4.

Segundo a documentação no MSDN, se a API VirtualAlloc recebe no primeiro parâmetro lpAddress o valor NULL, significa que o sistema deve determinar o endereço de destino onde realizar a alocação da memória:

“If this parameter is NULL, the system determines where to allocate the region.”

O parâmetro dwSize, por sua vez, determina a quantidade de memória a ser alocada. No nosso caso o valor é 80000h = 524288 / 1024 = 512 KB:

“The size of the region, in bytes.”

flAllocationType determina o tipo de alocação, mas a tabela na documentação não informa o valor 101000 que recebemos. É comum em C e C++ passarmos múltiplas flags em um único parâmetro realizando o que chamamos de opereação de OR lógico (| em C/C++), portanto 101000 equivale  a:

  • MEM_COMMIT – 0x00001000
  • MEM_TOP_DOWN – 0x00100000

Por fim o parâmetro flProtect recebe o valor 4, que indica a o tipo de proteção da memória:

  • PAGE_READWRITE – 0x04

Portanto a chamada que depuramos na screenshot corresponde a uma alocação de 512 KB de memória virtual com proteção de leitura e escrita realizada pelo processo do SQL Server!

Existem algumas considerações a serem feitas à respeito dessa chamada, mas não vou me adentrar nesse assunto neste post. A documentação do MSDN explica muito bem a chamada e seus parâmetros se você quiser se aprofundar um pouco mais nessa ou no restante das APIs do Windows.

Estou com alguns outros posts de WinDbg à caminho, mas não poderia ter começado a série sem dar uma introdução bem básica à ferramenta. Espero que eu tenha conseguido explicar e demonstrar um pouquinho da utilização do WinDbg e aberto caminho para mais investigações e sessões de debugging no futuro!

Pin It

Gerenciamento de Memória no Windows

Quero falar um pouco sobre o Buffer Manager do SQL Server, mas antes de começarmos acredito que é interessante repassar alguns conceitos de gerenciamento de memória do Windows:

Memória Virtual

O VAS, ou Virtual Address Space corresponde ao espaço de memória virtual de um processo. Através do VAS, cada processo no Windows tem a impressão de estar rodando em um enorme espaço de memória contígua não-compartilhada. Indeferente da real quantidade de memória física do sistema (16 GB, por exemplo), em sistemas x86, a memória virtual de cada processo tem um tamanho padrão de 2 GB, enquanto em sistemas X64 o processo recebe um espaço de endereçamento virtual de “exagerados” (para os padrões atuais) 8 TB.

Mesmo que a memória utilizada por um processo esteja (fisicamente) espalhada entre diversas áreas da memória física ou ainda paginadas no disco rígido, para o processo a memória acessível sempre terá um aspecto uniforme e contíguo.

Durante o tempo de execução do processo o componente do processador chamado de MMU (memory management unit) fará o mapeamento da memória virtual do processo à memória física do sistema através de page tables, ou tabelas de página, criadas pelo gerenciador de memória do Windows, e são os responsáveis por armazenar os mapeamentos entre os endereços virtuais da memória do processo e seus endereços reais correspondentes (físicos). A tabela de página do processo contém diversas Page Table Entries (PTEs), uma para cada página de memória virtual do processo.

Através deste mecanismo o sistema pode garantir que um processo não poderá afetar diretamente (propositada ou despropositadamente) a memória de outro processo rodando no mesmo sistema.

Outra característica de sistemas que utilizam memória virtual (como o Windows) é que ele permite que cada processo em execução em um sistema tenha acesso a uma quantidade possivelmente muito superior à memória física instalada. Nas versões 64-bit do Windows, por exemplo, a memória virtual de um processo corresponde a um total de 8 TB, e isso, como dito anteriormente, independente da memória física instalada.

Nas versões 32-bit, o processo fica restrito ao um VAS muito menor, de apenas 4 GB, sendo por padrão 2 GB para uso do sistema operacional e apenas 2 GB do usuário (aplicação). Essas configurações podem ser alteradas durante o processo de boot do Windows (através dos switchs /3GB no Windows 2003 e /set IncreateUserVa 3072 nas versões mais recentes), mas o processo continua restringido aos 4 GB, ou seja, a nova memória alocada para uso do processo foi tirada diretamente da memória disponível para a execução do SO, e agora é dividida como 3 GB + 1 GB.

Existem outras técnicas para superar esse limite mas não entraremos nos méritos aqui por não agregarem valor à nossa discussão. Você pode encontrar mais informações à respeito no fim deste artigo, nos links para leitura adicional.

É interessante notar que atualmente a edição mais robusta (Datacenter) do Windows Server mais atual (2008 R2) suporta um máximo de 2 TB de memória física em sistemas X64. É fácil notar, portanto, que o VAS não corresponde à memória física do sistema.

Memória Reservada e Memória Confirmada

As páginas em memória virtual podem estar em 3 estados:

  1. Reservada (MEM_RESERVE)
  2. Confirmada (MEM_COMMIT)
  3. Livre (MEM_FREE)

Uma thread de execução em um processo pode reservar um espaço de memória virtual sem que haja uso desnecessário de armazenamento (ex.: memória física) para aquele espaço, ou seja, reservar 10 MB de memória virtual não irá causar o consumo de 10 MB de armazenamento (ex.: memória física). Páginas de memória virtual confirmadas, por outro lado, são mapeadas à um armazenamento e consomem os recursos dos quais representam. Alocar e confirmar 10 MB de memória virtual irá causar o consumo de 10 MB de armazenamento (ex.: memória física).

Processos que necessitam de grande quantidades de memória (como o SQL Server) podem utilizar o processo de reservar e confirmar memória separadamente para evitar o desperdício de recursos preciosos do sistema (ex.: memória física), evitando confirmar o uso de memória até que esta seja realmente necessária, mas ainda assim manter o benefício de alocar grandes espaços de memória virtual contígua. Por exemplo, um thread poderia reservar 1 GB de memória virtual (do endereço 0x00f60000 ao endereço 0x40f6000000:

DWORD dwMemSize = 1024 * 1024 * 1024; // 0x40000000 = 1 GB
LPVOID lpMem = VirtualAlloc(0, dwMemSize, MEM_RESERVE, PAGE_READWRITE);
image 

Uma página reservada ou confirmada pode ainda ter 3 diferentes tipos:

  1. Privada (MEM_PRIVATE)
  2. Mapeada (MEM_MAPPED)
  3. Imagem (MEM_IMAGE)

Páginas privadas pertencem ao processo, não podem ser compartilhadas e podem ser escritas no arquivo de paginação do Windows se necessário (ex.: pressão de memória). Páginas confirmadas e mapeadas são páginas alocadas através de APIs de mapeamento de arquivos e estão associadas à um arquivo em disco:

image

Páginas de memória virtual do tipo MEM_IMAGE são páginas utilizadas para mapear um arquivo de imagem PE (.exe, .dll) no espaço de endereçamento virtual do processo, inclusive a sua própria imagem (ex.: sqlservr.exe):

0:059> !vadump
...
BaseAddress:       0000000000d70000
RegionSize:        0000000000001000
State:             00001000  MEM_COMMIT
Protect:           00000002  PAGE_READONLY
Type:              01000000  MEM_IMAGE
...
BaseAddress:       000000006ffe0000
RegionSize:        0000000000001000
State:             00001000  MEM_COMMIT
Protect:           00000002  PAGE_READONLY
Type:              01000000  MEM_IMAGE
...

E recuperamos informações sobre a imagem mapeada no endereço:

0:059> !dh 0000000000d70000

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
    8664 machine (X64)
       8 number of sections
4DB2C9AD time date stamp Sat Apr 23 09:44:29 2011
...
0000000000d70000 image base
0:059> !lmi 0000000000d70000
Loaded Module Info: [0000000000d70000]
         Module: sqlservr
   Base Address: 0000000000d70000
     Image Name: C:SQL ServerMSSQL10_50.MSSQLSERVERMSSQLBinnsqlservr.exe
   Machine Type: 34404 (X64)
     Time Stamp: 4db2c9ad Sat Apr 23 09:44:29 2011
           Size: 3bb0000
       CheckSum: 3b10238
Characteristics: 22  perf
Debug Data Dirs: Type  Size     VA  Pointer
             CODEVIEW    25, 2bbb324, 2bba724 RSDS - GUID: {E0E45C4E-B0C6-4908-A902-...}
               Age: 2, Pdb: sqlservr.pdb
                CLSID     4, 2bbb320, 2bba720 [Data not mapped]
     Image Type: FILE     - Image read successfully from debugger.
                 C:SQL ServerMSSQL10_50.MSSQLSERVERMSSQLBinnsqlservr.exe
    Symbol Type: PDB      - Symbols loaded successfully from symbol server.
                 c:symbolssqlservr.pdbE0E45C4EB0C64908A90279B30F42FACA2sqlservr.pdb
    Load Report: public symbols , not source indexed
                 c:symbolssqlservr.pdbE0E45C4EB0C64908A90279B30F42FACA2sqlservr.pdb
0:059> !lmi 000000006ffe0000
Loaded Module Info: [000000006ffe0000]
         Module: xpstar
   Base Address: 000000006ffe0000
     Image Name: c:SQL ServerMSSQL10_50.MSSQLSERVERMSSQLBinnxpstar.dll
   Machine Type: 34404 (X64)
     Time Stamp: 4bb67993 Fri Apr 02 20:11:15 2010
           Size: 88000
       CheckSum: 93cb2
Characteristics: 2022
Debug Data Dirs: Type  Size     VA  Pointer
             CODEVIEW    23,  a5b8,    99b8 RSDS - GUID: {6DEA79A6-908B-4178-9F73-...}
               Age: 1, Pdb: XPSTAR.pdb
     Image Type: FILE     - Image read successfully from debugger.
                 c:SQL ServerMSSQL10_50.MSSQLSERVERMSSQLBinnxpstar.dll
    Symbol Type: PDB      - Symbols loaded successfully from symbol server.
                 c:symbolsXPSTAR.pdb6DEA79A6908B41789F734A3EB6CA9EC41XPSTAR.pdb
    Load Report: public symbols , not source indexed
                 c:symbolsXPSTAR.pdb6DEA79A6908B41789F734A3EB6CA9EC41XPSTAR.pdb

Working Set

E no caso do processo utilizar mais memória virtual do que a memória física disponível no servidor?

Bom, nesse caso temos o que é chamado de paging, ou paginação.

Dentro do espaço de memória virtual (VAS) do processo, existe o Working Set, que nada mais é do que o conjunto de páginas de memória confirmadas que foram recentemente utilizados pelo processo e se encontram em memória física. Quando a memória de um processo é removida do seu Working Set, ela pode ser colocada em dois destinos:

  1. Se esta página não foi modificada enquanto estava no Working Set ela será colocada diretamente na lista de páginas em standby.
  2. Caso contrário essa página irá para a lista de páginas modificadas (modified pages), onde fica até ser escrita em disco (paged out), no arquivo de paginação ou em um arquivo mapeado em memória, e só então será colocada na lista de páginas em standby.

As páginas em standby, apesar de serem consideradas “memória livre”, ainda estão ligadas a um processo e caso esse processo necessite novamente de uma dessas páginas, haverá um soft memory fault, e essa página será rapidamente recuperada e recolocada no Working Set do processo.

É interessante notar, portanto, que memória livre no sistema não significa necessariamente memória desperdiçada. Essa memória está ligada a algum processo para permitir sua recuperação de forma rápida caso este processo venha a utilizá-la novamente.

Quando uma página permanece na lista de standby por mais que um determinado período, esta página é então zerada e colocada na lista de páginas zeradas. Se houver a necessidade de liberar essa página para uso por outro processo, ela estará pronta para ser colocada em seu Working Set.

Se o processo requisitar uma página da memória que não se encontra mais em seu Working Set e já foi removida da lista de standby, é gerado então um hard memory fault (ao contrário do soft memory fault), e esta página será buscada à partir do armazenamento de suporte à qual pertencia essa memória (arquivo de paginação ou um arquivo mapeado na memória pelo processo), colocada em memória física e alocada no Working Set do processo.

SQL Server

O assunto é bem extenso e ainda faltou muita coisa pra cobrir, mas acredito que foi possível dar uma revisada nos principais conceitos que vamos precisar para o próximo post sobre o gerenciamento de memória no SQL Server, em especial o gerenciamento do Buffer Pool, o maior e principal consumidor de memória de um servidor SQL.

Referências e leitura adicional

Physical Memory Limits: Windows Server 2008 R2
http://msdn.microsoft.com/en-us/library/windows/desktop/aa366778(v=vs.85).aspx#physical_memory_limits_windows_server_2008_r2
Working Set
http://msdn.microsoft.com/en-us/library/windows/desktop/cc441804(v=vs.85).aspx
How to use the /userva switch with the /3GB switch to tune the User-mode space to a value between 2 GB and 3 GB
http://support.microsoft.com/kb/316739
Windows Internals 5th Edition
http://technet.microsoft.com/en-us/sysinternals/bb963901.aspx
Windows via C/C++
http://www.microsoft.com/learning/en/us/book.aspx?id=11241&locale=en-us
Inside Windows Debugging
http://blogs.msdn.com/b/microsoft_press/archive/2012/05/31/new-book-inside-windows-debugging.aspx

Pin It