cc65 confusion...

Building your first 6502-based project? We'll help you get started here.
echidna
Posts: 8
Joined: 14 Feb 2022

cc65 confusion...

Post by echidna »

Hello!
First post here so I hope I'm doing things correctly..
I have the Ben Eater 6502 project done... mostly.
I'm using the cc65 tools and have managed to get printing to my LCD working.. sort of.
It handles printing a globally declared char string, but gets super weird printing a locally declared string.
I would expect the ','s to print, followed by the '!'s. Instead I get the ','s then garbage.
Any insight into what I'm doing wrong or misunderstanding about how all of this works would be greatly appreciated.

The following is my c code and then the generated assembly:

Code: Select all

#include "ek6502.h"

unsigned char *command = ",,,,,,,,";    
int main() { 
    unsigned char *c = "!!!!!!!!";

    lcdInit();
 
    lcdPrint(command);
    
    lcdPrint(c);
 
    return (0);
}

void handleInterrupt(void) {
}

Code: Select all

;
; File generated by cc65 v 2.18 - Ubuntu 2.18-1
;
	.fopt		compiler,"cc65 v 2.18 - Ubuntu 2.18-1"
	.setcpu		"65SC02"
	.smart		on
	.autoimport	on
	.case		on
	.debuginfo	off
	.importzp	sp, sreg, regsave, regbank
	.importzp	tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
	.macpack	longbranch
	.forceimport	__STARTUP__
	.import		_lcdInit
	.import		_lcdPrint
	.export		_handleInterrupt
	.export		_command
	.export		_main

.segment	"DATA"

_command:
	.addr	L0001

.segment	"RODATA"

L0004:
	.byte	$21,$21,$21,$21,$21,$21,$21,$21,$00
L0001:
	.byte	$2C,$2C,$2C,$2C,$2C,$2C,$2C,$2C,$00

; ---------------------------------------------------------------
; void __near__ __fastcall__ handleInterrupt (void)
; ---------------------------------------------------------------

.segment	"CODE"

.proc	_handleInterrupt: near

.segment	"CODE"

	rts

.endproc

; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------

.segment	"CODE"

.proc	_main: near

.segment	"CODE"

	lda     #<(L0004)
	ldx     #>(L0004)
	jsr     pushax
	jsr     _lcdInit
	lda     _command
	ldx     _command+1
	jsr     _lcdPrint
	ldy     #$01
	jsr     ldaxysp
	jsr     _lcdPrint
	ldx     #$00
	lda     #$00
	jmp     L0003
L0003:	jsr     incsp2
	rts

.endproc

User avatar
BigEd
Posts: 11463
Joined: 11 Dec 2008
Location: England
Contact:

Re: cc65 confusion...

Post by BigEd »

Welcome!

What happens if you print from c first and then from command?
leepivonka
Posts: 167
Joined: 15 Apr 2016

Re: cc65 confusion...

Post by leepivonka »

Is the CC65 environment configuration appropriate for your system?
Are you running the CC65 environment initialization routine before calling main?

The global string pointer is defined globally. It doesn't use the CC65 local stack.
The local string pointer is defined on the CC65 local stack.

The CC65 local stack needs to be allocated in RAM . The zero-page pointer to the current location in it needs to be initialized & not overwritten.

RAM & Zero-page space allocations are specified in the CC65 environment configuration.
If you aren't already familiar with this, look at the sample .cfg files (none, apple2, etc) supplied with CC65.

I believe this info is correct, but I won't claim to be an expert on it.
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: cc65 confusion...

Post by teamtempest »

Well, the nice thing about having the generated assembly is that you can play with it to see if it does what you expect.

You can change this:

Code: Select all

; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _main: near

.segment   "CODE"

   lda     #<(L0004)
   ldx     #>(L0004)
   jsr     pushax
   jsr     _lcdInit
   lda     _command
   ldx     _command+1
   jsr     _lcdPrint
   ldy     #$01
   jsr     ldaxysp
   jsr     _lcdPrint
   ldx     #$00
   lda     #$00
   jmp     L0003
L0003:   jsr     incsp2
   rts

.endproc
to this:

Code: Select all

; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _main: near

.segment   "CODE"

;   lda     #<(L0004)
;   ldx     #>(L0004)
;   jsr     pushax

   jsr     _lcdInit

;   lda     _command
;   ldx     _command+1
 
   lda     #<L0001
   ldx     #>L0001
   jsr     _lcdPrint

;   ldy     #$01
;   jsr     ldaxysp

    lda    #<L0004
    ldx    #>L0004

   jsr     _lcdPrint
   ldx     #$00
   lda     #$00

;   jmp     L0003
; L0003:   jsr     incsp2
   rts

.endproc
....and see if that changes anything. If it does, that might give you a clue as to where things might be going wrong.

