User Tools

Site Tools


blog:pushbx:2022:0424_ldebug_expression_evaluator_learns_to_use_types_poorly

lDebug expression evaluator learns to use types (poorly)

One week ago I finally had to confront a long-standing defect in lDebug's expression evaluator: It didn't support types. I found out because all numbers that were "negative" in two's complement (ie values that are >= 8000_0000h unsigned) were accepted for narrower callers, that is, getword or getbyte. These functions are called for things such as offsets, E/S/F command strings, and permanent breakpoint counters.

I didn't notice for a long time because I only used fitting values or -1, the latter which happened to be allowed through because of a bug. The bug was that the getexpression counting of significant bits ended up with a count of 32 bits for large numbers, yet the protocol defined the least five bits as holding the count. Therefore a count of 32 overflowed to 0 and the callers that did check the count (only getword and getbyte at the time) never detected that these large numbers were outside their limits.

Now the obvious fix was to use six bits to return the count. But that broke the acceptance of negative values such as -1. So the next step was to treat all numbers as signed. As marked in the changeset description we considered this a magic hack though.

The proper fix came along briefly after the magic: Almost complete support for types. In particular, the unary minus operator sets its value's type to negative, allowing literals prefixed by a unary minus to be handled as signed numbers by the significant bits counting. Explicit type keywords such as sword (signed word) versus word (unsigned word) also allow to specify signedness. Other than that, binary operators have a complicated relationship to types. As noted in the changeset message we may revisit some of those later on.

Eventually one test had to be dropped because it passed a literal of FFFF_FFFF for a permanent breakpoint's counter. (Literals without any type keyword or operator are considered unsigned by default, like most all expression terms, except for parens-surrounded subexpressions.) This is now considered broken and unsupported. Note that minus one is still allowed, as desired.

The type support soon was used to implement a feature we'd long desired: specifying a pointer type allows to use a 32-bit expression as a 16:16 far pointer. This is most useful with compound variables like csip and sssp or with a memory dword that we want to read as a far pointer without specifying the memory address twice, eg g pointer [ss:sp] to return from a 16-bit far function or interrupt handler. (This previously required a command like g word [ss:sp + 2]:word [ss:sp].) Besides, getaddr now also re-uses the check for 16 significant bits.

Another change that came along was checking the significant bit widths for R commands that write to a variable or memory location. The need for this check had been noted in a comment that originated in an early revision, in 2010 October. This had the knock-on effect of failing a test that used a command reading r ax ^:= -1 to bitwise invert a 16-bit register. We fixed this by inserting a word type keyword.

Writing of old revisions, the lack of types in the expression evaluator goes back all the way to the initial commit of lDebug in 2010 (still called NDebug at the time).

You could leave a comment if you were logged in.
blog/pushbx/2022/0424_ldebug_expression_evaluator_learns_to_use_types_poorly.txt · Last modified: 2022-04-24 14:12:06 +0200 Apr Sun by ecm