quinta-feira, maio 11, 2023

MSX: Bibliotecas e Arquivos Relocáveis no M80/L80

O M80/L80 talvez seja a ferramenta mais poderosa para o desenvolvimento em Assembly para Z80/CP/M/MSX-DOS, porém, mutas coisas não estão bem claras no manual, e tem sido difícil achar informações sobre o assunto na internet. Este é um trabalho em progresso, conforme for descobrindo funções, estarei atualizando esta página.

Introdução

Normalmente, o que mais se encontra nos projetos Assembly MSX, é um enorme arquivo .MAC/.ASM, que deve ser compilado e linkeditado para gerar um .COM. Uma outra abordagem é a de dividir o código em vários arquivos, mas usar o INCLUDE para gerar o .COM. Sei que o M80/L80 é capaz de trabalhar com múltiplos arquivos, compilá-los em arquivos .REL e depois linkeditá-los em arquivo .COM, com isso, reduz-se o tempo de compilação, pois, no caso de se alterar apenas um dos arquivos, não precisamos compilar tudo novamente, basta compilar o arquivo alterado e linkeditar com o resto.

O M80/L80 também possui um utilitário para se criar .LIB, facilitando mais ainda o trabalho de linkedição, quando você já possui arquivos estáveis que contenham funções que podem ser aproveitadas em outros projetos.

O problema é a falta de documentação. Então resolvi fuçar no programa e tentar entender melhor o funcionamento do kit, e ir postando o que conseguir descobrir aqui. Vai existir um tutorial específico do M80/L80, além de um outro tutorial completo sobre programação Assembly voltada para o MSX, mas por hora, esta página vai concentrar dicas e truques interessantes.

Dividindo um Projeto em 2 arquivos, o básico

Trabalhar com arquivos relocáveis no M80/L80 nem é tão difícil, a parte mais complicada e a falta de documentação mesmo. Consultando alguns links, pesquisando algumas ferramentas CP/M, algumas ferramentas para o 8080/8085 e até para o 8088/8086, consegui fazer pelo menus um protótipo de como dividir um projeto em múltiplos arquivos e evitar a recompilação de tudo (ou usando um único arquivo, ou usando includes) a cada alteração.

Aqui vou ilustrar, apenas basicamente, um exemplo prático de como o processo deve ser feito em sua essência.

Para tanto, vamos criar dois arquivos fontes, um contendo o código principal, e outro contendo uma rotina de impressão (BDOS)

Programa Principal:

; prog1.mac
.Z80

       extrn print           ;Define que print sera definida externamente
       cseg                   ;Inicio do segmento de código
       org 100H               ;Não é realmente necessário, mas definimos o início em 100h
prog1: ld de,texto            ;carrega DE com o endereço de inicio da string
       call print             ;chama a rotina externa print
       jp 0                   ;volta para o MSX-DOS
       dseg                   ;Inicio do segmento de dados
texto: defm "Ola Mundo!$"     ;Define string a ser impressa
end                           ;fim do programa

Rotina de Impressão:

; prog2.mac
.Z80

BDOS   equ 05h                ;Endereço da chamada para o BDOS
STROUT equ 09h                ;Rotina de impressão de string
       cseg                   ;Início do segmento de código
       public print           ;define a rotina print como pública
print: push bc                ;Salva BC
       ld c,STROUT            ;define a função de impressão a ser chamada
       call BDOS              ;executa através do BDOS
       pop bc                 ;Restaura BC
       ret                    ;retorna
end                           ;fim do programa

Para compilarmos os programas usamos o seguinte procedimento: No prompt do MSX-DOS, chamamos o M80:

A:> M80

*

No prompt do M80 (*), digitamos apenas:

*=prog1

Com a compilação terminada o Macro80 volta para o MSX-DOS, chamamos novamente ele para compilar o segundo programa, de maneira similar:

*=prog2

Com isto temos os arquivos .MAC contendo os fontes, e os arquivos .REL contendo os objetos relocáveis prontos para a linkedição.

Agora passaremos para a linkedição dos arquivos e a consequênte geração do arquivo executável .COM. Para tanto, chamaremos o L80:

A:> L80

*

Igualmente ao M80, o L80 apresenta seu prompt e aguarda comandos, vamos digitar o seguinte:

*prg/e/n/p:100/d:200,prog1,prog2

Com isto temos gerado o programa .COM, tendo o segmento de código iniciado em 100H e o segmento de dados em 200H.


Perceba que tivemos de especificar os endereços do segmento de código e de dados, ainda não consegui descobrir uma maneira de fazer com que o L80 faça isso automaticamente, ou seja, assim que acabar o segmento de código, iniciar, na sequência o segmento de dados. Pelo menos, o L80 vai dar uma mensagem de erro se um segmento se sobrescrever ao outro.

