Assembly

1. Assembly

João vitor kilberyt
joaovitor02

(usa Outra)

Enviado em 13/05/2016 - 19:03h

Boa noite, estou começando a estudar assembly e tenho dúvidas sobre o exercício que estou resolvendo. O primeiro estou tentando criar uma seção de variáveis nao inicializadas, e as inicializadas e fazer as seguintes cópias
n1 = v1
n2 = v2
n3 = v3
fiz este código, gostaria de ver se está realmente certo o que eu fiz ou se tem possível melhoras, usar algum outro registrador
section .data
v1: db 10d
v2: dd -20d
v3: dq -30d

section .bss
n1: resb 1
n2: resw 2
n3: resd 4

section .text
global _start

_start:
mov al , [v1]
mov [n1] , al
mov ebx , [v2]
mov [n2] , ebx
mov al , [v3]
mov [n3] , al

fim:
mov rax, 1
mov rbx, 0
int 0x80



  


2. Re: Assembly

Paulo
paulo1205

(usa Ubuntu)

Enviado em 15/05/2016 - 06:26h

Assembly não é muito a minha praia, mas vamos lá.

joaovitor02 escreveu:

Boa noite, estou começando a estudar assembly e tenho dúvidas sobre o exercício que estou resolvendo. O primeiro estou tentando criar uma seção de variáveis nao inicializadas, e as inicializadas e fazer as seguintes cópias
n1 = v1
n2 = v2
n3 = v3
fiz este código, gostaria de ver se está realmente certo o que eu fiz ou se tem possível melhoras, usar algum outro registrador


Você está usando sintaxe Intel. Está usando NASM, gas com opção “-msyntax=intel”, ou algum outro assembler? Algumas das coisas que eu vou dizer podem depender dos defaults do seu assembler.

section .data
v1: db 10d
v2: dd -20d
v3: dq -30d


Cuidado com o alinhamento de variáveis. v1, v2 e v3 têm tamanhos, respectivamente, de 1, 2 e 4 bytes. Quando eu testei seu programa aqui (depois de adaptá-lo para sintaxe AT&T, usando gas, antes de descobrir que o gas aceita também sintaxe Intel), essa ordem de declaração deixou v2 e v3 em endereços ímpares, o que pode prejudicar o desempenho de acesso.

Se o seu assembler fornecer alinhamento otimizado automaticamente, um efeito colateral pode ser um aumento no tamanho do código (nesse caso, de apenas um byte, ao empurrar v2 um byte para frente, para alinhá-lo num endereço múltiplo de 2; v3 vai de carona, e acaba alinhado num múltiplo de 4, como deveria mesmo ser).

Curiosamente (ou nem tanto), se você inverter a ordem das variáveis (nesse caso) e já começar alinhado em múltiplo de 4, não terá nenhum problema de alinhamento.

section .bss
n1: resb 1
n2: resw 2
n3: resd 4


Mesmos comentários a respeito de alinhamento.

section .text
global _start

_start:
mov al , [v1]
mov [n1] , al
mov ebx , [v2]
mov [n2] , ebx


Na cópia de v2 para n2, você usou um registrador de 4 bytes (EBX) para copiar um dado de apenas 2 (que caberia perfeitamente bem em apenas BX). Na leitura, isso pode significar perda de desempenho por possíveis problemas de alinhamento (BX supostamente usaria alinhamento 2 para não perder desempenho; para EBX, esse alinhamento teria de ser 4). Na escrita, além do alinhamento, existe a possibilidade de você acabar sobrescrevendo dois bytes adjacentes a n2 e que talvez pertençam a outro dado (isso de fato ocorreu na versão que eu testei aqui).

Se você garantir que os dados (particularmente o de destino) estão alinhados em endereços múltiplos de 4, não haverá maiores problemas com as transferências feitas com um registrador de 4 bytes (aliás, pode até ser uma otimização, pois as instruções que movimentam 2 bytes são ocupam um byte a mais na memória do que as que manipulam 1, 4 ou 8 bytes). Entretanto, se você eventualmente for fazer contas com tais dados, terá de se lembrar de usar apenas os bytes relevantes, e o leitor do seu código eventualmente poderá estranhar se você fizer transferências com EBX mas operar apenas com BX.

   mov al   , [v3] 
mov [n3] , al


Aqui você fez meio que o oposto: de um dado declarado como tendo 4 bytes de comprimento, está transferindo apenas um byte. Não é um erro, mas certifique-se de que é isso mesmo que você quer.

fim:
mov rax, 1
mov rbx, 0


Cada uma dessas instruções gera 7 bytes depois de montadas, num total de 14. Se você as substituir por

   xor rax, rax
inc rax
xor rbx, rbx

