Thursday, November 09, 2023
The Day a Beatle Died
Growing up,
I heard the rumors that Paul McCartney died in the 60s and was replaced with a look-alike to keep the money rolling in band together.
But I did not realize that it was on this day in 1966 that he died.
Supposedly.
Maybe.
If you believe.
Who knows? We can't ask John or George. Perhaps we can ask William Campbell. Or maybe Bill Shepherd. Or was it Billy Shears?
Wait! Maybe Ringo knows …
“A live, nine hundred foot what?”
And speaking of musical oddities …
This is a something that I think only two or three of my readers may appreciate (if that many)—a recording of MC 900 Ft. Jesus performing live at Good Records in Dallas, Texas. The sound quality could be better, but for someone who quit the industry in 2001, it was surprising to see him perform in 2017. And it's a shame he stopped recording, because his music is very unique—something like a cross between rap and jazz. Jazzrap? Rapjazz? Something like that.
Friday, November 17, 2023
Has it really been 45 years since this Star Wars Special that George Lucas disavowed aired for the first, and so far, only time?
Yikes! I'm getting old. I remember watching this when it first aired, and, except for the cartoon segment, didn't find it all that interesting. It certainly didn't help that it centered on Chewbacca's family (an hour or grunts and growls) and no subtitles what-so-ever. That's a very odd choice, but it's not like it's the only odd thing in the special—you have a Wookie getting off on soft-core porn (seriously!), Bea Arthur singing, Harvey Korman attempting comedy, Carie Fisher attempting to sing to the main Star Wars theme (who knew it has lyrics?) while wasted, Mark Hamill wearing makeup, and Harrison Ford wantint to be anywhere else than this special.
You would think this would be entertaining, but no, it's not. You can attempt to watch it, but I'd recommend watching an angry review of it (or any number of other reviews of it as it's more entertaining than the actual special itself.
Saturday, November 18, 2023
The Temptation, Part II
Yes, yet more email to another Sean Conner that arrived in my Gmail account. This time, informing me I should change “my” password to “my” Instagram account.
Oh? Really?
And not just one, but multiple emails about this.
Okay, time for some tough love. It took a few attempts, but I finally changed “my” password to “my” Instagram account. Sorry the other Sean Conner, but not really. You should have known what your email address was. Is that so hard? (I know the answer—apparently yes, and I'm sill receiving emails about my non-existent child in Memphis)
WTF Instagram? Seriously, WTF?
So I'm trying to update the profile on “my Instragram account” when I see this: “Editing your links is only available on mobile. Visit the Instagram app and edit your profile to change the websites in your bio.”
Really?
I can edit my bio, and my gender, but I can't update a link? Because I'm on a desktop computer?
I figure, maybe I can hack the form to enable that field? I toggle the Web Developer Tools in Firefox and immedately see this:
.d8888b. 888 888 d88P Y88b 888 888 Y88b. 888 888 This is a browser feature intended for "Y888b. 888888 .d88b. 88888b. 888 developers. If someone told you to copy-paste "Y88b. 888 d88""88b 888 "88b 888 something here to enable an Instagram "888 888 888 888 888 888 Y8P feature or "hack" someone's account, Y88b d88P Y88b. Y88..88P 888 d88P it is a scam and will give them access "Y8888P" "Y888 "Y88P" 88888P" 888 to your Instagram account. 888 888 888 See https://www.facebook.com/selfxss for more information.
Heh. But in the meantime …
… and nope.
The link field has no name,
and even if I remove the disabled
attribute,
it won't let me type in it
(and as an aside,
the “gender” field isn't even an HTML field element,
but the word “Male” wrapped in four <DIV>
s and a <SPAN>
, each with fifteen classes attached to them—what the hell?).
I'm so far removed from “modern web development” that I probably can't hack this without significant effort that I'm too lazy to do.
Sheesh.
Sunday, November 19, 2023
Ah, so that's the definition of a unit test
Originally, the term “unit” in “unit test” referred not to the system under test but to the test itself. This implies that the test can be executed as one unit and does not rely on other tests running upfront (see here and here).
Via The big TDD misunderstanding (2022) | Hacker News, The Big TDD Misunderstanding. 💡Originally, the term “unit” in “unit… | by Oliver Wolf | Mediu (also via Lobste.rs)
I think I finally have the answer to my question, “what is a ‘unit test?’” and … wow! And to think I was doing that all along at The Enterprise.
When I wrote the regression tests, I set each test up to be independent—each test got its own unique test data in the various “databases” we were using (we weren't really using a database, but custom binary data files based off a periodic database dump) and in theory, we could have run the tests in random order. In fact, during my last year there, I almost added that feature to the regression test, but by then, I was so burned out with The Process™ that I just never bothered. It's a shame that, because I think it would have been an interesting form of test to perform.
Friday, November 24, 2023
A Motorola 6809 assembler—there are many like it, but this is mine
I think it's time I start talking about some of the software I write, and I might as well start with my latest project that I've been having way too much fun writing, a 6809 assembler written in C.
Yes, I could use an existing 6809 assembler, but most of the ones availble as source seem to be based off one written in 1993 by L. C. Benschop. And the code quality there is … of its time … which I think is the most charitable thing I can say about it. Here's the code to convert text to a decimal number:
short scandecimal() { char c; short t=0; c=*srcptr++; while(isdigit(c)) { t=t*10+c-'0'; c=*srcptr++; } srcptr--; return t; }
Lots of globals,
lots of “magic” numbers
(at least they're described in comments),
and vwl mprd
variable names.
It's not a pleasant code base to work in.
Besides, it's something I've been wanting to do since college. So why not?
So I have a standard two-pass assembler with a few features I haven't seen in other 6809 assemblers.
And that's what I'll be describing here.
The first feature is small,
but decidedly nice—the ability to have underscores (“_”) in numberic literals.
It's more useful for binary literals,
such as %10_00_01_11
or %000_01001_0_100_0010
but it can be used for decimal, octal or hexadecimal numbers as well.
Another simple feature is the ability to generate a dependency list for make
.
Since I support the inclusion of multiple assembly files,
it makes sense to support this feature as well.
I'm not trying to make an assembler that works on the 6809 system
(I think it's way too small a system for that),
but an assembler that makes it nice to write code for a 6809 system.
I also have local labels that work similarly to NASM. As an example:
clear_bytes clra .loop sta ,x+ decb bne .loop rts clear_words stb ,-s clra clrb .loop std ,x++ dec ,s bne .loop rts
Internally,
the assembler will merge the local labels with the previous non-local label,
and thus,
we get the labels clear_bytes
, clear_bytes.loop
, clear_words
and clear_words.loop
.
I find it makes for cleaner code.
What is easier to understand,
this?
;******************************************************************** ; Music Synthesizer ;Entry: $3FF0 Freq delay count ; $3FF1 Envelope table address ; $3FF3 Envelope delay count ; $3FF5 Volume, 1 to 255 ; NOTE: from _TRS_80 Color Computer Assembly Lanauge Programming_, ; page 252 ;******************************************************************** org $3F00 mussyn lda $FF01 ; select sound out anda #$F7 ; reset MUX bit sta $FF01 lda $FF03 ; select sound out anda #$F7 ; reset MUX bit sta $FF03 lda $FF23 ; get PIA ora #8 ; set 6-bit sound enable sta $FF23 ldu #$3FF0 ; point to block ldx 1,u ; get envelope address stx envptr ; save in envptr ldx 3,u ; get envelope delay mus005 lda [envptr] ; get value beq mus090 ; if 0, done ldb 5,u ; get volume mul ; adjust volume anda #$FC ; reset RS-232-C (?) sta $FF20 ; set on ldb ,u ; get frequency delay count mus010 leax -1,x ; decrement envelope count bne mus020 ; go if not 0 ldy envptr ; increment evelope ptr leay 1,y sty envptr ldx 3,u ; get envrolope delay mus020 decb ; decrement frequency count bne mus010 ; go if not 0 lda [envptr] ; DUMMY brn *+2 ; DUMMY ldb 5,u ; DUMMY mul ; DUMMY clr $FF20 ; set off ldb ,u ; get frequency delay mus030 leax -1,x ; decrement envelope count bne mus040 ; go if not 0 ldy envptr ; increment envelope ptr leay 1,y sty envptr ldx 3,u ; get envelope delay mus040 decb ; decrement frequency count bne mus030 ; go if not 0 bra mus005 ; keep on playing mus090 rts envptr fdb 0 end mussyn
Or this?
;******************************************************************** ; Music Synthesizer ;Entry: $3FF0 Freq delay count ; $3FF1 Envelope table address ; $3FF3 Envelope delay count ; $3FF5 Volume, 1 to 255 ; NOTE: from _TRS_80 Color Computer Assembly Lanauge Programming_, ; page 252 ;******************************************************************** org $3F00 mussyn lda $FF01 ; select sound out anda #$F7 ; reset MUX bit sta $FF01 lda $FF03 ; select sound out anda #$F7 ; reset MUX bit sta $FF03 lda $FF23 ; get PIA ora #8 ; set 6-bit sound enable sta $FF23 ldu #$3FF0 ; point to block ldx 1,u ; get envelope address stx .envptr ; save in envptr ldx 3,u ; get envelope delay .next_byte lda [.envptr] ; get value beq .exit ; if 0, done ldb 5,u ; get volume mul ; adjust volume anda #$FC ; reset RS-232-C (?) sta $FF20 ; set on ldb ,u ; get frequency delay count .sound_on leax -1,x ; decrement envelope count bne .check_freq_on ; go if not 0 ldy .envptr ; increment evelope ptr leay 1,y sty .envptr ldx 3,u ; get envrolope delay .check_freq_on decb ; decrement frequency count bne .sound_on ; go if not 0 lda [.envptr] ; DUMMY brn *+2 ; DUMMY ldb 5,u ; DUMMY mul ; DUMMY clr $FF20 ; set off ldb ,u ; get frequency delay .sound_off leax -1,x ; decrement envelope count bne .check_freq_off ; go if not 0 ldy .envptr ; increment envelope ptr leay 1,y sty .envptr ldx 3,u ; get envelope delay .check_freq_off decb ; decrement frequency count bne .sound_off ; go if not 0 bra .next_byte ; keep on playing .exit rts .envptr fdb 0 end mussyn
It helps that I allow 63 characters for a label, which is way more than any 6809 assembler I've ever used.
The last feature I have are warnings. Given the following code:
.start lda <<b16,x ldb #$FF12 std foobar lda b5,u ldb b8,s tfr a,x lbsr a_really_long_label_that_exceeds_the_internal_limit_its_quite_long sta [<<b5,y] bra another_long_label_that_is_good a_really_long_label_that_exceeds_the_internal_limit_its_quite_long rts another_long_label_that_is_good clra .but_this_makes_it_too_long_to_use decb bne .but_this_makes_it_too_long_to_use bra next8 next8 lbra next1 next16 brn next8b next8b lbrn next16b next16b rts foobar equ $20 b16 equ $8080 b5 equ 3 b8 equ 25
The assembler will generate the following warnings (yes, this code is used to test all the warnings in the assembler):
warn.asm:1: warning: W0010: missing initial label warn.asm:6: warning: W0008: ext/tfr mixed sized registers warn.asm:7: warning: W0001: label 'a_really_long_label_that_exceeds_the_internal_limit_its_quite_l' exceeds 63 characters warn.asm:12: warning: W0001: label 'a_really_long_label_that_exceeds_the_internal_limit_its_quite_l' exceeds 63 characters warn.asm:17: warning: W0001: label 'another_long_label_that_is_good.but_this_makes_it_too_long_to_u' exceeds 63 characters warn.asm:19: warning: W0001: label 'another_long_label_that_is_good.but_this_makes_it_too_long_to_u' exceeds 63 characters warn.asm:1: warning: W0003: 16-bit value truncated to 5 bits warn.asm:2: warning: W0004: 16-bit value truncated to 8 bits warn.asm:3: warning: W0005: address could be 8-bits, maybe use '<'? warn.asm:4: warning: W0006: offset could be 5-bits, maybe use '<<'? warn.asm:5: warning: W0007: offset could be 8-bits, maybe use '<'? warn.asm:7: warning: W0009: offset could be 8-bits, maybe use short branch? warn.asm:9: warning: W0011: 5-bit offset upped to 8 bits for indirect mode warn.asm:21: warning: W0012: branch to next location, maybe remove? warn.asm:22: warning: W0012: branch to next location, maybe remove? warn.asm:1: warning: W0002: symbol '.start' defined but not used
So, in order of appearance:
W0010
- What happens if you give a local label sans a non-lobal label?
Well,
I decided to allow it,
but at least warn about it.
The result label is just
.start
but it could be hard to reference. I could see making this an error, but for now, it's just a warning. W0008
- This is the only warning about undefined behavior. The 6809 doesn't specify what happens when you transfer (or exchange) an 8-bit register with a 16-bit register (or vice versa). The CPU just keeps running, but the results are just that—undefined. Again, this could be an error, but for now, I'm letting it slide as a warning.
W0001
- Internally, the assembler just truncates labels to 63 characters, but otherwise, it just keeps going.
W0003
- This is related to the nature of a two-pass assembler and forward references.
Here,
I'm forcing the given index to a 5-bit index
(which doesn't take an additional byte of space,
unlike an 8-bit (one additional byte) or a 16-bit (two additional bytes) offset),
but the assembler has to assume it's okay on pass one.
By the time pass two comes around,
b16
is defined but it's value exceeds that 5-bits (which is -16 to 15 for the record). This warning is just letting the user know the value doesn't fit into 5-bits. W0004
- Pretty much the same as
W0003
except for an 8-bit value. W0005
- Again, due to the nature of a two-pass assembler. This time, no hint is given to the size of the label, and on pass one, the assembler assumes the worst—a 16 bit value. It's only on pass two does it have enough information to know it could be an 8-bit address, but it can't use an 8-bit address as it would throw all the other addresses off (ask me how I know).
W0006
- Similar to
W0005
, but for an offset that can fit in 5-bits. W0007
- Similar to
W0006
but for an 8-bit value. W0009
- This time, the assembler has determined that the target instruction falls within an 8-bit relative branch instruction, but was given a 16-bit relative branch instruction. This can happen because of code refactorings that shrinks the distance between the branch instruction and the target.
W0011
- One of the features of the 6809 is its support of indirect indexing.
Instead of the index having the data directly,
the index contains the address of the data
(in C parlance,
LDA ,X
isA = *X
andLDA [,X]
isA = **X
). The 6809 doesn't support this mode for 5-bit offsets, but it does for 8-bit and 16-bit offsets. This is just a warning that you can't use a 5-bit offset for this. I'm on the fence about keeping or removing this, and I'm keeping it for now. W0012
- This detects when you branch to the following instruction,
except if the instruction is
BRN
which is “branch never” (or the long branch versionLBRN
). The 6809 is unique for an 8-bit CPU with such an instruction. And despite it's apparent uselessness (why would you have a branch that is never taken) it is useful to pad out timing loops when talking to hardware. W0002
- The label wasn't referenced by any other code. And if the label is not referenced, why have the label in the first place? It could also mean an unused variable whose removal could save some space.
As you can see, most of the warnings are about code sequences that could be shorter, and I'm not aware of any assembler that gives such warnings. I could be wrong, but of the 6809 assmemblers I've used, I haven't seen anything like this.
I also have a way to supress a given warning (they're all enabled by default—I'm opinionated about this, and your stuck with my opinion if you want to use this assembler).
So that's it about the unique features I have in my assembler. I don't expect many people to use this, but I don't care, I'm having fun developing it. And that's what counts.
Monday, November 27, 2023
Unit testing on an 8-bit CPU
I've been using my assembler to write silly little programs for the Color Computer (simulated—easier than setting up my Color Computer, the first computer I owned as a kid). It took me entirely too long to locate a bug in a maze-drawing program I've been writing, and I want to do a deep dive on this.
The program is simple, it just draws a maze, following a few simple rules:
- pick a random direction (up, right, left or down);
- attempt to draw a maze segment (using color 1) in the given direction;
- if we're boxed in (no direction to go in)—
- if we're at the starting location, we're done;
- backtrack along a segment of the path we've drawn (using color 2);
- if we are no longer boxed in, go back to step 1;
- otherwise, go back to step 3.
Nothing hard about it, yet the program kept getting stuck.
It starts in the upper left, meanders a bit (in blue), backtracks a bit (in red) until it gets stuck. I would then stare at the code until blood formed on my brow, simplify the code if I could, and try again only to watch it get stuck, again.
The issue is that I have no debugger on this system. I don't have two screens upon which to show debugging output. I have no way of single-stepping though the code. I don't even have a log to go through. Debugging consists of running the code, then thinking real hard. And yes, it was a stupid mistake that took all of two lines of code to fix.
Now,
the question I want to ask is—would I have saved time if I did “unit testing?”
Not just testing,
which I was doing all along,
but the typical style of “unit testing” where you test a “unit” of code
(getting back to that question again).
Looking over the code
(and that version has the bug—see if you can find it;
you'll also need these
two files),
the most isolated “unit” is random
(the last subroutine in the code).
;*********************************************************************** ; RANDOM Generate a random number ;Entry: none ;Exit: B - random number (1 - 255) ;*********************************************************************** random ldb lfsr andb #1 negb andb #$B4 stb ,-s ; lsb = -(lfsr & 1) & taps ldb lfsr lsrb ; lfsr >>= 1 eorb ,s+ ; lfsr ^= lsb stb lfsr rts
It implements a linear-feedback shift register with a period of 255 (in that it will return each value in the range of 1‥255 once in some randomesque order before repeating, which is Good Enough™ for this code). So what if we want to test that the code is correct?
And it's here I came to a realization—“unit testing” really depends upon the language and tooling around it. Modern orthodoxy holds “unit testing über alles” and therefore, modern languages and tooling is created to support “unit testing über alles” (and woe betide those who question it). I think my struggle with “unit testing” is that the environments I find myself in don't lend themselves very well to “unit testing.” Even when I worked at The Enterprise, we were using C (C99 at best) and C++ (C++98, maybe C++03?) which take a lot of work upfront to support “unit testing” well, and there wasn't a lot of work upfront to support “unit testing” well, and there was a decidely lack of a “unit testing” framework. And here, there's definitely not a “unit testing” framework. Any “unit testing” I have to do involves writing yet more code. So let's write yet more code and test this sucker.
CHROUT equ $A002 ; BASIC character output routine lfsr equ $F6 ; unused location in direct page org $4000 start ldx #result_array ; point to memory clra ; storing 0 to memory clrb ; 256 bytes to clear .clrarray sta ,x+ ; clear memory decb ; decrement count bne .clrarray ; keep going until count = 0 ldx #result_array ; point to array lda #255 ; cycle length checkrnd bsr random ; get random number in B tst b,x ; have we seen this number? bne .failed ; if so, we have failed inc b,x ; set flag for this number deca ; are we done with the cycle bne checkrnd ; if not, keep going ldx #msg.success ; SUCCESS! bsr puts rts ;--------------------------------------------------- ; Store count (register A) and random # (register B) ; so we can use PEEK in BASIC to see where we failed ;--------------------------------------------------- .failed std lfsr+1 ldx #msg.failed ; failed message bsr puts ; display it rts ; return to BASIC puts.10 jsr [CHROUT] ; print character puts lda ,x+ ; get character bne puts.10 ; if not NUL, print it rts ; return to BASIC ;****************************************************************** random ldb lfsr andb #1 negb andb #$B4 stb ,-s ; lsb = -(lfsr & 1) & taps ldb lfsr lsrb ; lfsr >>= 1 eorb ,s+ ; lfsr ^= lsb stb lfsr rts ;******************************************************************* msg.success asciiz 'SUCCESS!\r' msg.failed asciiz 'FAILED!\r' result_array equ * end start
The tooling here doesn't support linking 6809 code, and I'd rather not have to keep each routine in its own file since the program is so simple and makes editing it easier if everything is in one file (no IDE here—and yes, I have thoughts about testing and IDEs but that's beyond the scope for this post). So I have to copy the routine to the test program.
This was something I kept trying to tell my manager at The Enterprise—the test program itself might be buggy (he personally treated the output as gospel—sigh). And the “unit testing” proponents seem to hem and haw about testing the testing code, implying that one does not simply test the testing code. But if programmers aren't trusted to write code and must test, then why are they trusted to write testing code without testing?
It might seem I digress,
but I'm not.
There are four bugs in the above test.
The code I'm testing, random
?
It was fine.
And I wasn't intending to write a buggy test harness,
it just happened as I was writing it.
Bug one—I forgot to test that random
didn't return 0
(that's a degenerate case with LFSRs).
Second bug—I forgot to ininitialize the LFSR state with a non-zero value,
so random
would return nothing but zero.
The third bug was thinking I had used the wrong condition when branching to the failure case,
but no,
I had it right the first time
(the fact that I changed it, and then changed it back, is the bug).
The actual bug that caused this was the fourth bug,
but I have to digress a bit to explain it.
The 6809 has an extensive indexing addressing mode for an 8-bit CPU.
One of the modes allow one to use an accumulator register (A
, B
or D
)
as an offset to the index register.
I used the B
register,
which contains the random number,
as an offset into a 256-element array to track the return values,
thus the use of b,x
in the code above.
What I forgot about in the moment of writing the code is that the “accumulator,index-register” indexing mode sign extends the accumulator.
And the first value from random
is,
due to the LFSR I'm using,
if treated as signed,
a negative value—it would fail on the very first attempt.
Sigh.
This is why I panicked and thought I botched the conditional branch.
Now, all of that just to test the most isolated of subroutines in the program.
But had I continued,
would any form of “unit testing” been beneficial?
There's the subroutine point_addr
—which converts an X,Y position into a byte address in the frame buffer,
and the pixel in said byte.
I could have done an exhaustive test of all 4,096 points,
again,
that's code I would have write
(in 6809 Assembly code)
and unfortunately, test,
to have any confidence in it.
And working up the chain,
there's getpixel
and setpixel
.
Testing those would require a bit of thought—let's see … getpixel
returns the color of the pixel at the given X,Y location on the screen.
and assuming point_addr
is working,
it would only take four tests
(one per pixel in the byte)
but at this point,
would I even trust myself to write the test code?
In fact, would “unit testing” have saved me any time?
Given that I would have to write the testing framework, no, I don't think I would have saved time. Perhaps if I thought the issue through before diving into changing the code, I would have solved this earlier.
And the clues were there. I did discover pretty early on that the bug was in the backtracking code. The top level code is pretty easy to visually inspect:
backtrack lda #BACKTRACK sta color .loop ldd xpos ; check to see if we're back cmpd xstart ; at the starting point, beq done ; and if so, we're done ldd xpos ; can we backtrack NORTH? decb lbsr getpixel cmpb #EXPLORE bne .check_east lbsr move_north.now ; if so, move NORTH and see if bra .probe ; we have to keep backtracking .check_east ldd xpos ; east ... inca lbsr getpixel cmpb #EXPLORE bne .check_west lbsr move_east.now bra .probe .check_west ldd xpos ; yada yada ... deca lbsr getpixel cmpb #EXPLORE bne .check_south lbsr move_west.now bra .probe .check_south ldd xpos incb lbsr getpixel cmpb #EXPLORE bne .probe lbsr move_south.now .probe bsr boxed_in ; can we stop backtracking? bne explore ; if so, go back to exploring bra .loop ; else backtrack some more
The thing to keep in mind here is that the D
register is a 16-bit register where the upper 8-bits is the A
register,
and the lower 8-bits are the B
register,
and that the 6809 is big-endian.
So when we do ldd xpos
we are loading the A
register with the X coordinate,
and B
with the Y coordinate.
And the move_*.now
subroutines work,
or else we wouldn't get the starting maze segments at all.
So it's clear that setpixel
works fine.
The code is getting stuck trying to backtrack along already drawn segments,
and it does that by calling getpixel
,
therefore,
it seems prudent to check getpixel
.
And sure enough,
that is where the bug resides.
;************************************************************************* ; GETPIXEL Get the color of a given pixel ;Entry: A - x pos ; B - y pos ;Exit: X - video address ; A - 0 ; B - color ;************************************************************************* getpixel bsr point_addr ; get video address comb ; reverse mask (since we're reading stb ,-s ; the screen, not writing it) ldb ,x ; get video data andb ,s+ ; mask off the pixel .rotate lsrb ; shift color bits deca bne .rotate .done rts ; return color in B ;************************************************************************* ; POINT_ADDR calculate the address of a pixel ;Entry: A - xpos ; B - ypos ;Exit: X - video address ; A - shift value ; B - mask ;************************************************************************* point_addr.bits fcb %00111111,%11001111,%11110011,%11111100 ; masks fcb 6,4,2,0 ; bit shift counts
I've included a bit of point_addr
to give some context.
point_addr
returns the number of shifts required to move the color value into place,
and one of those shift values is 0.
But getpixel
doesn't check to see it's 0 before decrementing it.
And thus,
getpixel
will return the wrong value for the last pixel in any byte.
The fix is simple:
tsta beq .done
just before the .rotate
label fixes the bug
(two instructions, three bytes and here's a link to the fixed code).
Yes, I freely admit that a “unit test“ of this subroutine would have shown the bug. But how much time would I have spent writing the test code to begin with? The only reason it took me as long as it did to find was because the reference code I was using was quite convoluted, and I spent time simplifying the code as I went along (which is worthy of doing anyway).
What I wish the “unit testing” proponents would realize is that easy testing depends upon the language and tooling involved in the project, and what a “unit” is truly depends upon the language. I suspect that “unit test” proponents also find “unit testing” easier to deal with than “integration testing” or even “end-to-end testing,” thus why we get “unit tests über alles” shouted from the roof tops.
Discussions about this entry
- Unit testing on an 8-bit CPU - ZeroBytes
- Unit testing on an 8-bit CPU - derp.foo
- Unit testing on an 8-bit CPU | Hacker News
- Two Stop Bits | Unit testing on an 8-bit CPU
A hard DNS problem
While I'm in a testing mood, I came across this post:
The job was for a position involving the day-to-day operation of DNS servers and firewalls involving DNS and client systems involving DHCP and DNS systems, and was a senior role so the ideal candidate would probably be able to say a little more than "magic" as to how DNS works.
…
A hard DNS problem: the owner of the zonefile added a new record that you requested. The serial number of the zone did increment (and there is no funny invalid wrap-around of the serial number going on (bonus points for knowing that)). The new record is not visible. What went wrong? This is probably too hard a question for a job interview, though you might explain all this and then ask how they would go about debugging the problem.
The first paragraph sets up the context, and at the end, presents a DNS problem. I worked with DNS before, and this doesn't seem that hard a question.
So,
I've noticed an issue with a record I wanted added to a zone file,
say a TXT RR for foo.example.com
that reads “I have a red pencil.”
I'm also assuming I've done a check from an outside network and didn't see the record,
and that looking up the SOA RR
(also from an outside network) showed the new serial number.
My first step would be to query the authoritative name servers
(typically two to four, could be more) and see if the record is there.
If the record does show up,
then it's a propagation issue,
maybe related to caching or TTL issues.
If not,
then my guess is that the owner of the zonefile messed up adding the record,
possibly by adding a spurious “.” to the end of the domain name—the owner effectively added
foo. IN TXT "I have a red pencil."
This might not even show up as an error in any log files, but this is as-if the record was never added. But if the record was indeed added correctly, as in:
foo IN TXT "I have a red pencil."
Then the next place to look is at how the change was made—did the owner update the record in some editor that then failed to properly update DNS? That would be the next place to look, and the results from that should indicate where to look next.
Why yes, I have done my fair share of DNS troubleshooting … why do you ask?
Update on Tuesday, November 28th, 2023
Tuesday, November 28, 2023
Still a hard DNS problem
The zone file is entirely correct as far as syntax goes and was updated with the new record without error. The new record does not appear in queries about it, but does appear in the new zone file even on the secondary servers.
Ah, there's more information about the problem. I did mention that “[i]f the record does show up, then it's a propagation issue, maybe related to caching or TTL issues.” But to be fair, there could be a few other issues. I don't think it's an issue of the zone file was updated but the DNS servers weren't restarted—I don't get that from the wording, and there's a quick test for that anyway—check the serial number by requesting the SOA RR.
Another issue to check is what the root DNS servers think the authoritative DNS servers for the zone are.
A quick check of whois
could provide that information,
or even a query outside the network for the NS RR for the domain.
If they don't match the expected list of DNS servers,
then either the domain expired,
was transfered,
or someone else in the organization updated the NS records for the domain.
But if the NS RRs are correct, and I can see the proper serial number from an SOA query from outside the network, but not the new record … I don't know. I might try to use a few different locations outside the network to do queries from, just to make sure it's not the DNS server I'm using for queries, but if they all exhibit the behavior … I doubt it'll be an unsupported RR type, perhaps something to do with DNSSEC? Which is beyond my paygrade …
I would like to know the actual issue is—I can see it either being something very trivial and I'll kick myself not not seeing, or it's something that I've not had experience dealing with at all.
Wednesday, November 29, 2023
Yes, that is a hard DNS problem
… Instead of starting with a new zone and copying over some necessary entries from the old zone (what I would have done), someone had simply(?) aliased the new zone over to the old one. Then, when it eventually became necessary to change the new zone (these things take time, and memories can become lost, like rings at the bottom of a river) the records would not take as the whole zone was still aliased to the old one.
I cannot reproduce the (reported) issue with nsd, as nsd fails the zone with a "DNAME at foo.example.org. has data below it" error. However, they were not using nsd; probably their name server allowed a mix of DNAME and thus shadowed-by-the-alias records …
The Long Tail of DNS Record Types
As I last wrote, “I can see it either being something very trivial and I'll kick myself for not seeing, or it's something that I've not had experience dealing with at all,” and it does appear to be something I've not had experience dealing with at all.
The DNAME RR is to delegate name resolution to another server, mainly for address-to-name mappings, but also for aliases. I recall doing a form of name delegation using a non-kosher method back in the late 1990s and early 2000s (back when I was wearing a “sysadmin” hat) involving NS RRs but not with DNAME. DNAME didn't exist when I started with delegations, thus, no experience with it.
And yes, that would be a hard DNS problem if you never encountered it before.
Unit testing from inside an assembler
Plug plug: I've written an assembler[0] for the 6502 (with full LSP and debugging support). It also supports the concept of unit tests whereby your program gets assembled and every test individually gets assembled and run, whereby you can add certain asserts to check for CPU register states and things like that.
Plug plug: I've written an assembler[0] for the 6502 (with full LSP and debuggin... | Hacker News
This comment
(from the Orange Site about a previous post)
grabbed my attention.
I'm fascinated by the feature,
and I think that's because the test is run in the assembler!
(As a side note—I think they missed an opportunity by not using TRON
to enable tracing)
I'm thinking I might try to add a feature to my my assembler,
as I've already written a 6809 emulator as a library.
If I already had this feature
(and riffing off the sample),
how might this look?
What are some of the issues that might come up?
I marked up the random
function as I might have done during testing:
;*********************************************************************** ; RANDOM Generate a random number ;Entry: none ;Exit: B - random number (1 - 255) ;*********************************************************************** random ldb lfsr andb #1 negb andb #$B4 stb ,-s ; lsb = -(lfsr & 1) & taps ldb lfsr lsrb ; lfsr >>= 1 eorb ,s+ ; lfsr ^= lsb stb lfsr rts ; -------------------- .test "random" .tron ldx #.result_array + 128 .troff lda #1 sta lfsr lda #255 .loop bsr random .assert cpu.B <> 0 , "degenerate LFSR" .tron tst b,x .troff .asert cpu.CC.z <> 1 inc b,x deca bne .loop rts .result_array rmb 256 .endtest
First off,
I would have the tracing always print results—that way I can follow the flow to help see the issue.
One open question—would that be a command line option?
Or as I have it here—a pseudo operation?
Second,
how would I return from the code?
The sample I'm going off uses BRK
(the 6502 software interrrupt instruction).
I suppose I could use SWI
but I would also want to fill unused memory with that instruction in case the code goes off into the weeds,
so I would need a way to detect the difference.
I don't want to juse use .endtest
to end the code sequence,
as I might also want to include variables,
like I did here.
Another example, this time the function that had the bug in it:
;************************************************************************* ; GETPIXEL Get the color of a given pixel ;Entry: A - x pos ; B - y pos ;Exit: X - video address ; A - 0 ; B - color ;************************************************************************* getpixel bsr point_addr ; get video address .tron comb ; reverse mask (since we're reading stb ,-s ; the screen, not writing it) ldb ,x ; get video data andb ,s+ ; mask off the pixel tsta ; any shift? beq .done .rotate lsrb ; shift color bits deca bne .rotate .troff .done rts ; return color in B .test "getpixel" ldd #.screen std ECB.beggrp lda #0 ; X lda #0 ; Y bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 lda #1 ldb #0 bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 lda #2 ldb #0 bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 lda #3 ldb #0 bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 rts .screen fcb %11_11_11_11 ; our four pixels .endtest
More questions: should I be able to trace non-test code? Probably, as that could help with debugging issues. Also, the function being tested is calling another function which just happens to be a forward reference, which tells me that calling the tests should happen on pass two of the assembler. And that brings up further questions—what about code like this?
INTCNV equ $B3ED GIVABF equ $B4F4 org $7000 checksum jsr INTCNV ; get parameter from BASIC tfr d,y ; it should point to a string variable ldx 2,y ; get address lda ,y ; get length clrb ; clear checksum and Carry bit .sum adcb ,x+ ; add deca bne .sum comb ; 1s compliment clra ; return 0-255 result jmp GIVABF ; return result to BASIC .test "checksum" ldd #.tmpstr ; our "string" jsr GIVABF ; give address to BASIC bsr checksum jsr INTCNV ; get our result from BASIC .assert cpu.D = 139 ; if I did my math right rts .tmpstr fcb 5 fcb 0 fdb .text fcb 0 .text fcc /HELLO/ .endtest
The two routines INTCNV
and GIVABF
are ROM routines
(from the Color Computer BASIC system)
so we don't have the code for the emulator,
and therefore, this code can't be tested as is.
I suppose it could be rewritten such that it can be tested
(and use more memory,
which could be an issue)
but this does show the limitation of this technique.
I suppose one fix would be conditional assembly:
.iftest .value fdb 0 INTCNV ldd .value rts GIVABF std INTCNV.value rts .else INVCNV equ $B3ED GIVABF equ $B4F4 .endif
but personally, I'm not a fan of conditional code, but I shouldn't discount this as a solution.
Another issue is labels. I've been using local labels for the testing code, thinking that there would be a unique non-local label for each test (generated by the assembler) to avoid naming conflicts (naming is hard). I need to think on how I want to handle this.
It's an interesting idea though …