Wanderley Caloni
(270 comments, 281 posts)
This user hasn't shared any profile information
Home page: http://www.caloni.com.br
Posts by Wanderley Caloni
Depuração na nuvem com o novo Visual Studio
3Uma das novidades do futuro Visual Studio pouco comentada ainda em fóruns por seu caráter sigiloso e ainda em testes (mas que pode facilmente ser observada pela engenharia reversa dos binários do Visual C++) é a possibilidade de depurar trechos de código "na nuvem", ou seja, dentro dos gigantescos servidores de clusters de serviços de escalabilidade da Amazon, do Google e, claro, da Microsoft.
Já é conhecido que será possível inserir comentários no código-fonte com o formato @nickname e incluir na listagem de bugs o estilo das #hashtags para que programadores vinculados à sua rede social possam enxergar referências a outros programadores e verificar o Developer TrendTopics, como um #blame-joel-on-software. Porém, o que poucos sabem, é que será também possível depurar as APIs de redes sociais em tempo real. Ou seja, caso seja usado o método Twitter::Tweet(), logo após o retorno da chamada será possível aguardar por uma resposta dos usuários envolvidos:
Twitter::Tweet
push ebp
mov ebp, esp
000007f9`bd590000 call __internal_tweet
000007f9`bd5900ac call _checkesp
000007f9`bd5900af ...
000007f9`bd5900ff ...
000007f9`bd59015f call __internal_wait_for_replies
000007f9`bd59017f pop esp
...
Ou seja, logo será possível além de perder horas navegando em saites de rede social perder também horas depurando os comentários e respostas das pessoas nessas redes direto do Visual Studio. É a Microsoft pensando nos programadores que gostam de perder tempo se envolver com pessoas (ainda que virtuais) e discussões acaloradas sobre tópicos irrelevantes e absurdos (ainda que virtuais).
8° Encontro de Programadores de C & C++
4Ando tendo alguns problemas de postagem no meu blog, por isso o aviso não foi feito com mais antecedência. Peço desculpas aos organizadores do evento, pois sei que todo tipo de divulgação é útil.
Chegamos em mais um evento do grupo C/C++ Brasil, dessa vez honrando a parte "Brasil" do nome. Sim, nosso próximo evento será fora de Sampa, mas ainda próximo, no Rio de Janeiro! Até onde eu sei, o primeiro que se tem notícia. Finalmente o grupo terá a chance de se reunir na terra de programadores C++ de referência internacional como Pedro Lamarão.
Os detalhes do evento estão, logicamente, no saite oficial do grupo. Ele ocorrerá no dia 25 de maio (ainda dá tempo de comprar passagem) e terá sua programação divulgada já em abril. Infelizmente o tempo para o call for papers quase se esgotou (vai até dia 30 desse mês).
Enfim, essa é a chance de intercâmbio esperada entre nossa comunidade de programadores C/C++ de outras partes do país e que ainda não tiveram a oportunidade de participar dos nossos tradicionais encontros.
eXtreme Go Horse
3O Go Horse Power (GHP) foi criado por um blogue hoje extinto. As premissas dessa nova metodologia de desenvolvimento era que o projeto fosse feito da maneira mais rápida possível.
Contudo, eles não contavam com a versão turbinada do desleixo humano.
A eXtreme Go Horse (XGP) é o suprassumo das metodologias do mercado brasileiro de desenvolvimento. Quem nunca trabalhou em uma empresa gerida por essas regras? (Bom, pelo menos XGH pelo jeito tem até controle de fonte, algo que era até meio raro uns anos atrás):
1- Pensou, não é XGH
XGH não pensa, faz a primeira coisa que vem à mente. Não existe segunda opção, a única opção é a mais rápida
2- Existem três formas de se resolver um problema
Estas são: a correta, a errada e a XGH, que é igual à errada, só que mais rápida. XGH é mais rápido que qualquer metodologia de desenvolvimento de software que você conhece (Vide Axioma 14).
3- Quanto mais XGH você faz, mais precisará fazer
Para cada problema resolvido usando XGH, mais uns 7 são criados. Mas todos eles serão resolvidos da forma XGH. XGH tende ao infinito.
4- XGH é totalmente reativo
Os erros só existem quando aparecem.
5- XGH vale tudo, só não vale dar o toba
Resolveu o problema? Compilou? Commit e era isso.
6- Commit sempre antes de update
Se der merda, a sua parte estará sempre correta.. E seus colegas que se fodam.
7- XGH não tem prazo
Os prazos passados pelo seu cliente são meros detalhes. Você SEMPRE conseguirá implementar TUDO no tempo necessário (nem que isso implique em acessar o BD por um script malaco)
8- Esteja preparado para pular fora quando o barco começar a afundar…
Ou coloque a culpa em alguém ou algo. Pra quem usa XGH, um dia o barco afunda. Quanto mais o tempo passa, mais o sistema vira um monstro. O dia que a casa cair, é melhor seu curriculum estar cadastrado na APInfo, ou ter algo pra colocar a culpa
9- Seja autêntico, XGH não respeita padrões
Escreva o código como você bem entender, se resolver o problema, commit e era isso
10- Não existe refactoring, apenas rework
Se der merda, refaça um XGH rápido que solucione o problema. O dia que o rework implicar em reescrever a aplicação toda, pule fora, o barco irá afundar (Vide Axioma 8)
11- XGH é totalmente anárquico
A figura de um gerente de projeto é totalmente descartável. Não tem dono, cada um faz o que quiser na hora que os problemas e requisitos vão surgindo (Vide Axioma 4)
12- Se iluda sempre com promessas de melhorias
Colocar TUDO no código como uma promessa de melhoria ajuda o desenvolvedor XGH a não sentir remorso ou culpa pela cagada que fez. É claro que o refactoring nunca será feito (Vide Axioma 10)
13- XGH é absoluto, não se prende à coisas relativas
Prazo e custo são absolutos, qualidade é totalmente relativa. Jamais pense na qualidade e sim no menor tempo que a solução será implementada, aliás… não pense, faça!
14- XGH é atemporal
Scrum, XP…Tudo isso é modinha. O XGH não se prende às modinhas do momento, isso é coisa de viado. XGH sempre foi e sempre será usado por aqueles que desprezam a qualidade
15- XGH nem sempre é POG
Muitas POG’s exigem um raciocínio muito elevado, XGH não raciocina (Vide Axioma 1).
16- Não tente remar contra a maré
Caso seus colegas de trabalho usam XGH para programar e você é um coxinha que gosta de fazer as coisas certinhas, esqueça! Pra cada Design Pattern que você usa corretamente, seus colegas gerarão dez vezes mais código podre usando XGH.
17- O XGH não é perigoso até surgir um pouco de ordem
Este axioma é muito complexo, mas sugere que o projeto utilizando XGH está em meio ao caos. Não tente por ordem no XGH (Vide Axioma 16), é inútil e você pode jogar um tempo precioso no lixo. Isto fará com que o projeto afunde mais rápido ainda (Vide Axioma 8). Não tente gerenciar o XGH, ele é auto suficiente (Vide Axioma 11), assim como o caos.
18- O XGH é seu brother, mas é vingativo
Enquanto você quiser, o XGH sempre estará do seu lado. Mas cuidado, não o abandone. Se começar um sistema utilizando XGH e abandoná-lo para utilizar uma metodologia da moda, você estará fudido. O XGH não permite refactoring (vide axioma 10), e seu novo sistema cheio de frescurites entrará em colapso. E nessa hora, somente o XGH poderá salvá-lo.
19- Se tiver funcionando, não rela a mão
Nunca altere, e muito menos questione um código funcionando. Isso é perda de tempo, mesmo porque refactoring não existe (Vide Axioma 10). Tempo é a engrenagem que move o XGH e qualidade é um detalhe desprezível.
20- Teste é para os fracos
Se você meteu a mão num sistema XGH, é melhor saber o que está fazendo. E se você sabe o que está fazendo, vai testar pra que? Testes são desperdício de tempo, se o código compilar, é o suficiente.
21- Acostume-se ao sentimento de fracasso iminente
O fracasso e o sucesso andam sempre de mãos dadas, e no XGH não é diferente. As pessoas costumam achar que as chances do projeto fracassar utilizando XGH são sempre maiores do que ele ser bem sucedido. Mas sucesso e fracasso são uma questão de ponto de vista. O projeto foi por água abaixo mas você aprendeu algo? Então pra você foi um sucesso!
22- O problema só é seu quando seu nome está no Doc da classe
Nunca ponha a mão numa classe cujo autor não é você. Caso um membro da equipe morra ou fique doente por muito tempo, o barco irá afundar! Nesse caso, utilize o Axioma 8.
Este texto foi copiado daqui e daqui. Não existem donos conhecidos do XGH (já devem ter morrido de desgosto). Fiquei com medo de não encontrar mais essa metologia, que é pouco divulgada e muito útil.
Minha palestra do TDC 2012
4Duas semanas atrás rolou a trilha C++ do TDC 2012, que contou com além da minha presença com a dos já conhecidos Fernando Roberto (DriverEntry), Rodrigo Strauss (1Bit), etc. Uma novidade: meu colega e programador .nerd Gabriel Guilherme também participou em uma palestra sobre um assunto que acredito que deveria ser mais promovido: interop. Afinal de contas, o poder de C++ não seria nada se não houvesse motivos práticos para usá-lo. Entre esses motivos, construir soluções com linguagens mais acessíveis é um deles.
Na minha palestra foquei no conteúdo dos meus dois artigos sobre um fictício Patch de Emergência (parte 1 e parte 2). No entanto, para agregar um pouco de valor ao conteúdo, estendi os exemplos para fornecer os caminhos para o desenvolvimento de uma solução profissional e usável de um quem-sabe real patch desses (ouvi falar que empresas como Microsoft utilizam isso).
Patch de Emergencia from Wanderley Caloni
Os fontes e a apresentação se encontram no meu já conhecido branch de exemplos no GitHub, mas para quem não sabe do que eu estou falando segue um linque com um pacote 7zip.
GetTickCount não é um gerador de IDs únicos
0Muitas vezes uma solução intuitiva não é exatamente o que esperamos que seja quando o código está rodando. Gerar IDs únicos, por exemplo. Se você analisar por 5 minutos pode chegar à conclusão que um simples GetTickCount, que tem resolução de clock boa e que se repete apenas depois de 50 dias pode ser um ótimo facilitador para gerar IDs exclusivos durante o dia.
Porém, nada como código para provar que estamos errados:
#include <windows.h> #include <iostream> #include <list> #include <algorithm> #include <stdlib.h> #include <time.h> using namespace std; list<DWORD> g_ticks; list<LONG> g_increments; DWORD WINAPI Ticks(PVOID) { for( int i = 1; i <= 100; ++i ) { DWORD tick = GetTickCount(); g_ticks.push_back(tick); Sleep(rand() % 20); } return 0; } DWORD WINAPI Increment(PVOID) { static LONG st_prevIncrement = 0; for( int i = 1; i <= 100; ++i ) { LONG increment = InterlockedIncrement(&st_prevIncrement); g_increments.push_back(increment); Sleep(rand() % 20); } return 0; } int main() { const size_t threadsCount = 20; HANDLE threads[threadsCount]; srand((unsigned int) time(0)); for( size_t i = 0; i < threadsCount / 2; ++i ) threads[i] = CreateThread(NULL, 0, Ticks, NULL, 0, NULL); for( size_t i = threadsCount / 2; i < threadsCount; ++i ) threads[i] = CreateThread(NULL, 0, Increment, NULL, 0, NULL); WaitForMultipleObjects(threadsCount, threads, TRUE, INFINITE); for( auto it = g_ticks.begin(); it != g_ticks.end(); ++it ) { DWORD tick = *it; size_t incrementOccurrence = count(g_increments.begin(), g_increments.end(), tick); if( incrementOccurrence ) { cout << "Ocorrencia de incremento duplicado!\n"; break; } } for( auto it = g_ticks.begin(); it != g_ticks.end(); ++it ) { DWORD tick = *it; size_t tickOccurrence = count(g_ticks.begin(), g_ticks.end(), tick); if( tickOccurrence ) { cout << "Ocorrencia de tick duplicado!\n"; break; } } }
O motivo do GetTickCount retornar números iguais remete tanto ao fato que o espaço de tempo entre uma execução e outra pode ser muito pequeno quanto ao fato de várias threads podem ser executadas efetivamente ao mesmo tempo em ambientes de dois ou mais cores.
Já o motivo do InterlockedIncrement funcionar sempre é porque aqui estamos usando uma solução de incremento atômico, ou seja, usamos a mesma base contadora e incrementamos ela em uma operação que não pode ocorrer ao mesmo tempo com outra thread.
O que aprendemos aqui? Que por mais que seja intuitiva uma solução, nunca podemos nos basear nas nossas falhas cabeças. Um computador está aí não apenas para ser mais rápido, mas para ser assertivo em nossas elucubrações. Nesse sentido, é o nosso companheiro vulcaniano.
Novos atalhos aprendidos no Vim
0Sempre é bom reler as referências e tentar melhorar o que já está bom. No momento minha inspiração é o excelente Vim: From Essentials to Mastery, uma coleção de slides bem-humorada que a cada releitura fornece dicas importantes para aprimorar o dia-a-dia com um dos editores mais poderosos do planeta.
A lista abaixo é pessoal e, como disse Bram Moolenar, "You should not try to learn every command an editor offers. That would be a complete waste of time. Most people only need to learn 10 to 20 percent of the commands for their work. But it’s a different set of commands for everybody."
- <C-W><C-W> Alterna entre janelas.
- <C-W>-c Fecha a janela atual.
- <C-W>-o Fecha todas as janelas menos a atual.
- :ball Abre todos os buffers em janelas distintas.
- g <C-G> Conta linhas, palavras, etc, no texto todo ou na seleção atual.
Meus repositórios no GitHub
0Depois de vacilar por alguns meses, incentivado pelo meu amigo Chico Pimenta, resolvi experimentar o tal do GitHub, e consequentemente o sistema de controle de fontes distribuído Git, que antes era meio exclusivo do Linux (continua meio sendo, mas com suporte um pouco melhor para Windows).
Com isso, dei uma pequena lida no livro de introdução e comecei a migrar meus fontes perdidos num canto do HD. O que notei de vantagem com relação a outros DRCSs foi que é muito fácil e rápido criar branches e que a comunicação remota e os commits são feitos de uma maneira mais organizada e estruturada, além da própria estrutura interna do repositório ser muito simples de entender: um bando de arquivos compactados cujo nome é o hash do que ele contém.
Meus repositórios estão armazenados em alguns branches que distribuí de acordo com o uso/importância:
- OpenSource. Projetos de fonte aberto que mantenho/ive e que poderiam se perder se alguém não fizesse backup (como o mouse tool ou regmon).
- Samples. Códigos de exemplo, de palestras e de testes feitos para escrever os artigos do blogue cujo autor vos fala.
- Caloni. Os códigos que fazem algo de útil, como o Houaiss2Babyulon, CopiaExata e DayToDay.
- Book. Um projeto em estado de larva sobre escrever um livro de engenharia reversa. Já possui um índice básico. Sugestões são bem-vindas.
- DriverEntry. Códigos do curso de desenvolvimento de drivers que estou fazendo com o Fernando, da DriverEntry Company. Recomendo!
Problemas comuns no WinDbg e suas soluções
0Depois de uma agradável manhã e tarde acompanhando o curso de desenvolvimento de drivers do meu amigo Ferdinando voltei para a casa para brincar um pouco mais com o mundo kernel e voltar a encontrar problemas com o WinDbg & Cia que há mais ou menos 1 ano atrás não tinha.
Pesquisando por um problema específico envolvendo PDBs reencontrei o blogue do Ken Johnson, MVP Microsoft e analista por profissão e diversão, é conhecido por suas excelentes contribuições no mundo da depuração de sistema (notadamente WinDbg). Existe um post específico que ele escreveu para economizar tempo com problemas que ocorrem de vez em quando em uma sessão ou outra de depuração, mas nunca paramos tempo o suficiente para resolver.
Além de outros, ele lista alguns que particularmente já aconteceram comigo ou com colegas de depuração:
O WinDbg demora um tempo absurdo para processar o carregamento dos módulos e está usando tempo máximo de processamento em apenas uma CPU.
Isso ocorre porque existem breakpoints ainda não resolvidos. Resolva deixando apenas esses tipos de breakpoints que são absolutamente necessários, pois cada vez que um módulo é carregado o depurador precisa fazer o parser de cada um deles para verificar se ele já consegue resolve-lo.
Às vezes, porém, existe algum lixo nos workspaces carregados por ele que permanecem mesmo depois de apagarmos todos os breakpoints inúteis ou reiniciar o sistema. Em último caso, sempre podemos apagar o workspace do registro, em HKCU\Software\Microsoft\Windbg\Workspaces.
O WinDbg continua demorando décadas para analisar o carregamento, mas agora nem consome tanta CPU assim.
Isso ocorre porque na cadeia de paths para procurar por símbolos existe algum endereço de rede/internet errado que faz com que ele tenha que caminhar em falso diversas vezes. Esse e outros erros de símbolos sempre poderão ser analisados através do universal !sym noisy, que imprime todo tipo de informação útil do que pode dar errado durante um .reload explícito (eu digitei) ou implícito (lazy reload).
O WinDbg continua recusando carregar um símbolo que eu sei que existe e sei que é válido.
Talvez ele exista, mas por algum motivo foi copiado corrompido para o symbol server. Mais uma vez, !sym noisy nele e deve acontecer algum erro de E_PDB_CORRUPT. Nesse caso, apague o PDB culpado e tente de novo.
E, como brinde, um grande aliado da produtividade: como evitar que o WinDbg bloqueie seu PDB enquanto você precisa constantemente recompilar seu driver:
.reload -u modulo
Fonte: Blog do Nynaeve.
Sobrecarga de função às avessas
4Navegando pelo Archive.org, que possibilita viajar no tempo e encontrar coisas enterradas que seria melhor deixar por lá, consegui encontrar um post que se perdeu na dobra espaço-temporal entre o old-fashioned Caloni.com.br (com direito à velha joaninha psicodélica, desenho do meu amigo t@z) e o finado CThings. No final, consegui matar a marmota, chegar a 80 milhas por hora e voltar para o presente. Enjoy it!
Alguém já se perguntou se é possível usar sobrecarga de função quando a diferença não está nos parâmetros recebidos, mas no tipo de retorno? Melhor dizendo, imagine que eu tenha o seguinte código:
GUID guid; wstring guidS; CreateNewGUID(guidS); // chama void CreateNewGUID(wstring&) CreateNewGUID(guid); // chama void CreateNewGUID(GUID&) (o compilador sabe disso)
É um uso sensato de sobrecarga. Mas vamos supor que eu queira uma sintaxe mais intuitiva, com o retorno sendo atribuído à variável:
GUID guid; wstring guidS; guidS = CreateNewGUID(); // chama wstring CreateNewGUID() guid = CreateNewGUID(); // chama GUID CreateNewGUID() (o compilador sabe disso?)
Voltando às teorias de C++, veremos que o código acima NÃO funciona. Ou, pelo menos, não deveria. Só pelo fato das duas funções serem definidas o compilador já reclama:
error C2556: 'GUID CreateNewGUID(void)' : overloaded function differs only by return type from 'std::wstring CreateNewGUID(void)'
Correto. O tipo de retorno não é uma propriedade da função que exclua a ambigüidade. Apenas a assinatura pode fazer isso (que são os tipos dos parâmetros recebidos pela função).
Pois bem. Não podemos fazer isso utilizando funções ordinárias. Então o jeito é criar nosso próprio "tipo de função" que dê conta do recado:
struct CreateNewGUID { // o que vai aqui? };
Pronto. Agora podemos "chamar" a nossa função criando uma nova instância e atribuindo o "retorno" a wstring ou à nossa GUID struct:
guidS = CreateNewGUID(); // instancia um CreateNewGUID guid = CreateNewGUID(); // instancia um CreateNewGUID. A diferença está no "retorno"
Uma vez que criamos um novo tipo, e considerando que este tipo é, portanto, diferente dos tipos wstring e GUID já existentes, devemos simplesmente converter nosso novo tipo para cada um dos tipos de retorno desejados:
struct CreateNewGUID { operator wstring () { ... } // a conversão é a "chamada da função". operator GUID () { ... } // E como existem duas conversões... sobrecarga! };
E isso conclui a solução meio esquizofrênica de nossa sobrecarga às avessas:
// instancia um CreateNewGUID e chama CreateNewGUID::operator wstring() guidS = CreateNewGUID(); // instancia um CreateNewGUID e chama CreateNewGUID::operator GUID() guid = CreateNewGUID();
Eis o fonte completo:
#include <windows.h> #include <objbase.h> #include <iostream> #include <string> using namespace std; struct CreateNewGUID { operator wstring () { GUID guid = operator GUID(); OLECHAR buf[40] = { }; ::StringFromGUID2(guid, buf, sizeof(buf)); return wstring(buf); } operator GUID () { GUID guid = { }; ::CoCreateGuid(&guid); return guid; } }; int _tmain(int argc, _TCHAR* argv[]) { wstring guidS; GUID guid; // instancia um CreateNewGUID e chama CreateNewGUID::operator wstring() guidS = CreateNewGUID(); // instancia um CreateNewGUID e chama CreateNewGUID::operator GUID() guid = CreateNewGUID(); wcout << L"Pra nao dizer que esse exemplo nao imprime nada:\n" << guidS << L'\n'; return 0; }
Voltando à pergunta original: penso que, com criatividade e C++, nada é impossível =)
Consumo abusivo de memória
0Era um belo dia em um ambiente de processamento fictício de filas fictícias e threads fictícias. Eis um belo código com filas, threads e processamentos feitos em stop-motion:
#include <windows.h> // critical section, create thread... #include <list> // nossa lista interna #include <time.h> // randomização struct Queue // uma fila (duh) { size_t bufferSize; // cada item é um buffer de tamanho fixo DWORD wait; // antes de processar, aguardemos esse tempo fixo CRITICAL_SECTION cs; // stl é thread-safe, pero no mucho std::list<char*> items; // os itens! }; DWORD WINAPI InsertItems(LPVOID pvQueue) // insere, insere, insere.... { Queue& queue = *(Queue*) pvQueue; for( int i = 0; i < 10 * 1000; ++i ) // 10k itens! { char* buffer = new char[queue.bufferSize]; memset(buffer, (int) (i % ('Z' - 'A')) + 'A', queue.bufferSize); // teoricamente de A a Z buffer[queue.bufferSize - 1] = 0; // string C pra facilitar nossa depuração EnterCriticalSection(&queue.cs); // deixa eu entrar! queue.items.push_back(buffer); LeaveCriticalSection(&queue.cs); // deixa eu sair! Sleep(10); // dá uma dormidinha (sempre menor dormidinhas do processamento) } return ERROR_SUCCESS; // "tá tudo certo!" (by Starcraft 2) } DWORD WINAPI ProcessItems(LPVOID pvQueue) // processa, processa, processa... { Queue& queue = *(Queue*) pvQueue; DWORD wait = 2; Sleep(10000); // como um advogado oportunista, aguardamos por alguém pra processar while( ! queue.items.empty() ) // agora vai até esvaziar o recinto { EnterCriticalSection(&queue.cs); // deixa eu entrar! char* buffer = queue.items.front(); queue.items.pop_front(); LeaveCriticalSection(&queue.cs); // deixa eu sair! delete [] buffer; Sleep(queue.wait); // aguarda por... por quanto mesmo? } return ERROR_SUCCESS; // "tá tudo certo!" (by Starcraft 2) } int main(int argc, char* argv[]) // No princípio havia a pilha, quando Deus disse: 'int main!' { static const size_t QUEUES_SIZE = 20; // número de filas sendo processadas static const size_t QUEUE_ITEM_SIZE = 0x1000; // 1KB é o chunk alocado por item static const DWORD WAIT_TIMES[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1000 }; // alguém vai esperar demais Queue queues[QUEUES_SIZE]; // as filas HANDLE queueThreads[QUEUES_SIZE * 2]; // as threads que processam as filas srand((unsigned int)time(0)); // randomizemos tudo for( size_t i = 0; i < QUEUES_SIZE; ++i ) { queues[i].bufferSize = QUEUE_ITEM_SIZE + i; // para diferenciarmos as filas queues[i].wait = WAIT_TIMES[ rand() % (sizeof(WAIT_TIMES) / sizeof(DWORD)) ]; // vamos esperar por... por quanto mesmo? InitializeCriticalSection(&queues[i].cs); // deu crash em algumas situações em release (stl deveria ser thread-safe...) queueThreads[i] = CreateThread(NULL, 0, InsertItems, &queues[i], 0, NULL); // criamos thread de inserção queueThreads[QUEUES_SIZE + i] = CreateThread(NULL, 0, ProcessItems, &queues[i], 0, NULL); // criamos thread de processamento } WaitForMultipleObjects(QUEUES_SIZE * 2, queueThreads, TRUE, INFINITE); // espera a 'gaguera' return 0; // "tá tudo certo!" (by Starcraft 2) }
Se olharmos de perto o processamento e a memória consumida por esse processo, veremos que no início existe um boom de ambos, mas após um momento de pico, o processamento praticamente pára, mas a memória se mantém:
Depois de pesquisar por meus tweets favoritos, fica fácil ter a receita para verificarmos isso usando nosso depurador favorito: Visual Studio WinDbg!
windbg -pn MemoryConsumption.exe
Achamos onde está a memória consumida. Agora precisamos de dicas do que pode estar consumindo essa memória. Vamos começar por listar os chunks alocados por tamanho de alocação:
0:004> !heap -stat -h 0 Allocations statistics for heap @ 00670000 group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes) 1037 25e5 - 2667433 (33.04) 1025 25e6 - 263da3e (32.90) 1024 25e4 - 2639410 (32.89) ...
O Top 3 é de tamanhos conhecidos pelo código, de 1024 a 1024 + QUEUES_SIZE - 1. O de tamanho 1037, por exemplo, possui 0x25e5 blocos alocados. Vamos listar cada um deles:
0:004> !heap -flt s 1037
_HEAP @ 420000
_HEAP @ 670000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
00558600 0221 0000 [00] 00558618 01037 - (busy) <--- vamos usar esse primeiro mais tarde
0055fd38 0221 0221 [00] 0055fd50 01037 - (busy)
00561f48 0221 0221 [00] 00561f60 01037 - (busy)
00565260 0221 0221 [00] 00565278 01037 - (busy)
0056c998 0221 0221 [00] 0056c9b0 01037 - (busy)
0056daa0 0221 0221 [00] 0056dab8 01037 - (busy)
0056eba8 0221 0221 [00] 0056ebc0 01037 - (busy)
00570db8 0221 0221 [00] 00570dd0 01037 - (busy)
00572fc8 0221 0221 [00] 00572fe0 01037 - (busy)
005740d0 0221 0221 [00] 005740e8 01037 - (busy)
0058abc8 0221 0221 [00] 0058abe0 01037 - (busy)
00595618 0221 0221 [00] 00595630 01037 - (busy)
00599a38 0221 0221 [00] 00599a50 01037 - (busy)
0059de58 0(...)
A listagem do depurador nos dá o endereço onde o chunk foi alocado no heap e o endereço devolvido para o usuário, onde colocamos nossas tralhas. Através de ambos é possível trackear a pilha da chamada que alocou cada pedaço de memória. Isso, claro, se previamente tivermos habilitado essa informação através do GFlags:
0:004> !heap -p -a 00558600 address 00558600 found in _HEAP @ 670000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 00558600 0221 0000 [00] 00558618 01037 - (busy) Trace: b7a24 7722dfa2 ntdll!RtlAllocateHeap+0x00000274 5b628343 MSVCR100D!_heap_alloc_base+0x00000053 5b63697c MSVCR100D!_nh_malloc_dbg+0x000002dc 5b63671f MSVCR100D!_nh_malloc_dbg+0x0000007f 5b6366cc MSVCR100D!_nh_malloc_dbg+0x0000002c 5b639c5b MSVCR100D!malloc+0x0000001b 5b627db1 MSVCR100D!operator new+0x00000011 e84dee MemoryConsumption!operator new[]+0x0000000e e818be MemoryConsumption!InsertItems+0x0000004e 7679339a kernel32!BaseThreadInitThunk+0x0000000e 771e9ef2 ntdll!__RtlUserThreadStart+0x00000070 771e9ec5 ntdll!_RtlUserThreadStart+0x0000001b
Dessa forma temos onde cada memória foi alocada, o que nos dará uma informação valiosa, dependendo qual o tipo de problema estamos tentando resolver.
0:004> u e818be
MemoryConsumption!InsertItems+0x4e [c:\...\memoryconsumption.cpp @ 18]:
00e818be 83c404 add esp,4
00e818c1 898514ffffff mov dword ptr [ebp-0ECh],eax
00e818c7 8b9514ffffff mov edx,dword ptr [ebp-0ECh]
00e818cd 8955e0 mov dword ptr [ebp-20h],edx
00e818d0 8b45f8 mov eax,dword ptr [ebp-8]
00e818d3 8b08 mov ecx,dword ptr [eax]
00e818d5 51 push ecx
00e818d6 8b45ec mov eax,dword ptr [ebp-14h]
Outra informação relevante é o que está gravado na memória, que pode nos dar insights de que tipo de objeto estamos lidando:
0:004> db 00558618
00558618 c0 b7 8c 0b 98 03 55 00-00 00 00 00 00 00 00 00 ......U.........
00558628 13 10 00 00 01 00 00 00-15 94 00 00 fd fd fd fd ................
00558638 51 51 51 51 51 51 51 51-51 51 51 51 51 51 51 51 QQQQQQQQQQQQQQQQ
00558648 51 51 51 51 51 51 51 51-51 51 51 51 51 51 51 51 QQQQQQQQQQQQQQQQ
00558658 51 51 51 51 51 51 51 51-51 51 51 51 51 51 51 51 QQQQQQQQQQQQQQQQ
00558668 51 51 51 51 51 51 51 51-51 51 51 51 51 51 51 51 QQQQQQQQQQQQQQQQ
00558678 51 51 51 51 51 51 51 51-51 51 51 51 51 51 51 51 QQQQQQQQQQQQQQQQ
00558688 51 51 51 51 51 51 51 51-51 51 51 51 51 51 51 51 QQQQQQQQQQQQQQQQ
Não é o caso, mas vamos supor que fosse um objeto/tipo conhecido. Poderíamos simplesmente "importar" o tipo diretamente do PDB que estamos para modelar a memória que encontramos em volta. Mais detalhes em outro artigo.
Funções/classes usadas nesse artigo
- CreateThread. Cria uma nova linha de execução.
- WaitForMultipleObjects. Pode aguardar diferentes linhas de execução terminarem.
- std::list. Lista na STL para inserir/remover objetos na frente e atrás (ui).
- Initialize, Enter e LeaveCriticalSection. Uma maneira simples de criar blocos de entrada atômica (apenas uma thread entra por vez).
- memset. Se você não sabe usar memset, provavelmente não entendeu nada desse artigo.
Coletando dumps automaticamente
0HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps key.
| Value | Description | Type | Default value |
| DumpFolder | The path where the dump files are to be stored. If you do not use the default path, then make sure that the folder contains ACLs that allow the crashing process to write data to the folder.
For service crashes, the dump is written to service specific profile folders depending on the service account used. For example, the profile folder for System services is %WINDIR%\System32\Config\SystemProfile. For Network and Local Services, the folder is %WINDIR%\ServiceProfiles. |
REG_EXPAND_SZ | %LOCALAPPDATA%\CrashDumps |
| DumpCount | The maximum number of dump files in the folder. When the maximum value is exceeded, the oldest dump file in the folder will be replaced with the new dump file. | REG_DWORD | 10 |
| DumpType | Specify one of the following dump types:
|
REG_DWORD | 1 |
| CustomDumpFlags | The custom dump options to be used. This value is used only when DumpType is set to 0.
The options are a bitwise combination of the MINIDUMP_TYPE enumeration values. |
REG_DWORD | MiniDumpWithDataSegs | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData. |
Fonte: Collecting User-Mode Dumps (Windows).
E mais! WER Settings
Engenharia Reversa em Itajubá
5Essa segunda (ontem, 16 de abril de 2012) foi dia da minha palestra na Unifei, parte do II Composium Unifei, que reúne diversos profissionais e suas especialidades para que estudantes vejam o quão útil pode ser o curso de engenharia.
Tive o prazer de conversar com diversos alunos após a palestra, e me animou muito saber que existem pessoas no mundo acadêmico considerando a possibilidade de escovar bits como um meio de vida. Espero que muitos pesquisem e testem seus conhecimentos em torno da arte da Engenharia Reversa e, em casos extremos, entrem em contato comigo, pois estarei feliz em responder dúvidas pontuais ou direcionar melhor o aprendizado.
O que eu aprendi durante o evento foi que a Unifei já é um polo de engenharia dedicada à formação completa dos seus alunos e uma base de pesquisas realmente interessantes. No entanto, eles querem mais, e me parece que o futuro reserva muitos desafios e conquistas naquela região no que diz respeito à P&D de novas tecnologias em um grau de maturidade que o país, infelizmente, ainda não foi capaz de atingir.
Tudo isso, contudo, depende do grupo dos focados professores e organizadores do evento, cuja companhia tive o privilégio de compartilhar. É preciso tirar o chapéu para o que estão fazendo em Itajubá. E preciso agradecer a Rodrigo Almeida pelo convite para a palestra; foi de fato uma experiência única conhecer o campus e a maravilhosa cidade mineira.
Como prometido
Resolvi juntar as duas apresentações e todo o material envolvido na palestra em um único pacotão, que pode ser baixado através deste linque.
Header Inútil
4O Visual Studio é uma ótima ferramenta para depurar rapidamente programas sendo desenvolvidos e para editar vários arquivos ao mesmo tempo para o resto usamos Vim. No entanto, a versão 2010 do ambiente (ainda não testei a 2011 beta) possui um pequeno deslize com sua árvore de dependências que não chega a prejudica o desenvolvedor, mas o deixa com um bug atrás da orelha.
Vamos supor que você crie seu super-projeto ZeroMQ e no meio dele acabe evoluindo uma nova forma de vida inútil e descartável, que aqui iremos chamar de HeaderInutil e seu fiel companheiro CppInutil:
OK. Ele não está fazendo nada, mas e daí? Compilo meu projeto normalmente e depuro ele como se nada estivesse acontecendo.
------ Rebuild All started: Project: ZeroMasQueCoisaProj, Configuration: Debug Win32 ------
Build started 27/03/2012 11:40:32.
_PrepareForClean:
Deleting file "Debug\ZeroMasQueCoisaProj.lastbuildstate".
InitializeBuildStatus:
Creating "Debug\ZeroMasQueCoisaProj.unsuccessfulbuild" because "AlwaysCreate" was specified.
ClCompile:
stdafx.cpp
ZeroMasQueCoisaProj.cpp
CppInutil.cpp
Generating Code...
Manifest:
Deleting file "Debug\ZeroMasQueCoisaProj.exe.embed.manifest".
LinkEmbedManifest:
ZeroMasQueCoisaProj.vcxproj -> c:\...\Debug\ZeroMasQueCoisaProj.exe
FinalizeBuildStatus:
Deleting file "Debug\ZeroMasQueCoisaProj.unsuccessfulbuild".
Touching "Debug\ZeroMasQueCoisaProj.lastbuildstate".
Build succeeded.
Time Elapsed 00:00:00.73
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
'ZeroMasQueCoisaProj.exe': Loaded 'C:\...\Debug\ZeroMasQueCoisaProj.exe', Symbols loaded.
'ZeroMasQueCoisaProj.exe': Loaded 'C:\Windows\SysWOW64\ntdll.dll', Cannot find or open the PDB file
'ZeroMasQueCoisaProj.exe': Loaded 'C:\Windows\SysWOW64\kernel32.dll', Cannot find or open the PDB file
'ZeroMasQueCoisaProj.exe': Loaded 'C:\Windows\SysWOW64\KernelBase.dll', Cannot find or open the PDB file
'ZeroMasQueCoisaProj.exe': Loaded 'C:\Windows\SysWOW64\msvcr100d.dll', Symbols loaded.
The program '[5212] ZeroMasQueCoisaProj.exe: Native' has exited with code 0 (0x0).
Show.
Mas o que acontece se eu precisar no momento do refactory (que deve, sim, existir) eu decidir remover meus arquivos inúteis?
Continuo compilando normalmente o projeto, mas na hora de depurar...
Mas o que ocorre? Eu acabei de compilar o projeto! E se eu compilar novamente e pressionar F5, ele continua apresentando o mesmo problema!
OK, não estou admitindo aqui o famigerado Rebuild All. Se você mantém projetos com mais de 200 arquivos, acho que deve repensar seus conceitos ao usar Rebuild All para tudo nessa vida.
Acontece que existe uma árvore de dependências que o Visual Studio mantém para saber se seu projeto foi atualizado com tudo que tem mais de novo no que diz respeito ao File System, mas às vezes se esquece de checar o FS com o que está na solution. Por conta disso, o HeaderInutil e o CppInutil continuam dentro da árvore de dependência como zumbis.
O que pode ser feito nesse caso (além do que os personagens de The Walking Dead costumam fazer) é configurar o arquivo devenv.exe.config (presente em %programfiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE) e adicionar as seguintes linhas após a seção configSections. (Esses passos estão descritos no blogue da equipe do VC.)
<system.diagnostics>
<switches>
<add name="CPS" value="4" />
</switches>
</system.diagnostics>
Depois de modificar o arquivo, reinicie o Visual Studio e tente novamente apertar F5 no mesmo projeto, mas com o DebugView aberto.
Como um amigo meu diria: "AHÁ!!". Descobrimos o culpado.
A solução? Nesse caso não tem jeito: dar um clean no projeto e build novamente para que o VS reconstrua a árvore de dependências. Porém, agora sabemos por que precisamos do Rebuild All. Não é RebuildAllMania.
Conteúdo da Palestra
0Uma nuvem mágica...
O evento de C++ na Microsoft (e organizada pelo nosso grupo C/C++ Brasil) aconteceu. Quem esteve lá teve o privilégio de passar algumas horas com programadores e entusiastas de ambas as linguagens e acompanhar o raciocínio dos palestrantes sobre Move Semantics, COM (good times), programação na placa gráfica e a solução para todos os problemas do universo (vulgo ZeroMQ). Fora isso, a palestra que me surpreendeu no dia foi a do Sr. Basílio Miranda, cômica e inspirada, mas sempre nos fornecendo um pouquinho mais de conhecimento acerca do Qt framework, que felizmente ainda não morreu (a Nokia recentemente liberou uma série nova que ainda usa a versão evoluída do Symbian), e graças a isso não precisaremos nos preocupar por enquanto com o destino de nosso ilustríssimo Sr. Basílio.
Fora isso tivemos uma telepalestra com um dos membro do time do Visual Studio. Mas, francamente, estou cada vez menos interessado no VS e mais no Vim. Portanto...
Segue minha palestra e meu código-fonte sobre Move Semantics devidamente compactados para apreciação dos interessados. Comentários, sugestões, dicas são sempre bem-vindos, como bem sabem os que me procuram eventualmente através de e-mails ou no blogue.
RValue é o novo LValue
2As grandes discussões filosóficas que participei durante meu estudo da linguagem C, e mais tarde de C++, muitas vezes convergiam para o significado místico daquela figura que nós da gramática da linguagem conhecemos como lvalue, ou l-value, ou left-value. Enfim, a definição de uma expressão que representa um lugar na memória e, portanto, pode ocupar o lado esquerdo de uma atribuição/cópia/passagem de argumentos qualquer. Porém, os "grandes" embates daquela época hoje parecem brincadeira de criança, como a diferença sutil entre ++x e x++ ou convergência de tipos em templates.
Agora o buraco é mais embaixo. Agora temos referências r-value.
Agora o mundo mudou.
Foi necessário que mudasse. C++, conhecido internacionalmente como a vanguarda das linguagens, mesmo mantendo sua fama de alta performance, precisava voltar às suas origens performáticas de qualquer forma. O Criador da linguagem e seus seguidores estavam cientes: cópia de strings é uma coisa muito, muito má. Imperfect forwarding (direcionamento imperfeito?) é algo ainda pior, pois é mais sutil.
Todos concordam, então, que a mudança é necessária. Nem todos concordam, contudo, com o preço a ser pago. As coisas começam a ficar cada vez mais difíceis de entender, e agora, com r-values vindo à superfície, o universo de criaturas bizarras volta a mostrar as caras.
Desde o começo de meus estudos em C++ tenho admirado a linguagem com um certo distanciamento. Enquanto a linguagem C continua sendo o supra-sumo das linguagens de médio-nível, C++ continua sendo uma abominação cujos detalhes muitos preferem esquecer. Mas esquecer tem se tornado cada vez mais difícil frente às gambiarras adaptações técnicas que a linguagem vem sofrendo.
No caso de Rvalues, se antes existia uma discussão interminável sobre sua inclusão no novo padrão, agora existem discussões acerca do que tudo isso significa. Existe até um ótimo guia (thanks to pepper_chico) sobre as principais mudanças de conceitos, feito para simplificar o entendimento. Mas ele mesmo é exageradamente complexo para o programador médio. É de forçar a barra, mesmo. É pedir demais.
Conversemos
No próximo dia 28, sábado, nos reuniremos em mais um evento C++ organizado pela Microsoft pelo Grupo C/C++ Brasil e pelos agora dois MVPs do Brasil, o veterano Fabio Galuppo e o novato Rodrigo Strauss (meu amigo, mas acima de tudo muito bem-vindo ao cargo). Estou na lista de palestrantes e conversarei com vocês sobre as otimizações que o famigerado RValue deve trazer à mesa. Espero conseguir entender um pouco mais sobre essa criatura fantástica até lá.
Se o Cebolinha for um programador C++, deve estar se debatendo nesse momento.
Linques úteis
Vida e Software
5Desenvolver software está bem longe de ser uma ciência exata e muito próxima de ser uma ciência humana. Eu, pessoalmente, considero um ramo esotérico, onde vale muito mais ter a atitude de reconhecer que não temos muito controle sobre como as coisas funcionam do que querer ter as rédeas de algo desgovernado.
Não sei bem o motivo. Talvez por existirem muitas camadas o software não consegue ser normalizado como as propriedades físicas dos blocos que montam um prédio ou um transatlântico. Ou simplesmente existem variáveis demais na própria camada da aplicação que impedem a compreensão total do problema (como sabemos se estamos dentro da Matrix? Como o software sabe se está em um ambiente virtualizado?).
Para os piores casos, o software sempre vai ter que confiar em seu ambiente e as regras, aparentemente imutáveis, que o regem. Ele precisar confiar não o exime de controlar seus próprio limites, e é aí que recai a atitude do programador, atitude essa que reflete diretamente nossa visão sobre a vida.
Já vi muitos programadores (eu me incluo) deixando o código pronto para o melhor dos mundos e dando a tarefa por concluída. Otimismo demais? Desleixo? Ou arrogância? Qualquer que seja o motivo, ele está incrustado na própria visão de vida do programador e de como ele se vê no mundo. Eu, particularmente, fico horrorizado com código assim. Não com todos, mas com os que são visivelmente importantes e que necessitam de um carinho especial. São os alicerces para outros códigos, de repente.
Outra atitude igualmente deformada é achar que o mundo vai acabar se não existirem todos os sanity checks (até os mais bizarros) em qualquer meia-dúzia de linhas. É o paranóico, que pode se dar bem no tipo de código importante já citado, mas que nunca vai conseguir entregar um projeto trivial se for baseado em libs escritas pelos outros e cujo código nunca deu uma "lida rápida". Aliás, essa mesma atitude "deu uma lida" demonstra que o pessimista pode ser ainda mais arrogante, pois acredita conseguir capturar toda a complexidade do sistema apenas lendo seu código en passant.
De uma forma ou de outra, como toda atividade humana, escrever código nos define não apenas como bons programadores, mas como bons humanos, com seu conjunto de crenças e valores. O que, de certa forma, é um bom sinal, pois melhorando como seres humanos, melhoramos como programadores. E vice-versa.
Para devanear mais
- Para quê filosofia? – Marilena Chauí
Depuração de emergência: receita de bolo
0Continuando o papo sobre o que fazer para analisar rapidamente um crash no servidor com o pacote WinDbg, na maioria das vezes a exceção lançada pelo processo está diretamente relacionada com um acesso indevido à memória, o que tem diversas vantagens sobre problemas mais complexos:
- Possui localização precisa de onde ocorreu a violação (inclusive com nome do arquivo-fonte e linha).
- Não corrompe a pilha (ou, se corrompe, não chega a afetá-la a ponto da thread ficar irreconhecível).
- A thread que contém a janela de crash é a culpada imediata (basta olha a pilha!).
- Ajeitar o path dos símbolos.
- Recarregar o PDB do executável suspeito.
- Mostrar a pilha de todas as threads (até descobrir a culpada).
0:001> .symfix 0:001> .reload /f CrashOnServer.exe *** WARNING: Unable to verify checksum for C:\Users\wanderley.caloni\Documents\Projetos\Caloni\Posts\Debug\CrashOnServer.exe 0:001> kv Child-SP RetAddr : Args to Child : Call Site 0030f918 77679198 : 00000000`00000000 `00000000 : ntdll!DbgBreakPoint 0030f920 775e244d : 00000000`00000000 `00000000 : ntdll!DbgUiRemoteBreakin+0x38 0030f950 00000000 : 00000000`00000000 `00000000 : ntdll!RtlUserThreadStart+0x25 0:001> ~* kv 0 Id: 1dc.978 Suspend: 1 Teb: 00000000`7efdb000 Unfrozen Child-SP RetAddr : Args to Child : Call Site 0008ea48 751f282c : 00000000`77770190 00000000`001dfb50 : wow64cpu!CpupSyscallStub+0x9 0008ea50 7526d07e : 00000000`00000000 00000000`775b3501 : wow64cpu!WaitForMultipleObjects32+0x32 0008eb10 7526c549 : 00000000`00000000 00000000`7ffe0030 : wow64!RunCpuSimulation+0xa 0008eb60 775cae27 : 00000000`003b3710 00000000`7efdf000 : wow64!Wow64LdrpInitialize+0x429 0008f0b0 775c72f8 : 00000000`00000000 00000000`00000000 : ntdll!LdrpInitializeProcess+0x1780 0008f5b0 775b2ace : 00000000`0008f670 00000000`00000000 : ntdll! ?? ::FNODOBFM::`string'+0x2af20 0008f620 00000000 : 00000000`00000000 00000000`00000000 : ntdll!LdrInitializeThunk+0xe
Ops! Estamos rodando um processo 32 dentro de um SO 64 (Windows 7, por exemplo). Isso pode acontecer. Seguimos com o workaround .load wow64exts e .effmach x86:
0:001> .load wow64exts 0:001> .effmach x86 Effective machine: x86 compatible (x86) 0:001:x86> ~* kv 0 Id: 1dc.978 Suspend: 1 Teb: 7efdb000 Unfrozen ChildEBP RetAddr Args to Child 001df24c 761a0bdd 00000002 001df29c 00000001 ntdll_77760000!NtWaitForMultipleObjects+0x15 (FPO: [5,0,0]) 001df2e8 7727162d 001df29c 001df310 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100 (FPO: [Non-Fpo]) 001df330 77271921 00000002 7efde000 00000000 KERNEL32!WaitForMultipleObjectsExImplementation+0xe0 (FPO: [Non-Fpo]) 001df34c 77299b2d 00000002 001df380 00000000 KERNEL32!WaitForMultipleObjects+0x18 (FPO: [Non-Fpo]) 001df3b8 77299bca 001df498 00000001 00000001 KERNEL32!WerpReportFaultInternal+0x186 (FPO: [Non-Fpo]) 001df3cc 772998f8 001df498 00000001 001df468 KERNEL32!WerpReportFault+0x70 (FPO: [Non-Fpo]) 001df3dc 77299875 001df498 00000001 38239b1e KERNEL32!BasepReportFault+0x20 (FPO: [Non-Fpo]) 001df468 777d0df7 00000000 777d0cd4 00000000 KERNEL32!UnhandledExceptionFilter+0x1af (FPO: [Non-Fpo]) 001df470 777d0cd4 00000000 001dfb34 7778c550 ntdll_77760000!__RtlUserThreadStart+0x62 (FPO: [SEH]) 001df484 777d0b71 00000000 00000000 00000000 ntdll_77760000!_EH4_CallFilterFunc+0x12 (FPO: [Uses EBP] [0,0,4]) 001df4ac 777a6ac9 fffffffe 001dfb24 001df5e8 ntdll_77760000!_except_handler4+0x8e (FPO: [Non-Fpo]) 001df4d0 777a6a9b 001df598 001dfb24 001df5e8 ntdll_77760000!ExecuteHandler2+0x26 001df580 7777010f 001df598 001df5e8 001df598 ntdll_77760000!ExecuteHandler+0x24 001df584 001df598 001df5e8 001df598 001df5e8 ntdll_77760000!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) WARNING: Frame IP not in any known module. Following frames may be wrong. 001df9ac 010d141e 00000000 00000000 00000000 0x1df598 001dfa90 010d19af 00000001 00321410 00321c70 CrashOnServer!main+0x2e (FPO: [Non-Fpo]) (CONV: cdecl) [c:\users\wanderley.caloni\documents\projetos\caloni\posts\crashonserver\crashonserver.cpp @ 13] 001dfae0 010d17df 001dfaf4 77273677 7efde000 CrashOnServer!__tmainCRTStartup+0x1bf (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 555] 001dfae8 77273677 7efde000 001dfb34 77799f02 CrashOnServer!mainCRTStartup+0xf (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 371] 001dfaf4 77799f02 7efde000 6b3e1b48 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo]) 001dfb34 77799ed5 010d1109 7efde000 00000000 ntdll_77760000!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo]) 001dfb4c 00000000 010d1109 7efde000 00000000 ntdll_77760000!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo]) # 1 Id: 1dc.1b0 Suspend: 1 Teb: 7efd8000 Unfrozen ChildEBP RetAddr Args to Child 0056ffe8 00000000 00000000 00000000 00000000 ntdll_77760000!RtlUserThreadStart (FPO: [0,2,0])
Nosso depurador favorito acusa uma pilha que contém a função WerpReportFault (Web Error Report, mas qualquer outra função com Exception no meio seria uma candidata). E, nessa mesma thread, a última linha nossa conhecida está no arquivo crashonserver.cpp:13. Isso nos revela o seguinte:
E essa situação, caro leitor, é 10% de tudo o que você precisa saber sobre WinDbg para resolver, mas que já resolve 90% dos casos. Belo custo-benefício, não?
Coders at Work: Brendan Eich, threads e depuração
0"Proofs are hard. Most people are lazy. Larry Wall is right. Laziness should be a virtue. So that’s why I prefer automation. Proofs are something that academics love and most programmers hate." - Brendan Eich
Esse pequeno trecho da entrevista de Brendan Eich, de Coders at Work, revela parte das frustações que os programadores de linha de frente sofrem com os ambientes de depuração, muitas vezes aquém dos desafios atuais. Sinceramente, não sinto isso em meu dia-a-dia, e acho o Visual Studio um excelente depurador com interface (mas que perde feio para o WinDbg em casos mais hardcore). Porém, fica a percepção curiosa do criador do JavaScript.
Sobre SGI
"Diagnosing it was hard because it was timing-sensitive. It had to do with these machines being abused by terminal concentrators. People were hooking up a bunch of PTYs to real terminals. Students in a lab or a bunch of people in a mining software company in Brisbane, Australia in this sort of ’70s sea of cubes with a glass wall at ’70s sea of cubes with a glass wall at the end, behind which was a bunch of machines including the SGI two-processor machine. That was hard and I’m glad we found it. These bugs generally don’t linger for years but they are really hard to find. And you have to sort of suspend your life and think about them all the time and dream about them and so on. You end up doing very basic stuff, though. It’s like a lot of other bugs. You end up bisecting—you know “wolf fence.” You try to figure out by monitoring execution and the state of memory and try to bound the extent of the bug and control flow and data that can be addressed. If it’s a wild pointer store then you’re kinda screwed and you have to really start looking at harder-to-use tools, which have only come to the fore recently, thanks to those gigahertz processors, like Valgrind and Purify."
Ferramentas de Depuração Avançadas
"Instrumenting and having a checked model of the entire memory hierarchy is big. Robert O’Callahan, our big brain in New Zealand, did his own debugger based on the Valgrind framework, which efficiently logs every instruction so he can re-create the entire program state at any point. It’s not just a time-traveling debugger. It’s a full database so you see a data structure and there’s a field with a scrogged value and you can say, “Who wrote to that last?” and you get the full stack. You can reason from effects back to causes. Which is the whole game in debugging. So it’s very slow. It’s like a hundred times slower than real time, but there’s hope."
"Or you can use one of these faster recording VMs—they checkpoint only at system call and I/O boundaries. They can re-create corrupt program states at any boundary but to go in between those is harder. But if you use that you can probably close in quickly at near real time and then once you get to that stage you can transfer it into Rob’s Chronomancer and run it much slower and get all the program states and find the bug."
Depuradores da Indústria
"Debugging technology has been sadly underresearched. That’s another example where there’s a big gulf between industry and academia: the academics are doing proofs, sometimes by hand, more and more mechanized thanks to the POPLmark challenge and things like that. But in the real world we’re all in debuggers and they’re pieces of shit from the ’70s like GDB."
"In the real world one big split is between people who use symbolic debuggers and people who use print statements." - Peter Seibel
"Yeah. So I use GDB, and I’m glad GDB, at least on the Mac, has a watch-point facility that mostly works. So I can watch an address and I can catch it changing from good bits to bad bits. That’s pretty helpful. Otherwise I’m using printfs to bisect. Once I get close enough usually I can just try things inside GDB or use some amount of command scripting. But it’s incredibly weak. The scripting language itself is weak. I think Van Jacobson added loops and I don’t even know if those made it into the real GDB, past the FSF hall monitors."
Multithreading
"But there’s so much more debugging can do for you and these attempts, like Chronomancer and Replay, are good. They certainly changed the game for me recently. But I don’t know about multithreading. There’s The multithreaded stuff, frankly, scares me because before I was married and had kids it took a lot of my life. And not everybody was ready to think about concurrency and all the possible combinations of orders that are out there for even small scenarios. Once you combine code with other people’s code it just gets out of control. You can’t possibly model the state space in your head. Most people aren’t up to it. I could be like one of these chestthumpers on Slashdot—when I blogged about “Threads suck” someone was saying, “Oh he doesn’t know anything. He’s not a real man.” Come on, you idiot. I got a trip to New Zealand and Australia. I got some perks. But it was definitely painful and it takes too long. As Oscar Wilde said of socialism, “It takes too many evenings.”
Coders at Work: Jamie Zawinski e Douglas Crockford
0"Personally I have never believed that it is possible to be a good coder without being a good programmer nor a good programmer without being a good designer, communicator, and thinker." - Jamie Zawinski
Como Joel e Atwood disseram, a leitura de Coders At Work é tão útil quanto ler o código dos outros, só que em um estilo mais condensado, que se aproveita das décadas de experiência dessa gente para aprimorarmos nossos processos de desenvolvimento e, muitas vezes, a forma de pensarmos sobre software.
No meu estilo de leitura circular, adaptada do brilhante (maluco?) método de Dmitry Vostokov, as coisas vão mais devagar, e estou apenas no início do livro, tendo passado por Jamie Zawinski (desenvolvedor da equipe original do Netscape), Brad Fitzpatrick (criador do Live Journal) e terminado recentemente Douglas Crockford. O artigo de Joel sobre Zawinski demonstra seu apreço pelo codificador pensante, ou aquele que faz as coisas acontecerem e não fica preso eternamente na armadilha da arquitetura. Eu acredito que as seguintes passagens do livro demonstram seu pensamento melhor do que se eu fosse tentar traduzi-los.
Jamie Zawinski
"Personally I have never believed that it is possible to be a good coder without being a good programmer nor a good programmer without being a good designer, communicator, and thinker. (...) Start converting it into the bad one until it stops working. That’s primary tool of reverse engineering. (...) Your competitor’s six-month 1.0 has crap code and they’re going to have to rewrite it in two years but, guess what: they can rewrite it because you don’t have a job anymore. (...) The design process is definitely an ongoing thing; you never know what the design is until the program is done. So I prefer to get my feet wet as early as possible; get something on the screen so I can look at it sideways.(...) I’ve noticed that one thing that separates good programmers from bad programmers is that good programmers are more facile at jumping between layers of abstraction—they can keep the layers distinct while making changes and choose the right layer to make changes in. (...) I think one of the most important things, for me anyway, when building something from the ground up like that is, as quickly as possible, getting the program to a state that you, the programmer, can use it. Even a little bit. Because that tells you where to go next in a really visceral way. (...) I don’t want to be a mathematician but I’m not going to criticize someone who is a mathematician. It’s weird that people often confuse those two pursuits. People who are into very theoretical computer science are thought of in this same way as people who are shipping desktop applications. And they don’t really have a lot to do with each other. (...) Then there was another book that everybody thought was the greatest thing ever in that same period—Design Patterns—which I just thought was crap. It was just like, programming via cut and paste. Rather than thinking through your task you looked through the recipe book and found something that maybe, kinda, sorta felt like it, and then just aped it. That’s not programming; that’s a coloring book. (...)"
Douglas Crockford
De certa forma, o mesmo pragmatismo pode ser observado em Douglas Crockford, que utiliza o método de leitura de código tanto na entrevista por candidatos ("traga-me o código que tem orgulho de ter escrito e explique-o pra mim") quanto no dia-a-dia do projeto, para que todos entendam e aproveitem a evolução do projeto como um todo, além de constituir, na minha visão, uma das melhores dicas de auto-management que uma equipe de programadores poderia ter.
"One of the things I’ve been pushing is code reading. I think that is the most useful thing that a community of programmers can do for each other—spend time on a regular basis reading each other’s code. There’s a tendency in project management just to let the programmers go off independently and then we have the big merge and then we have the big merge and if it builds then we ship it and we’re done and we forget about it. One of the consequences of that is that if you have weak or confused programmers you’re not aware of their actual situation until much too late. And so the risks to the project, that you’re that you’re going to have to build with stuff that’s bad and the delays that that causes, that’s unacceptable. The other thing is that you may have brilliant programmers on the project who are not adequately mentoring the other people on the team. Code reading solves both of those problems."
Seibel: Can you talk a bit about how you conduct a code reading?
"At each meeting, someone’s responsible for reading their code, and they’ll walk us through everything, and the rest of us will observe. It’s a really good chance for the rest of the team to understand how their stuff is going to have to fit with that stuff."
"We get everybody around the table; everybody gets a stack of paper. We also blow it up on the screen. And we all read through it together. And we’re all commenting on the code as we go along. People say, “I don’t understand this comment,” or, “This comment doesn’t seem to describe the code.” That kind of stuff can be so valuable because as a programmer you stop reading your own comments and you’re not aware that you’re misdirecting the reader. Having the people you work with helping to keep your code clean is a huge service—you find defects that you never would’ve found on your own."
"I think an hour of code reading is worth two weeks of QA. It’s just a really effective way of removing errors. If you have someone who is strong reading, then the novices around them are going to learn a lot that they wouldn’t be learning otherwise, and if you have a novice reading, he’s going to get a lot of really good advice."
Seibel: So if you don’t clean up every seventh cycle you may be faced with the choice of whether or not to do a big rewrite. How do you know when, if ever, it’s time for a big rewrite?
"Generally the team knows when it’s time. Management finds out a lot later. The team is getting beat up pretty regularly, making too many pretty regularly, making too many bugs; the code’s too big, it’s too slow; we’re falling behind. They know why. It’s not because they became stupider or lazier. It’s because the code base is no longer serving the purpose that it needs to."
Oitavo Encontro do Grupo C/C++ Brasil
4Esse final de semana ocorreu mais um dos inesquecíveis encontros dos programadores C++, dessa vez ao estilo "velhos tempos", com direito a entrada gratuita e um grupo mais coeso, quase já íntimo, e mais animado.
Não só pela informalidade da ocasião, tivemos palestras interessantes que dessa vez conseguiram abranger temas bem diversificados e fugir um pouco do feijão-com-arroz de desenvolvimento Windows que muitos encontros anteriores haviam se transformado.
Logo no começo do dia tivemos Vinicius Jarina apresentando-nos a possibilidade de usar Lua como um script rápido de embutir em código C++ sem muitos percalços.
Já o mesmo não se pode dizer dos detalhes quase sempre matadores do desenvolvimento mobile, como vimos com Gianni Rossi.
Como não poderia deixar de faltar, Rodrigo Strauss nos apresenta, agora formalizadas, as mudanças do novo padrão C++ que acaba de ser aprovado.
Em seguida, Rodrigo Kumpera mais uma vez apresenta um debate que parece recorrente no grupo, discorrendo sobre modelos de memória e a dificuldade de sicronizar diversos recursos em múltiplas CPUs.
Para finalizar, uma palestra-bônus de um rapaz que infelizmente não consegui lembrar o nome (nosso saite, nesse momento, está fora do ar), mas Marcelo Zimbres Silva (thanks to @AlbertoFabiano, que postou o linque para sua palestra), que fez uma breve apresentação do ROOT, um framework de análise de dados usados pelos físicos e que poderia ser utilizado facilmente pela comunidade C++. Fiquei particularmente interessado no exemplo de análise do histórico Bovespa e o pontencial da biblioteca. Também foi muito bem-vinda a presença de um físico em um grupo muitas vezes acusado de xiita.
E por falar em xiita, vejam só: uma série inédita de palestras sem nenhuma estar falando especificadamente sobre Windows. O que acham?
Como bônus, dessa vez tivemos nossas palestras filmadas. Apenas peço paciência para a fase de edição, onde devo utilizar os slides dos palestrantes para tornar o visual mais didátivo do que uma câmera se movendo freneticamente de um lado para o outro. Algumas partes podem ter ficado de fora por alguns problemas técnicos (sou marinheiro de primeira viagem), mas o mais importante, com certeza, está gravado.
Depuração de emergência
0O programa está rodando no servidor do cliente, que é acessível por sessão remota do Windows, mas de repente ele capota. Existem aí duas possibilidades fora o debug remoto (que, nesse caso, não é possível):
- Analisar um dump gerado.
- Depurar localmente o problema.
Analisar um dump gerado
Para a primeira opção, basta abrir o Gerenciador de Tarefas, localizar o processo e gerar o dump através do menu de contexto.
Com o dump e o Windbg em mãos, basta analisá-lo. Porém, se o seu processo é 32 bits e o servidor é 64 bits (geralmente é), o dump gerado será de 64 bits, EMBORA seja de um process 32. Ou seja, ao abri-lo, o sistema vai mostrar as threads de manipulação do SO para sistemas 32 (todos com o nosso amigo wow64cpu).
Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64 Copyright (c) Microsoft Corporation. All rights reserved.
Loading Dump File [C:\Tests\CrashOnServer.DMP] User Mini Dump File with Full Memory: Only application data is available
Executable search path is:
Windows 7 Version 7600 MP (2 procs) Free x64
Product: WinNt, suite: SingleUserTS
Machine Name:
Debug session time: Tue Jul 26 09:26:23.000 2011 (UTC - 3:00)
System Uptime: 0 days 0:35:47.425
Process Uptime: 0 days 0:00:42.000
...........WARNING: MSVCR100D overlaps MSVCP100D
*** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll - *** ERROR: Symbol file could not be found. Defaulted to export symbols for wow64cpu.dll - wow64cpu!TurboDispatchJumpAddressEnd+0x690: 00000000`745d2dd9 c3 ret 0:000> kv Child-SP RetAddr : Args to Child : Call Site 00000000`001ce6c8 00000000`745d282c : : wow64cpu!TurboDispatchJumpAddressEnd+0x690 *** ERROR: Symbol file could not be found. Defaulted to export symbols for wow64.dll - 00000000`001ce6d0 00000000`7464d07e : : wow64cpu!TurboDispatchJumpAddressEnd+0xe3 00000000`001ce790 00000000`7464c549 : : wow64!Wow64SystemServiceEx+0x1ce 00000000`001ce7e0 00000000`76deae27 : : wow64!Wow64LdrpInitialize+0x429 00000000`001ced30 00000000`76de72f8 : : ntdll!LdrGetKnownDllSectionHandle+0x1a7 00000000`001cf230 00000000`76dd2ace : : ntdll!RtlInitCodePageTable+0xe8 00000000`001cf2a0 00000000`00000000 : : ntdll!LdrInitializeThunk+0xe
Para entrar dentro do Inception, é necessário usar a extensão wow64exts e usar o comando ".effmach x86".
0:000> .load wow64exts 0:000> .effmach x86 Effective machine: x86 compatible (x86) 0:000:x86> kv ChildEBP RetAddr Args to Child .. .. .. 0035ec98 0035ecac 0035ecfc 0035ecac 0035ecfc ntdll_76f80000!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) *** WARNING: Unable to verify checksum for CrashOnServer.exe WARNING: Frame IP not in any known module. Following frames may be wrong. 0035f0bc 01181ca9 0035f198 0035f19c 00000000 0x35ecac 0035f190 01181b7d 009d80a0 5fb4d717 00000000 CrashOnServer!Log::LogError+0x29 0035fb08 01186f1f 00000001 009d1410 009d1c68 CrashOnServer!main+0x12d 0035fb58 01186d4f 0035fb6c 76543677 7efde000 CrashOnServer!__tmainCRTStartup+0x1bf 0035fb60 76543677 7efde000 0035fbac 76fb9f02 CrashOnServer!mainCRTStartup+0xf 0035fb6c 76fb9f02 7efde000 771dc110 00000000 kernel32!BaseThreadInitThunk+0xe 0035fbac 76fb9ed5 01181316 7efde000 00000000 ntdll_76f80000!__RtlUserThreadStart+0x70 0035fbc4 00000000 01181316 7efde000 00000000 ntdll_76f80000!_RtlUserThreadStart+0x1b
Após esse último passo, siga para o último passo desse tutorial. Ou escolha a segunda opção:
Depurar localmente o problema
Para depurar localmente, supondo que seja um executável simples, você precisa dos seguintes itens:
- Pasta do WinDbg copiado (a Debugging Tools instalada pelo SDK, ou sua pastinha particular guardada no PenDrive).
- Símbolos dos binários envolvidos (em sincronia com os binários que iremos analisar).
- Fontes da compilação dos binários (a versão exata seria ideal; grave o revno do controle de fonte pra facilitar).
Os fontes, no caso de uma conexão por Terminal Server, podem ser disponibilizados através do mapeamento de drives entre as máquinas. Os símbolos, no entanto, por serem usados extensivamente pelo WinDbg, é recomendável que estejam locais na máquina depurada, pois do contrário você terá que tomar uma quantidade excessiva de cafés para executar meia-dúzia de instruções.
Supondo que temos tudo isso, só precisamos executar alguns passos básicos para o setup:
1. Abrir o WinDbg e escolher File, Open Executable. Escolha o executável e pare por aí.
2. Na tela de comando do WinDbg (View, Command, ou Alt + 1) execute os comandos abaixo:
.symfix .sympath+ .reload .srcpath .reload /f CrashOnServer.exe
3. Ao executar lm, o módulo cujo símbolo foi carregado deve conter o nome do pdb logo à frente.
0:000> .symfix c:\tools\symbols 0:000> .sympath+ C:\Projetos\Caloni\Posts\Debug Symbol search path is: srv*;C:\Projetos\Caloni\Posts\Debug Expanded Symbol search path is: SRV*c:\tools\symbols*http://msdl.microsoft.com/download/symbols;c:\projetos\caloni\posts\debug 0:000> .reload Reloading current modules ...... 0:000> .srcpath C:\Projetos\Caloni\Posts Source search path is: C:\Projetos\Caloni\Posts 0:000> .reload /f CrashOnServer.exe *** WARNING: Unable to verify checksum for CrashOnServer.exe 0:000> lm start end module name 00000000`01170000 00000000`01193000 CrashOnServer C (private pdb symbols) C:\Projetos\Caloni\Posts\Debug\CrashOnServer.pdb 00000000`745d0000 00000000`745d8000 wow64cpu (deferred) 00000000`745e0000 00000000`7463c000 wow64win (deferred) 00000000`74640000 00000000`7467f000 wow64 (deferred) 00000000`76da0000 00000000`76f4c000 ntdll (pdb symbols) c:\tools\symbols\ntdll.pdb\\ntdll.pdb 00000000`76f80000 00000000`77100000 ntdll32 (deferred)
4. Feito isso, está tudo OK. Podemos colocar breakpoints, monitorar variáveis, verificar stacks, etc.
Por último, execute o seguinte comando na tela de comandos do WinDbg:
.hh
E boa sorte =)
Cuidado com variáveis temporárias
2Um dos problemas que a linguagem C++ possui para seus iniciantes é o de não deixar muito explícito partes do seu comportamento, principalmente as partes que lidam com ponteiros/referências e o jogo da vida dos objetos. Às vezes a coisa fica de tal como complexa que fica até difícil explicar o porquê das coisas.
Por exemplo, vejamos o singelo caso de alguém que precisa formatar uma saída de erro e para isso escolheu um stringstream:
#include <sstream> #include <exception> #include <iostream> using namespace std; void LogError(const char* msg) { cerr << "** " << msg << endl; } void func() { //doSomething(); throw exception("sbrubles exception"); } int main() { try { func(); } catch(exception& e) { stringstream ss; ss << "Error calling func: " << e.what() << endl; const char* errorMessage = ss.str().c_str(); LogError(errorMessage); } }
Quando chamamos func, ele lança uma exceção que é capturada no main que, por sua vez, formata uma stream e obtém sua string (através do método str) e através dessa string obtém o ponteiro da string em C puro (através do método c_str). Porém, a mensagem resultante na saída-padrão de erro não era o esperado:
Depurando diretamente, vemos que a stream, de fato, contém o que esperávamos. O único elemento errante é justamente o ponteiro obtido através da chamada dupla de métodos.
O porquê isso ocorre só fica óbvio quando vemos a ajuda (ou a assinatura) da função str da classe stringstream:
string str ( ) const; void str ( const string & s ); Get/set the associated string objectThe first version returns a copy of the string object currently associated with the string stream buffer.
Ora, a função str retorna uma cópia do objeto string usado internamento pelo buffer de nossa string stream. Duas coisas ocorrem em qualquer cópia de um objeto retornada por uma função:
- A cópia do objeto original e seu desacoplamento (óbvio).
- A construção de um objeto baseado no original e que, após o fim da expressão onde foi chamado o método, é destruído.
Uma vez que a chamada a str termina, é entregue uma instância de uma string que contém a string original que está sendo usada pela string stream para a expressão da chamada, que geralmente vem seguida de uma cópia:
// // 1. str retorna uma cópia; // 2. atribuição copia retorno para buf. // string buf = ss.str();
A variável buf no exemplo acima será, portanto, a terceira string usada aqui até então. Ao final da expressão, a string intermediária retornada por str é automaticamente destruída, por se trata de uma cópia temporária para obedecer a sintaxe de retorno da função.
Agora, o que acontece se, na cópia temporária, é feita uma operação para obter seu ponteiro interno usado para armazenar sua string estilo C?
Obviamente ele fica inválido após o fim da expressão!
Vamos ver em câmera lenta:
Nada como assembly fresquinho para refrescar os conceitos de C++ por baixo dos panos.
Update
Após uma enxurrada de programadores gerenciáveis perguntarem qual seria, então, a solução ideal, segue o snipet mais explicitado:
// 1. Copie a string retornada para uma variável não-temporária string buf = message.str(); // 2. Use essa string dentro de seu escopo válido (até o final do catch, no exemplo do artigo). const char* text = buf.c_str();
Update 2
Outro leitor sugeriu fazer toda a chamada em uma única instrução, economizando em expressividade e ainda evitando a destruição da variável temporária criada ao chamar str.
// 1. Matar três coelhos com uma instrução só. LogError(ss.str().c_str());
Particularmente, gosto de instruções simples que me permitam ver claramente o que está acontecendo de forma simples pelo depurador (até porque sei que o compilador irá otimizar tudo no final em versão Release, ainda mais se estiver quebrado em instruções simples). Porém, toda solução que evita o uso da variável temporária após a execução do método str é válida.
E foi-se o TDC
2Dessa vez, talvez pelo dia de semana, havia poucos participantes. Isso, contudo, não evitou que o conteúdo e a qualidade das palestras fosse, como sempre, de alto nível.
Infelizmente, só cheguei a partir da palestra de Antonio Ribeiro sobre uma Simulação Distribuída focando no trânsito de São Paulo. Há tempos esse é um tema debatido e ainda acredito que a tecnologia ainda vai resolver isso da melhor maneira possível: cada um em sua casa.
Depois do agradável almoço, onde tive a oportunidade de rever DQSoft e conversar sobre leituras de ficção-não-necessariamente-científica, participamos de um pequeno review sobre escrita de código seguro. Ao menos não tivemos que ouvir novamente algum representante da Microsoft falando sobre a famigerada lib que refazia as funções do C para a versão com copyright_s.
Então chega a vez da palestra mais bagunçada: a minha. Juro que perdi totalmente o fio da meada no início, e quanto mais me esforçava para lembrar o que devia ser dito, mais esquecia. Então resolvi partir direto para o hands-on, onde consegui, imagino eu, com um ritmo adequado, demonstrar todo o cenário da análise do Dicionário Houaiss que tinha feito anos atrás. E pelo visto, muitas pessoas gostaram, pois fui abordado por três ou quatro participantes muito simpáticos e entusiasmados com o tema. Talvez seja hora de voltar para esse submundo mais um pouquinho, fazer coisas mais hardcore pra variar...
Depois do coffee-açucarado-break, tivemos a palestra mais interessante do dia: desenvolvimento de microkernels, apresentado por Rodrigo Almeida, professor de Eletrônica e Programação de Embarcados da Unifei. Não só o tema me interessou ao máximo, como a didática da apresentação foi impecável, abordando tanto os pontos for-dummies quanto um gostinho do que seria fazer por nós mesmos um controle intermediário entre a placa e os programas.
Ao final, nosso mestre-de-cerimônias habitual Alberto Techberto Fabiano fez a abertura de um painel tentando mesclar problemas + experiência + vontade de aprender. Dos assuntos que me lembro, chamou a atenção o fato dos profissionais da linguagem (C/C++) estarem escasseando cada vez mais e também o fato de nosso Grupo C/C++ parecer praticamente invisível aos olhos do Google, pois parece que quase ninguém consegue encontrar o dito cujo.
Infelizmente, alguns compromissos inadiáveis me fizeram ter que sair pela esquerda e esquecer o Lado B da questão. Felizmente, esse não é o nosso último encontro, e espero participar de futuras oportunidades de me integrar à nossa agora, como diz meu amigo Pikachu, comunidade maçônica.
TDC 2011
2
Estarei no TDC esse ano. A trilha é de C++, mas farei uma palestra sobre engenharia reversa. Para ser mais específico, falaremos sobre a análise do Dicionário Houaiss, cujo projeto venho mantendo nos últimos anos. Acho legal termos um espaço para falarmos de quando fazemos realmente a coisa, em vez de ficar sempre na teoria de como seria ou o que um programador precisa saber para começar a analisar um binário.
Se você gosta do tema e possui dúvidas a respeito, ou gostaria de mais detalhes sobre outros projetos, não deixe de comparecer. Antes e depois da palestra estarei disponível para conversarmos. O mais interessante de termos uma trilha em C++ é reunir pessoas envolvidas em torno da linguagem, não importando muito a área. Somos um grupo pequeno, e é importante que tenhamos um contato mais próximo de vez em quando.
C/C++ Caso de Uso: Engenharia Reversa com Windbg
Esta palestra é sobre desmontar e montar novamente. Iremos descobrir como as entradas do dicionário Houaiss eletrônico estão gravadas em um primeiro momento, para depois remontarmos essa informação de maneira que ela possa ser usada em um outro dicionário.
Ferramentas que serão usadas: Windows, WinDbg, Visual Studio (qualquer versão).
Conhecimentos necessários: C/C++, Assembly 8086, Win32 API.
Passo-a-passo da palestra:
- Sobre Pirataria. Como identificar brechas na licença para que você possa usufruir do seu trabalho de refatoração binária.
- Análise. Desmontando o dicionário Houaiss e desvendando seu funcionamento interno.
- Programação. Remontando a estrutura identificada pela Engenharia Reversa em um formato aberto.
- Sobre Fair Use. Explicando como abrir portas para o desenvolvimento de soluções baseada em nossa análise.
Outros conteúdos
Assuntos "similares" também nos esperam com Sergio Prado e programação segura e Rodrigo Almeida, abordando o desenvolvimento de microkernel. Além disso, também teremos Bruno Koga e Guilherme Andrade destrinchando o compilador LLVM para Objective-C, enquanto Antonio Ribeiro Alves Júnior explica sobre t100, um Middleware para Simulação Distribuída.
Nos vemos lá.
Vídeos do boostcon
0Para se aquecer enquanto a trilha de C++ do TDC não acontece, o Grupo C/C++ passou um linque para os vídeos do boostcon, que, pelo que entendi, é um evento onde as pessoas falam como boost é legal e por que você deveria usar boost em seu projeto C++. Pelos títulos (ainda não os assisti) existem assuntos dos mais diversos, e talvez seja uma forma de catequizar a equipe onde você trabalha e que usa coisas bem piores do que boost para programar.
Trilha de C++ organizada pelo grupo C/C++ Brasil
0Sim, nós temos C++! Apesar de ser uma linguagem dita ultrapassada pelos brazucas, o TDC desse ano terá uma trilha de C++ disponível para todo tipo de público que aprecia ou precisa dos poderes de C++ em seu dia-a-dia.
O evento será organizado (do lado C++) pelos nossos habituais organizadores dos eventos do grupo Alberto Fabiano e Rodrigo Strauss, o que é um ótimo sinal, pois, geralmente, ter o nome C++ associado a alguém que nunca compilou um código na vida não é algo que atraia um conteúdo de qualidade.
A trilha de C++ será Quarta, dia 6 de Julho.
Cronogramas baseados em fatos reais
0Já falei sobre cronogramas por aqui e tudo que disse ainda se aplica. Contudo, comentei brevemente sobre entender seu próprio ritmo, que, instintivamente, sabia ser verdade. Depois que li um pouco mais sobre técnicas XP/Scrum (que nada mais são do que formalizações do que os programadores Agile perceberam no decorrer dos seus projetos) achei uma fórmula simples para transformar o tempo estimado em tempo realista.
Vejamos o texto original (auto-plágio):
Regra # 5: não inclua o ócio no cronograma
Seja honesto consigo mesmo e com seu chefe: você realmente trabalha 8 horas por dia? É lógico que não! E não é nenhuma vergonha admitir isso. Todos nós temos emails para ler e responder, reuniões para presenciar e bloques importantes para acompanhar. Portanto, ignore essa conversa fiada de 8 horas e admita: não se deve contar os dias como se eles tivessem 8 horas.
Qual o valor de um dia, então? Cada um sabe o valor que deve ser decrementado desse valor simbólico de 8 horas, mas esse valor sempre será menor. Não se iluda!
Exatamente. Não se iluda! Isso tem seu reflexo na metodologia Agile. Basicamente quer dizer que você precisa aplicar índices que reflitam a realidade do seu próprio ritmo. Além disso:
Regra # 4: uma tarefa estimada é uma tarefa completada
É muito simples ilustrar e entender esse conceito com código. Voltando ao caso da função, digamos que você consiga terminar a bendita função em exata uma hora. Você é bom, hein?
Porém, essa função ainda 1) não foi comentada, 2) não foi testada e 3) não foi testada em release.
Logo, essa é uma tarefa em que você termina o mais importante em uma hora... mas não termina tudo. Deve-se sempre considerar a tarefa por completo, pois no final de quinze tarefas vai faltar comentar e testar tudo isso, o que aumentará consideravelmente a imprevisiblidade no seu cronograma.
O que, novamente traduzindo, é mais um indicador a ser aplicado sobre seus números.
E o que são seus números?
Basicamente, o que a própria metodologia ensina: meça o esforço necessário para fazer código (mas é pra isso mesmo que somos contratados, não?) como se pudéssemos programar por todo esse tempo sem parar por um momento sequer (mesmo que sejam dezenas de horas). Lógico, aprenda a dividir o esforço em pequenos passos, mas estime o tempo considerando APENAS o esforço de fazer o código.
Pronto? Agora é hora de aplicar os indicadores.
1. Foco
Mais uma vez, admita: programadores raramente conseguem manter o foco por muito tempo. São pessoas ao redor te desviando a atenção, o tweet que salta de uma janela ou até mesmo as necessidades orgânicas que todo ser humano tem. São elementos, enfim, que, em conjunto, nunca te possibilitarão ter 100% do foco durante todo o trabalho.
Portanto, criemos um indicador: foco. Ele é um valor entre 0 e 1 e estima a porcentagem de foco que você consegue obter, em média, durante o dia. Por exemplo: eu consigo me focar 70% do dia inteiro em apenas codificar e o resto é perdido em reuniões e e-mails. OK. Esse número é, então, 0,7. Aplique sobre seu total de horas e terá o tempo real para codificar a tarefa:
Levarei 35 horas para codificar todo o processo de autenticação por reconhecimento de face, trabalhando sem parar.
35 / 0.7 = 50
No entanto, como consigo apenas 70% de foco em média, sei que essa tarefa irá levar 50 horas na verdade.
2. Finalização
Já temos o tempo para o código ficar pronto, mas... é apenas código. Temos que reescalonar o tempo do projeto inserindo testes, retrabalho, comentários e documentação. Tudo ainda nas mãos do programador, que está ainda "aquecido" e que pode resolver retrabalhos em questões de segundos, se ninguém mais passar nada pra ele.
Mesmo assim,é um indicador importante. Sem ele, a qualidade do serviço final fica muito restrita e sensível a testes de caixa preta, gerando a revolta da equipe de testes.
Vamos supor, então, que, historicamente, essa fase tem sido, digamos, 20% do período de codificação (um chute bem otimista). Agora é fácil dizer o tempo final:
Levarei 50 horas para codificar tudo considerando o quesito foco.
50 * 1,2 = 60
Porém, para poder entregar, preciso dedicar cerca de 20% aos testes, retrabalho e uma documentação mínima. Nesse caso, 60 horas é o prazo de entrega.
Note que, se quiser, pode fazer a análise contrária também, tanto de um quanto de outro. Assim, se geralmente você gasta 20% a mais na codificação do que estima, então use o fator foco como 1.2 e multiplique em vez de dividir. Da mesma forma, se codificar é 60% de todo o trabalho, o fator finalização é 0.6 e deve-se dividir as horas pós-indicador de foco.
Conclusão
O número de horas ficou muito maior que o esperado? Não me admira que os projetos geralmente atrasem, então. Por pior que pareça o cálculo final, ele foi construído com base na realidade. E não há nada melhor do que nos basearmos na realidade para estimar seriamente o quanto pode custar à empresa um projeto qualquer.
Novo branch para projetos do Caloni.com.br
0Reestruturei meus projetos caseiros e coloquei todos em um branch no repositório do Assembla. A partir dele começarei a reestruturas os códigos de exemplo do saite, o deve facilitar o acesso. Para usuários do Bazaar, como eu, basta puxar o branch usando seu endereço:
bzr get http://subversion.assembla.com/svn/caloni/trunk
Para os usuários do Subversion, ou qualquer outro controle de fonte que consiga ler um branch feito em SVN, google for it.
Comparando strings no WinDbg
2O WinDbg fornece aos programadores diversos meios (muitos redundantes) de comparar valores inteiros em quaquer lugar da memória, em qualquer tamanho (8, 16, 32, 64 bits). Porém, quando precisamos comparar strings, que todos sabem ser uma sequência de bytes de tamanho arbitrário (se for em C, até o zero terminador).
Uma solução simples e rápida é comparar os 4 primeiros bytes de uma string, ou os 4 primeiros bytes que diferem de uma lista grande.
Por exemplo, imagine o seguinte código que abre todos os arquivos da pasta de sistema:
#define _CRT_SECURE_NO_WARNINGS #include <Windows.h> #include <stdio.h> int main() { CHAR sysPath[MAX_PATH]; CHAR findPath[MAX_PATH]; GetSystemDirectory(sysPath, MAX_PATH); sprintf(findPath, "%s\\*.*", sysPath); WIN32_FIND_DATA findData; HANDLE findH = FindFirstFile(findPath, &findData); if( findH != INVALID_HANDLE_VALUE ) { do { CHAR filePath[MAX_PATH]; sprintf(filePath, "%s\\%s", sysPath, findData.cFileName); HANDLE fileH = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if( fileH ) { CHAR firstBytes[4]; DWORD wasRead = 0; if( ReadFile(fileH, firstBytes, 4, &wasRead, NULL) && wasRead == 4 ) { printf("%s: %02X %02X %02X %02X\n", findData.cFileName, (int) firstBytes[0], (int) firstBytes[1], (int) firstBytes[2], (int) firstBytes[3]); } CloseHandle(fileH); } } while( FindNextFile(findH, &findData) ); FindClose(findH); } }
Queremos colocar um breakpoint no momento em que o arquivo shell32.dll estiver sendo aberto. Para isso, devemos nos atentar para os parâmetros passados para a função CreateFile.
windbg strcmpwindbg1.exe 0:000> bp kernel32!CreateFileA Breakpoint 0 hit eax=001bf918 ebx=7efde000 ecx=001bf7e0 edx=001bf7e0 esi=001bf824 edi=001bfd90 eip=7663ca6e esp=001bf804 ebp=001bfd90 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 kernel32!CreateFileA: 7663ca6e 8bff mov edi,edi 0:000> da poi(esp+4) 001bf918 "C:\Windows\system32\accessibilit" 001bf938 "ycpl.dll" 0:000> g Breakpoint 0 hit eax=001bf918 ebx=7efde000 ecx=001bf7e0 edx=001bf7e0 esi=001bf824 edi=001bfd90 eip=7663ca6e esp=001bf804 ebp=001bfd90 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 kernel32!CreateFileA: 7663ca6e 8bff mov edi,edi 0:000> da poi(esp+4) 001bf918 "C:\Windows\system32\ACCTRES.dll" O padrão aqui é que todo path passado para o CreateFile vai começar com c:\windows\system32, o que não é uma informação que podemos usar para buscar um arquivo específico.
Temos que nos atentar para o padrão de bits após esse path. Vamos dar uma olhada por dentro da string.
0:000> db 001bf918
001bf918 43 3a 5c 57 69 6e 64 6f-77 73 5c 73 79 73 74 65 C:\Windows\syste
001bf928 6d 33 32 5c 41 43 43 54-52 45 53 2e 64 6c 6c 00 m32\ACCTRES.dll.
001bf938 79 63 70 6c 2e 64 6c 6c-00 cc cc cc cc cc cc cc ycpl.dll........
O nome do arquivo começa no offset 16+4 = 20, ou 14 em hexa. Dessa forma, podemos capturar o padrão de bits da seguinte maneira:
0:000> dd poi(esp+4)+14 l1
001bf92c 54434341
Para nos certificarmos que é realmente esse o padrão, e para já montarmos nosso próprio padrão para o shell32.dll, vamos alocar um pedaço de memória e verificar se a sequência de bits está correta.
0:000> dd poi(esp+4)+14 l1
001bf92c 54434341
0:000> .dvalloc 100
Allocated 1000 bytes starting at 00030000
0:000> ea 00030000 "ACCTRES.dll"
0:000> dd 00030000 l1
00030000 54434341
Ótimo. Os padrões bateram, então podemos colocar um breakpoint condicional partindo do padrão de bits do nome do arquivo que precisamos.
0:000> bp kernel32!CreateFileA "j (poi(poi(esp+4)+14)=6c656873) ''; 'g'"
breakpoint 0 redefined
0:000> g
eax=0021f48c ebx=7efde000 ecx=0021f354 edx=0021f354 esi=0021f398 edi=0021f904
eip=7663ca6e esp=0021f378 ebp=0021f904 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
kernel32!CreateFileA:
7663ca6e 8bff mov edi,edi
0:000> da poi(esp+4)
0021f48c "C:\Windows\system32\shell32.dll"
Com isso, economizamos alguns minutos de puro tédio, verificando os nomes um a um conforme eles são abertos. Ou, dependendo da massa de dados, algumas décadas. Quem sabe. Pode ser muito mais útil um outro dia.
Sem reflection
2Em C++ não temos (ainda) a possibilidade de listarmos, por exemplo, a lista de métodos de um determinado tipo, a fim de chamá-lo pelo nome em tempo de execução. Algo assim:
class MyClass { public: void Method1(); void Method2(); void Method3(); }; int main() { MyClass c; if( auto m = typeid(c).methods.getaddresof("Method1") ) m(); }
OK, foi apenas um exemplo tosco de como seria um reflection em C++.
Porém, existem algumas maneiras de contornar esse problema. A solução, é claro, depende de qual problema você está tentando resolver.
Vamos supor, por exemplo, que você queira cadastrar funções para serem chamadas de maneira uniforme pelo prompt de comando. Vamos chamar nossa classe tratadora de CommandPrompt.
typedef void (Method*)(vector<string>& args); class CommandPrompt { public: void Add(string name, Method m); // adiciona novo método void Interact(ostream& os, istream& is); // começa interação com usuário };
Internamente, para armazenar as funções de acordo com o nome dado, basta criarmos um mapeamento entre esses dois tipos e fazemos a amarração necessária para o método principal de parseamento:
typedef map<string, Method> MethodList; // uma variável desse tipo armazena todas as funções void CommandPrompt::Interact(ostream& os, istream& is) { while( is ) { string func; vector<string> args; if( ParseLine(is, func, args) ) { // se a função desejada está em nossa lista, // podemos chamá-la, mesmo sem conhecer qual é if( Method m = m_funcs[func] ) m(args); } } }
Essa solução não é exatamente um reflection, mas apenas parte do que o verdadeiro reflection possibilita. Existem outras funcionalidades, como traits, que a STL já consegue se virar razoavelmente bem, por exemplo.











































