DevSecOps

10 ago, 2018

MS-DOS, APM com C e Assembly

Publicidade

Para brincar de retro-computação, eu uso o VirtualBox com o MS-DOS 6.22 e o DOSBox. Os dois funcionam muito bem, mas a tela fica ligeiramente melhor no VirtualBox e o DOSBox é mais rápido e fácil para desenvolver e ir rapidamente de um sistema para outro; então acabo usando os dois.

No DOSBox você pode fechar o simulador simplesmente digitando o comando “exit” que vai fechar uma sessão do interpretador de comandos, e se for a ultima sessão, ela vai fechar o próprio DOSBox, o que é bem legal, porque não precisa mover mouse nem nada, e é um comportamento mais parecido com um terminal.

Já no VirtualBox é preciso comandar isso mandando fechar a simulação. Se o sistema operacional fosse um pouco mais moderno, seria possível usar a combinação de teclas host+u que comanda o shutdown via ACPI (Advanced Configuration and Power Interface), mas no caso do DOS, o VirtualBox vai dar uma mensagem de erro avisando que o guest não suporta shutdown por software.

Mas eu sabia que o sistema suporta APM (Advanced Power Management), que é o antecessor do ACPI e é possível fazer shutdown por software chamando a int 15h e passando os parâmetros para desligar a maquina, então juntei tudo isso e criei alguns pequenos utilitários para desligar e reiniciar a maquina, e com um pouco de engenhosidade poder até mesmo desligar digitando “exit” no DOS.

Reboot usando BIOS

Esse utilitário para reboot é extremamente simples, ele não usa APM. No lugar disso, ele chama a int 19h que faz com que a BIOS reinicie e ela leva todo o sistema com ela. A ideia não é minha; li essa dica em uma revista lá no inicio dos anos 90, e é tão simples que toda vez que vou em uma maquina DOS, simplesmente crio o pequeno executável usando o debug.

reboot.com usando debug

Esse é o script do debug para gerar o reboot.com. Precisa digitar exatamente assim, incluindo a linha em branco.

debug
a100
int 19

rcx
2
n reboot.com
w

Isso vai gerar um executável .com que tem apenas dois bytes!

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

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

org 100h
section .text

start: int 19h

.end

Shutdown usando APM

A primeira coisa que eu queria fazer é validar se o APM é suportado ou não. Isso é importante porque se eu fizer um script bat, eu quero que o meu executável retorne um valor na variável errorlevel, e assim eu posso tomar atitudes diferentes como, por exemplo, o DOSBox não suporta APM, só o VirtualBox, então se eu tentar usar esse utilitário no DOSBox, preciso de uma forma de saber que falhou.

;chke if APM is 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 “APM error or not available”,0

O trecho de código acima vai verificar o suporte a APM. Se estiver tudo ok, o programa vai em frente, caso contrario ele salta para o label APM_error, que mostra uma mensagem de erro na tela e fecha o programa deixando em errorlevel o valor 255.

Se o APM estiver ok, o programa faz mais alguns preparativos para conectar e ajustar a versão para a 1.2 (ultima revisão lá de 1996) e finalmente desliga a maquina com o seguinte trecho de código:

;turn off the system
mov ax, 5307h
mov bx, 0001h
mov cx, 0003h
int 15h

Se você estiver usando FreeDOS, acredito que ele suporta a versão 1.11, então talvez precise de algum ajuste.

Combinando reset e shutdown usando C

Alem da versão em assembly puro mais direta e sem parâmetros, também fiz um aplicativo em C, combinando as duas funcionalidades, reset e shutdown, que podem ser selecionadas por parâmetros na linha de comando. Fiz apenas pela brincadeira de usar o Borland Turbo C e ainda integrar com asm.

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

// APM installation check
char chkAPM() {
  asm {
    mov ax, 5300h 
    xor bx, bx 
    int 15h 
    jc APM_error
  }
  return 0;
APM_error:
  return -1;
}

void usage() {
  printf("shutdown for DOS\r\n"
         "Cesar Gimenes, @crgimenes\r\n"
         "https://github.com/crgimenes/shutdown\r\n\r\n"
         "Usage:\r\n"
         "\t-h halt require APM\r\n"
         "\t-r reboot (call BIOS int 19h)\r\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("APM error or not available");
    return 1;
  }

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

      /* set APM version */
      mov     ax, 530Eh
      mov     cx, 0102h
      xor     bx, bx
      int     15h

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

  return 0;
}

E finalmente, para fazer 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 fim do autoexec.bat e em seguida chamamos o comando de shutdown. A desvantagem dessa abordagem é consumir mais RAM e ter outra instancia rodando.

...
command
sd -h

Espero que seja útil o código fonte, e também as versões compiladas estão no GitHub. Você vai notar que os aplicativos .com são extremamente pequenos. Na verdade, reboot.com tem apenas 2 bytes – não kilo – só bytes, e o maior deles o sd.com tem apenas 6.4K, isso porque eles são instruções diretas para o processador, sem cabeçalhos, nem nada. O sistema operacional apenas carrega esse arquivo na RAM em um endereço especifico e coloca o ponteiro de execução lá, mas isso é historia para um outro dia.