The Boston Diaries

The ongoing saga of a programmer who doesn't live in Boston, nor does he even like Boston, but yet named his weblog/journal “The Boston Diaries.”

Go figure.

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

I did not wuss out this time.

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 is A = *X and LDA [,X] is A = **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 version LBRN). 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:

  1. pick a random direction (up, right, left or down);
  2. attempt to draw a maze segment (using color 1) in the given direction;
  3. if we're boxed in (no direction to go in)—
    1. if we're at the starting location, we're done;
    2. backtrack along a segment of the path we've drawn (using color 2);
    3. if we are no longer boxed in, go back to step 1;
    4. otherwise, go back to step 3.

Nothing hard about it, yet the program kept getting stuck.

[Image of a partially drawn maze]

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.

[Image of a fully drawn maze]

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


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.

Magic

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

Some more information came back.

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.

Re: A hard DNS problem

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.

[0] See https://mos.datatra.sh/guide/unit-testing.html

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 …

Obligatory Picture

[The future's so bright, I gotta wear shades]

Obligatory Contact Info

Obligatory Feeds

Obligatory Links

Obligatory Miscellaneous

You have my permission to link freely to any entry here. Go ahead, I won't bite. I promise.

The dates are the permanent links to that day's entries (or entry, if there is only one entry). The titles are the permanent links to that entry only. The format for the links are simple: Start with the base link for this site: https://boston.conman.org/, then add the date you are interested in, say 2000/08/01, so that would make the final URL:

https://boston.conman.org/2000/08/01

You can also specify the entire month by leaving off the day portion. You can even select an arbitrary portion of time.

You may also note subtle shading of the links and that's intentional: the “closer” the link is (relative to the page) the “brighter” it appears. It's an experiment in using color shading to denote the distance a link is from here. If you don't notice it, don't worry; it's not all that important.

It is assumed that every brand name, slogan, corporate name, symbol, design element, et cetera mentioned in these pages is a protected and/or trademarked entity, the sole property of its owner(s), and acknowledgement of this status is implied.

Copyright © 1999-2024 by Sean Conner. All Rights Reserved.