Como referido no artigo anterior, existem espaços definidos na RAM para carregar código máquina, isto também incluindo o LOAD "" CODE, que normalmente devolve controle ao BASIC para executar a rotina instrução USR, para saltar para código máquina.
No entanto, tradicionalmente depois estar em código máquina, desde que não se necessite de rotinas BASIC e/ou rotinas da ROM, pode-se usar mais áreas da RAM.
O truque para carregar código máquina em RAM a partir do BASIC/LOAD "" CODE, sem estar sujeito às limitações de voltar ao controle do BASIC e sem ter que manter a integridade do sistema operativo/ROM (com "excepção" do stack do processador) pode passar por o LOAD "" CODE nunca devolver o controle ao BASIC.
Nesse caso, estaremos livres para usar a RAM como bem entendermos, no pressuposto que modificamos / esmagamos o stack de código máquina para os nossos propósitos, de forma a saltarmos para uma rotina nossa (e nunca devolver o controle às rotinas da ROM/BASIC no fim do LOAD "" CODE, como mencionado).
Até podemos carregar os 48KB de RAM de uma só vez, podendo inclusive reescrever a área do BASIC (e áreas associadas) completamente (ignorando a estrutura da RAM definida pelas rotinas da ROM no artigo anterior), sem ter que escrever rotinas extras em assembly para aproveitar os cerca de 2.5KB usados pelas variaveis de sistema/BASIC.
Temos então de subverter o stack. Analisando o stack do processador, apontado por SP, depois de invocar em BASIC:
CLEAR 65518 : LOAD "" CODE:
65518 = $FFEE
SP = $FFFD
$FFDF 6F 05 return address to middle LD_BYTES
$FFE1 3F 05 SA/LD_RET <---- called at the END of LOAD "" CODE (via RET)
$FFE3 71 07 return address to middle SAVE_ETC
$FFE5 E3 5C (some saved variable)
$FFE7 00 00 (some saved variable)
$FFE9 76 1B STMT-RET
Primeira word em $FFDF: $056F : retorno de CALL a LD_EDGE_1 na rotina LD_BYTES (i.e. a meio da rotina de carregamento de tape).
Segunda word em $FFE1 : $053F: retorno a SA/LD_RET, rotina que é colocada no stack como retorno de LD_BYTES no inicio do mesmo, que trata do tratamento de erro em caso de TAPE LOADING ERROR.
Terceira word em $FFE3 : $0771: retorno à rotina SAVE_ETC, invocada para processamento do comando LOAD "", depois de CALL LD_BYTES (i.e. retorno depois de carregar o bloco de dados do LOAD "" CODE)
Portanto, para não voltarmos ao BASIC, e devido a alguns emuladores aparentarem não voltar a SA/LD_RET, temos de reescrever no stack, quer o retorno por SA/LD_RET, quer o retorno de LD_BYTES. Ou seja, no nossos dados carregados por LOAD "" CODE, vamos garantir que é feito LOAD do(s) valor(es) nestas posições, da posição em memória em que se executa a nossa rotina código máquina em substituição dos valores que permitem o software da ROM voltar ao interprete de BASIC.
Ou concretamente, nas words em $FFE1 e $FFE3, fazemos reescrita destes endereços com a WORD do endereço da nossa rotina, que indica para onde vai passar a execução após abandonar a rotina que faz o load do bloco de tape.
Ou seja, fazemos LOAD "" CODE do seguinte código:
ORG $FFE1
DW PRINT_STRING
DW PRINT_STRING
PRINT_STRING:
DI
LD IX,STRING
PRLOOP: LD A,(IX)
OR A
JR Z,LOOP
RST $10
INC IX
JR PRLOOP
LOOP: JR LOOP
STRING:
DEFB "Testing",0
BASIC
10 CLEAR 65518 : LOAD "" CODE
PS. Basicamente, isto é um exploit para a ROM do Spectrum 48K.
Note-se que este método pode até ser abusado, para executar código, sem que alguém mais distraído a olhar para o BASIC se aperceba que isso foi feito desde que o exploit/rotina esteja escrita para preservar os valores necessários para voltar ao BASIC.
No entanto, a maior utilidade é ultrapassar a limitação de nunca ter sido incluído um auto-arranque em LOAD "" CODE de código máquina, para ultrapassar a limitação de não ter o LOAD "" CODE + invocação de arranque código máquina, deixando assim de estar dependente do bom funcionamento do BASIC para arrancar rotinas do código máquina, depois de uma LOAD "" CODE (feito a partir do BASIC).
Muito bom!
ResponderEliminar