Ir para conteúdo

Fluxo na execução do Programa


Luciano Estevam Rangel

Posts Recomendados

Primeiramente, gostaria de parabenizar pelo excelente trabalho com o curso de Engenharia Reversa. Na minha opinião, unico e sensacional.

Vendo os videos, comecei a querer ir mais afundo nos entendimentos sobre Assembly, e acabei dando um pequeno no na minha cabeça.

Minha duvida é o seguinte. Gostaria de ouvir de voces, ou alguma documentação (pois nao consegui encontrar na NET), como é o fluxo quando executo um programa. Quando manipulo apenas os registradores, quando acesso a memoria RAM. Quando vejo nos videos as manipulações nos registradores, fico me perguntando (OK, estamos manipulando os registradores, mas em que momento efetuamos o acesso na memoria RAM? Quando esses dados são enviados para a memoria? No meu entendimento, quando e explicado no video as instruções (MOV registrado x pra y, ADD registrador X com Y) essas são manipulações apenas com os registradors, onde os resultados ficam armazendos nesses registradores, mas em que momento isso e gravado em memoria? Desculpa se a duvida e basica, mas isso provocou uma confusão na minha cabeça

Alguem conhece alguma boa documentação sobre, ou conseguiria me explicar esse fluxo?

 

Obrigado e uma boa Segunda a todos.
 

 

Link para o comentário
Compartilhar em outros sites

Olá Luciano,

Faz um tempo que não vejo isso, então eu posso falar alguma besteira. A internet tá cheia de exemplos (em ingês).

Quando vc faz:

MOV eax,ebx

Você está movendo o CONTEÚDO - o valor - de ebx para eax. Ou seja, a operação se passa de registrador para registrador.

 

Para mover o conteúdo de um registro para a memória - ou vice-versa - você precisa fazer a chamada por referência - passar o endereço.

Então, 

MOV eax,[ebx]     ; <== Repara nos colchetes

Você está movendo o que está no ENDEREÇO - contido em ebx - para eax.  (Memória para registrador)

 

MOV [eax],ebx     ; <== Repara nos colchetes

Você esta movendo o que está contido em ebx para o ENDEREÇO contido em eax. (Registrador para memória)

 

Você pode criar static data regions (tipo variáveis globais)...

.DATA
var    DD    3000   ; Inicializa com 3000, uma área de memória de 4 bytes e dá o nome para essa área de var.

E aí, usar a seguinte instrução:

mov [var], ebx	; Move o conteúdo de EBX para os 4 bytes no endereço de memória var

Dá uma olhada nesse site, é um exemplo dos que te falei.

http://www.cs.virginia.edu/~evans/cs216/guides/x86.html

 

Espero ter ajudado. =D

Abs,

 

Link para o comentário
Compartilhar em outros sites

7 horas atrás, Luciano Estevam Rangel disse:

Primeiramente, gostaria de parabenizar pelo excelente trabalho com o curso de Engenharia Reversa. Na minha opinião, unico e sensacional.

Vendo os videos, comecei a querer ir mais afundo nos entendimentos sobre Assembly, e acabei dando um pequeno no na minha cabeça.

Minha duvida é o seguinte. Gostaria de ouvir de voces, ou alguma documentação (pois nao consegui encontrar na NET), como é o fluxo quando executo um programa. Quando manipulo apenas os registradores, quando acesso a memoria RAM. Quando vejo nos videos as manipulações nos registradores, fico me perguntando (OK, estamos manipulando os registradores, mas em que momento efetuamos o acesso na memoria RAM? Quando esses dados são enviados para a memoria? No meu entendimento, quando e explicado no video as instruções (MOV registrado x pra y, ADD registrador X com Y) essas são manipulações apenas com os registradors, onde os resultados ficam armazendos nesses registradores, mas em que momento isso e gravado em memoria? Desculpa se a duvida e basica, mas isso provocou uma confusão na minha cabeça

Alguem conhece alguma boa documentação sobre, ou conseguiria me explicar esse fluxo?

 

Obrigado e uma boa Segunda a todos.
 

 

Não entendi bem sua pergunta, Luciano.

Contudo acredito que a sua dúvida seja com relação ao acesso à memória. Para agilizar o processamento na arquitetura IA-32, que foi a arquitetura utilizada no curso, eles arquitetaram os registradores como uma memória interna à CPU (onde ficam os registradores). Pois dessa forma o acesso aos dados computados ficaria MUITO mais rápido. O acesso a memória em si, para guardar dados por exemplo, é feito geralmente utilizando os segmentos. Segmento no caso é só um pré-endereço, basicamente denota o início do segmento de memória, como utilizado para dados. No caso dos dados é o DS, então geralmente os disassemblers representam isso como mov eax, dword ptr ds:[ebx]

Em assembly [] denotam acesso à alguma região da memória, portando se um registrador estiver dentro de colchetes vejam qual endereço está neste registrador.

Não sei se respondi bem a sua pergunta. Qualquer coisa manda um alô ai. :)
Abs!

Link para o comentário
Compartilhar em outros sites

9 horas atrás, Luciano Estevam Rangel disse:

OK, estamos manipulando os registradores, mas em que momento efetuamos o acesso na memoria RAM? Quando esses dados são enviados para a memoria?

Resposta curta: Nunca

Acho que você não entendeu o que é um registrador.
Um registrador é uma pequena área de memória que fica dentro do processador, ela pode ser acessada de maneira muito mais rápida do que a memória RAM. (por causa do hardware)

Quando você define o valor de um registrador está mudando o valor daquele registrador e apenas isso, o processador não vai "enviar" os dados nos registradores para a memória RAM.
O registrador não é tipo um cache para acessar a RAM.

Você deve ter se confundido porque deve ter lido algo sobre os registradores serem usados para armazenar dados temporariamente.
Essa explicação se dá porque é muito mais rápido se você carregar os dados da memória e jogar em um registrador, trabalhar com esses dados no registrador e depois você jogar os dados de volta na RAM.
O processador não vai fazer isso magicamente, é só um "truque" que o programador faz para otimizar tarefas de loop, por exemplo.

Se quiser aprender sobre as instruções da linguagem Assembly, eu recomendo usar como referência o site c9x.me.
Ele não tem um sistema de pesquisa mas você pode usar o Google para encontrar referências das instruções.
Exemplo de pesquisa: site:c9x.me mov

Com a pesquisa acima podemos encontrar a referência da instrução mov.
No site tem uma explicação sobre a instrução e até mesmo um pseudo-código para explicar o que exatamente a instrução faz.

Link para o comentário
Compartilhar em outros sites

50 minutos atrás, Felipe.Silva disse:

Resposta curta: Nunca

Acho que você não entendeu o que é um registrador.
Um registrador é uma pequena área de memória que fica dentro do processador, ela pode ser acessada de maneira muito mais rápida do que a memória RAM. (por causa do hardware)

Quando você define o valor de um registrador está mudando o valor daquele registrador e apenas isso, o processador não vai "enviar" os dados nos registradores para a memória RAM.
O registrador não é tipo um cache para acessar a RAM.

Você deve ter se confundido porque deve ter lido algo sobre os registradores serem usados para armazenar dados temporariamente.
Essa explicação se dá porque é muito mais rápido se você carregar os dados da memória e jogar em um registrador, trabalhar com esses dados no registrador e depois você jogar os dados de volta na RAM.
O processador não vai fazer isso magicamente, é só um "truque" que o programador faz para otimizar tarefas de loop, por exemplo.

Se quiser aprender sobre as instruções da linguagem Assembly, eu recomendo usar como referência o site c9x.me.
Ele não tem um sistema de pesquisa mas você pode usar o Google para encontrar referências das instruções.
Exemplo de pesquisa: site:c9x.me mov

Com a pesquisa acima podemos encontrar a referência da instrução mov.
No site tem uma explicação sobre a instrução e até mesmo um pseudo-código para explicar o que exatamente a instrução faz.

Quando utilizamos o Modo de Endereçamento direto o acesso é "direto" à memória, não? É utilizado um registrador de Segmento junto do Offset (que pode ser de 16 ou 32 bits) ou endereço linear do operando. Outra coisa é que um registrador pode sim conter um endereço de memória para ser lido e/ou escrito, a própria instrução MOV lê para depois escrever em algum lugar ?

Link para o comentário
Compartilhar em outros sites

@Leandro Fróes. Posso ter interpretado errado, mas eu trabalhei minha resposta com a teoria que o Luciano acreditava que os dados inseridos em um registrador em algum momento iriam parar na memória RAM.

Ele não fez perguntas tipo: "como gravar X em memória?"
Ele perguntou algo como: "em que momento isso vai ser gravado na memória?"

Ele também perguntou sobre como funcionaria esse fluxo (de dados).
Então eu acredito que na cabeça dele, ele pensava que os dados eram salvos "temporariamente" nos registradores e em algum momento o processador ia mover aquilo para a memória RAM.
 

1 hora atrás, Leandro Fróes disse:

Outra coisa é que um registrador pode sim conter um endereço de memória para ser lido e/ou escrito, a própria instrução MOV lê para depois escrever em algum lugar ?

Eu não afirmei o contrário. Creio que houve uma falha na nossa comunicação.

Link para o comentário
Compartilhar em outros sites

Pessoal, muito obrigado pelas respostas. Realmente estava confuso com relação ao fluxo de como as COISAS acontecem.

Exemplo:

Quando criamos um programa em assembly: A apartir do momento em que executo este programa, como seria esse fluxo? As instruções são carregadas na memoria RAM -> Depois tratadas pelo processador -> Devolvidas a memoria RAM. Ai começam a vir uma serie de duvidas, do tipo. Quem carrega as instruções na RAM? E o processador em alguma area interna? Nao sei se consegui explicar muito bem? Pelo que ficou do meu entendimento, o programa e carregado na memoria com uma seria de instruções, essas instruções sao carregadas nos registradores para permitir que o processador execute o calculo atraves da ALU, e depois retorna esse resultado para armazenamento em memoria.

Eu inclusive achei um doc na NET, que chega bem proximo a essa explicação.

http://savannah.c3sl.ufpr.br/pgubook/ProgrammingGroundUp-0-8.pdf

Desculpem novamente pelas duvidas basicas, mas pra quem nao tem essa vivencia, fica um pouco confuso. Mas é so o começo

Obrigado a todos.

Link para o comentário
Compartilhar em outros sites

1 hora atrás, Luciano Estevam Rangel disse:

Pessoal, muito obrigado pelas respostas. Realmente estava confuso com relação ao fluxo de como as COISAS acontecem.

Exemplo:

Quando criamos um programa em assembly: A apartir do momento em que executo este programa, como seria esse fluxo? As instruções são carregadas na memoria RAM -> Depois tratadas pelo processador -> Devolvidas a memoria RAM. Ai começam a vir uma serie de duvidas, do tipo. Quem carrega as instruções na RAM? E o processador em alguma area interna? Nao sei se consegui explicar muito bem? Pelo que ficou do meu entendimento, o programa e carregado na memoria com uma seria de instruções, essas instruções sao carregadas nos registradores para permitir que o processador execute o calculo atraves da ALU, e depois retorna esse resultado para armazenamento em memoria.

Eu inclusive achei um doc na NET, que chega bem proximo a essa explicação.

http://savannah.c3sl.ufpr.br/pgubook/ProgrammingGroundUp-0-8.pdf

Desculpem novamente pelas duvidas basicas, mas pra quem nao tem essa vivencia, fica um pouco confuso. Mas é so o começo

Obrigado a todos.

Vou falar como um executável roda no Windows de forma resumida, mas creio que já ajude a entender melhor...

Quando temos um .exe, por exemplo, aquilo é um arquivo e este está no DISCO (todos os bytes bonitinhos, um do lado do outro etc). Este arquivo tem um padrão, uma especificação para o Sistema Operacional entender o que de fato ele é, no caso de binários windows este formato se chama PE.

Quando executamos este programa quem entra em ação é o Loader. Este carinha lê os cabeçalhos do arquivo em disco para recolher informações de como irá rodar este binário, informações como quanto de mem´ória tem pra Stack, Heap, número de seções etc etc. Depois disso ele pega todas as seções do arquivo em disco e as mapeia para a memória. Só pra deixar claro, quando executamos um programa, ou seja, um processo passa a rodar etc, estamos trabalhando com endereços VIRTUAIS e não físicos (quem traduz de um pro outro é o próprio sistema operacional).

Cada seção possui um tipo de dado diferente, com permissões diferentes etc. A que contém código executável, por padrão na maioria das vezes, se chama .text. Nela estão os opcodes, ou seja, as INTRUÇÕES própriamente ditas que irão executar no processador, resumindo, bytes ? . Quando dizemos opcodes estamos dizendo as intruções em si, por exemplo, estes opcodes indicam uma chamada de função para o endereço 12345678:

E8 12 34 56 78

Note que a instrução e o endereço já estão nos opcodes dentro da seção em disco, o loader apenas as carrega em memória e depois o processador as executa, sem mágica ?. A mesma coisa acontece com os registradores, quando movemos algo de um registrador para o outro esta instrução já foi "decodificada" pelo compilador.

 

Espero ter ajudado, se quiser mais detalhes só pedir pra galera ou mandar uma msg no discord, vc que manda. Aconselho também a continuar acompanhando  o CERO, lá está sendo passada toda a base necessária pra você entender tudo isso.

 

Abs!!!

Link para o comentário
Compartilhar em outros sites

Leandro, muito obrigado pela explicação. Tinha um impasse na minha mente com esta questão de quando e como eram carregadas as informações na memoria, mas com sua explicaçõa acredito que ficou claro. Estou acompanhado o curso "CERO", e foi justamente por conta dele que acabei me interessando e querendo entender tudo direitinho.

Brigadao mais uma vez pra voce e pra todos que contribuiram com a minha duvida e dificuldade.

 

 

Link para o comentário
Compartilhar em outros sites

39 minutos atrás, Leandro Fróes disse:

Quando dizemos opcodes estamos dizendo as intruções em si, por exemplo, estes opcodes indicam uma chamada de função para o endereço 12345678:


E8 12 34 56 78

Desculpe, mas eu devo fazer três correções.

1) Na verdade o opcode nessa instrução é somente o byte E8. Os outros quatro bytes são um valor imediato.
Como uma analogia, pense que o opcode é "o nome da função" e os bytes seguintes são "argumentos" para essa função.
Algo como:
 

