É, para qualquer plataforma de computação de ISA, a linguagem de nível mais baixo que expressa tipicamente uma sequência linear e não estruturada de instruções de CPU, ela mapeia 1:1 para código de máquina, instruções binárias reais da CPU, e basicamente só difere do código de máquina real ao utilizar uma forma mais legível por humanos, dandos apelidos mnemônicos, para diferentes combinações de 1s e 0s. Assembly é convertido pelo assembler no código de máquina. Assembly é similar ao bytecode, mas o bytecode deve ser interpretado ou usado como uma representação intermediária em compiladores, enquanto assembly representa código nativo real executado por hardware. Antigamente, quando não havia linguagens high-level, Assembly era usado para escrever programas de computador. Hoje, a maioria dos programadores não escreve em assembly, a maioria dos programadores modernos nunca tocou em nada parecido, porque é difícil, leva muito tempo, e não é portátil. No entanto, softwares escritos em assembly são conhecidos por serem extremamente rápidos, pois o programador tem controle absoluto sobre cada instrução, isso não quer dizer que você não pode errar e escrever um programa lento em assembly. Assembly não é uma linguagem única, ela difere para cada ISA, cada modelo de CPU tem uma arquitetura diferente, entende um código de máquina diferente, logo, tem um assembly diferente, embora existam famílias padronizadas de assembly como x86 que funcionam em ampla gama de CPUs, portanto, assembly não é portátil, o programa geralmente não funciona em um tipo diferente de CPU ou em um sistema operacional diferente. E mesmo o mesmo tipo de linguagem assembly pode ter vários formatos de sintaxe diferentes que podem diferir no estilo de comentário, ordem de escrita de argumentos e até mesmo abreviações de instruções, x86 pode ser escrito na sintaxe Intel e AT&T.
Por motivos de não portabilidade, e também pelo fato de que "assembly é difícil", você não deve escrever seus programas diretamente em assembly, mas sim em uma linguagem de nível um pouco mais alto, como C, que pode ser compilada para qualquer assembly de CPU. Você deve saber pelo menos o básico da programação em assembly, pois um bom programador entrará em contato com isso às vezes, e.g, durante otimização hardcore, muitas linguagens oferecem uma opção para incorporar assembly inline em locais específicos, depuração, engenharia reversa, ao escrever um compilador C para uma plataforma completamente nova ou mesmo ao projetar sua própria plataforma nova. Você deve escrever ao menos um programa em assembly, ele lhe dá uma ótima visão de como um computador realmente funciona e você terá uma ideia melhor de como seus programas de alto nível são traduzidos para código de máquina, o que pode ajudá-lo a escrever um código otimizado. As linguagens assembly mais comuns que você encontrará hoje são x86, usada pela maioria das CPUs de desktop, e ARM, usada pela maioria das CPUs móveis, ambas são usadas por hardware proprietário e, embora um assembly em si não possa, até o momento, ser protegida por direitos autorais, mas arquiteturas associadas podem ser protegidas por patentes. Já RISC-V é uma alternativa aberta, mas não tão difundida. Outras linguagens de montagem incluem AVR, CPUs de 8 bits usadas, Arduinos e PowerPC. Uma linguagem assembly típica é mais do que um conjunto de apelidos para instruções de código de máquina, ela pode oferecer auxiliares como macros, algo como o pré-processador C, pseudoinstruções, comandos que na verdade são traduzidos para várias instruções, comentários, diretivas, rótulos nomeados para saltos, já que escrever endereços de salto literais seria extremamente tedioso.
Assembly é low-level, você não tem ajuda ou muita segurança de programação, além de modos de operação da CPU, você tem que fazer tudo sozinho, você estará lidando com convenções de chamada de função, interrupções, syscalls e suas convenções, quantos ciclos de CPU diferentes instruções levam, segmentos de memória, endianness, endereços bruto, saltos goto, quadros de chamada e por aí. Observe que apenas substituir mnemônicos de assembly por instruções de código binário de máquina ainda não é suficiente para fazer um programa executável. Mais coisas precisam ser feitas, como vincular bibliotecas e converter o resultado para algum formato executável, como elf, que contém cabeçalho com metainformações sobre o programa.
Em Assembly, não há estruturas de controle como if ou while, elas precisam ser implementadas manualmente usando rótulos e instruções de salto. Podem existir macros que imitam estruturas de controle. A aparência típica de um assembly é uma única coluna de instruções com argumentos, cada um representando uma instrução de máquina. O funcionamento da linguagem reflete a arquitetura de hardware, a maioria das arquiteturas é baseada em registradores, então geralmente há um pequeno número de registradores que podem ser chamados de algo como R0 a R15, ou A, B, C. Os registradores podem até ser subdivididos, e.g, em x86 há um registrador eax de 32 bits e metade dele pode ser usado como o registrador ax de 16 bits. Esses registradores são a memória mais rápida disponível, mais rápida que a memória RAM principal, e são usados para executar cálculos. Alguns registradores são de propósito geral e alguns são especiais, normalmente haverá, o registrador FLAGS que contém vários resultados de 1 bit de operações executadas, e.g overflow. Algumas instruções podem funcionar apenas com alguns registradores, pode haver um tipo de registrador ponteiro usado para armazenar endereços junto com instruções que funcionam com registrador, que se destina a implementar matrizes. Os valores podem ser movidos entre registradores e a memória principal.
Escrever instruções funciona de forma parecida como você chama uma função em linguagem high-level, você escreve seu nome e então argumentos, mas em assembly as coisas são complicadas porque uma instrução pode permitir apenas certos tipos de argumentos, permitir um registrador e constante imediata, tipo de literal/constante de número, mas não dois registradores. Você tem que ler a documentação para cada instrução. Enquanto em linguagem high-level você pode escrever expressões gerais como argumentos. Também não há dados complexos, o assembly só funciona com números de tamanhos diferentes, por exemplo. Strings são apenas sequências de números representando valores ASCII, cabe a você implementar strings terminadas em nulo ou strings no estilo Pascal. Ponteiros são apenas números representando endereços. Cabe a você interpretar um número como assinado ou não assinado, algumas instruções tratam números como não assinados, outras como assinados. As instruções normalmente são escritas como abreviações e seguem algumas convenções de nomenclatura não escritas para que diferentes linguagens assembly pelo menos pareçam semelhantes. Instruções comuns encontradas na maioria das linguagens assembly são:
Para instruções específicas de assembly, veja seus próprios artigos. No UNIX, o utilitário objdump do GNU binutils pode ser usado para desmontar programas compilados, visualizar as instruções do programa em assembly, outras ferramentas como ndisasm também podem ser usadas.
objdump -d my_compiled_program
Vamos agora escrever um programa UNIX simples em assembly x86 de 64 bits, usaremos a sintaxe AT&T usada pela GNU. Escreva o seguinte código-fonte em um arquivo program.s.
.global _start str: .ascii "Unixtopia\n" .text _start: mov $5, %rbx .loop: mov $1, %rax mov $1, %rdi mov $str, %rsi # mov $9, %rdx # syscall # sub $1, %rbx cmp $0, %rbx # jne .loop mov $60, %rax mov $0, %rdi syscall
O programa escreve Unixtopia 5 vezes usando um loop simples e uma chamada de sistema UNIX para gravar uma string na saída padrão, não funcionará em Windows e merdas semelhantes. Agora o código-fonte pode ser manualmente montado em executável executando assemblers como as ou nasm para obter o arquivo de objeto intermediário e então vinculá-lo com ld, mas para montar o código escrito acima simplesmente podemos usar um compilador.
tcc -nostdlib -no-pie -o program program.s
./program
Unixtopia Unixtopia Unixtopia Unixtopia Unixtopia
Como exercício, você pode objdump o executável final e ver que a saída corresponde ao código-fonte original. E tente desmontar alguns programas primitivos em C e veja como um compilador transforma instruções if ou funções em assembly.
Assembly é para um computador, o que tinta cara é para um pintor, o famoso "creme de lá creme", é simplesmente a maior magnificência que um programador pode desfrutar, Atente-se ao fato de que eu disse "para um computador", isso porquê assembly tende a ser difícil para seres hum.... CALA A BOCA PORRA, assembly não é nem um pouco difícil, e honestamente, depois de C, é a linguagem mais fácil que você vai usar, "ah, mais ela tem 381 instruções a depender da arquitetura", bom, quem disse que você precisa decorar as X instruções de uma linguagem assembly? Em primeiro lugar, devemos considerar que assembly traz um programador: Facilidade na identificação de erros, Detenção "total" sobre o controle do SEU programa, e claro, Portabilidade segura e completa, ou simplesmente FDP. Mas espera um pouco, isso não me parece muito correto, bom, se você duvida, então iremos dissecar cada um dos pontos:
F: Para provar o ponto F, vamos supor dois programadores, um programador JS, que chamaremos carinhosamente de OOPer, e um programador Assembly, que chamaremos de UNIXer. Agora, suponha que ambos se deparem com um erro em seu programa, o programador JS recebe a seguinte informação de seu caro NodeJS:
Sua primeira reação é bem clara: "Que porra é essa?", agora, nosso caro programador JS abrirá o google, e passará horas tentando resolver o erro. Perceba como o interpretador é pouco expositivo, ela não diz de forma direta onde está o erro, mas aponta pra um suposto problema nos modulos do NodeJS. Nosso "heroi", OOPer, então, faz uma pequena mudança na forma como executa seu código, ao invés de usar "node .", ele usa "node main.js", e seu código magicamente funciona.... Isso não me parece nem um pouco legível. Agora, vamos supor nosso caro programador Assembly, o sr. UNIXer, ele está programando, quando de repente, se depara com um simples "__segmentation fault__", só por essa informação, já fica óbvio onde está o erro, muito provavelmente algum problema com a stack, ou algum tipo de erro em relação um index de memória, rápido, simples, e principalmente DIRETO.
D: Bom, é bem autoexplicativo, se você deseja ter controle total sobre seu programa, simplesmente escreva em zero e um, é simples, poxa, olha para a seguinte bagunça:
Eu duvido que uma pessoa sã consiga identificar de primeira o que CARALHOS cada parte do seguinte programa faz, e assim, pode choramingar com seu Clean Code, ou sei lá, falar que "ain, o nome é autoexplicativo, mimimmimimimi", foda-se, é uma merda e ponto final, o programa é pouco explícito, ele não declara o que está fazendo, diferentemente do Assembly, veja bem a seguinte instrução:
mov $5, %ax
É tão autoexplicativa, que chega a ser semántica, bonita, bela, expléndida, olha como soa bem: "Mova 5 ao registrador ax". Viu, é simples, é direto, você tem controle total sobre o que vai fazer, e não depende de um conjunto de funções escrotas e desgraçadas e pra não me acusarem de perseguir o javascript, vou criticar C. Olhe a seguinte desgraça:
#include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> #ifndef major #include <sys/sysmacros.h> #endif #include <errno.h> #include <fcntl.h> #include <grp.h> #include <libgen.h> #include <pwd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "fs.h" #include "util.h"
Acha mesmo que enfiar meio quilo de depedências em seu programa é realmente legível? Óbvio que não, um programa precisa ser explícito, e ditar exatamente o que ele vai fazer, e quando ele vai fazer, declarando suas funções, suas dependências, e seu objetivo. No fim das contas, o programador não tem controle sobre o que está programando, e fica a mercê de um conjunto de dependências, porra, está no nome, "dependente", você pode ser comparado a um dependente químico quando se abstrai a isso.
OBS.: Atente-se a 'Detenção "total" sobre o controle do SEU programa', total está entre aspas porque você só terá 100% de controle sobre seu programa quando criar uma arquitetura do zero.
"Se você quiser fazer uma torta de maçã do zero, você deve primeiro inventar o Universo." - Carl Sagan
P: Espera aí! Portabilidade + Assembly na mesma frase? Sim, exatamente, portabilidade e assembly na mesma fase. A questão é, assembly é realmente portável, a diferença: assembly é qualitativamente portável, isso significa que, nas mãos de bons programadores, o port será basicamente identico ao produto original, lembre-se, estamos falando sobre esculpir uma obra de arte em um pedaço de bife, usando apenas um bisturi e uma colher, o seu programa será um reflexo exato do que você escreveu. Sendo assim, isso significa que sim, assembly é portável, a diferença é que assembly leve bastante tempo pra ser portado para outra plataforma/sistema. Para meios de comparação, usaremos qualquer software originalmente feito para windows, ou que a empresa só dá enfoque ao windows. O software se torna péssimo, depende de 10^(30^10) bibliotecas diferentes, e é extremamente instável, poxa, fazer software portável se usando de ferramentas já abstratas é a mesma coisa que construir um prédio de 10 andares com uma sustentação feita de areia, é ÓBVIO que ele vai desabar.
Em resumo, assembly é incrível, use assembly.
Impulsionado por nada. Todo conteúdo é disponível sob CC0 1.0 domínio público. Envie comentários e correções para Mr. Unix em victor_hermian@disroot.org.