|
Neste tutorial vamos realizar um projeto extremamente simples, copiado descaradamente do tutorial 3 do Iczelion. A idéia é genial: um programa cuja única função é retornar ao Windows, ou seja, dá a impressão de que não faz nada - um folgado. Apesar disso, é um programa! Na verdade, o que interessa realmente são os conceitos necessários para programar "o folgado". Sei que ainda é muita teoria para pouca prática, mas o começo é assim mesmo.
|
||||||||||||||||||||||||||||||||||||||||
| PLANEJANDO O FOLGADO | ||||||||||||||||||||||||||||||||||||||||
|
Vamos escrever um programa que tem apenas uma linha de código. Através deste projeto temos a oportunidade de rever a estrutura de um programa, além de ampliar nossos conhecimentos. Então vamos planejar:
Execute o MASM e digite o seguinte no editor de texto do próprio: .386 .MODEL FLAT,STDCALL .CODE
O sistema operacional win32 possui uma quantidade muito grande de funções que ele mesmo utiliza e nada impede que nós também as usemos. Esta imensa coleção de funções do win32 é chamada de API (Application Programming Interface). As funções estão organizadas em bibliotecas denominadas bibliotecas de vínculo dinâmico (dynamic-linked libraries) ou DLL. Três delas são as mais importantes e mais utilizadas: kernel32.dll, user32.dll e gdi32.dll. A kernel32.dll contém funções API que lidam com a memória e com a administração de processos. A user32.dll possui funções que controlam a aparência da interface com o usuário e a gdi32.dll tem funções responsáveis por operações gráficas. Caso exista uma função na API win32 que execute exatamente o trabalho que pretendemos realizar, podemos usá-la diretamente no nosso programa ao invés de escrever todo o procedimento.
A função de que necessitamos chama-se ExitProcess e está localizada na biblioteca kernel32.lib. Dando uma olhada na referência da API do Windows, encontramos o seguinte: VOID ExitProcess(
);
A função não tem valor de retorno (é VOID) e exige um parâmetro (uExitCode) do tipo UINT. UINT é apenas um dos muitos nomes que o Windows usa para DWORD. Para poder usar esta função, é preciso passar algumas informações para o assembler e para o linker: o nome da função e a biblioteca onde ela está. Com estas informações, o assembler/linker indicará ao executável onde ele deve buscar a função que deve ser executada. O código da função NÃO é adicionado ao nosso executável, apenas as informações de qual função (ExitProcess) deve ser executada e onde encontrá-la (na kernel32.dll) em tempo de execução. De posse dessas informações, nosso programa será capaz de executar uma operação de chamada, ou seja, um call ExitProcess. A instrução call faz parte do conjunto de instruções do 386 e funciona da seguinte maneira:
Antes de incluirmos a instrução call ExitProcess no nosso código, precisamos colocar os parâmetros correspondentes na pilha. A pilha é um registrador especial da CPU, cujo conteúdo indica o endereço da memória onde se deposita uma informação que deva ser temporariamente guardada no curso do processamento. No nosso exemplo, apenas o parâmetro uExitCode, que é o valor que o Windows recebe quando o nosso programa termina. Para isto usamos a instrução push e o fragmento de código fica assim: push 0 call ExitProcess
Se esquecermos de "pushar" o valor do parâmetro para a pilha, o assembler/linker não irá notar a falta. Usamos uma instrução do conjunto de instruções do 386 e o assembler não tem como checar os parâmetros (quem usa a instrução deve saber o que está fazendo...) ao produzir o executável. Você só vai notar o erro quando executar o programa que, logicamente, dá pau.
Existe um modo mais seguro e cômodo de fazer chamadas. É através do INVOKE, uma sintaxe de chamada de alto nível. A sintaxe de INVOKE é a seguinte: INVOKE expressão[,argumentos] onde expressão pode ser o nome de uma função ou um ponteiro da função e os argumentos (parâmetros) são separados por vírgulas. Neste caso o assembler/linker tem condições de verificar a sintaxe porque os parâmetros são citados claramente (e não apenas "pushados" para a pilha). Dá para perceber que um pouquinho de alto nível no assembly não faz mal a ninguém. Só tem um porém... para poder utilizar o INVOKE é necessário fornecer um protótipo da função que se quer usar. O protótipo de uma função informa os atributos desta função para que o assembler (e o linker) possam fazer uma checagem dos tipos. O formato de um protótipo é constituído pelo nome da função seguido da palavra-chave PROTO, e esta seguida da lista de parâmetros formando pares de nome:tipo de dado separados por vírgulas. Veja abaixo: NomeDaFunção PROTO [NomeDoParâmetro]:TipoDeDado,[NomeDoParâmetro]:TipoDeDado...
De posse dessas informações podemos construir o protótipo, que nada mais faz do que definir ExitProcess como uma função que usa apenas um parâmetro do tipo DWORD: ExitProcess PROTO uExitCode:DWORD
Uau! Agora podemos mostrar o protótipo da ExitProcess ao construtor (assembler). É claro que o construtor precisa ser apresentado à função ANTES que façamos uso da mesma no nosso código. A apresentação consiste no protótipo e na biblioteca que contém a função. Colocamos então este par junto com as outras solicitações: .MODEL FLAT,STDCALL includelib \masm32\lib\kernel32.lib ExitProcess PROTO uExitCode:DWORD .CODE
Mas que negócio é este de includelib? A diretiva includelib é apenas uma maneira de indicar ao assembler quais as bibliotecas de importação que o programa usa. Quando o assembler encontra este tipo de diretiva, ele põe um comando para o linker no arquivo objeto, para que o linker saiba quais as bibliotecas de importação que precisam ser vinculadas ao programa. É o caminho das pedras...
Como eu disse na introdução, é muita teoria. Espero que tudo tenha ficado claro para que possamos criar nosso fantástico programa ;-)
|
||||||||||||||||||||||||||||||||||||||||
| CRIANDO O FOLGADO | ||||||||||||||||||||||||||||||||||||||||
|
Está tudo em riba? O arquivo texto que você digitou no editor de texto do MASM deve ter esta cara:
.386 .MODEL FLAT,STDCALL includelib \masm32\lib\kernel32.lib ExitProcess PROTO uExitCode:DWORD .CODE
E isso é tudo. Nosso arquivo texto está pronto. Agora o trabalho vai ser o de clicar no menu do MASM:
Se por acaso esquecermos o parâmetro da função (neste caso seria invoke ExitProcess ou invés de invoke ExitProcess,0), na etapa 2 o construtor (assembler) dará a seguinte mensagem de erro: Assembling E:\masm32\Projetos\Folgado\folgado.asm E:\masm32\Projetos\Folgado\folgado.asm (6) : error A2137 : too few arguments to INVOKE Introduza o erro no texto e faça o teste só para ir pegando o jeito. E não se esqueça de SEMPRE salvar o arquivo texto antes de assemblar ou linkar.
Se tudo correu bem após a estapa 2, o que significa que texto está sem erros que o construtor possa detectar, temos dois arquivos no diretório indicado: o folgado.asm, de 163 bytes, e o folgado.obj, de 463 bytes. E se tudo continuou a correr bem, após a etapa 3 tem mais um arquivo no diretório: folgado.exe, de 1.536 bytes. Enxutinho, não é mesmo? Tá certo que o programa não faz grande coisa, mas mesmo assim...
Execute o folgado.exe e... surpresa! Tá pensando que eu vou dizer novamente que dá a impressão de que o programa não faz nada? Nananinanão! A surpresa é que o programa fez exatamente o que se esperava dele e NÃO DEU ERRO!
|
||||||||||||||||||||||||||||||||||||||||
| MAIS UM POUCO DE TEORIA | ||||||||||||||||||||||||||||||||||||||||
|
Folgado é o programa, nóis não! Só mais uma coisinha...
Já pensou ficar montando protótipos de todas as funções que se queira importar de DLLs? É muito pra cabeça e vai acabar dando calo nos dedos (e nas teclas). Existe um modo muito mais elegante e folgado de obter o mesmo resultado: guardando os protótipos das funções em arquivos que possam ser incluídos no texto dos nossos programas. Na verdade, estes arquivos já estão prontos. Para cada biblioteca existe um arquivo de inclusão com todos os protótipos de todas as funções desta biblioteca. São os arquivos .inc. Para a kernel32.lib existe a kernel32.inc, para a user32.lib existe a user32.inc e assim por diante. A diretiva include é a que faz a mágica. Basta usar include seguido pelo nome do arquivo que você quiser inserir no local onde se encontra a diretiva. Nosso texto ficaria assim: .386 .MODEL FLAT,STDCALL includelib \masm32\lib\kernel32.lib include \masm32\include\kernel32.inc .CODE
Quando o MASM32 processa a linha "include \masm32\include\kernel32.inc", ele abrirá o arquivo kernel32.inc, que se encontra no diretório \masm32\include, e processará o conteúdo do arquivo como se o conteúdo do kernel32.inc estivesse presente. Desta forma, podemos substituir um caminhão de linhas com protótipos de funções por umas poucas linhas de include. Se você tiver curiosidade, abra num editor de texto qualquer arquivo include. Estes arquivos estão em texto ASCII raso, o que significa que você pode lê-los com facilidade além de poder fazer seus próprios arquivos include e incorporá-los aos seus programas quando necessário. Os arquivos .inc podem conter outras definições (constantes, estruturas, etc) além de protótipos de funções. Um dos mais completos (e constantemente atualizado) é o arquivo windows.inc do Iczelion & Hutch. É pau pra toda obra. Você encontra este arquivo no pacote do MASM32 versão 7 do Hutch no site do Iczelion e no site do hutch.
|
||||||||||||||||||||||||||||||||||||||||
| CONFERINDO O TRABALHO DO MASM32 | ||||||||||||||||||||||||||||||||||||||||
|
Existem diversos desassembladores muito bons, como o WDasm e o ADA. Eu trabalho muito com o OllyDbg, um debugger gratuito para Windows com um desassemblador excelente. Você pode encontrá-lo no site do autor em http://home.t-online.de/home/Ollydbg. Abrindo o executável com o OllyDbg encontramos o seguinte:
No endereço 401000, ponto de entrada do executável (module entrypoint), já encontramos o push do parâmetro da função KERNEL32.ExitProcess. Logo depois o call para a função. O MASM trabalhou direitinho. Note que o nosso programa ocupa 4096 bytes (ou 4 Kb) de memória - vai do endereço 401000 até 401FFF - apesar de só ocupar 14 bytes com o código. O restante é preenchido com zeros.
|
||||||||||||||||||||||||||||||||||||||||
| ATÉLOGO | ||||||||||||||||||||||||||||||||||||||||
|
Vamos ao resumo:
Faça o download do arquivo tutNB01.zip (12 Kb) com o tutorial completo.
|