saída de montagem do GCC de um programa vazio na x86, win32
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 ??li> -
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!
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 int
s e float
s.
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 ...