e8(0x78563412);


No artigo da Wikipédia sobre código de máquina eu detalhei como uma instrução em código de máquina da arquitetura x86 é formada.
Nele eu especifiquei sobre o formato da instrução e expliquei sobre os prefixos.

Em resumo uma instrução de código de máquina pode haver prefixo, opcode, o byte ModR/M, o byte SIB, endereço de deslocamento e um valor imediato.
É muito mais do que simplesmente opcode.

2) Na verdade os dados ficam em little-endian, logo a sequência de bytes 12 34 56 78 não formam o valor 0x12345678...
Mas sim 0x78563412.

3) Tanto jmps quanto calls "curtos" (near) em código de máquina trabalham com endereços relativos.
Então o call E8 12 34 56 78 na verdade não irá chamar uma instrução localizada no endereço 0x78563412...
Mas sim executar a instrução 0x78563412 bytes a frente.
Um exemplo básico:
 

org 0x100

call teste
ret

teste:
    nop
    ret


Trabalhando na sua suposição o call na linha 3 iria ser: E8 06 01
Ou seja, um call para o endereço 0x106.

Mas na verdade é E8 01 00
Que seria um call para a instrução 1 byte a frente. (ret tem o tamanho de 1 byte, C3)
 

Apenas calls e jmps "longos" (far) que usam o endereço exato. Os curtos usam endereço relativo.
Então por exemplo a instrução call 0x7000:teste iria gerar:
9A 06 01 00 70

