Informática Numaboa - Tutoriais e Programação
O folgado (masm)
Sex 15 Dez 2006 21:23 |
- Detalhes
- Categoria: Assembly Numaboa (antigo oiciliS)
- Atualização: Terça, 16 Fevereiro 2010 22:29
- Autor: vovó Vicki
- Acessos: 17924
Aprender fazendo, este é o segredo. Neste tutorial ainda tem muita teoria e o primeiro resultado é, no mínimo, frustrante. Mas não desanime: veja como fazer a API do Windows trabalhar para você.
Projeto
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 teremos a oportunidade de rever a estrutura de um programa, além de ampliar nossos conhecimentos. Então vamos planejar:
- Nosso sistema operacional é o Windows de 32 bits (ou 64, se você é dos apressadinhos).
- Não precisamos mais do que o conjunto de instruções do processador 386.
- Temos o MASM à disposição para fazer o trabalho pesado.
- O objetivo do programa é voltar para o Windows assim que for executado. Nada mais
Botando a mão na massa
Chame o QEditor do MASM e digite o seguinte:
.386
.MODEL FLAT,STDCALL
.CODE
inicio:
end inicio
O sistema operacional win32 possui uma quantidade muito grande de funções que ele utiliza - nada impede que a gente pegue uma carona e 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 as 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 que precisamos 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(
UINT uExitCode // código de saída para todos as linhas de execução (threads)
);
Esta 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 uma DWORD (double word - palavra de 32 bits).
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, o valor que o Windows recebe quando o nosso programa termina. Para isto usamos a instrução push (empurre para a pilha) 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 para a 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 explicitamente (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 é o 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.
NomeDaFunção PROTO [NomeDoParâmetro]:TipoDeDado,[NomeDoParâmetro]:TipoDeDado...
Tendo essas 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 de fazer uso da mesma no 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:
.386
.MODEL FLAT,STDCALL
includelib \masm32\lib\kernel32.lib
ExitProcess PROTO uExitCode:DWORD
.CODE
inicio:
end inicio
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 bibliotecas de importação precisam ser vinculadas ao programa. É o caminho das pedras...
Como eu disse na introdução, é muita teoria. Espero que, até este ponto, tudo tenha ficado claro para que possamos criar nosso fantástico programa que não faz nada :blush:
- Anterior
- Próximo >>