O Conto dos Cursos

Olá. Quero falar sobre meu curso ou sobre o que a curiosidade leva.


Durante muito tempo, do nada para fazer, escrevo programas sob o Symbian. E de tempos em tempos eu me deparava com coisas estranhas durante a montagem. Tudo apontava para o utilitário elf2e32. Sua tarefa é converter o arquivo binário de entrada do formato elf para outro, específico para a imagem Symbian - e32. Fiquei intrigado por um longo tempo com a curiosidade - como esse utilitário funciona e por que às vezes é de buggy? Um pouco mais tarde, outra pergunta começou a me incomodar - o tópico do trabalho do curso =) Decidi combinar negócios com prazer e baixei seu código-fonte. E lá vamos nós ...


O primeiro commit não vai


A segunda - incluímos extensões gcc não padrão, adicionamos classes, funções e constantes ausentes da fonte. O sujeito se reúne e cai alegremente. Progresso, no entanto. Começamos sob o depurador - ele coloca o depurador em uma classe que apenas inicializa outra, que inicializa a próxima ... Hurra! Uma característica real! Entre. Opa Onde estamos? !!! Pare o depurador! Cirurgião! Bisturi! Álcool! Pepino! Apêndice das aulas f firebox! Dê nullptr em vez de NULL! Nós temos C ++ 14! Uau, que construtor aterrorizante para inicializar tudo com zeros! E, no entanto, de novo e de novo - mas conosco, o C ++ 14 exige a inicialização padrão das classes! O que é tudo sucinto agora ...


Ok. Consertamos o maior número possível de cada vez. Eu descobri por que o depurador salta para o código-fonte como um ajuste - aftars atingem suas cabeças na abstração, aumentando a herança de 80 níveis da classe UseCaseBase :) Então, as classes construtoras de instâncias estáticas para as classes Message & ParameterManager voaram dos meus olhos. Singleton Myers? Não, não ouvi. Abstração Firebox! Viva revolucion !!! Viva POD !!!


Uau Que interessante essa árvore foi levantada. O trabalho principal é realizado pela função BuildAll (). Se todos os parâmetros forem especificados, a função coletará a biblioteca de importação, um arquivo especificando os nomes das funções e variáveis ​​e a ordem em que eles estão disponíveis na biblioteca de importação e no próprio binário. Todos os descendentes de UseCaseBase alteraram seu algoritmo por sobrecarga. Às vezes, nos descendentes, preparamos dados auxiliares, mas na maioria das vezes eles simplesmente desativaram a criação de alguns arquivos. Por exemplo, o nome do arquivo para montar algo não é especificado - uma nova classe é criada. Idiotas. É suficiente interromper a execução de um coletor de funções, se necessário. É fácil entender minhas ações B-)


Continuamos excluindo classes vazias, substituindo NULL por nullptr_t, substituindo range-iterators por for (auto x: *).


Corrigimos erros no processamento de parâmetros de linha de comando.


É necessário verificar o código com um analisador estático. Por onde começar? Hmm, no XP, a escolha é pequena - cppcheck, e o codeblock suporta isso imediatamente. Uau, que problema! Há até excluir para char []! Porra, eu sei onde metade da RAM livre foi =)


Portanto, adicionamos os arquivos gerados a partir do arquivo elf libcrypto.dll e o próprio arquivo que descreve os parâmetros da linha de comando para sua criação.


Opa CPPCheck estava errado ... Deve ser (a || b) ...


Vou tentar criar no Visual Studio 15 e o Win10 ficaria com um pau. Coloque em uma máquina virtual. Feito, faça o download e execute o instalador do estúdio online. O que? Não deseja salvar o salto na pasta compartilhada com o host ?! Sim, engasgue com você! Baixe onde você foi ensinado ... E agora transferimos o download para a pasta e iniciamos a instalação. O que? Mais uma vez ignora a pasta compartilhada ??! Sim, engasgue com você! Torne-se onde você foi ensinado ...


Em princípio, uma dúzia de poços sussurra em um núcleo e 3 shows do quadro. Estúdio para estúdio! Pensativo, mas não por muito tempo. Abrimos meu projeto no estúdio e, novamente, jura na pasta ... Quanto já é possível? Sim, você engasga ... Nós coletamos, jura sobre extensões não padrão STL hash_set. Remoto. Remote ??? Ligue o cérebro =)
Uau, que código atraente:


int ElfFileSupplied::UnWantedSymbolp(const char * aSymbol) { static hash_set<const char*, hash<const char*>, eqstr> aSymbolSet; int symbollistsize=sizeof(Unwantedruntimesymbols)/sizeof(Unwantedruntimesymbols[0]); static bool FLAG=false; while(!FLAG) { for(int i=0;i<symbollistsize;i++) { aSymbolSet.insert(Unwantedruntimesymbols[i]); } FLAG=true; } hash_set<const char*, hash<const char*>, eqstr>::const_iterator it = aSymbolSet.find(aSymbol); if(it != aSymbolSet.end()) return 1; else return 0; } 