Dessa vez sim especificando o endereço exato. (0x106 no segmento 0x7000)

Link para o comentário
Compartilhar em outros sites

45 minutos atrás, Felipe.Silva disse:

Desculpe, mas eu devo fazer três correções.

1) Na verdade o opcode nessa instrução é somente o byte E8. Os outros quatro bytes são um valor imediato.
Como uma analogia, pense que o opcode é "o nome da função" e os bytes seguintes são "argumentos" para essa função.
Algo como:
 


e8(0x78563412);


No artigo da Wikipédia sobre código de máquina eu detalhei como uma instrução em código de máquina da arquitetura x86 é formada.
Nele eu especifiquei sobre o formato da instrução e expliquei sobre os prefixos.

Em resumo uma instrução de código de máquina pode haver prefixo, opcode, o byte ModR/M, o byte SIB, endereço de deslocamento e um valor imediato.
É muito mais do que simplesmente opcode.

2) Na verdade os dados ficam em little-endian, logo a sequência de bytes 12 34 56 78 não formam o valor 0x12345678...
Mas sim 0x78563412.

3) Tanto jmps quanto calls "curtos" (near) em código de máquina trabalham com endereços relativos.
Então o call E8 12 34 56 78 na verdade não irá chamar uma instrução localizada no endereço 0x78563412...
Mas sim executar a instrução 0x78563412 bytes a frente.
Um exemplo básico:
 


