Wiki

Clone wiki

UniPCemu / Debugging

The emulator has a build in debugger available. It also has the port E9 hack from Bochs implemented.

Detection

Detection process and retrieving the enter/reset string (only when not in command mode, so do this once at boot only or when you've terminated the command mode):

  1. Read port 0xE9 to detect the debugger. If it returns 0xE9, it's there. Else abort.
  2. Read bytes from port 0xEA until a 0xFF byte is found to start at the correct value.
  3. Read bytes from port 0xEA into a string buffer until a 0xFF end-of-string byte is given.
  4. If the read string is empty, command mode doesn't exist. Else, it contains the special identifier string to enter command mode.

Keep this string value ready when working with the debugger. It is used to start and reset the debugger routines to command mode.

Starting/resetting the debugger to the initial command mode:

  1. Write a newline followed by the read string (making sure we're at the start of a new line), next followed again by a newline(n or r) to port 0xE9 (port E9 hack). This will activate and reset the command mode.
  2. Read one byte from port 0xEA. If it returns 0, you have entered command mode and the interpreter is ready to receive a command.

Command mode input/output uses byte quantities, bigger values are in little endian format.

Command Mode

Both IN and OUT are done on port 0xEA, after the command mode is enabled.

All errors redirect to this point in execution, after reading the ERROR(0) code or error codes depending on the function.

OUT First byte: group selection. IN result (optional): 1 for OK, 0 for error(resets command). OUT Second byte: function selection. IN result: 1 for OK, 0 for error (resets command). Without parameters: a reset to command mode is executed, if it's still active after the command (a quit command won't give a result).

Parameter phase: (depending on the function) OUT Parameters: Sets the parameters to use. IN result: 3 for parameters acnowledged and result phase started, 2 for parameters acnowledged and reset to command mode, 1 for not enough/too much written and reset to command mode, 0 for error (reset to command mode).

Without a result, a reset to command mode is executed.

Result phase: (depending on the function) First read gives the low byte (little endian) of the result size. Second read gives the high byte (little endian) of the result size. Above low and high bytes give the size of the below read result data (if any). Third and further reads give the result data. Overflow or reading no first, second or all result data will cause the result to give an error.

Result verification phase (only with a result phase) OUT Anyvalue: Moves the result phase to the verification phase, causing an IN to give the result. IN result: 4 for OK. 0 for error (also caused by the above result phase going wrong (not reading enough or too much)). After this result is read, command mode is entered again.

Command List

Values are in little-endian format.

Group #0: Basic functions(implemented).

Function #0: Quit to debugger. This will cause the command mode to quit and re-enter normal mode. Function #1: Change output filename: Specify length.

Parameter:
WORD The size of the zero-terminated string containing the new filename (word).
Result:
1 on success, 0 on error.

Function #2: Change output filename: Specify filename (after above function only).

Parameter:
The zero-terminated string containing the new filename, of the length in bytes of the specify length command.

Function #3: Enable/disable device logging

Parameter:
BYTE The disable/enable of a device logging. Bit 0 is the disable(0)/enable(1) toggle. The other bits are the device ID shifted left by 1 bit.
Device IDs: 0: VGA memory logging.
Result:
BYTE the value inputted for success, the bits flipped value inputted for errors.

Function #4: Terminate emulator. This will cause the emulator to terminate as soon as possible, if implemented. If not implemented this will error out.

Function #5: Arm debugger logging mode. This will arm the debugger logging mode to start logging after a certain amount of instructions. If not implemented, this will error out.

Parameter:
BYTE The logging mode (see the settings file). Must be greater than 0. DWORD The amount of instructions (including the initial read from port E9) after which to start logging.
Result:
0 if invalid parameters or errored out. 1 if armed. If armed, a read from port E9 will start the countdown (including said read). When the countdown times out, logging is started. After the countdown is started or debugging is running, a read from port E9 will terminate the logging after the instruction finishes.
Group #1: Disk functions (unimplemented).
Function #0: Check for disk mount
Parameters:
BYTE disk number (0,1=Floppy; 2,3=HDD, 4,5=CDROM, 6,7,8,9=USB)
Result:
BYTE mounted (0 on not mounted, 1 on mounted)
Function #1: Swap next disk (based on disk number, starting at 1 (at the end of the filename))
Parameters:
BYTE disk number (see above, except HDD)
Result:
BYTE OK (1 on same mount, 2 on mounted, 0 on error)
Function #2: List available mounts.
Parameters:
BYTE disk number (see above, except HDD)
Result:
A list of mountable disks:
Each disk has the following structure: WORD disk number WORD filename length byte filename
Function #3: Mount disk
Parameters:
WORD disk number
Group #2: Soundfont
TODO

C routines for working with the debugger.


int detectCommandMode(char *identifier) //Detection routine. Only use when not in command mode.
{
while (inp(0xEA)!=0xFF) //Not done yet?
{
//Read till start!
}
char *identifierpos = identifierholder; //The identifier!
byte val;
while ((val = inp(0xEA))!=0xFF) //Not done reading yet?
{
*identifierpos++ = val; //Add to the result!
}
*identifierpos++ = '0'; //End of string!
return !!strcmp(identifier,""); //TRUE on detected, FALSE on not detected!
}
int resetCommandMode(char *identifier) //Enter/reset the command mode (after detection or later)
{
if (!identifier) return 0; //Not entered: invalid identifier!
outp(0xE9,'n');
outp(0xE9,'n');
while (*identifier)
{
outp(0xE9,*identifier++); //Process the string!
}
outp(0xE9,'n');
if (!inp(0xEA)) //Command mode entered?
{
return 1;
}
return 0; //Failed: command mode not entered!
}

Emulator debugger

Various options for debugging also exist:

  • Advanced menu > CPU Settings menu > Debug mode: lets you select various debug modes for debugging running applications.
  • Advanced menu > CPU Settings menu > Debugger log: lets you log debugging information.
  • Advanced menu > CPU Settings menu > Diagnostics code: lets you set breakpoints directly after diagnostics values being written to the Diagnostcs port(port 60h on XT, 80h on AT+). Using Square(Numpad 4) on this option sets the breakpoint to the current value.
  • Advanced menu > CPU Settings menu > Breakpoint: lets you set breakpoints for the combination of a segment:offset address combined with processor mode. To use Protected mode or Virtual 8086 mode, simply add the P(rotected) or V(irtual 8086) suffix to it's value. Clear the value that's set to change it to Not set(No breakpoint). After the adress and mode, either M or I can be added for Ignoring EIP(I ignores the (E)IP value, triggering only on CS and processor mode) or only triggering on the processor mode itself(by using the M(ode)-suffix). A S-suffix will enforce single-step being triggered on said breakpoint(No matter if the debugger is skipping anything). Using square on it loads the current segment, offset and processor mode into the breakpoint. This breakpoint option is always disabled to modify from the Settings menu on PSP builds due to compiler issues.

Updated