PCODE do Clipper
Pcode é uma abreviação de pseudo código e serve para intermediar o código de máquina com a linguagem Clipper. Cada pcode é substituído por um código de máquina.
Se você está interessado em saber sobre pcode é porque provavelmente perdeu os fontes do seu sistema e precisaria fazer alguma alteração módica no programa, tipo alterar o valor de uma variável ou parâmetro de uma função, tipo o SET EPOCH TO 1910 para SET EPOCH TO 1980 (Caso concreto que me levou a estudar esse assunto). Se você dominar o PCODE pode até construir um descompilador no próprio Clipper, assim como fez Wagner Nunes da Silva com o seu DCLIP.
Se você pegar o WDASM que é um "disassembler" (descompilador) que funciona com programas em 16 bits como os aplicativos gerados pelo Clipper, você poderá abrir o aplicativo *.EXE e ver a tradução do código de máquina em Assembly.
Assembly é uma das linguagens de baixo nível mais próximas do código de máquina que existe senão a mais próxima. Mas, daí você teria que estudar Assembly para modificar o seu *.EXE.
No caso, o SET EPOCH TO 1910 estaria aqui:
Observe que o código de máquina fica do lado esquerdo (13 09 00 3B 05 00 3B 76 07) enquanto que o Assembly correspondente fica do lado direito. Mesmo tendo lido um pouco a respeito desta linguagem, esse pedaço de código parece não representar nada para mim, nunca que saberia que esse Assembly aí seria o SET EPOCH TO 1910.
Aliás, se você escrever um código em Assembly, tipo o clássico "Olá, mundo!" e abrir depois com o debug do MS-DOS ou o WDASM mesmo, verá que o código fica diferente. Veja:
.model small .stack .data message db "Hello world, I'm learning Assembly !!!", "$" .code main proc mov ax,seg message mov ds,ax mov ah,09 lea dx,message int 21h mov ax,4c00h int 21h main endp end main
Esse programa compilado como first.exe ao ser visto pelo MS-DOS com o comando "debug first.exe", mostra a tela do debug, daí tecle "u", então verá o programa do jeito parecido como vê no WDASM com o código de máquina do lado esquerdo e o Assembly do lado direito, desse jeito:
0F77:0000 B8790F MOV AX,0F79 0F77:0003 8ED8 MOV DS,AX 0F77:0005 B409 MOV AH,09
Veja o quanto mudou! Não dá pra entender isso assim tão fácil pra mexer direto no *.EXE. Portanto, é melhor partir pro PCODE mesmo!
Observe que o pcode é um código de máquina em hexadecimal.
Agora veja a tela do Valkyrie daquele mesmo pedaço do programa em pcode:
Saiba que o pcode do SYMF é 13; PUSHW é 3B; SET(5, 1910) é igual a SET EPOCH TO 1910.
No nosso exemplo, o "SYMF [SET]" equivale a "13 09 00". Nem sempre o 13 09 00 equivale a SYMF [SET], mas isto é outra estória mais complicada, pois o "09 00" é uma word que depende da Tabela de Símbolos do Clipper (CA-Clipper´s Symbol Table) que pode ser diferente em cada compilação de um *.EXE. O nome dos programas, funções criadas nele, variáveis e seus tipos são criados nesta tabela. Ela contém 16 bytes por símbolo. Todo código no arquivo .OBJ se refere a esta Tabela de Símbolos que pode crescer até 64Kb.
Para mexer diretamente no *.EXE precisamos de um editor Hexadecimal para trabalhar. No caso, sugerimos o Hex Editor Neo que é o Freeware que utilizamos.
Vejamos como fica a tela no Hex Editor:
A coluna da direita na tela acima é arquivo *.EXE, como veríamos na tela, representados por caracteres ASCII. Como trabalhamos com código de máquina em Hexadecimal, precisamos utilizar o código hexadecimal do caracter da tabela ASCII.
Os parâmetros de PUSHW são valores do tipo numérico inteiro binário de 16 bits (word) que podemos obter com a função nativa do Clipper chamada I2BIN(). Esta função sempre retornará 2 bytes porque cada um equivale a apenas 8 bits (8 bits [byte] + 8 bits [byte] = 16 bits [word]). Depois você tem que converter esses 2 caracteres em código hexadecimal. Podemos obter o código decimal do caracter com ASC() e depois convertê-lo em hexadecimal por meio de fórmulas matemáticas.
Como o databus do 8086 (MS-DOS) é 16-bits, ele pode mover e armazenar 16-bits (1 word = 2 bytes) ao mesmo tempo. Se o processador armazena uma "word" (16-bits) ela o faz em ordem reversa na memória, por exemplo: 1234h (word) ---> memory 34h (byte) 12h (byte). Então, se a memória parece como 34h 12h e você pega uma word da memória, você pegará o valor 1234h (Note que usamos o "h" depois do número para indicar que é hexadecimal). Se apenas pegar um byte da word 1234h, o primeiro byte será 34h.
Na pesquisa (Find) ilustrada na figura acima, sabemos que sempre terá 3B 05 00 que é o equivalente a PUSHW 5 ( do SET(5,X) ), pois o hexadecimal é um sistema numérico que vai de 0 a F equivalente a 0 a 15 em decimal (base 16), portanto o 5, como é pequeno (menor que 15) sabemos de cara sem fazer nenhum cálculo que é 5 mesmo, ou seja 5 é 05h. Como devem ser 2 bytes, seriam 00h 05h, mas como acabamos de aprender, devido à segmentação de memória do MS-DOS, ficará como como 05h 00h.
Já estamos vendo na figura que 1910 equivale a 76h 07h. Precisamos alterar para 1980, então vejamos:
ANO := I2BIN(1980) // converte o número em uma palavra (word, 16 bits). ? LEFT(ANO, 1), "=", ASC( LEFT(ANO, 1) ) // ? = 188 decimal ? RIGHT(ANO, 1), "=", ASC( RIGHT(ANO, 1) ) // • = 7 decimal
Observe que não vamos simplesmente converter o número 1910 em hexadecimal. Precisamos converter o número em uma palavra de 16 bits (word) primeiro. Aí sim, converteremos cada caracter (byte) em seu valor hexadecimal. No exemplo acima obtivemos valores decimais 188 e 7 que teremos que converter em hexadecimal. Vemos de cara que 7 é 7h mesmo, mas 188 é maior que 15, então vamos aprender como é que transformamos ele em hexadecimal seguindo a inteligência do exemplo abaixo.
Sistema Hexadecimal
Este é um sistema numérico posicional bastante usado em informática, especialmente em programação Assembly. Neste sistema dispomos de 16 símbolos conforme mostra a tabela abaixo:
Símbolo | Valor absoluto |
---|---|
0 | 0 |
1 | 1 |
2 | 2 |
3 | 3 |
4 | 4 |
5 | 5 |
6 | 6 |
7 | 7 |
8 | 8 |
9 | 9 |
A | 10 |
B | 11 |
C | 12 |
D | 13 |
E | 14 |
F | 15 |
Convertendo de Decimal para Hexadecimal
Sabendo-se que o sistema hexadecimal dispõe de 16 símbolos (você conta com o zero), concluímos que sua base é 16. Podemos converter qualquer número decimal em hexadecimal dividindo-o sucessivamente por 16, que é a base do sistema numérico desejado, até seu quociente ser 0. Vejamos o exemplo da conversão do número decimal 23870 para hexadecimal:
Dividendo | Divisor | Quociente | Resto |
---|---|---|---|
23870 | 16 | 1491 | 14 |
1491 | 16 | 93 | 3 |
93 | 16 | 5 | 13 |
5 | 16 | 0 | 5 |
Tomando-se os restos na ordem inversa e seus respectivos símbolos, temos:
Resto | 5 | 13 | 3 | 14 |
Símbolo | 5 | D | 3 | E |
Assim concluímos que o número decimal 23870 convertido para hexadecimal é: 5D3E.
Seguindo o mesmo entendimento, descobrimos que 188 é BCh ou simplesmente pegue a calculadora científica do Windows, marque "Dec" e digite 188, depois marque "Hex". Então, descobrimos que o valor 1980 passado pelo SET é equivalente a: BCh 07h. Da mesma forma teríamos descoberto que 1910 é: 76h 07h.
O Clipper não tem uma função que converta decimal para hexadecimal, mas que tal fazermos uma? Vamos traduzir essa matemática toda em um programa bem simples?
Vamos chamá-la de DEC2HEX(). Vejamos:
* AUTOR: ANDERSON CARDOSO SILVA * www.linguagemclipper.com.br CLS ? 23870, " EM DECIMAL É: ", DEC2HEX(23870) // RETORNA: 5D3E ? 188, " EM DECIMAL É: ", DEC2HEX(188) // RETORNA: BC QUIT FUNCTION DEC2HEX(nDEC) cHEX :="" IF VALTYPE(nDEC)="N" aBASE16 := {"0","1","2","3","4","5","6","7","8","9",; "A","B","C","D","E","F"} aHEX := {} nDIVIDENDO := nDEC nQUOCIENTE := 1 // só pra entrar no DO WHILE DO WHILE nQUOCIENTE # 0 nQUOCIENTE := INT(nDIVIDENDO/16) nRESTO := nDIVIDENDO % 16 nRESTO++ // SOMA 1 PORQUE aBASE16 COMEÇA COM "0" E NÃO "1" cHEX:= aBASE16[nRESTO] aADD(aHEX, cHEX) nDIVIDENDO := nQUOCIENTE ENDDO ENDIF cHEX := "" FOR X=LEN(aHEX) TO 1 STEP -1 cHEX += aHEX[X] NEXT RETURN cHEX
Sabendo que 1910 é "76h 07h" constatamos que o bloco marcado no Hex Editor é realmente o que pretendemos mexer, pois temos "3B 05 00 3B 76 07" que equivale ao (5, 1910) da função SET. Então, agora basta mudar o "76 07" por "BC 07", ou simplesmente o 76h pelo BCh e voilà!
Runtime error R6003 / Error Divide by 0
Agora que você já está craque, caso você tenha perdido os fontes de um determinado sistema que tenha esse erro do "divide by 0", basta procurar a seguinte sequência no seu programa com o Hex Editor:
B8 52 17 8B CA 33 D2 F7 F1
Então, substitua os últimos bytes F7 F1 com 90 90.
=> Em nossa página de downloads tem um patch que faz isso automaticamente pra você, chama-se div0.
ATENÇÃO: Mexer diretamente no código de máquina do seu sistema pode fazer com que ele não funcione mais, portanto em qualquer caso FAÇA BACKUP PRIMEIRO antes de tentar isso.
____________________________
Fontes consultadas:
http://www.inf.unisinos.br/~barbosa/paradigmas/consipa3/61/s16/arquivos/curiosidades.html (Pcode)
http://www.genesys.4mg.com/pcode.htm (Pcode)
http://www.xs4all.nl/~smit/asm01001.htm#ready (Assembly, segmentação de memória do MS-DOS, em inglês)
http://www.tecnobyte.com.br/sisnum1.htm (sistemas numéricos)
http://www.donnay-software.com/memory.htm#SymbolTable (Symbol table)
Comentários recentes