Pergunta

Eu escrevo programas vazias para irritar o inferno fora de codificadores stackoverflow, NÃO. Estou apenas explorando o Conjunto de ferramentas GNU.

Agora, o seguinte pode ser muito profundo para mim, mas para continuie a saga programa vazio Eu já começaram a examinar a saída do compilador C, o GNU coisas como consome.

gcc version 4.4.0 (TDM-1 mingw32)

test.c:

int main()
{
    return 0;
}

gcc -S test.c

    .file   "test.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    call    ___main
    movl    $0, %eax
    leave
    ret 

Você pode explicar o que acontece aqui? Aqui está o meu esforço para compreendê-lo. Eu tenho usado o as manual e minha mínima conhecimento x86 ASM:

  • .file "test.c" é a directiva para o nome do arquivo lógico.
  • .def: de acordo com os docs "começar a definir as informações de depuração para um nome de símbolo" . O que é um símbolo (um nome de função / variável?) E que tipo de informações de depuração?
  • .scl: docs dizer "Classe de armazenagem podem sinalizar se um símbolo é estático ou externo" . É este o mesmo estática e externo Eu sei da C? E o que é que '2'?
  • .type:. Armazena o parâmetro "como o atributo tipo de uma entrada da tabela de símbolos" , eu não tenho idéia
  • .endef:. Nenhum problema
  • .text:. Agora isso é problemático, parece ser algo chamado seção e eu li que é o lugar para o código, mas os docs não me disse muito
  • .globl "faz o símbolo visível para ld". , o manual é bastante clara sobre isso.
  • _main: Este pode ser o endereço inicial (?) Para o meu principal função
  • pushl_: Uma longa (32 bits) de pressão, o que coloca EBP na pilha
  • movl: 32-bit movimento. Pseudo-C: EBP = ESP;
  • andl: E lógico. Pseudo-C:. ESP = -16 & ESP, eu realmente não vejo o que é o ponto desta
  • call: Empurra o IP para a pilha (de modo que o procedimento chamado pode encontrar o seu caminho de volta) e continua onde __main é. (O que é __main?)
  • movl: este zero deve ser a constante eu voltar no final do meu código. O MOV coloca este zero no EAX.
  • leave: restaurações empilhar após uma instrução ENTER (?). Por quê?
  • ret: vai voltar para o endereço de instrução que é salvo na pilha

Obrigado por sua ajuda!

Foi útil?

Solução

.file "test.c"

Comandos começando com. são diretrizes para a montador. Isso só diz que esta é "file.c", essa informação pode ser exportado para as informações de depuração do exe.

.def ___main; .scl 2; .type 32; .endef

.def directivas define um símbolo de depuração. scl classe de armazenamento 2 significa 2 (classe de armazenamento externo) .type 32, esta é uma função sumbol. Esses números serão definidos pela exe formato pe-coff

___ principal é uma função chamada que cuida de bootstrapping que as necessidades do CCG (ele vai fazer coisas como correr c ++ inicializadores estáticos e outras tarefas domésticas necessário).

.text

Inicia uma seção de texto -. Código vive aqui

.globl _main

define o símbolo _main como global, que irá torná-lo visível para o vinculador e outros módulos que está vinculado em.

.def        _main;  .scl    2;      .type   32;     .endef

A mesma coisa como _main, cria depuração símbolos que indicam que _main é uma função. Isto pode ser usado por depuradores.

_main:

Inicia uma nova etiqueta (Ele vai acabar um endereço). a directiva .globl acima faz este endereço visível para outras entidades.

pushl       %ebp

Salva o antigo ponteiro do quadro (registo ebp) na pilha (para que ele possa ser colocado de volta no lugar quando esta função extremidades)

movl        %esp, %ebp

Move o ponteiro de pilha para o registo ebp. ebp é muitas vezes chamado o ponteiro do quadro, ele aponta no topo dos valores da pilha dentro do "frame" atual (função normalmente), (referindo-se a variáveis ??na pilha via ebp pode ajudar depuradores)

andl $ -16,% esp

ANDS a pilha com fffffff0 que alinha-lo em um limite de 16 bytes effectivly. Acesso aos valores alinhados na pilha são muito mais rápidos do que se estivessem desalinhadas. Todas estas instruções anteriores são praticamente um prólogo função padrão.

call        ___main

Chama a função ___main que irá fazer coisas inicializar que as necessidades do CCG. Chamada vai empurrar o ponteiro de instrução atual na pilha e saltar para o endereço de ___ principal

movl        $0, %eax

