PCODE do Clipper

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.caclipperwebsite.com

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)

AnexoTamanho
D2H.PRG [Função DEC2HEX()]772 bytes

Comentários

Pcode

Agradeço ao Anderson pelo texto "PCODE do Clipper". Com esse pequeno artigo, tive subsídio para resolver um problema que encontrei em um programa em clipper do Ministerio da Saude usado para digitar boletins de chagas, pois o mesmo estava bloqueando a digitação de boletins cuja ano fosse superior a 2010. Dessa forma pude:

a)Liberar o uso do mesmo até 2030;

b)Alterar a versão de 3.01 para X3.01 a fim de evitar confusão entre versões.

Novamente agradeço e parabéns pelo artigo tão esclarecedor sobre editores hexadecimais e linguagem assembly, tão obscura e misteriosa para a maioria das pessoas.

The housebreaker.