I'm assuming the compiler is doing what it does because of the initial assignment to the variable 'c'. That and not performing enough analysis to see that it doesn't have to do that first (or at all, really).

I wonder what it would do if you made the assignment closer to the point of actual use. Or used a string constant in the call instead of a variable. You might try replacing both variables with string constants to see what happens.
User avatar
Druzyek
Posts: 367
Joined: 12 May 2014
Contact:

Re: cc65 confusion...

Post by Druzyek »

What happens when main returns? Just to be sure nothing weird is happening after that, you could try putting while(1); before return (0);. Also, no need to put parentheses around the value in a return statement.
echidna
Posts: 8
Joined: 14 Feb 2022

Re: cc65 confusion...

Post by echidna »

BigEd wrote:
Welcome!

What happens if you print from c first and then from command?
Oh man, I've tried so many combinations that I honestly can't be sure, but I *think* it prints garbage then the good stuff
echidna
Posts: 8
Joined: 14 Feb 2022

Re: cc65 confusion...

Post by echidna »

leepivonka wrote:
Is the CC65 environment configuration appropriate for your system?
Are you running the CC65 environment initialization routine before calling main?

The global string pointer is defined globally. It doesn't use the CC65 local stack.
The local string pointer is defined on the CC65 local stack.

The CC65 local stack needs to be allocated in RAM . The zero-page pointer to the current location in it needs to be initialized & not overwritten.

RAM & Zero-page space allocations are specified in the CC65 environment configuration.
If you aren't already familiar with this, look at the sample .cfg files (none, apple2, etc) supplied with CC65.

I believe this info is correct, but I won't claim to be an expert on it.
This sounds right. I'm SURE my config is wonky. I had a feeling this was the road I was about to go down. Perhaps trying to learn assembly AND the learn intricacies of the CC65 tools is too big of a bite.
echidna
Posts: 8
Joined: 14 Feb 2022

Re: cc65 confusion...

Post by echidna »

teamtempest wrote:
Well, the nice thing about having the generated assembly is that you can play with it to see if it does what you expect.

You can change this:

Code: Select all

; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _main: near

.segment   "CODE"

   lda     #<(L0004)
   ldx     #>(L0004)
   jsr     pushax
   jsr     _lcdInit
   lda     _command
   ldx     _command+1
   jsr     _lcdPrint
   ldy     #$01
   jsr     ldaxysp
   jsr     _lcdPrint
   ldx     #$00
   lda     #$00
   jmp     L0003
L0003:   jsr     incsp2
   rts

.endproc
to this:

Code: Select all