move 0 para o registo EAX, (a 0 no return 0;). Registo eax é usado para valores de retorno de função hold para a convenção de chamada stdcall

licença

A instrução licença é praticamente uma abreviação para

movl     ebp,esp
popl     ebp

i. ele "undos" o material feito no início da função -. restaurar o ponteiro do quadro e pilha ao seu estado anterior

ret

Retorna para quem chamou esta função. Vai aparecer o ponteiro de instrução da pilha (que uma instrução de chamada correspondente terá colocado lá) e saltar lá.

Outras dicas

Há um exercício muito semelhante descrito aqui: http://en.wikibooks.org/wiki/ X86_Assembly / GAS_Syntax

Você já descobriu a maior parte dele. - Vou apenas fazer anotações adicionais para ênfase e adições

__main é uma sub-rotina na biblioteca padrão GNU que cuida de vários inicialização start-up. Não é estritamente necessário para programas em C, mas é necessária apenas no caso do código C está ligando com C ++.

_main é a sua principal sub-rotina. Como ambos _main e __main são locais de código têm a mesma classe de armazenamento e tipo. Eu ainda não desenterrou as definições para .scl e .type ainda. Você pode obter alguma iluminação, definindo algumas variáveis ??globais.

As três primeiras instruções estão a criação de um quadro de pilha que é um termo técnico para o armazenamento de trabalho de uma sub-rotina - as variáveis ??locais e temporários para a maior parte. Empurrando ebp salva a base do quadro de pilha do chamador. Colocando esp em ebp define a base do nosso quadro de pilha. Os alinha andl o quadro de pilha para um limite de 16 bytes apenas no caso de quaisquer variáveis ??locais na pilha requerem 16 alinhamento byte (para as instruções x86 SIMD exigem que o alinhamento, mas o alinhamento faz acelerar tipos comuns, como ints e floats.

Neste ponto, você normalmente esperaria esp para se moveu para baixo na memória para alocar espaço de pilha para variáveis ??locais. Seu main tem nenhum tão gcc não incomoda.

A chamada para __main é especial para o ponto de entrada principal e não normalmente aparecem em sub-rotinas.

O resto vai como você imaginou. Register eax é o lugar para colocar inteiros códigos de retorno na especificação binário. leave desfaz o quadro de pilha e ret volta para o chamador. Neste caso, o chamador é o tempo de execução de baixo nível C, que vai fazer a mágica adicional (como chamar funções atexit(), definir o código de saída para o processo e pedir ao sistema operacional para finalizar o processo.

No que se refere que andl $ -16,% esp

  • 32 bits: -16 em decimal é igual a 0xfffffff0 em representação hexadecimal
  • 64 bits: -16 em decimal é igual a 0xfffffffffffffff0 em representação hexadecimal

Por isso vai mascarar os últimos 4 bits de ESP (btw: 2 ** 4 iguais a 16). E reterá todos os outros bits (não importa se o sistema de destino é 32 ou 64 bits)

Na sequência da andl $-16,%esp, isso funciona porque a definição dos bits de baixa a zero sempre ajustar %esp abaixo em valor, ea pilha cresce para baixo em x86.

Eu não tenho todas as respostas, mas eu posso explicar o que eu sei.

ebp é utilizado pela função para armazenar o estado inicial de esp durante seu fluxo, uma referência ao local onde estão os argumentos passados ??para a função e onde estão suas próprias variáveis ??locais. A primeira coisa que uma função faz é para salvar o estado do ebp dada fazendo pushl %ebp, é vital para a função que faz a chamada, e de substitui-lo por sua própria posição atual pilha esp fazendo movl %esp, %ebp. Zerar os últimos 4 bits de ebp neste momento é GCC específico, eu não sei porque este compilador faz isso. Ele iria trabalhar sem fazê-lo. Agora, finalmente, entrar no negócio, call ___main, que é __main? Eu também não sei ... talvez mais procedimentos específicos do CCG, e, finalmente, a única coisa que seu main () faz, valor de retorno conjunto como 0 com movl $0, %eax e leave que é o mesmo que fazer movl %ebp, %esp; popl %ebp para restaurar o estado ebp, então ret para terminar. pops ret eip e continuar o fluxo de fio a partir desse ponto, onde quer que seja (como o main (), este ret provavelmente leva a algum procedimento kernel que lida com o fim do programa).

A maior parte é toda sobre o gerenciamento da pilha. Eu escrevi um tutorial detalhado sobre como pilha é usada há algum tempo, que seria útil para explicar por que todas essas coisas são feitas. Mas sua em português ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top