Bibliotecas

As bibliotecas nada mais são do que um conjunto de módulos compilados em .REL e adicionados em um arquivo maior que contém todos estes módulos. As bibliotecas, ou librarys, ou libs, são um jeito organizado de distribuir módulos específicos, além de um jeito de reduzir o tamanho da linha de comando do LINK (que é limitada em 128 bytes).

Imagine que você está desenvolvendo um projeto, para MSX chamado DSKPATCH que contém vários módulos com procedures agrupadas por relação, como: DISKIO.MAC, VIDEOIO.MAC, DSKPATCH.MAC, CURSOR.MAC. Imagine também que estes arquivos usem funções específicas do BIOS ou do MSX-DOS por exemplo e isso gere arquivos .MAC diferentes para cada bloco de procedures primárias, por exemplo: BIOS.MAC, DOS.MAC, VRAM.MAC, PSG.MAC, MAPPER.MAC, VDP.MAC. E por fim funções de apoio como: MATHPACK, ALPHABET.

Da maneira direta, você compilaria cada módulo pelo M80 separadamente, em seguida linkaria todos eles para gerar um executável, teriamos um comando parecido com:

L80 DSKPATCH/E/N/P:103/D:400,DSKPATCH,VIDEOIO,DISKIO,CURSOR,BIOS,DOS,VRAM,PSG,MAPPR,VDP,MATHPACK,ALPHABET

Perceba que esta linha começa a ficar muito grande. Você pode minimizar a digitação de toda a linha usando um utilitário MAKE para MSX, mas mesmo assim, em algum momento você vai ultrapassar a linha de comando. Além deste problema, quando você iniciar outro projeto de MSX, vai ter que repetir os arquivos BIOS, DOS, etc. para o mesmo. Um outro problema também seria você repassar estes arquivos para um outro desenvolvedor trabalhar, teria que ficar lembrando de todos os arquivos .REL que devem ser repassados.

Uma solução mais elegante é criarmos uma LIB específica que agrupe módulos que possuam afinidade, gerando assim um único arquivo, que é mais fácil de gerenciar. Vamos imaginar uma LIB MSX que contenha os arquivos BIOS, DOS, etc.:

LIB80 MSX/C/E=BIOS,DOS,VRAM,PSG,MAPPER,VDP,ALPHABET,MATHPACK

Após a execução, teremos o arquivo MSX.REL contendo todos os módulos agregados, e o LINKer já teria menos arquivos para trabalhar:

L80 DSKPATCH/E/N/P:103/D:400,DSKPATCH,VIDEOIO,DISKIO,CURSOR,MSX

Conforme o desenvolvimento do programa vai avançando, até mesmo módulos dele podem ser agrupados:

LIB80 DSKLIB/E=VIDEOIO,DISKIO,CURSOR

L80 DSKPATCH/E/N/P:103/D:400,DSKPATCH,DSKLIB,MSX

Veja que reduzimos bastante a linha de comando. Mas melhor que isso, você pode disponibilizar o pacote MSX por exemplo para outras pessoas, que não precisam se preocupar como funciona cada módulo, ou ter acesso direto aos fontes, basta fornecer o .REL resultante do LIB80 e a documentação de cada procedures disponível no pacote, e o usuário pode incluir em sua própria compilação. Um detalhe melhor ainda, mesmo a LIB MSX tendo várias procedures, apenas as usadas pelo projeto serão incluídas no executável final, gerando assim arquivos mais eficientes.

Gerenciar tudo isso pode ser um pouco trabalhoso, mas os benefícios valem o sacrifício, ainda mais se você usar um utilitário MAKE.

MAKE

Um utilitário muito útil na criação de projetos com múltiplos módulos é o MAKE. Ele consegue verificar quais arquivos FONTES foram alterados desde a última compilação, e compilar apenas estes arquivos e não todo o conjunto. No exemplo acima, caso apenas o arquivo DISKIO.MAC fosse alterado em nosso projeto, o MAKE vai gerar uma saída de compilação apenas dele além da linka do LINKer. O conjunto da modularização, a criação de bibliotecas e por fim o utilitário MAKE nos permite ter uma eficiência bem maior na compilação de projetos maiores, pois além das compilações serem menores, não temos que ficar lembrando de cabeça o que foi ou não alterado.

O MAKE depende de um arquivo do tipo MAKEFILE (.MF) que contém todas as regras de compilação, sendo que sua sintaxe é bem simples:

MASTER: SLAVE1 SLAVE2 ... SLAVEN
        operation1
        operation2

Por exemplo, se temos um programa chamado DSKPATCH.MAC e queremos criar a regra de compilação dele, podemos ter um arquivo .MF assim:

DSKPATCH.REL: DSKPATCH.MAC
              M80 =DSKPATCH.MAC/R/Z
