User Tools

Site Tools


blog:pushbx:2023:0212_pr_command_recreation_tracing_and_access_variables_recent_changes_to_ldebug

PR command recreation, tracing and access variables, recent changes to lDebug

2023-02-08

Recreating Enhanced Debug's PR command with lDebug scripting.

Enhanced Debug, the nonfree PC DOS Retro fork of the FreeDOS Debug family, introduced the PR command. Albeit lDebug does not offer this feature it can be recreated with the RE command buffer and a repeated, silent P command. This is how to do it:

re.replace @r
re.append @r dco2 or= v22
re.append @r v22 := 0
re.append @if (value byte [cs:cip] in C3, C2, CB, CA, CF) then r v22 or= 8000
p FFFFF silent 1

DCO2 flag 8000h aborts a running T, TP, or P command. byte [cs:cip] reads the first opcode byte of the next instruction to be run, either at cs:ip or cs:eip (depending on the D bit of the code segment). When the next byte to run is a return instruction then we want to run that next instruction and abort the run afterwards. So we match the opcodes for return instructions and indirectly set a flag so that after the next instruction has ran the run will be ended.

Finally, we start the P command with a high repetition count to proceed-trace the debuggee until a return instruction has run. The silent 1 specifier means that only the very last register dump, after the return, is displayed eventually.

Here is the file pr.sld as currently saved on the HP 95LX:

re.replace @r
re.append @r dco2 or= v22
re.append @r v22 := 0
re.append @if (value byte [cs:ip] in C3, C2, CB, CA, CF) then r v22 or= 8000
re.append @if (byte [cs:ip] == 8F && byte [cs:ip + 1] clr 7 == C0) then r dco2 or= 8000
; p FFFFF silent 1

This has a special detection added to end the run if the next instruction is an overly-longly encoded pop reg instruction. The instruction 8F C1 is used in lDebug's own initialisation (as well as the cpulevel tool) to detect a NEC V20 or V30. However, as noted elsewhere and now in the sources too, this instruction must not be traced nor have a breakpoint written right after it, as it will crash the machine on the 95LX's NEC V20.

Using access variables

2023-02-09

Tracing in lDebug until a memory variable changes is easily done without the access variables, by simply comparing a byte, word, 3byte, or dword variable to the current value in the while clause of a repeated trace command. For example:

tp FFFFF while byte [psp:18+5] == FF

This also allows to trace until an interrupt service call (or a hardware interrupt handler) changes the value.

To trace until before executing an instruction that changes memory, the access variables can be used. However, this only works if the instruction in question is actually traced. That is, if an interrupt call is proceeded past (TM=0) then its handler changing the value cannot be detected this way. This is an example:

tp FFFFF while value from linear 1234:ABCD length 4 bytes in writing

The benefit is twofold: The about to be write can be detected even if the same value is written as was stored previously, and the write will end the trace before it actually runs and changes the value. (This is crucial if the change would confuse or break the debugger, like writing to the interrupt 1 or interrupt 3 handlers.)

The really powerful twist is that this works with the keyword reading in the in list as well, either alongside (comma-separated) or without the writing keyword. Other than hardware memory read breakpoints (which lDebug does not use) access variables are the only way to trace until a read access. This is advantageous because it also works when no hardware support is present, and any number of variables can be monitored at once (subject to the below 256 bytes text length of a while clause or the total RE command buffer size). Of course, hardware breakpoints can run a program at full speed (eg like lDebug G command) whereas access variables only work this way if the program is traced, which slows down the program's run a lot. Also, the RE buffer should contain an R command first to set up the access variables.

2023-02-10

Possible access variable sources:

  • explicit memory operands (RO, WO, or RW – RW shows up as two variables, one for reading and the other for writing)
  • implicit memory operands (string instructions, RO or WO)
  • stack memory operands (RO or WO)

Several memory accesses can be combined, eg:

  • 'movsb' has implicit read and implicit write
  • 'cmpsb' has two different implicit reads
  • 'push' and 'pop' with explicit memory operand have one read and one write, of which one involves the stack as an implicit operand
  • arithmetic RMW operations on memory have one explicit memory operand which is read and written

2023-02-12

The reads of an instruction itself, including immediate operands, never shows up in access variables. Instead, the keyword EXECUTING can be used in a VALUE … IN construct (either as the value range before the IN or as a match range after the IN). It expands to FROM LINEAR cs:cip LENGTH abo - cip which means it works correctly when evaluated after an R command ran, which sets up the ABO variable. Just like the access variables this construct only works for instructions being traced, and only after an R command has run.

lDebug changes

  • CIP and CSP variables added to allow accessing either a 16-bit or 32-bit variable depending on the CS D-bit or SS B-bit
  • Repeat disassembly after the NEC detection long-form pop instruction, to aid skipping past it as needed
  • Fix some warnings in mktables (the prior change needed a change to mktables)
  • Fix the EXECUTING keyword to use CIP rather than EIP, making it work on machines without the 386 registers
  • Refactor NEC detection into a subfunction, to aid proceeding past it using the P command
You could leave a comment if you were logged in.
blog/pushbx/2023/0212_pr_command_recreation_tracing_and_access_variables_recent_changes_to_ldebug.txt · Last modified: 2023-02-12 17:46:57 +0100 Feb Sun by ecm