MS-DOS, APM com C e Assembly

Veja o vídeo deste arquivo aqui.

Para brincar de retrocomputação, eu uso o VirtualBox com o MS-DOS 6.22 e o DOSBox. Ambos funcionam muito bem, mas a tela fica um pouco melhor no VirtualBox, e o DOSBox é mais rápido e fácil para desenvolver e alternar rapidamente entre sistemas. Por isso, uso os dois.

No DOSBox, você pode fechar o simulador digitando o comando exit. Isso fecha uma sessão do interpretador de comandos e, se for a última sessão, fecha o próprio DOSBox. É conveniente porque não precisa usar o mouse e se comporta mais como um terminal.

No VirtualBox, é necessário fechar a simulação por comando. Se o sistema operacional fosse mais moderno, seria possível usar a combinação de teclas host+u para desligar via ACPI (Advanced Configuration and Power Interface). Porém, no DOS, o VirtualBox exibe uma mensagem de erro informando que o guest não suporta shutdown por software.

Eu descobri que o sistema suporta APM (Advanced Power Management), que é o antecessor do ACPI. É possível desligar por software chamando a int 15h e passando os parâmetros para desligar a máquina.

Combinei tudo isso e criei pequenos utilitários para desligar e reiniciar a máquina. Com um pouco de engenhosidade, consegui até desligar digitando exit no DOS.

Reboot usando BIOS

Esse utilitário para reiniciar é simples. Ele não usa APM; em vez disso, chama a int 19h, que faz a BIOS reiniciar o sistema. A ideia não é minha; li essa dica em uma revista no início dos anos 90. É tão simples que, sempre que vou a uma máquina DOS, crio o executável usando o debug.

reboot.com usando debug

Este é o script do debug para gerar o reboot.com. Digite exatamente assim, incluindo a linha em branco.

debug
a100
int 19

rcx
2
n reboot.com
w

Isso gera um executável .com com apenas 2 bytes!

Agora, uma versão mais elaborada usando o compilador NASM, mas que gera exatamente o mesmo executável.

;reboot usando BIOS.
;nasm -f bin -o reboot.com reboot.asm

org 100h
section .text

start:  int 19h

.end

Shutdown usando APM

Primeiro, queria verificar se o APM é suportado. Isso é importante porque, se eu criar um script bat, quero que meu executável retorne um valor na variável errorlevel. Assim, posso tomar ações diferentes. Por exemplo, o DOSBox não suporta APM, apenas o VirtualBox. Então, se tentar usar esse utilitário no DOSBox, preciso saber que falhou.

;checa se APM está ok
mov ax, 5300h
xor bx, bx
int 15h
jc APM_error
.
.
.
APM_error: mov  dx, msgAPMError
           mov  ah, 9
           int  21h

exitError: mov  ax, 4CFFh
           int  21h

msgAPMError db "Erro de APM ou não disponível",0

O código acima verifica o suporte ao APM. Se estiver tudo certo, o programa continua. Caso contrário, salta para o label APM_error, que mostra uma mensagem de erro na tela e fecha o programa, definindo errorlevel como 255.

Se o APM estiver ok, o programa faz preparativos para conectar e ajustar para a versão 1.2 (última revisão de 1996) e finalmente desliga a máquina com o seguinte trecho de código.

;desligar o sistema
mov     ax, 5307h
mov     bx, 0001h
mov     cx, 0003h
int     15h

Se você usar FreeDOS, acredito que ele suporte a versão 1.11, então talvez precise de ajustes.

Combinando reset e shutdown usando C

Além da versão em assembly puro, fiz um aplicativo em C combinando as duas funcionalidades: reset e shutdown. Elas podem ser selecionadas por parâmetros na linha de comando. Fiz apenas para experimentar o Borland Turbo C e integrar com assembly.

/*
compile com:
tcc -mt -tDc main.c
*/
#include <stdio.h>

// Verifica instalação do APM
char chkAPM() {
  asm {
    mov ax, 5300h
    xor bx, bx
    int 15h
    jc APM_error
  }
  return 0;
APM_error:
  return -1;
}

void usage() {
  printf("shutdown para DOS\n"
         "Cesar Gimenes, @crgimenes\n"
         "https://github.com/crgimenes/shutdown\n"
         "Uso:\n"
         "	-h halt requer APM\n"
         "	-r reboot (chama BIOS int 19h)\n");
}

int main(int argc, char *argv[]) {
  if (argc == 1) {
    usage();
    return 1;
  }

  if (strcmp(argv[1], "-r") == 0) {
    asm int 19h
  }

  if (chkAPM()) {
    printf("Erro de APM ou não disponível");
    return 1;
  }

  if (strcmp(argv[1], "-h") == 0) {
    asm {
      /* conecta ao APM */
      mov     ax, 5301h
      xor     bx, bx
      int     15h

      /* define versão do APM */
      mov     ax, 530Eh
      mov     cx, 0102h
      xor     bx, bx
      int     15h

      /* desligar */
      mov     ax, 5307h
      mov     bx, 0001h
      mov     cx, 0003h
      int     15h
      hlt
    }
  }

  return 0;
}

Finalmente, para fazer o VirtualBox ter o mesmo comportamento do DOSBox e fechar quando digitarmos o comando exit, podemos chamar uma nova sessão do interpretador de comandos command.com no final do autoexec.bat e, em seguida, chamar o comando de shutdown. A desvantagem dessa abordagem é consumir mais RAM e ter outra instância rodando.

...
command
sd -h

Espero que seja útil. O código fonte e as versões compiladas estão no GitHub. Você notará que os aplicativos .com são extremamente pequenos. Na verdade, reboot.com tem apenas 2 bytes, não kilobytes, apenas bytes. O maior deles, sd.com, tem apenas 6.4K. Isso acontece porque são instruções diretas para o processador, sem cabeçalhos. O sistema operacional apenas carrega esse arquivo na RAM em um endereço específico e coloca o ponteiro de execução lá. Mas isso é assunto para outro dia.

Cesar Gimenes

Última modificação