org 0x100

call teste
ret

teste:
    nop
    ret


Trabalhando na sua suposição o call na linha 3 iria ser: E8 06 01
Ou seja, um call para o endereço 0x106.

Mas na verdade é E8 01 00
Que seria um call para a instrução 1 byte a frente. (ret tem o tamanho de 1 byte, C3)
 

Apenas calls e jmps "longos" (far) que usam o endereço exato. Os curtos usam endereço relativo.
Então por exemplo a instrução call 0x7000:teste iria gerar:
9A 06 01 00 70

Dessa vez sim especificando o endereço exato. (0x106 no segmento 0x7000)

Felipe, minha intenção com o exemplo foi meramente ilustrativa, até porque o Luciano está aparentemente começando a estudar sobre o assunto agora. Tendo em vista isto eu não me preocupei com tantos detalhes e os omiti, até porque tem trilhões ai nesse meio que o Loader executa, como GetProcAddress, preencher a IAT e por ai vai ? .

Sobre o opcode, sim, verdade, o exemplo foi ruim e errado.

Valeu de qualquer forma!!!

Link para o comentário
Compartilhar em outros sites

2 minutos atrás, Leandro Fróes disse:

Felipe, minha intenção com o exemplo foi meramente ilustrativa, até porque o Luciano está aparentemente começando a estudar sobre o assunto agora. Tendo em vista isto eu não me preocupei com tantos detalhes e os omiti, até porque tem trilhões ai nesse meio que o Loader executa, como GetProcAddress, preencher a IAT e por ai vai ? .

