Page 1 of 2
cc65 confusion...
Posted: Sun Mar 06, 2022 2:18 am
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
Re: cc65 confusion...
Posted: Sun Mar 06, 2022 9:03 am
by BigEd
Welcome!
What happens if you print from c first and then from command?
Re: cc65 confusion...
Posted: Sun Mar 06, 2022 10:19 am
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.
Re: cc65 confusion...
Posted: Sun Mar 06, 2022 9:10 pm
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.
Re: cc65 confusion...
Posted: Sun Mar 06, 2022 9:52 pm
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.
Re: cc65 confusion...
Posted: Mon Mar 07, 2022 5:45 am
by echidna
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
Re: cc65 confusion...
Posted: Mon Mar 07, 2022 5:48 am
by echidna
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.
Re: cc65 confusion...
Posted: Mon Mar 07, 2022 5:49 am
by echidna
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.
Re: cc65 confusion...
Posted: Mon Mar 07, 2022 5:53 am
by echidna
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?
Re: cc65 confusion...
Posted: Mon Mar 07, 2022 12:59 pm
by handyandy
What’s the handleinterrupt function doing?
Normally, an irq or brk would return with an rti not rts.
Cheers,
Andy
Re: cc65 confusion...
Posted: Mon Mar 07, 2022 5:37 pm
by echidna
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.
Re: cc65 confusion...
Posted: Tue Mar 08, 2022 4:40 pm
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.
Re: cc65 confusion...
Posted: Tue Mar 08, 2022 5:38 pm
by echidna
Thank you. This makes tons of sense.
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.
Re: cc65 confusion...
Posted: Tue Mar 08, 2022 6:44 pm
by Windfall
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.
Re: cc65 confusion...
Posted: Tue Mar 08, 2022 7:11 pm
by Windfall
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).