; ---------------------------------------------------------------
; int __near__ main (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _main: near

.segment   "CODE"

;   lda     #<(L0004)
;   ldx     #>(L0004)
;   jsr     pushax

   jsr     _lcdInit

;   lda     _command
;   ldx     _command+1
 
   lda     #<L0001
   ldx     #>L0001
   jsr     _lcdPrint

;   ldy     #$01
;   jsr     ldaxysp

    lda    #<L0004
    ldx    #>L0004

   jsr     _lcdPrint
   ldx     #$00
   lda     #$00

;   jmp     L0003
; L0003:   jsr     incsp2
   rts

.endproc
....and see if that changes anything. If it does, that might give you a clue as to where things might be going wrong.

I'm assuming the compiler is doing what it does because of the initial assignment to the variable 'c'. That and not performing enough analysis to see that it doesn't have to do that first (or at all, really).

I wonder what it would do if you made the assignment closer to the point of actual use. Or used a string constant in the call instead of a variable. You might try replacing both variables with string constants to see what happens.
excellent suggestion, I'll try it out.
echidna
Posts: 8
Joined: 14 Feb 2022

Re: cc65 confusion...

Post by echidna »

Druzyek wrote:
What happens when main returns? Just to be sure nothing weird is happening after that, you could try putting while(1); before return (0);. Also, no need to put parentheses around the value in a return statement.
same results with or without an infinite loop.
Please forgive my C styling, 20 years of Java coding shows, huh?
handyandy
Posts: 113
Joined: 14 Sep 2015
Location: Virginia USA

Re: cc65 confusion...

Post by handyandy »

What’s the handleinterrupt function doing?

Normally, an irq or brk would return with an rti not rts.

Cheers,
Andy
echidna
Posts: 8
Joined: 14 Feb 2022

Re: cc65 confusion...

Post by echidna »

handyandy wrote:
What’s the handleinterrupt function doing?

Normally, an irq or brk would return with an rti not rts.

Cheers,
Andy
It's cruft, I was lazy, but thanks for the rti vs rts.
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: cc65 confusion...

Post by SamCoVT »

Hello!

Code: Select all

#include "ek6502.h"

unsigned char *command = ",,,,,,,,";    
int main() { 
    unsigned char *c = "!!!!!!!!";

    lcdInit();
 
    lcdPrint(command);
    
    lcdPrint(c);
 
    return (0);
}

void handleInterrupt(void) {
}
Your C code has some issues. The biggest issue is that you never actually allocate the memory for your strings. While you can get away with that (to some extent) in the global scope, you can't inside the function. Try the following change:

Code: Select all

unsigned char command[] = ",,,,,,,,";
and inside main:
unsigned char c[] = "!!!!!!!!";
If cc65 doesn't like the empty []s, put one larger than the number of characters in your string (so there will be room for the null character on the end of the string, which the compiler will only add if there is room).
If you only want the strings to live in ROM, you can toss a "const" keyword in front of those declarations.

What you had before was only declaring pointers to character and then you pointed them to a constant string. The issue is that the constant string is an unnamed "temporary" value that only needs to be valid while the line is being evaluated. When you get into a local scope (eg. inside a function), these temporary values are often placed on the stack. This means your string of !s is placed on the stack and then that address is placed into the pointer. Once the code moves on to the next line, the compiler is not required to keep those temporary !s in memory. and the stack may be overwritten (especially with jsr instructions in the mix). The constant strings only needed to be there long enough for the assignment into the c pointer to take place.

It's a little more complicated than that, as the optimizer is optimizing away some of the operations, but the main point here is you actually need to store those strings in a variable (or constant) to have them guaranteed to still be around later. Changing your declarations to be an array of chars will guarantee the value exists beyond that one line of code.

You got lucky on the global scope because the compiler handles global variables a little differently and it "happened to work", but you should change that declaration as well.
echidna
Posts: 8
Joined: 14 Feb 2022

Re: cc65 confusion...

Post by echidna »

Thank you. This makes tons of sense.
SamCoVT wrote:
echidna wrote:
Hello!

Code: Select all

#include "ek6502.h"

unsigned char *command = ",,,,,,,,";    
int main() { 
    unsigned char *c = "!!!!!!!!";

    lcdInit();
 
    lcdPrint(command);
    
    lcdPrint(c);
 
    return (0);
}

void handleInterrupt(void) {
}
Your C code has some issues. The biggest issue is that you never actually allocate the memory for your strings. While you can get away with that (to some extent) in the global scope, you can't inside the function. Try the following change:

Code: Select all

unsigned char command[] = ",,,,,,,,";
and inside main:
unsigned char c[] = "!!!!!!!!";
If cc65 doesn't like the empty []s, put one larger than the number of characters in your string (so there will be room for the null character on the end of the string, which the compiler will only add if there is room).
If you only want the strings to live in ROM, you can toss a "const" keyword in front of those declarations.

What you had before was only declaring pointers to character and then you pointed them to a constant string. The issue is that the constant string is an unnamed "temporary" value that only needs to be valid while the line is being evaluated. When you get into a local scope (eg. inside a function), these temporary values are often placed on the stack. This means your string of !s is placed on the stack and then that address is placed into the pointer. Once the code moves on to the next line, the compiler is not required to keep those temporary !s in memory. and the stack may be overwritten (especially with jsr instructions in the mix). The constant strings only needed to be there long enough for the assignment into the c pointer to take place.

It's a little more complicated than that, as the optimizer is optimizing away some of the operations, but the main point here is you actually need to store those strings in a variable (or constant) to have them guaranteed to still be around later. Changing your declarations to be an array of chars will guarantee the value exists beyond that one line of code.

You got lucky on the global scope because the compiler handles global variables a little differently and it "happened to work", but you should change that declaration as well.
User avatar
Windfall
Posts: 229
Joined: 27 Nov 2011
Location: Amsterdam, Netherlands
Contact:

Re: cc65 confusion...

Post by Windfall »

SamCoVT wrote:
Your C code has some issues. The biggest issue is that you never actually allocate the memory for your strings. While you can get away with that (to some extent) in the global scope, you can't inside the function.
That is not true.

In general cases, a string constant (anything between double quotes) is statically allocated (clearly visible in the assembly code as well ...), i.e. it exists for the entire program run, and its type is either 'const char*' or 'char*' (depending on the implementation). There is no need here to copy it by value.
User avatar
Windfall
Posts: 229
Joined: 27 Nov 2011
Location: Amsterdam, Netherlands
Contact:

Re: cc65 confusion...

Post by Windfall »

echidna wrote:
I would expect the ','s to print, followed by the '!'s. Instead I get the ','s then garbage.
Any insight into what I'm doing wrong or misunderstanding about how all of this works would be greatly appreciated.
My first guess would be that something in _lcdPrint corrupts the stack (and therefore the pointer to the string of plings, which is stacked at the time).
Post Reply