Vamos pensar um pouco ... E pronto:


 int ElfFileSupplied::UnWantedSymbolp(const char * aSymbol) { int symbollistsize = sizeof(Unwantedruntimesymbols) / sizeof(Unwantedruntimesymbols[0]); for (int i = 0; i<symbollistsize; i++) { if (strstr(Unwantedruntimesymbols[i], aSymbol)) return 1; } return 0; } 

Minha beleza ...


Assim, por que o programa lança uma exceção se esse sinalizador está definido incorretamente ou não está definido? Por que você é tão cruel, linda longe? Vamos redefinir esta bandeira para um valor seguro. E essa bandeira também seria legal ... E isso, e isso, e estes. Ou talvez seja melhor colocá-lo em uma função separada? Boa ideia! Vamos chamá-lo de ParameterManager :: CheckOptions ()!


Um passo para a esquerda é uma queda, um passo para a direita é uma exceção não capturada, um salto no lugar - graças ao menos não ao BSOD =)


Chato ... Falhas e curvatura ...


Olya-la !!! Emule o Symbian CleanUpStack no STL?:
Basicamente, nada de especial:


 std::vector<char*> cleanupStack; 

Limpeza:


 std::vector<char*>::iterator aPos; char *aPtr; aPos = cleanupStack.begin(); while( aPos != cleanupStack.end() ) { aPtr = *aPos; delete[] aPtr; ++aPos; } 

Alguma cabeça brilhante em vez de esquerda / direita usava l / r. Obrigado cppcheck.


Ah, preguiçoso na frente do monitor os logs do cppcheck para desmontar ... O que o github nos oferecerá? .. Codacy ... Conectamos o projeto ... Pensei um pouco e está pronto! Agora você pode ler as mensagens de sucesso na luta contra os erros no sofá ^^


Então, parece que não é de buggy ... Vamos coletar algo, por exemplo libcrypto.dll. Funciona, embora o arquivo descompactado tenha cem bytes a mais do que o criado pelo utilitário a partir do SDK. Em seguida, os binários criados por esta versão do utilitário e pelo SDK serão comparados constantemente. As opções de linha de comando são idênticas por si mesmas.
Dachshund, onde obter o analógico diff para arquivos binários? Hmm, pegue o script no pistão. Muita informação - você precisa de algo muito mais simples. Dll de reconhecimento de pdf / djvu - AlternateReaderRecog.dll é uma boa opção, o escape é inferior a 4 kilobytes. Dachshunds, as compensações variam na seção de importação. Nós os abrimos no editor hexadecimal. O começo é o mesmo, na minha versão o lixo está mais longe, logo após o final da seção na versão original. Mas na minha versão, a próxima seção começa 100 bytes depois. Pelo mesmo valor em bytes, os arquivos diferem! As compensações indicam ainda os endereços corretos ... O binário está correto !!! Ahhhh !!!


Um mês depois. Então, de onde vieram esses cem bytes?
Bem, como não está claro como isso funciona, começamos a quebrar o algoritmo para criar o E32Image. Continuando a zombar do AlternateReaderRecog.dll. Aumentamos o tamanho do binário na saída - de jeito nenhum, sobrescrevemos as seções do memset - de jeito nenhum, reduzimos o tamanho do binário - de jeito nenhum. Grrrr. Que merda !!! Eu quebro o escape na versão de lançamento e inicio a depuração? !!! Oi bast, comece de novo ... Seção muuuuito destruída - tudo bem! Aumentou o tamanho do binário! Bom !!! Reduza o tamanho da seção de importação! Existe !!! Byte idêntico à mesma seção na exaustão deste utilitário do SDK!
Analisamos o código para criar esta seção. "sizeof (char *)" - algo voltou ao artigo de Andrey Karpov, um dos desenvolvedores do Pvs-studio, que tipos podem ocupar diferentes quantidades de memória - e quanto espaço é necessário? MinGW - 8 bytes, Visual Studio - 4 !!! Dividimos esses 8 bytes pela metade, algo comercial. FFse! E como está a seção de código? Esta DLL sem variáveis ​​globais. Não há variáveis ​​globais - não há seção ... Vamos tomar algo mais pesado - libcrypto.dll.
O arquivo na saída do meu utilitário agora é 100+ bytes menor ... O que ??? A seção de importação é idêntica a bytes - válida. Seção de Código - Não? !!


