A Aldeia Numaboa ancestral ainda está disponível para visitação. É a versão mais antiga da Aldeia que eu não quis simplesmente descartar depois de mais de 10 milhões de pageviews. Como diz a Sirley, nossa cozinheira e filósofa de plantão: "Misericórdia, ai que dó!"

Se você tiver curiosidade, o endereço é numaboa.net.br.

Leia mais...

Informática Numaboa - Tutoriais e Programação

O folgado (masm)

Sex

15

Dez

2006


21:23

(13 votos, média 4.62 de 5) 


Nível intermediário 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:

  1. Nosso sistema operacional é o Windows de 32 bits (ou 64, se você é dos apressadinhos).
  2. Não precisamos mais do que o conjunto de instruções do processador 386.
  3. Temos o MASM à disposição para fazer o trabalho pesado.
  4. O objetivo do programa é voltar para o Windows assim que for executado. Nada mais smile

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:

Chamada de função

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:

Informações adicionais