terá os mesmos resultados, mas com um total de apenas 9 bytes (3 de cada instrução; no caso da atribuição de RAX, fica um byte mais curta, na de RBX, diminui 4 bytes). Entretanto, note que essa alternativa altera os valores dos flags. Se os valores dos flags forem importantes para a operação que você fizer em seguida, a forma original, com MOV, pode ser preferível.

   int 0x80 



Outra coisa, de que quase esqueci (e aqui vou usar a sintaxe AT&T porque não sei como é o equivalente com sintaxe Intel): em todas as operações com mov de/para endereço fixo, as formas “mov v1(%rip), %al” e “mov %al, n1(%rip)” produzem, cada uma, um byte a menos do que as respectivas e equivalentes versões “mov v1, %al” e “mov %al, n1”, que foi o que você usou originalmente.


3. Assembly

Bruno
uNclear

(usa Slackware)

Enviado em 15/05/2016 - 10:23h

joaovitor02 escreveu:

Boa noite, estou começando a estudar assembly e tenho dúvidas sobre o exercício que estou resolvendo. O primeiro estou tentando criar uma seção de variáveis nao inicializadas, e as inicializadas e fazer as seguintes cópias
n1 = v1
n2 = v2
n3 = v3
fiz este código, gostaria de ver se está realmente certo o que eu fiz ou se tem possível melhoras, usar algum outro registrador
section .data
v1: db 10d
v2: dd -20d
v3: dq -30d

section .bss
n1: resb 1
n2: resw 2
n3: resd 4

section .text
global _start

_start:
mov al , [v1]
mov [n1] , al
mov ebx , [v2]
mov [n2] , ebx
mov al , [v3]
mov [n3] , al

fim:
mov rax, 1
mov rbx, 0
int 0x80


Nao vou contribuir para sua pergunta ;s, mas queria saber se vc tem um conteudo legal para estudar assembly, eu nao encontrei muitos


4. Re: Assembly

Perfil removido
removido

(usa Nenhuma)

Enviado em 15/05/2016 - 11:44h

Eu tenho uns programas bem meia-boca de Assembly na minha conta aqui no VOL.
Um detalhe desse programa aqui deste tópico é que rax é um registrador de 64 bits e int 0x80 é a chamada de 32 bits.

----------------------------------------------------------------------------------------------------------------
# apt-get purge systemd (não é prá digitar isso!)

Encryption works. Properly implemented strong crypto systems are one of the few things that you can rely on. Unfortunately, endpoint security is so terrifically weak that NSA can frequently find ways around it. — Edward Snowden



5. Re: Assembly

Paulo
paulo1205

(usa Ubuntu)

Enviado em 15/05/2016 - 16:39h

listeiro_037 escreveu:

Eu tenho uns programas bem meia-boca de Assembly na minha conta aqui no VOL.
Um detalhe desse programa aqui deste tópico é que rax é um registrador de 64 bits e int 0x80 é a chamada de 32 bits.


Hummm... Acho que não é bem isso.

A instrução INT é uma instrução para a execução de desvios incondicionais, para um endereço de memória que fica guardado numa tabela de endereços, que geralmente é inicializada pelo sistema operacional assim que ele é carregado para a memória. Com isso, as aplicações não precisam se preocupar com qual é o endereço para onde têm de desviar, mas apenas chamam a interrupção por software, que consulta a tabela definida pelo S.O. Essa forma de acesso era usada pelos PCs com Intel desde o MS-DOS ou o CP/M-86. No DOS, a interrupção para chamada ao sistema era a 0x21; no Linux, é a 0x80.

Nos processadores mais modernos, chamar o sistema operacional é mais custoso do que era na época do MS-DOS. Os processadores antigos não tinham separação de memória entre diferentes processos, nem diferenças de privilégios entre o sistema operacional (modo kernel, ring 0) e as aplicações em nível de usuário (modo de aplicações, ring 3). Isso passou a existir nos processadores Intel a partir do 80386 (em alguma medida, já existia proteção de memória no 80286, mas o suporte era muito fraco, e não foi muito usado). Do 80386 em diante, chamar o S.O. implica ter de mudar modo de operação, mudar uma série de registradores de segmentos, etc.

Com o Pentium II, a Intel criou um outro mecanismo para otimizar chamadas ao sistema, que é ligeiramente mais eficiente do que usar interrupções. Não tem muito a ver com o binário ser de 32 bits ou de 64, portanto. De todo modo, interrupções continuam funcionando, para ter compatibilidade com programas antigos.


6. Re: Assembly

Perfil removido
removido

(usa Nenhuma)

Enviado em 15/05/2016 - 17:24h

Mas para 64 bits a tabela de códigos a serem preenchidos nos registradores é diferente das de 32 bits.
Além disto são usados outros registradores.