A olho nu, não posso comparar uma parede de texto ... vou procurar diff para uma comparação de bytes ...
Depois de alguns dias, eu ainda encontrei jogos com a pesquisa no Google. vbindiff é um utilitário de console com uma interface ala do Norton Commander, mostrando a diferença entre dois arquivos em dois painéis horizontais. Para ir para o local da diferença, pressione enter. Bom! Você pode arrastar dois arquivos para o ícone para comparação e o programa os abrirá! Ótimo !!!
Compare - então, no cabeçalho, seu CRC e o tempo de criação são diferentes. Nada disso. Aqui o byte é diferente, aqui estão outras cem ... Uau !!! Dezenas, centenas, milhares de bytes de diferença? !!! Então veja, a que seção eles pertencem ... Vemos as compensações ... Sim, a seção de dados ...
Nós fazemos o truque, como para a seção de importação ... Redefinimos o memset, existe. Aumente o tamanho da seção ... Caindo ... Aumente. Oferece a mão e o coração de um depurador ... Droga. Abrimos a função criando a seção - mingau a partir das funções ... Grr.
... Ah, amanhã ... Até eu arrumar outra coisa ...


Por exemplo, adicionarei testes, mas há tanta confusão que é impossível dividir o programa em pequenos módulos. Não é possível inserir testes diretamente no código; você entenderá o rábano. A ideia! Lançamentos constantes de programas com argumentos diferentes - eu tenho testado o programa o tempo todo ... E vamos fazer disso tudo um script separado em python. Sim, ótima idéia, ótima. O script deve continuar funcionando ao relatar erros de teste, mas sem travar. Feito!


Voltamos aos nossos carneiros ... Essa função chama isso, depois isso, vamos aqui ... Então, senhor, onde isso me trouxe? Ugh, confuso ... Ah, amanhã ... Até eu arrumar outra coisa ...


E então dois meses se passaram ...


Porra, onde esta seção de código é formada? Eu tive que sair de licença acadêmica, então pelo menos vou descobrir com você !!! Soooo Aqui os símbolos da seção vão para a entrada ... O que será impresso? Tudo não se encaixa no buffer do console ... Vamos salvar o escape em um arquivo ... Sooo, até agora nada de especial ... Pare! Mesmas linhas !!! Muitas linhas idênticas !!! De onde ?! Adicione printf a cada fonte de dados (houve paciência suficiente para 3 em 5, ha). Está vazio! Nós olhamos para uma das chamadas de função restantes ... Taak. Incremento do iterador após o loop ??? E aviso de codificação do todo ??? Nós transferimos para o ciclo. Lançamento !!! Há uma correspondência de tamanho! Há uma correspondência de bytes !!! Fixed !!! culpa git se recusa a nomear o herói ... Nós olhamos para o original - não foi isso que eu fiz. Ou talvez tenha sido uma "bomba" para desenvolvedores não afiliados à Nokia? Grrr.


Verifique cuidadosamente os testes de exaustão, verifique os arquivos de bytes. Tudo funciona como deveria! Em lançamento!


Olya! É hora de uma grande limpeza !!! É hora de arrancar a árvore UseCaseBase com a raiz !!!
A maioria dos descendentes já está desgastada, removemos funções úteis da classe do gerador. Apenas UseCaseBase e seu descendente ElfFileSupplied permanecem. UseCaseBase - é um wrapper sobre uma classe que processa parâmetros de comando e declara várias funções puramente virtuais para a classe ElfFileSupplied. Em suma, o violinista não é necessário ... Que céu é azul, bom ... Mais uma hora ... Eu vou descobrir com essa classe e você pode dar um passeio ... E tomar um ar, aquecer, bom ... Vamos lá! Portanto, comente esse recurso. Coletando! Então, você precisa pensar em como refazê-lo lindamente ... Feito !!! O próximo recurso! Feito! Próximo! Feito! Feito! Sim Sim Sim Última função ... Ufff. Executar após a montagem ... Aceleração sete vezes maior do trabalho? !!! O escape está correto ... Engraçado. A versão de depuração também é reduzida em 2 metros? !!! Uau !!! Você pode dar um passeio. À noite? !!! Kaak ??? Onde está o meu dia? !!! Vou lançar testes e relaxar ... Os testes funcionaram silenciosamente - você pode relaxar ...


Deixe-me escrever algo para mim agora ... Ah, a classe que trabalha com funções e variáveis ​​disponíveis de fora parece assustadora. O princípio de operação: leitura de um arquivo, análise de linhas e salvamento em um arquivo. Para a análise das linhas, uma classe inteira de macarrão selecionado foi isolada em S ... Sooooh ... Vamos pensar ... Que beleza saiu:
lemos a linha std :: getline (), removemos espaços das bordas das linhas e do parsim.


Para continuar ... Código fonte - https://github.com/fedor4ever/elf2e32

Source: https://habr.com/ru/post/pt430794/


All Articles