DKSPATCH.COM: DSKPATCH.REL
              L80 DSKPATCH/E/N,DSKPATCH

Sabendo que o M80 pega um arquivo .MAC e gera um .REL e o L80 pega um .REL e gera um .COM, fica fácil entender o programa acima. DSKPATCH.REL depende de DSKPATCH.MAC, se a data/hora do arquivo DSKPATCH.MAC for maior que a data/hora do arquivo DSKPATCH.REL (ou o atributo ARCHIVE de DSKPATCH.MAC estiver on, dependendo de qual versão você está usando), então o MAKE vai executar a operação seguinte, que nada mais é do que o comando para compilar DSKPATCH.MAC, ou seja M80 =DSKPATCH.MAC/R/Z, com isso gerando o DSKPATCH.REL.

Na sequência, ele faz a mesma comparação entre o DSKPATCH.REL e o DSKPATCH.COM e executa o L80 DSKPATCH/E/N,DSKPATCH. Parece que não ajudou muito a primeira vista, mas logo de cara temos uma informação mais detalhada.

Agora vamos ao exemplo que demos no início deste artigo, envolvendo os arquivos prog1.mac e prog2.mac que dependem entre si para gerar um executável, teremos o MF assim:

PROG1.REL: PROG1.MAC
           M80 =PROG1.MAC/R/Z
PROG2.REL: PROG2.MAC
           M80 =PROG2.MAC/R/Z
PROG.COM: PROG1.REL PROG2.REL
          L80 PROG/E/N/P:103/D:200,PROG1,PROG2

Neste exemplo já temos uma vantagem mais real. Se alterarmos apenas PROG1.MAC, o MAKE vai gerar apenas as informações para compilar PROG1.MAC e LINKar os PROG1.REL mais o PROG2.REL (já compilado) em PROG.COM.

Veja um exemplo do tutorial de Assembly (DSKPATCH) para ver um .MF maior:

alphabet.rel: alphabet.mac
              m80 =alphabet.mac/r/z
diskio.rel:   diskio.mac
              m80 =diskio.mac/r/z
videoio.rel:  videoio.mac
              m80 =videoio.mac/r/z
mathpack.rel: mathpack.mac
              m80 =mathpack.mac/r/z
cursor.rel:   cursor.mac
              m80 =cursor.mac/r/z
dispsect.rel: dispsect.mac
              m80 =dispsect.mac/r/z
msx.rel:      msx.mac
              m80 =msx.mac/r/z
dos.rel:      dos.mac
              m80 =dos.mac/r/z
dskpatch.rel: dskpatch.mac
              m80 =dskpatch.mac/r/z
bios.rel: msx.rel dos.rel mathpack.rel alphabet.rel
          lib80 bios/e=msx,dos,mathpack,alphabet
dskpatch.com:   dskpatch.rel diskio.rel videoio.rel cursor.rel dispsect.rel bios.rel
                l80 dskpatch/e/n/p:103/d:400,dskpatch,diskio,dispsect,cursor,videoio,bios

Se alterarmos o MSX.MAC, ele vai gerar as informações para criar o MSX.REL, para gerar o BIOS.REL e para gerar o DSKPATCH.COM assim ganhando um bom tempo no processo.

Veja que estou falando em gerar informações. Sim esta versão de MAKE em particular, não executa os comandos efetivamente, ela apenas gera as informações que deveriam ser digitadas para a compilação concisa. Mas, permite o redirecionamento da saída para um arquivo .BAT, seguindo o mesmo exemplo, poderiamos digitar:

MAKE DSKPATCH > DS.BAT

Um último detalhe importante. Se você acabou de criar um novo arquivo .MAC para seu projeto, e incluir as regras dele no seu arquivo .MF e executar pela primeira vez, vai obter um erro pois o arquivo .REL não existe ainda, sendo assim não há com o que comparar. Neste caso você deve fazer a primeira compilação manualmente para ter o .REL gerado, e a partir das próximas compilações o MAKE vai funcionar sem problema algum.

E em seguida apenas chamar DS para efetivamente compilar os programas necessários. Veja a documentação do MAKE para mais detalhes e outras opções de execução.

Existem algumas opções do MAKE que podem ajudar, por exemplo, se você usar o comando:

MAKE DSKPATCH /Y

O MAKE entenderá que precisa executar o procedimento de todos os arquivos, independentemente de terem sido alterados ou não. Usamos esta opção quando desejamos recriar todo o projeto, lembrando que caso os arquivos master (.REL) não existam num primeiro momento, o MAKE vai acusar um erro.

As opções /A e /T checam ou o atributo "archive" (alterado) ou a data para verificar quais arquivos sofreram alterações, ao passo que a opção /M verifica e reseta o atributo "archive".

Download