Enquanto em 32 bits são usados os mais comuns como eax, ebx, ecx e edx, em 64 bits são usados rax, rdi, rsi e rdx.
E a chamada ao sistema não é com int 0x80, mas com simplesmente a palavra syscall.

Até aí constatei na prática. O problema é que material sobre este tema é pouco encontrado.
Para 64 bits então piorou. Eu tenho um livro de Assembly para Linux 32 bits.
Aprender um mínimo de 64 bits deu trabalho. E ainda assim pode estar faltando algo nessa minha teoria.

----------------------------------------------------------------------------------------------------------------
# apt-get purge systemd (não é prá digitar isso!)

Encryption works. Properly implemented strong crypto systems are one of the few things that you can rely on. Unfortunately, endpoint security is so terrifically weak that NSA can frequently find ways around it. — Edward Snowden



7. Re: Assembly

Paulo
paulo1205

(usa Ubuntu)

Enviado em 16/05/2016 - 08:43h

Listeiro,

Tem razão, e obrigado pelos esclarecimentos. Pelo que você falou, e com algumas pesquisas a respeito, acho que a coisa ficou mais bem explicada para mim.

Historicamente falando, o mecanismo de chamada ao S.O. via instrução, como alternativa ao uso de interrupção, foi introduzido com o Pentium II (32 bits). Essa instrução se chamava SYSENTER, mas aparentemente seu uso não foi incorporado ao Linux/x86. Especulo que sua não adoção tem relação com compatibilidade com processadores antigos, que não implementam tal instrução mas que ainda são suportados pelo kernel do Linux.

Na arquitetura amd64, uma instrução semelhante (mas não exatamente idêntica) foi incorporada a todos os processadores da linha. Sendo uma instrução universal para todos os processadores dessa arquitetura, o Linux/x86_64 a adotou como mecanismo de invocação ao kernel. Essa instrução se chama SYSCALL.

Contudo, é interessante notar que a presença da instrução SYSCALL não impede o uso de interrupções, nem implica que seu uso necessariamente significa código de 32 bits. Escolhas feitas pelo Linux para uma determinada arquitetura não são necessariamente verdades para a arquitetura como um todo, fora do contexto do Linux.

Pelo que eu li no volume I do manual de programação da AMD, quando a instrução INT utiliza vetores de interrupção de 4 bytes (dois de segmento e dois de offset, como nos processadores de 16 bits) quando operando em modo real, 8 bytes (4 de segmentação/paginação e 4 de offset) em modo protegido de 32 bits, e 16 bytes (8+8) em modo de 64 bits. Logo, o Linux poderia ter usado interrupções para chamar o S.O. também em 64 bits, se assim tivesse sido definido. De certa maneira, até se fez isso: o programa discutido neste tópico é um exemplo de programa que usa instruções de 64 bits foi (e que, mesmo que não as usasse, poderia ser) montado com um assembler de 64 bits e ligado com um linker de 64 bits (que foi, aliás, como eu o testei), e que funciona perfeitamente bem com a chamada à interrupção. Posso estar enganado, mas se esse programa, que não pede para mudar o modo de execução de 64 bits para 32 bits, pode chamar a interrupção, então o vetor de interrupções está usando os offsets de 16 bytes, requeridos pelo modo de operação de 64 bits. Daí, concluo que o Linux programou o vetor de interrupções do modo adequado ao uso em 64 bits -- mesmo que de fato ele limite os operandos a 32 bits.

Com relação à mudança no modo de passar argumentos para o kernel, por meio de registradores distintos, acho que a mudança faz bastante sentido. Comparativamente, a arquitetura i386 tinha menos registradores; a fim de poupá-los, as funções normais do programa passavam argumentos entre si através da pilha, e as interrupções, que não podiam usar diretamente a pilha sem algum esforço adicional, é que se comunicavam diretamente através dos registradores. Na amd64, com um número maior de registradores, usar tais registradores já é o modo normal de comunicação entre funções, de acordo com uma convenção que indica a ordem em que os registradores devem ser usados para sucessivos argumentos. Essa mesma convenção (com possíveis adaptações) foi então estendida ao uso com interrupções.


8. Re: Assembly

Perfil removido
removido

(usa Nenhuma)

Enviado em 16/05/2016 - 15:13h

Blz.

Eu cheguei a testar um dos programas que eu fiz de Assembly 32 bits x86 para ver se o montador acusava erro e o GNU Assembly para x86_64 64 bits e ele criou o executável de código 32 bits funcionando perfeitamente no ambiente de 64 bits. Vai saber ...

----------------------------------------------------------------------------------------------------------------
# apt-get purge systemd (não é prá digitar isso!)

Encryption works. Properly implemented strong crypto systems are one of the few things that you can rely on. Unfortunately, endpoint security is so terrifically weak that NSA can frequently find ways around it. — Edward Snowden







Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts