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:

pcode do clipper

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:

[prism:clipper]
.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
[/prism:clipper]

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:

[prism:clipper]
0F77:0000  B8790F          MOV AX,0F79
0F77:0003  8ED8            MOV DS,AX
0F77:0005  B409            MOV AH,09
[/prism:clipper]

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:

código de máquina

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:

[prism:clipper]
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
[/prism:clipper]

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:

[prism:clipper]
* 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
[/prism:clipper]

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)

Anexo: 

Total votes: 0