Index wersja polskawersja polska

Executing machine code on the Casio PB-2000C

The PB-2000C supposedly doesn't permit to execute arbitrary machine code, but can be tricked into it by changing the address of the [BRK] key handle routine using the built-in monitor.

When the [BRK] key is pressed, an indirect jump is taken to the address contained in the ACJMP system variable &H0CE2, but only in bank 0 (ROM), while the user code can be placed only in bank 1 (RAM). The change of the PC memory bank proved to be possible by pointing the ACJMP vector to the following piece of code in the microprocessor's internal ROM:

0276:   PPS   $6
0277:   PST   UA,$6
0278:   RTN

By a fortunate coincidence of circumstances it leads to address &HC021 in bank 1 (the UA register contains &H11). It is safe to change the PC memory bank, because the interrupts are disabled at this point (the IE register contains &H00).


System setup

A PB-2000C with 64kB RAM is required.

Select the menu item [memory] and resize the memory block allocated for the files to 16350 bytes. Now the first file will start from address &HC021. It provides a safe place for the machine code.

<memory>      c    file    work 
   58879  41505   16350    1024
    free          16350    1024
c,file?41505,16350

The first directory entry reserved for the file that stores the machine code will be modified by the loader described below. Ensure that it doesn't contain any valuable data, since it will be overwritten.


Assembly program example

The program displays the text "Hello World!" and waits for pressing any key before returning to the system. The source should be assembled with the HD61 assembler with the option /p to produce a PBF-type output file.

        ORG     &HC021
        START   &HC021

        PST     UA,&H55
        LDW     $2,&H2AF0       ;restore the default jump address
        LDW     $23,&H3280      ;BRSTR, set ACJMP
        CAL     SYSCAL
        LDW     $23,&H328F      ;select the LCD as the output device
        CAL     SYSCAL
        LDW     $15,M1          ;pointer to the string
        LD      $17,M2-M1       ;length of the string
        LDW     $23,&H293B      ;display string
        CAL     SYSCAL
        LDW     $23,&H5774      ;KYIN, wait for a pressed key
        CAL     SYSCAL
        PST     UA,&H54
        JP      &H707C          ;return to the system, mode CAL

; call to a routine in bank 0 pointed to by $23,$24
SYSCAL: GST     UA,$6
        PHS     $6
        LDW     $6,&H0276-1
        PHSW    $7
        PST     UA,&H54
        JP      $23

M1:     DB      &H0C,"Hello World!",&H0D,&H0A
M2:

Loading the machine code

The program loads machine code in the PBF format from the PB-2000C ramdisk, or from the floppy disk or from the RS232 interface, converts it to binary and stores in the first ramdisk file. The previous file in the first directory entry will be overwritten.

main()
{
  FILE *infp,*outfp;
  char filename[24];
  char byte[2];
  unsigned addr1,addr2,x,sum;

/* sanity check */
  if (*(short*)0x0AAA != 0xC021)
  {
    printf ("Bad system configuration\n");
    abort();
  }
/* open the PBF file */
  for (x=0;x<24;x++)
    filename[x]='\0';
  printf ("PBF file name: ");
  scanf ("%23s",filename);
  if ((infp=fopen(filename,"rt")) == NULL)
  {
    printf ("Input file open error\n");
    abort();
  }
/* change the name and type of the first file in the directory (if exists) */
  addr2 = *(unsigned short*)0x0AB4;
  if (*(unsigned short*)0x0AB0 < addr2)
  {
    for (addr1=addr2-27; addr1<addr2-15; addr1++)
      *(char*)addr1 = '$';
  }
  if ((outfp=fopen("$$$$$$$$.$$$","wb")) == NULL)
  {
    printf ("Output file open error\n");
    abort();
  }
/* read the input file header */
  for (x=0;x<13;x++)
  {
    if ((filename[x]=fgetc(infp)) == ',')
      break;
  }
  filename[x]='\0';
  if (x>12 || fscanf(infp,"%u,%u,%u\n",&addr1,&addr2,&x) != 3)
  {
    printf ("Bad header\n");
    abort();
  }
  if (addr1 != 0xC021)
  {
    printf ("Bad ORG\n");
    abort();
  }
/* PBF->BIN conversion */
  sum=0;
  while (addr1<=addr2 && feof(infp)==0)
  {
    if ((byte[0]=fgetc(infp)) != ',')
    {
      byte[1]=fgetc(infp);
      sscanf(byte,"%2X",&x);
      if (fputc(x,outfp) != x)
      {
        printf ("File write error\n");
        abort();
      }
      sum+=x;
      addr1++;
    }
    else
    {
      if (fscanf(infp,"%u\n",&x) != 1 || x != sum)
      {
        printf ("Bad checksum\n");
        abort();
      }
      sum=0;
    }
  }
  fclose(outfp);
  fclose(infp);
  if (addr1<=addr2)
  {
    printf ("Unexpected EOF\n");
    abort();
  }
  if (rename("$$$$$$$$.$$$",filename))
  {
    printf ("Rename failed\n");
    abort();
  }
  printf ("Done!\n");
}

Starting the machine code

Type CLEAR MON in the CALC mode to enter the monitor.

Write value &H0276 to the ACJMP system variable at address &H0CE2.

>E0CE2
0CE2 AE-76 6F-02 FF-            
 
 

Press the [BRK] key.

Hello World!                    
_