Linguagem de montagem
Na programação de computadores, linguagem de montagem (ou linguagem de montagem, ou código de máquina simbólico), muitas vezes referido simplesmente como Assembly e comumente abreviado como ASM ou asm, é qualquer linguagem de programação de baixo nível com uma correspondência muito forte entre as instruções na linguagem e a arquitetura' s instruções de código de máquina. A linguagem assembly geralmente tem uma instrução por instrução de máquina (1:1), mas constantes, comentários, diretivas assembler, rótulos simbólicos de, por exemplo, locais de memória, registradores e macros geralmente também são suportados.
O primeiro código assembly no qual uma linguagem é usada para representar instruções de código de máquina é encontrado na obra de Kathleen e Andrew Donald Booth de 1947, Coding for A.R.C.. O código assembly é convertido em código de máquina executável por um programa utilitário conhecido como assembler. O termo "montador" é geralmente atribuído a Wilkes, Wheeler e Gill em seu livro de 1951 A preparação de programas para um computador digital eletrônico, que, no entanto, usou o termo para significar "um programa que monta outro programa que consiste de várias seções em um único programa". O processo de conversão é referido como montagem, como em montagem do código-fonte. A etapa computacional quando um montador está processando um programa é chamada de tempo de montagem.
Como o assembly depende das instruções do código de máquina, cada linguagem assembly é específica para uma determinada arquitetura de computador.
Às vezes, há mais de um montador para a mesma arquitetura e, às vezes, um montador é específico para um sistema operacional ou para sistemas operacionais específicos. A maioria das linguagens assembly não fornece sintaxe específica para chamadas do sistema operacional, e a maioria das linguagens assembly pode ser usada universalmente com qualquer sistema operacional, pois a linguagem fornece acesso a todos os recursos reais do processador, sobre os quais todos os mecanismos de chamada do sistema se baseiam. Em contraste com as linguagens de montagem, a maioria das linguagens de programação de alto nível geralmente é portátil em várias arquiteturas, mas requer interpretação ou compilação, tarefas muito mais complicadas do que a montagem.
Nas primeiras décadas da computação, era comum que tanto a programação de sistemas quanto a programação de aplicativos ocorressem inteiramente em linguagem assembly. Embora ainda insubstituível para alguns propósitos, a maior parte da programação agora é conduzida em linguagens interpretadas e compiladas de alto nível. Em "No Silver Bullet", Fred Brooks resumiu os efeitos da mudança da programação em linguagem assembly: "Certamente o golpe mais poderoso para produtividade, confiabilidade e simplicidade de software tem sido o uso progressivo de alta linguagens de nível para programação. A maioria dos observadores atribui a esse desenvolvimento pelo menos um fator de cinco em produtividade e com ganhos concomitantes em confiabilidade, simplicidade e compreensão."
Atualmente, é comum usar pequenas quantidades de código em linguagem assembly em sistemas maiores implementados em uma linguagem de nível superior, por motivos de desempenho ou para interagir diretamente com o hardware de maneiras não suportadas pela linguagem de nível superior. Por exemplo, pouco menos de 2% da versão 4.9 do código-fonte do kernel do Linux é escrito em assembly; mais de 97% está escrito em C.
Sintaxe da linguagem de montagem
A linguagem de montagem usa um mnemônico para representar, por exemplo, cada instrução de máquina de baixo nível ou opcode, cada diretiva, normalmente também cada registro arquitetônico, sinalizador, etc. Alguns dos mnemônicos podem ser incorporados e alguns definidos pelo usuário. Muitas operações requerem um ou mais operandos para formar uma instrução completa. A maioria dos montadores permite constantes nomeadas, registradores e rótulos para localizações de programa e memória e pode calcular expressões para operandos. Assim, os programadores ficam livres de cálculos repetitivos tediosos e os programas montadores são muito mais legíveis do que o código de máquina. Dependendo da arquitetura, esses elementos também podem ser combinados para instruções específicas ou modos de endereçamento usando deslocamentos ou outros dados, bem como endereços fixos. Muitos montadores oferecem mecanismos adicionais para facilitar o desenvolvimento do programa, controlar o processo de montagem e auxiliar na depuração.
Alguns são orientados a colunas, com campos específicos em colunas específicas; isso era muito comum para máquinas que usavam cartões perfurados na década de 1950 e início da década de 1960. Alguns montadores têm sintaxe de forma livre, com campos separados por delimitadores, por exemplo, pontuação, espaço em branco. Alguns montadores são híbridos, com, por exemplo, labels, em uma coluna específica e outros campos separados por delimitadores; isso se tornou mais comum do que a sintaxe orientada a colunas na década de 1960.
Sistema IBM/360
Todos os montadores IBM para System/360, por padrão, possuem um rótulo na coluna 1, campos separados por delimitadores nas colunas 2-71, um indicador de continuação na coluna 72 e um número de sequência nas colunas 73-80. O delimitador para label, opcode, operandos e comentários são espaços, enquanto os operandos individuais são separados por vírgulas e parênteses.
Terminologia
- A macro assembler é um montador que inclui uma instalação de macroinstrução para que (parametro) texto de linguagem de montagem pode ser representado por um nome, e esse nome pode ser usado para inserir o texto expandido em outro código.
- Código aberto refere-se a qualquer entrada do montador fora de uma definição de macro.
- A cruz montador (ver também cross compiler) é um montador que é executado em um computador ou sistema operacional (o anfitrião sistema) de um tipo diferente do sistema em que o código resultante é executado (o sistema de destino). A montagem cruzada facilita o desenvolvimento de programas para sistemas que não têm recursos para apoiar o desenvolvimento de software, como um sistema embarcado ou um microcontrolador. Nesse caso, o código de objeto resultante deve ser transferido para o sistema de destino, via memória somente leitura (ROM, EPROM, etc.), um programador (quando a memória somente leitura é integrada no dispositivo, como em microcontroladores), ou um link de dados usando uma cópia exata de bit-por-bit do código de objeto ou uma representação baseada em texto desse código (como Intel hex ou Motorola S-record).
- A montador de alto nível é um programa que fornece abstrações linguísticas mais frequentemente associadas a linguagens de alto nível, como estruturas de controle avançadas (IF/THEN/ELSE, DO CASE, etc.) e tipos de dados abstratos de alto nível, incluindo estruturas/regras, sindicatos, classes e conjuntos.
- A micromontagem é um programa que ajuda a preparar um microprograma, chamado firmware, para controlar a operação de baixo nível de um computador.
- A meta-montagem é "um programa que aceita a descrição sintática e semântica de uma linguagem de montagem e gera um montador para essa linguagem", ou que aceita um arquivo de origem do montador junto com tal descrição e monta o arquivo de origem de acordo com essa descrição. "Meta-Symbol" montadores para a série SDS 9 e SDS Sigma de computadores são meta-montadores. Sperry Univac também forneceu um Meta-Assembler para a série UNIVAC 1100/2200.
- Montador de linha (ou montador embutido) é o código do montador contido dentro de um programa de linguagem de alto nível. Isso é mais frequentemente usado em programas de sistemas que precisam de acesso direto ao hardware.
Conceitos-chave
Montador
Um programa assembler cria código de objeto traduzindo combinações de mnemônicos e sintaxe para operações e modos de endereçamento em seus equivalentes numéricos. Essa representação normalmente inclui um código de operação ("opcode"), bem como outros bits e dados de controle. O montador também calcula expressões constantes e resolve nomes simbólicos para locais de memória e outras entidades. O uso de referências simbólicas é uma característica fundamental dos montadores, economizando cálculos tediosos e atualizações manuais de endereços após modificações no programa. A maioria dos montadores também inclui recursos de macro para realizar substituição textual – por exemplo, para gerar sequências curtas comuns de instruções como inline, em vez de sub-rotinas chamadas.
Alguns montadores também podem executar alguns tipos simples de otimizações específicas do conjunto de instruções. Um exemplo concreto disso pode ser os onipresentes montadores x86 de vários fornecedores. Chamados de dimensionamento de salto, a maioria deles é capaz de realizar substituições de instrução de salto (saltos longos substituídos por saltos curtos ou relativos) em qualquer número de passagens, mediante solicitação. Outros podem até fazer um simples rearranjo ou inserção de instruções, como alguns montadores para arquiteturas RISC que podem ajudar a otimizar um escalonamento de instruções sensato para explorar o pipeline da CPU da maneira mais eficiente possível.
Assemblers estão disponíveis desde a década de 1950, como o primeiro passo acima da linguagem de máquina e antes das linguagens de programação de alto nível, como Fortran, Algol, COBOL e Lisp. Também houve várias classes de tradutores e geradores de código semiautomáticos com propriedades semelhantes às linguagens assembly e de alto nível, com o Speedcode talvez como um dos exemplos mais conhecidos.
Pode haver vários montadores com sintaxe diferente para uma determinada CPU ou arquitetura de conjunto de instruções. Por exemplo, uma instrução para adicionar dados de memória a um registro em um processador da família x86 pode ser add eax,[ebx]
, na sintaxe Intel original, enquanto isso seria escrito addl (%ebx),%eax
na sintaxe AT&T usada pelo GNU Assembler. Apesar das diferentes aparências, diferentes formas sintáticas geralmente geram o mesmo código de máquina numérico. Um único montador também pode ter modos diferentes para suportar variações nas formas sintáticas, bem como suas interpretações semânticas exatas (como sintaxe FASM, sintaxe TASM, modo ideal, etc., no caso especial de programação em assembly x86).
Número de passes
Existem dois tipos de montadores com base em quantas passagens pelo código-fonte são necessárias (quantas vezes o montador lê o código-fonte) para produzir o arquivo objeto.
- Montadores de uma passagem processar o código fonte uma vez. Para símbolos usados antes de serem definidos, o montador irá emitir "errata" após a definição eventual, dizendo ao linker ou ao carregador para corrigir os locais onde os símbolos ainda não definidos foram usados.
- Multi-pass montadores criar uma tabela com todos os símbolos e seus valores nos primeiros passes, em seguida, use a tabela em passes posteriores para gerar código.
Em ambos os casos, o montador deve ser capaz de determinar o tamanho de cada instrução nas passagens iniciais para calcular os endereços dos símbolos subsequentes. Isso significa que se o tamanho de uma operação referente a um operando definido posteriormente depender do tipo ou distância do operando, o montador fará uma estimativa pessimista ao encontrar a operação pela primeira vez e, se necessário, preenchê-la com um ou mais "sem operação" instruções em uma passagem posterior ou na errata. Em um montador com otimização de olho mágico, os endereços podem ser recalculados entre as passagens para permitir a substituição de código pessimista por código adaptado à distância exata do alvo.
O motivo original para o uso de montadores de uma passagem era o tamanho da memória e a velocidade de montagem - geralmente uma segunda passagem exigiria o armazenamento da tabela de símbolos na memória (para lidar com referências futuras), rebobinar e reler a fonte do programa na fita, ou reler um baralho de cartas ou uma fita de papel perfurada. Computadores posteriores com memórias muito maiores (especialmente armazenamento em disco) tinham espaço para realizar todo o processamento necessário sem essa releitura. A vantagem do montador multi-pass é que a ausência de errata torna o processo de ligação (ou o carregamento do programa se o montador produzir diretamente o código executável) mais rápido.
Exemplo: no trecho de código a seguir, um montador de passagem única seria capaz de determinar o endereço da referência inversa BKWD ao montar a instrução S2, mas não seria capaz de determinar o endereço da referência direta FWD ao montar a instrução de ramificação S1; de fato, FWD pode ser indefinido. Um montador de duas passagens determinaria ambos os endereços na passagem 1, para que fossem conhecidos ao gerar o código na passagem 2.
S1 B FWD... FWD EQU ... BKWD EQU ... S2 B BKWD
Montadores de alto nível
Assembladores de alto nível mais sofisticados fornecem abstrações de linguagem como:
- Procedimento de alto nível / declarações de função e invocações
- Estruturas avançadas de controle (IF/THEN/ELSE, SWITCH)
- Tipos de dados abstratos de alto nível, incluindo estruturas / registros, sindicatos, classes e conjuntos
- Processamento de macros sofisticado (embora disponível em montadores comuns desde o final da década de 1950 para, por exemplo, a série IBM 700 e a série IBM 7000, e desde a década de 1960 para IBM System/360 (S/360), entre outras máquinas)
- Características de programação orientadas a objetos, como classes, objetos, abstração, polimorfismo e herança
Consulte o design da linguagem abaixo para obter mais detalhes.
Linguagem de montagem
Um programa escrito em linguagem assembly consiste em uma série de instruções mnemônicas do processador e meta-declarações (conhecidas também como operações declarativas, diretivas, pseudo-instruções, pseudo-operações e pseudo-operações), comentários e dados. As instruções em linguagem assembly geralmente consistem em um mnemônico opcode seguido por um operando, que pode ser uma lista de dados, argumentos ou parâmetros. Algumas instruções podem ser "implícitas" o que significa que os dados sobre os quais a instrução opera são definidos implicitamente pela própria instrução - tal instrução não requer um operando. A instrução resultante é traduzida por um montador em instruções de linguagem de máquina que podem ser carregadas na memória e executadas.
Por exemplo, a instrução abaixo diz a um processador x86/IA-32 para mover um valor imediato de 8 bits para um registrador. O código binário para esta instrução é 10110 seguido por um identificador de 3 bits para qual registrador usar. O identificador para o registrador AL é 000, então o seguinte código de máquina carrega o registrador AL com os dados 01100001.
10110000 01100001
Este código de computador binário pode ser mais legível por humanos, expressando-o em hexadecimal da seguinte forma.
B0 61
Aqui, B0
significa 'Mover uma cópia do seguinte valor para AL, e 61
é uma representação hexadecimal do valor 01100001, que é 97 em decimal. A linguagem assembly para a família 8086 fornece o mnemônico MOV (uma abreviação de move) para instruções como esta, portanto, o código de máquina acima pode ser escrito da seguinte maneira em linguagem assembly, completo com um comentário explicativo, se necessário, após o ponto e vírgula. Isso é muito mais fácil de ler e lembrar.
MOVIMENTO - Sim., 61h ; Carga AL com 97 decimal (61 hex)
Em algumas linguagens assembly (incluindo esta), o mesmo mnemônico, como MOV, pode ser usado para uma família de instruções relacionadas para carregar, copiar e mover dados, sejam eles valores imediatos, valores em registradores ou locais de memória apontado por valores em registradores ou por endereços imediatos (também conhecidos como diretos). Outros montadores podem usar mnemônicos opcode separados, como L para "mover memória para registrar", ST para "mover registro para memória", LR para "mover registro para registrar", MVI para "mover operando imediato para a memória", etc.
Se o mesmo mnemônico for usado para instruções diferentes, isso significa que o mnemônico corresponde a vários códigos de instrução binária diferentes, excluindo dados (por exemplo, o 61h neste exemplo), dependendo dos operandos que seguem o mnemônico. Por exemplo, para as CPUs x86/IA-32, a sintaxe da linguagem assembly Intel MOV AL, AH
representa uma instrução que move o conteúdo do registrador AH para o registrador AL. A forma hexadecimal desta instrução é:
88 E0
O primeiro byte, 88h, identifica um movimento entre um registrador de tamanho de byte e outro registrador ou memória, e o segundo byte, E0h, é codificado (com três campos de bit) para especificar que ambos os operandos são registradores, o a origem é AH e o destino é AL.
Em um caso como este em que o mesmo mnemônico pode representar mais de uma instrução binária, o montador determina qual instrução gerar examinando os operandos. No primeiro exemplo, o operando 61h
é uma constante numérica hexadecimal válida e não é um nome de registro válido, portanto, somente a instrução B0
pode ser aplicável. No segundo exemplo, o operando AH
é um nome de registro válido e não uma constante numérica válida (hexadecimal, decimal, octal ou binária), portanto, somente a instrução 88
pode seja aplicável.
As linguagens de montagem são sempre projetadas para que esse tipo de inequívoco seja universalmente aplicado por sua sintaxe. Por exemplo, na linguagem assembly Intel x86, uma constante hexadecimal deve começar com um dígito numérico, para que o número hexadecimal 'A' (igual a dez decimais) seria escrito como 0Ah
ou 0AH
, não AH
, especificamente para que não pareça ser o nome do registrador AH. (A mesma regra também evita ambiguidade com os nomes dos registradores BH, CH e DH, bem como com qualquer símbolo definido pelo usuário que termina com a letra H e contém apenas caracteres que são dígitos hexadecimais, como a palavra "BEACH".)
Voltando ao exemplo original, enquanto o opcode x86 10110000 (B0
) copia um valor de 8 bits no registrador AL, 10110001 (B1
) o move para CL e 10110010 (B2
) o faz para DL. Exemplos de linguagem de montagem para estes seguem.
MOVIMENTO - Sim., 1h ; Carga AL com valor imediato 1MOVIMENTO CLUB, 2h ; Carga CL com valor imediato 2MOVIMENTO DL, 3h ; Carga DL com valor imediato 3
A sintaxe do MOV também pode ser mais complexa, como mostram os exemplos a seguir.
MOVIMENTO EAX, Não.EBX] ; Mover os 4 bytes em memória no endereço contido no EBX para EAXMOVIMENTO Não.ESI+EAX] CLUB ; Mover o conteúdo de CL para o byte no endereço ESI+EAXMOVIMENTO DS, DX ; Mover o conteúdo do DX para o registro de segmento DS
Em cada caso, o mnemônico MOV é traduzido diretamente para um dos opcodes 88-8C, 8E, A0-A3, B0-BF, C6 ou C7 por um montador, e o programador normalmente não precisa saber ou lembrar qual.
Transformar a linguagem assembly em código de máquina é o trabalho de um montador, e o inverso pode, pelo menos parcialmente, ser alcançado por um desmontador. Ao contrário das linguagens de alto nível, há uma correspondência um-para-um entre muitas instruções de montagem simples e instruções de linguagem de máquina. No entanto, em alguns casos, um montador pode fornecer pseudoinstruções (essencialmente macros) que se expandem em várias instruções de linguagem de máquina para fornecer a funcionalidade comumente necessária. Por exemplo, para uma máquina que não possui um "ramo se maior ou igual" instrução, um montador pode fornecer uma pseudoinstrução que se expande para o "conjunto se menor que" e "branch if zero (no resultado da instrução set)". A maioria dos montadores com recursos completos também fornece uma linguagem macro rica (discutida abaixo) que é usada por fornecedores e programadores para gerar códigos e sequências de dados mais complexos. Como as informações sobre pseudoinstruções e macros definidas no ambiente do montador não estão presentes no programa de objeto, um desmontador não pode reconstruir as invocações de macro e pseudoinstrução, mas pode apenas desmontar as instruções de máquina reais que o montador gerou a partir dessas entidades abstratas da linguagem assembly. Da mesma forma, como os comentários no arquivo de origem da linguagem assembly são ignorados pelo montador e não têm efeito no código-objeto que ele gera, um desmontador sempre é completamente incapaz de recuperar os comentários de origem.
Cada arquitetura de computador tem sua própria linguagem de máquina. Os computadores diferem no número e tipo de operações que suportam, nos diferentes tamanhos e números de registradores e nas representações dos dados armazenados. Embora a maioria dos computadores de uso geral sejam capazes de realizar essencialmente a mesma funcionalidade, as formas como o fazem diferem; as linguagens assembly correspondentes refletem essas diferenças.
Múltiplos conjuntos de mnemônicos ou sintaxe de linguagem assembly podem existir para um único conjunto de instruções, normalmente instanciado em diferentes programas assembler. Nesses casos, o mais popular costuma ser o fornecido pelo fabricante da CPU e utilizado em sua documentação.
Dois exemplos de CPUs que possuem dois conjuntos diferentes de mnemônicos são a família Intel 8080 e a Intel 8086/8088. Como a Intel reivindicou os direitos autorais de seus mnemônicos em linguagem assembly (em cada página de sua documentação publicada na década de 1970 e no início dos anos 1980, pelo menos), algumas empresas que produziram CPUs independentemente compatíveis com os conjuntos de instruções da Intel inventaram seus próprios mnemônicos. A CPU Zilog Z80, um aprimoramento do Intel 8080A, suporta todas as instruções do 8080A e muito mais; A Zilog inventou uma linguagem assembly totalmente nova, não apenas para as novas instruções, mas também para todas as instruções do 8080A. Por exemplo, onde a Intel usa os mnemônicos MOV, MVI, LDA, STA, LXI, LDAX, STAX, LHLD e SHLD para várias instruções de transferência de dados, a linguagem assembly Z80 usa o mnemônico LD para todos eles. Um caso semelhante são as CPUs NEC V20 e V30, cópias aprimoradas do Intel 8086 e 8088, respectivamente. Como o Zilog com o Z80, a NEC inventou novos mnemônicos para todas as instruções 8086 e 8088, para evitar acusações de violação dos direitos autorais da Intel. (É questionável se tais direitos autorais podem ser válidos, e empresas de CPU posteriores, como AMD e Cyrix, republicaram os mnemônicos de instrução x86/IA-32 da Intel exatamente sem permissão ou penalidade legal.) É duvidoso que, na prática, muitas pessoas que programou o V20 e o V30, na verdade, escreveu na linguagem assembly da NEC em vez da linguagem da Intel; como quaisquer duas linguagens assembly para a mesma arquitetura de conjunto de instruções são isomórficas (um pouco como o inglês e o Pig Latin), não há necessidade de usar a linguagem assembly publicada do próprio fabricante com os produtos desse fabricante.
Design de linguagem
Elementos básicos
Existe um grande grau de diversidade na maneira como os autores dos montadores categorizam as declarações e na nomenclatura que usam. Em particular, alguns descrevem qualquer coisa que não seja um mnemônico de máquina ou um mnemônico estendido como uma pseudo-operação (pseudo-op). Uma linguagem assembly típica consiste em 3 tipos de declarações de instrução que são usadas para definir as operações do programa:
- Mnemonics de código de acesso
- Definições de dados
- Directivas de montagem
Mnemônicos Opcode e mnemônicos estendidos
As instruções (declarações) em linguagem assembly são geralmente muito simples, ao contrário daquelas em linguagens de alto nível. Geralmente, um mnemônico é um nome simbólico para uma única instrução de linguagem de máquina executável (um opcode) e há pelo menos um mnemônico de opcode definido para cada instrução de linguagem de máquina. Cada instrução normalmente consiste em uma operação ou opcode mais zero ou mais operandos. A maioria das instruções se refere a um único valor ou a um par de valores. Os operandos podem ser imediatos (valor codificado na própria instrução), registradores especificados na instrução ou implícitos, ou endereços de dados localizados em outro local no armazenamento. Isso é determinado pela arquitetura subjacente do processador: o montador apenas reflete como essa arquitetura funciona. Mnemônicos estendidos são frequentemente usados para especificar uma combinação de um opcode com um operando específico, por exemplo, os montadores do System/360 usam B
como um mnemônico estendido para BC
com uma máscara de 15 e NOP
("NO OPeration" – não faça nada por uma etapa) para BC
com uma máscara de 0.
Mnemônicos estendidos são frequentemente usados para dar suporte a usos especializados de instruções, muitas vezes para propósitos não óbvios pelo nome da instrução. Por exemplo, muitas CPUs não possuem uma instrução NOP explícita, mas possuem instruções que podem ser usadas para esse fim. Em CPUs 8086, a instrução xchg machado,machado
é usado para nop
, com nop
sendo um pseudo-opcode para codificar a instrução xchg ax, machado
. Alguns desmontadores reconhecem isso e decodificam o xchg ax,ax
instrução como nop
. Da mesma forma, os montadores IBM para System/360 e System/370 usam os mnemônicos estendidos NOP
e NOPR
para BC
e BCR
com zero máscaras. Para a arquitetura SPARC, elas são conhecidas como instruções sintéticas.
Alguns montadores também suportam macro-instruções integradas simples que geram duas ou mais instruções de máquina. Por exemplo, com alguns montadores Z80, a instrução ld hl,bc
é reconhecido para gerar ld l,c
seguido por ld h,b
. Às vezes, eles são conhecidos como pseudo-opcodes.
Mnemônicos são símbolos arbitrários; em 1985, o IEEE publicou o Padrão 694 para um conjunto uniforme de mnemônicos a ser usado por todos os montadores. O padrão já foi retirado.
Diretivas de dados
Existem instruções usadas para definir elementos de dados para armazenar dados e variáveis. Eles definem o tipo de dados, o comprimento e o alinhamento dos dados. Estas instruções também podem definir se os dados estão disponíveis para programas externos (programas montados separadamente) ou apenas para o programa no qual a seção de dados é definida. Alguns montadores os classificam como pseudo-operações.
Diretivas de montagem
Diretivas de montagem, também chamadas de pseudo-opcodes, pseudo-operações ou pseudo-ops, são comandos dados a um montador "direcionando-o para executar operações diferentes de instruções de montagem". As diretivas afetam como o montador opera e "podem afetar o código do objeto, a tabela de símbolos, o arquivo de listagem e os valores dos parâmetros internos do montador". Às vezes, o termo pseudo-opcode é reservado para diretivas que geram código-objeto, como aquelas que geram dados.
Os nomes dos pseudo-ops geralmente começam com um ponto para distingui-los das instruções da máquina. Pseudo-ops pode tornar a montagem do programa dependente de parâmetros inseridos por um programador, de modo que um programa possa ser montado de diferentes maneiras, talvez para diferentes aplicações. Ou, um pseudo-op pode ser usado para manipular a apresentação de um programa para torná-lo mais fácil de ler e manter. Outro uso comum de pseudo-ops é reservar áreas de armazenamento para dados em tempo de execução e, opcionalmente, inicializar seu conteúdo com valores conhecidos.
Os montadores simbólicos permitem que os programadores associem nomes arbitrários (rótulos ou símbolos) com locais de memória e várias constantes. Normalmente, cada constante e variável recebe um nome para que as instruções possam fazer referência a esses locais pelo nome, promovendo assim o código de autodocumentação. No código executável, o nome de cada sub-rotina está associado ao seu ponto de entrada, portanto qualquer chamada a uma sub-rotina pode usar seu nome. Dentro das sub-rotinas, os destinos GOTO recebem rótulos. Alguns montadores suportam símbolos locais que geralmente são lexicamente distintos dos símbolos normais (por exemplo, o uso de "10$" como um destino GOTO).
Alguns montadores, como o NASM, fornecem gerenciamento flexível de símbolos, permitindo que os programadores gerenciem diferentes namespaces, calculem automaticamente deslocamentos dentro de estruturas de dados e atribuam rótulos que se referem a valores literais ou ao resultado de cálculos simples executados pelo montador. Os rótulos também podem ser usados para inicializar constantes e variáveis com endereços relocáveis.
Linguagens de montagem, como a maioria das outras linguagens de computador, permitem que comentários sejam adicionados ao código-fonte do programa que serão ignorados durante a montagem. Comentários criteriosos são essenciais em programas em linguagem assembly, pois o significado e o propósito de uma sequência de instruções binárias de máquina podem ser difíceis de determinar. O "cru" linguagem assembly (sem comentários) gerada por compiladores ou desmontadores é muito difícil de ler quando mudanças devem ser feitas.
Macros
Muitos montadores suportam macros predefinidos, e outros suportam macros definidas pelo programador (e repetidamente redefiníveis) envolvendo sequências de linhas de texto nas quais variáveis e constantes são incorporadas. A definição de macro é mais comumente uma mistura de instruções de montador, por exemplo, diretivas, instruções de máquina simbólicas e modelos para instruções de montador. Essa sequência de linhas de texto pode incluir opcodes ou diretivas. Uma vez definida uma macro, seu nome pode ser usado no lugar de um mnemônico. Quando o montador processa tal instrução, ele substitui a instrução pelas linhas de texto associadas a essa macro e as processa como se existissem no arquivo de código-fonte (incluindo, em alguns montadores, a expansão de quaisquer macros existentes no texto de substituição). As macros neste sentido datam dos autocodificadores IBM da década de 1950.
Os montadores de macro geralmente têm diretivas para, por exemplo, definir macros, definir variáveis, definir variáveis para o resultado de uma expressão aritmética, lógica ou de string, iterar, gerar código condicionalmente. Algumas dessas diretivas podem ser restritas ao uso em uma definição de macro, por exemplo, MEXIT em HLASM, enquanto outras podem ser permitidas em código aberto (fora das definições de macro), por exemplo, AIF e CÓPIA em HLASM.
Na linguagem assembly, o termo "macro" representa um conceito mais abrangente do que em alguns outros contextos, como o pré-processador na linguagem de programação C, onde sua diretiva #define normalmente é usada para criar macros curtas de linha única. As instruções de macro do Assembler, como macros em PL/I e algumas outras linguagens, podem ser longos "programas" por si só, executado por interpretação do montador durante a montagem.
Como as macros podem ter caracteres 'curtos' nomes, mas expandem para várias ou mesmo muitas linhas de código, eles podem ser usados para fazer com que os programas em linguagem assembly pareçam muito mais curtos, exigindo menos linhas de código-fonte, como nas linguagens de nível superior. Eles também podem ser usados para adicionar níveis mais altos de estrutura a programas de montagem, opcionalmente, introduzir código de depuração incorporado por meio de parâmetros e outros recursos semelhantes.
Os montadores de macro geralmente permitem que as macros recebam parâmetros. Alguns montadores incluem linguagens de macro bastante sofisticadas, incorporando elementos de linguagem de alto nível como parâmetros opcionais, variáveis simbólicas, condicionais, manipulação de strings e operações aritméticas, todos utilizáveis durante a execução de uma determinada macro e permitindo que as macros salvem o contexto ou troquem informações. Assim, uma macro pode gerar várias instruções em linguagem assembly ou definições de dados, com base nos argumentos da macro. Isso pode ser usado para gerar estruturas de dados de estilo de registro ou "desenrolado" loops, por exemplo, ou podem gerar algoritmos inteiros baseados em parâmetros complexos. Por exemplo, um "classificar" macro poderia aceitar a especificação de uma chave de classificação complexa e gerar código criado para essa chave específica, não precisando dos testes de tempo de execução que seriam necessários para um procedimento geral interpretando a especificação. Uma organização que usa a linguagem assembly que foi amplamente estendida usando esse conjunto de macros pode ser considerada como trabalhando em uma linguagem de nível superior, pois esses programadores não estão trabalhando com os elementos conceituais de nível mais baixo de um computador. Sublinhando este ponto, as macros foram usadas para implementar uma máquina virtual inicial em SNOBOL4 (1967), que foi escrita na SNOBOL Implementation Language (SIL), uma linguagem de montagem para uma máquina virtual. A máquina de destino traduziria isso para seu código nativo usando um montador de macro. Isso permitiu um alto grau de portabilidade para a época.
As macros eram usadas para personalizar sistemas de software em grande escala para clientes específicos na era do mainframe e também eram usadas pelo pessoal do cliente para satisfazer as exigências de seus empregadores. necessidades fazendo versões específicas de sistemas operacionais do fabricante. Isso foi feito, por exemplo, por programadores de sistemas trabalhando com o Conversational Monitor System/Virtual Machine (VM/CMS) da IBM e com o "processamento de transações em tempo real" complementos, CICS do Sistema de Controle de Informações do Cliente e ACP/TPF, o sistema financeiro/aéreo que começou na década de 1970 e ainda opera muitos sistemas de reserva por computador (CRS) grandes e sistemas de cartão de crédito atualmente.
Também é possível usar apenas as habilidades de processamento de macro de um montador para gerar código escrito em linguagens completamente diferentes, por exemplo, para gerar uma versão de um programa em COBOL usando um programa de montagem de macro puro contendo linhas de código COBOL dentro operadores de tempo de montagem instruindo o montador a gerar código arbitrário. O IBM OS/360 usa macros para executar a geração do sistema. O usuário especifica opções codificando uma série de macros de montador. A montagem dessas macros gera um fluxo de tarefa para construir o sistema, incluindo linguagem de controle de tarefa e instruções de controle de utilitário.
Isso porque, como foi percebido na década de 1960, o conceito de "macroprocessamento" é independente do conceito de "montagem", sendo o primeiro, em termos modernos, mais processamento de texto, processamento de texto, do que geração de código de objeto. O conceito de processamento de macros apareceu, e aparece, na linguagem de programação C, que suporta "instruções do pré-processador" para definir variáveis e fazer testes condicionais em seus valores. Ao contrário de certos processadores de macro anteriores dentro dos montadores, o pré-processador C não é Turing-completo porque não possui a capacidade de fazer um loop ou "ir para", o último permitindo que os programas façam um loop.
Apesar do poder do processamento de macros, ele caiu em desuso em muitas linguagens de alto nível (as principais exceções são C, C++ e PL/I), permanecendo uma perene para montadores.
A substituição do parâmetro da macro é estritamente por nome: no momento do processamento da macro, o valor de um parâmetro é substituído textualmente por seu nome. A classe mais famosa de bugs resultante foi o uso de um parâmetro que era uma expressão e não um simples nome quando o criador da macro esperava um nome. Na macro:
foo: macro a carga a*b
a intenção era que o chamador fornecesse o nome de uma variável e o "global" variável ou constante b seria usada para multiplicar "a". Se foo for chamado com o parâmetro a-c
, ocorrerá a expansão da macro de load a-c*b
. Para evitar qualquer possível ambiguidade, os usuários de processadores de macro podem colocar entre parênteses os parâmetros formais dentro das definições de macro, ou os chamadores podem colocar entre parênteses os parâmetros de entrada.
Suporte para programação estruturada
Os pacotes de macros foram escritos fornecendo elementos de programação estruturados para codificar o fluxo de execução. O exemplo mais antigo dessa abordagem foi o conjunto de macros Concept-14, originalmente proposto por Harlan Mills (março de 1970) e implementado por Marvin Kessler na Divisão Federal de Sistemas da IBM, que fornecia IF/ELSE/ENDIF e controle semelhante blocos de fluxo para programas assembler do OS/360. Essa foi uma forma de reduzir ou eliminar o uso de operações GOTO no código assembly, um dos principais fatores que causam o código espaguete na linguagem assembly. Essa abordagem foi amplamente aceita no início dos anos 80 (últimos dias de uso da linguagem assembly em larga escala). O High Level Assembler Toolkit da IBM inclui esse pacote de macro.
Um design curioso foi o A-Natural, um design "orientado para o fluxo" montador para 8080/Z80, processadores da Whitesmiths Ltd. (desenvolvedores do sistema operacional Idris semelhante ao Unix e o que foi relatado como o primeiro compilador C comercial). A linguagem foi classificada como montadora porque trabalhava com elementos brutos de máquina, como opcodes, registradores e referências de memória; mas incorporou uma sintaxe de expressão para indicar a ordem de execução. Parênteses e outros símbolos especiais, juntamente com construções de programação estruturada orientada a blocos, controlavam a sequência das instruções geradas. A-natural foi construído como a linguagem de objeto de um compilador C, em vez de codificação manual, mas sua sintaxe lógica conquistou alguns fãs.
Tem havido pouca demanda aparente por montadores mais sofisticados desde o declínio do desenvolvimento da linguagem assembly em larga escala. Apesar disso, eles ainda estão sendo desenvolvidos e aplicados nos casos em que restrições de recursos ou peculiaridades na arquitetura do sistema de destino impeçam o uso efetivo de linguagens de alto nível.
Assemblers com um poderoso mecanismo de macro permitem programação estruturada por meio de macros, como a macro switch fornecida com o pacote Masm32 (este código é um programa completo):
incluir masm32incluirmasm32rt.inc; use a biblioteca Masm32Códigodemomain: REPEITAÇÃO 20.interruptor rv(nrandom, 9); gerar um número entre 0 e 8Mov Ecx, 7Processo 0impressão "case 0"Processo Ecx; em contraste com a maioria das outras linguagens de programação,impressão "caso 7"; o interruptor Masm32 permite "casos variáveis"Processo 1 ... 3.if em linha- Sim.1impressão "caso 1". em linha- Sim.2impressão "caso 2".impressão "casos 1 a 3: outros".Processo 4, 6, 8impressão "casos 4, 6 ou 8"padrãoMov ebx, 19 ; imprimir 20 estrelas. Repitoimpressão "*"dec ebx. Até lá. Assina? ; loop até que a bandeira do sinal esteja definidaTerminadoimpressão Por favor.(13, 10.) ENTRADA saídafim demoprimento
Uso da linguagem assembly
Perspectiva histórica
As linguagens de montagem não estavam disponíveis na época em que o computador de programa armazenado foi introduzido. Kathleen Booth "é creditada por inventar a linguagem assembly" com base no trabalho teórico que ela começou em 1947, enquanto trabalhava no ARC2 em Birkbeck, Universidade de Londres, após consulta de Andrew Booth (mais tarde seu marido) com o matemático John von Neumann e o físico Herman Goldstine no Instituto de Estudos Avançados.
No final de 1948, o Electronic Delay Storage Automatic Calculator (EDSAC) tinha um montador (chamado "ordens iniciais") integrado em seu programa bootstrap. Ele usou mnemônicos de uma letra desenvolvidos por David Wheeler, que é creditado pela IEEE Computer Society como o criador do primeiro "assembler". Relatórios sobre o EDSAC introduziram o termo "montagem" para o processo de combinação de campos em uma palavra de instrução. SOAP (Symbolic Optimal Assembly Program) foi uma linguagem assembly para o computador IBM 650 escrita por Stan Poley em 1955.
As linguagens de montagem eliminam grande parte da programação de primeira geração propensa a erros, tediosa e demorada necessária com os primeiros computadores, liberando os programadores do tédio, como lembrar códigos numéricos e calcular endereços. Eles já foram amplamente utilizados para todos os tipos de programação. No entanto, no final da década de 1950, seu uso foi amplamente suplantado por linguagens de alto nível, na busca por maior produtividade de programação. Hoje, a linguagem assembly ainda é usada para manipulação direta de hardware, acesso a instruções especializadas do processador ou para resolver problemas críticos de desempenho. Os usos típicos são drivers de dispositivo, sistemas incorporados de baixo nível e sistemas de tempo real (consulte § Uso atual).
Muitos programas foram escritos inteiramente em linguagem assembly. O Burroughs MCP (1961) foi o primeiro computador para o qual um sistema operacional não foi desenvolvido inteiramente em linguagem assembly; foi escrito em Linguagem Orientada a Problemas de Sistemas Executivos (ESPOL), um dialeto Algol. Muitos aplicativos comerciais também foram escritos em linguagem assembly, incluindo uma grande quantidade de software de mainframe IBM escrito por grandes corporações. COBOL, FORTRAN e alguns PL/I acabaram substituindo muito desse trabalho, embora várias grandes organizações tenham mantido as infraestruturas de aplicativos em linguagem assembly até a década de 1990.
A linguagem Assembly tem sido a principal linguagem de desenvolvimento para computadores domésticos de 8 bits, como a família Atari de 8 bits, Apple II, MSX, ZX Spectrum e Commodore 64. Os dialetos BASIC interpretados nesses sistemas oferecem velocidade de execução e recursos insuficientes para aproveitar ao máximo o hardware disponível. Esses sistemas têm restrições severas de recursos, arquiteturas de exibição e memória idiossincráticas e fornecem serviços de sistema limitados. Existem também alguns compiladores de linguagem de alto nível adequados para uso em microcomputadores. Da mesma forma, a linguagem assembly é a escolha padrão para consoles de 8 bits, como o Atari 2600 e o Nintendo Entertainment System.
Os principais softwares compatíveis com IBM PC foram escritos em linguagem assembly, como MS-DOS, Turbo Pascal e a planilha Lotus 1-2-3. À medida que a velocidade do computador crescia exponencialmente, a linguagem assembly tornou-se uma ferramenta para acelerar partes de programas, como a renderização de Doom, em vez de uma linguagem de desenvolvimento dominante. Na década de 1990, a linguagem assembly foi usada para obter desempenho de sistemas como o Sega Saturn e como a linguagem principal para hardware de arcade baseado na CPU/GPU integrada TMS34010, como Mortal Kombat e NBA Jam.
Uso atual
Houve um debate sobre a utilidade e o desempenho da linguagem assembly em relação às linguagens de alto nível.
Embora a linguagem assembly tenha usos de nicho específicos onde é importante (veja abaixo), existem outras ferramentas para otimização.
Em julho de 2017, o índice TIOBE de popularidade da linguagem de programação classifica a linguagem assembly em 11, à frente do Visual Basic, por exemplo. O Assembler pode ser usado para otimizar a velocidade ou otimizar o tamanho. No caso da otimização de velocidade, os compiladores de otimização modernos são reivindicados para renderizar linguagens de alto nível em código que pode ser executado tão rápido quanto uma montagem escrita à mão, apesar dos contra-exemplos que podem ser encontrados. A complexidade dos processadores modernos e dos subsistemas de memória torna a otimização efetiva cada vez mais difícil para os compiladores, bem como para os programadores de montagem. Além disso, o aumento do desempenho do processador significa que a maioria das CPUs fica ociosa na maior parte do tempo, com atrasos causados por gargalos previsíveis, como faltas de cache, operações de E/S e paginação. Isso fez com que a velocidade de execução do código bruto não fosse um problema para muitos programadores.
Existem algumas situações em que os desenvolvedores podem optar por usar a linguagem assembly:
- Código de escrita para sistemas com processadores mais antigos que têm opções de linguagem de alto nível limitadas, como o Atari 2600, Commodore 64 e calculadoras de grafos. Programas para esses computadores dos anos 1970 e 1980 são frequentemente escritos no contexto de subculturas demoscenas ou retrogaming.
- Código que deve interagir diretamente com o hardware, por exemplo em drivers de dispositivo e manipuladores de interrupção.
- Em um processador incorporado ou DSP, interrupções de alta repetição exigem o menor número de ciclos por interrupção, como uma interrupção que ocorre 1000 ou 10000 vezes por segundo.
- Programas que precisam usar instruções específicas do processador não implementadas em um compilador. Um exemplo comum é a instrução de rotação bitwise no núcleo de muitos algoritmos de criptografia, bem como consultar a paridade de um byte ou o transporte de 4 bits de uma adição.
- Um executável autônomo de tamanho compacto é necessário que deve executar sem recorrer aos componentes de tempo de execução ou bibliotecas associadas a uma linguagem de alto nível. Exemplos incluem firmware para telefones, sistemas de combustível e ignição automóvel, sistemas de controle de ar condicionado, sistemas de segurança e sensores.
- Programas com loops internos sensíveis ao desempenho, onde a linguagem de montagem oferece oportunidades de otimização difíceis de alcançar em uma linguagem de alto nível. Por exemplo, álgebra linear com BLAS ou transformação cosina discreta (por exemplo, versão de montagem SIMD de x264).
- Programas que criam funções vetoriais para programas em idiomas de alto nível, como C. Na linguagem de alto nível, isso às vezes é auxiliado por funções intrínsecas compiladoras que mapeiam diretamente para mnemônicas SIMD, mas, no entanto, resultam em uma conversão de montagem única específica para o processador de vetores.
- Programas em tempo real, como simulações, sistemas de navegação de voo e equipamentos médicos. Por exemplo, em um sistema fly-by-wire, a telemetria deve ser interpretada e agida dentro de restrições de tempo rígidas. Esses sistemas devem eliminar fontes de atrasos imprevisíveis, que podem ser criados por (alguns) linguagens interpretadas, coleta automática de lixo, operações de paging ou multitarefa preemptiva. No entanto, algumas linguagens de nível superior incorporam componentes de tempo de execução e interfaces do sistema operacional que podem introduzir tais atrasos. Escolher linguagens de montagem ou de nível inferior para tais sistemas dá aos programadores maior visibilidade e controle sobre detalhes de processamento.
- Algoritmos criptográficos que devem sempre levar estritamente o mesmo tempo para executar, evitando ataques de tempo.
- Codificadores de vídeo e decodificadores como rav1e (um codificador para AV1) e dav1d (o decodificador de referência para AV1) contêm montagem para alavancar as instruções AVX2 e ARM Neon quando disponível.
- Modificar e estender o código legado escrito para computadores IBM mainframe.
- As situações em que o controlo total sobre o ambiente é necessário, em situações de extrema segurança onde nada pode ser tomado como garantido.
- vírus de computador, bootloaders, determinados drivers de dispositivo, ou outros itens muito próximos ao hardware ou sistema operacional de baixo nível.
- Simuladores de conjunto de instruções para monitoramento, rastreamento e depuração onde a sobrecarga adicional é mantida ao mínimo.
- Situações onde nenhuma linguagem de alto nível existe, em um processador novo ou especializado para o qual nenhum compilador cruz está disponível.
- Reversa-engenharia e modificação de arquivos de programas, tais como:
- binários existentes que podem ou não ter sido originalmente escrito em uma linguagem de alto nível, por exemplo, ao tentar recriar programas para os quais o código fonte não está disponível ou foi perdido, ou quebrando a proteção de cópia do software proprietário.
- Vídeo jogos (também chamado ROM hacking), que é possível através de vários métodos. O método mais amplamente empregado é alterar o código do programa no nível da linguagem de montagem.
A linguagem assembly ainda é ensinada na maioria dos programas de ciência da computação e engenharia eletrônica. Embora poucos programadores atualmente trabalhem regularmente com a linguagem assembly como ferramenta, os conceitos subjacentes permanecem importantes. Tópicos fundamentais como aritmética binária, alocação de memória, processamento de pilha, codificação de conjunto de caracteres, processamento de interrupção e projeto de compilador seriam difíceis de estudar em detalhes sem uma compreensão de como um computador opera no nível do hardware. Como o comportamento de um computador é fundamentalmente definido por seu conjunto de instruções, a maneira lógica de aprender tais conceitos é estudar uma linguagem assembly. A maioria dos computadores modernos tem conjuntos de instruções semelhantes. Portanto, o estudo de uma única linguagem assembly é suficiente para aprender: I) os conceitos básicos; II) reconhecer situações em que o uso da linguagem assembly pode ser adequado; e III) para ver como um código executável eficiente pode ser criado a partir de linguagens de alto nível.
Aplicações típicas
- A linguagem de montagem é normalmente usada no código de inicialização de um sistema, o código de baixo nível que inicializa e testa o hardware do sistema antes de inicializar o sistema operacional e é frequentemente armazenado em ROM. (BIOS em sistemas de PC compatíveis com IBM e CP/M é um exemplo.)
- A linguagem de montagem é frequentemente usada para código de baixo nível, por exemplo, para kernels do sistema operacional, o que não pode confiar na disponibilidade de chamadas do sistema pré-existentes e deve realmente implementá-las para a arquitetura do processador em particular em que o sistema será executado.
- Alguns compiladores traduzem linguagens de alto nível em montagem antes de compilar totalmente, permitindo que o código de montagem seja visualizado para fins de depuração e otimização.
- Alguns compiladores para linguagens de nível relativamente baixo, como Pascal ou C, permitem que o programador incorporar linguagem de montagem diretamente no código fonte (chamado de montagem em linha). Programas usando tais instalações podem então construir abstrações usando diferentes linguagem de montagem em cada plataforma de hardware. O código portátil do sistema pode então usar esses componentes específicos do processador através de uma interface uniforme.
- A linguagem de montagem é útil na engenharia reversa. Muitos programas são distribuídos apenas na forma de código da máquina que é simples de traduzir em linguagem de montagem por um desmontador, mas mais difícil de traduzir em uma linguagem de nível superior através de um decompilador. Ferramentas como o Desmontador Interativo fazem uso extensivo de desmontagem para tal finalidade. Esta técnica é usada por hackers para crack software comercial, e concorrentes para produzir software com resultados semelhantes de empresas concorrentes.
- A linguagem de montagem é usada para aumentar a velocidade de execução, especialmente em computadores pessoais precoces com poder de processamento limitado e RAM.
- Os conjuntos podem ser usados para gerar blocos de dados, sem sobrecarga de linguagem de alto nível, de código fonte formatado e comentado, para ser usado por outro código.