Sobre o opcode, sim, verdade, o exemplo foi ruim.

Valeu de qualquer forma!!! 

Não é questão de ter faltado detalhes, mas sim os que você deu estavam errados.
Mas eu entendo o seu ponto.

Link para o comentário
Compartilhar em outros sites

35 minutos atrás, Felipe.Silva disse:

Não é questão de ter faltado detalhes, mas sim os que você deu estavam errados.
Mas eu entendo o seu ponto.

Saquei, o que estava errado além do exemplo da instrução então? Até onde eu sei os 3 pontos levantados foram relacionados ao exemplo errado que dei da instrução (endianness etc), mas não aos outros detalhes. Gostaria muito de saber =D

Link para o comentário
Compartilhar em outros sites

Em 28/05/2018 em 20:02, Leandro Fróes disse:

Quando utilizamos o Modo de Endereçamento direto o acesso é "direto" à memória, não? É utilizado um registrador de Segmento junto do Offset (que pode ser de 16 ou 32 bits) ou endereço linear do operando. Outra coisa é que um registrador pode sim conter um endereço de memória para ser lido e/ou escrito, a própria instrução MOV lê para depois escrever em algum lugar ?

Em teoria o endereçamento "direto" acessa memória "diretamente", na prática, o acesso à memória é indireto, feito através dos caches.

É bom lembrar que um endereço lógico (segmento:offset) só é válido no modo de 16 bits. Em 32, o "segmento" é um "seletor", um índice, para uma das tabelas de "descritores de segmentos" (global ou local), que tem um endereço físico (sem paginação) ou linear (com paginação)... Mas, os seletores de segmento não são usados no modo x86-64 (a não ser pelo seletor CS, que mantém apenas o CPL (current privilege level)... Todos os demais seletores são inúteis (e não são usados - embora SS seja "salvo" e "recuperado" em interrupções!).

Link para o comentário
Compartilhar em outros sites

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...