User Tools

Site Tools


blog:pushbx:2023:0911_live_patching_the_debugger_itself

Live patching the debugger itself

As mentioned a while ago I'd prepared a patch in the form of a Script for lDebug file that patches out a mean error in the debugger's release 6 executables. However, this script has some limits:

  • It can only be applied to an uncompressed executable
  • It can only be used if the entire executable fits in the Low Memory Area, precluding use if arbitrary data is appended to the executable
  • It has to make some assumptions about where to patch and the minimum auxiliary buffer size
  • It has to patch in the minimum size for the auxiliary buffer, disallowing the DIL command from using the full buffer if it is larger

So here's a revised patch that improves upon all these disadvantages. It is called patchl6.sld. This is its content:

if (dif & 800) then goto :nopm
;   205                              <1> protectedmode	equ	 800h	; in (DPMI) protected mode
s dcode1seg:0 l dcode1len 88 65 04 80 65 05 FD 89 4D 06
;  2491 00001C74 886504              <1> 	mov byte [di + 4], ah		; store the multiplex number
;  2492 00001C77 806505FD            <1> 	clropt [di + 4], 200h		; indicate it is claimed
;  2493 00001C7B 894D06              <1> 	mov word [di + 6], cx		; = how many list entries before ours,
;  2494                              <1> 					;  or = -1 if not from a list
;  2495 00001C7E E9B900              <1> 	jmp .done
;  2496                              <1> 
;  2497                              <1> @@:
;  2498                              <1> 		; ds:di -> second terminator (will be overwritten)
;  2499                              <1> %if _AUXBUFFSIZE == _AUXBUFFMAXSIZE
;  2500                              <1> 	cmp di, _AUXBUFFSIZE - 8 * 3	; enough for 1 entry + 2 terminators ?
;  2501                              <1> %else
;  2502 00001C81 3B3E[540A]          <1> 	cmp di, word [auxbuff_current_size_minus_24]
;  2503                              <1> %endif
if (src != 1) then goto :error
r v0 := 2
if (byte [srs:sro + count] == EB) then goto :short
r v0 += 1
if (byte [srs:sro + count] != E9) then goto :error
:short
if (byte [srs:sro + count + v0] == 36) then goto :notneedednew
if (word [srs:sro + count + v0] == FF81) then goto :notneededold
if (word [srs:sro + count + v0] != 3E3B) then goto :error
r word [srs:sro + count + v0 + 2] .
s dcode1seg:0 l dcode1len range srs:sro + count + v0 l 4
if (src != 3) then goto :error
r v1 := 8 * #1024 + #16 - #24
if exists r dauxbuflen then r v1 := dauxbuflen - #24
a srs:sro0
 cmp di, (v1)
 .
a srs:sro1
 cmp di, (v1)
 .
a srs:sro2
 cmp di, (v1)
 .
@goto :eof

:nopm
; Cannot operate in PM
@goto :eof

:notneededold
; Patch not needed, auxiliary buffer has fixed size (or already patched)
@goto :eof

:notneedednew
; Patch not needed, comparison uses ss: segment override
@goto :eof

:error
; Error detected

It isn't all better, however. This patch must be applied at run time of the debugger (by the debugger itself), so it isn't permanent to the executable the debugger loaded from. That means to use a patched debugger, this script must be run once for every instance of the debugger. When reloading a new session, the patch must be applied again. (Of course, the startup config scripts could call out to run the live patch script.)

Another improvement of the script is it will detect four usual exit conditions:

  • Cannot operate in Protected Mode
  • Patch is not needed due to already being patched, or a revision of the debugger that hardcodes an auxiliary buffer size
  • Patch is not needed for a revision of the debugger with the segment addressing bug fixed
  • Patch succeeded

(The fifth exit condition is an unspecified error.)

I also wrote about the live patch script on the freedos-user mailing list. Quoting from there:

Today I figured I could make an online patch in which the debugger that needs to be patched can, itself, run a Script for lDebug file to patch its own code. The patch result is the same as for the offline patch, however the online method works on any executable, including the compressed executables.

With the insight into the exact location of the code section (read from the read-only variables DCODE1SEG and DCODE1LEN), the online patch is even more reliable than the offline one. However, the detection of the correct offset used by the instructions to replace is a bit different as, at script run time, the init section is no longer accessible. Because the online debugger is already loaded, the patch can also patch in the exact allowable size of the auxiliary buffer (calculated as DAUXBUFLEN - #24 if this variable is available), as at this point this size cannot change after the control flow has left init behind. (The /A switch to init can be used to enlarge the buffer, which is also the cause of this bug to begin with.)

As an addition, this script will end in one of five different paths: Success, patch not needed (source patch already applied), patch not needed (binary patch is already applied or auxiliary buffer size is hardcoded as before introduction of the /A switch), attempt to run the script in Protected Mode (which is not supported), and other error.

The patch is applied by simply running a command like "y patchl6.sld" in the affected debugger. It will write the SRC (Search Result Count), SRS (Search Result Segment), SRO (Search Result Offsets), COUNT (search pattern length), AAS (Address Assembly Segment), AAO (Address Assembly Offset), and V0 and V1 variables.

You could leave a comment if you were logged in.
blog/pushbx/2023/0911_live_patching_the_debugger_itself.txt · Last modified: 2023-09-11 20:46:50 +0200 Sep Mon by ecm