[AVR] Perplexed with PROGMEM-stored pointers to functions - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 12, 2019, 08:49:12 pm
content: I have been writing some AVR code that involves storing some structs within program memory (using PROGMEM), but I was unsure as to the exact approach for retrieving and making use the struct's members, especially considering I would be storing strings, pointers to variables and pointers to functions. So I thought I would experiment with a test program and run it within the Atmel Studio simulator.However, there is one thing I just cannot get to work: executing a function from a pointer within the PROGMEM-stored struct. I don't know whether it's something I'm doing wrong in the code, or whether it's the simulator acting incorrectly, or the compiler generating nonsensical assembly.The code of my test program is as follows:Code:#include <stdio.h>#include <avr/io.h>#include <avr/pgmspace.h>typedef uint8_t (*foo_func_t)(const void *val);typedef struct {char title;uint8_t count;void *value;foo_func_t func;} foo_t;static void blah(const foo_t *foo);static uint8_t yadda(const void *val);static uint8_t abc = 99;static const foo_t xyz PROGMEM = {"a string",123,&abc,yadda};void blah(const foo_t *foo) {uint8_t c, v, z;char t;foo_func_t *f;c = pgm_read_byte(&foo->count); // Workssnprintf_P(t, 11, PSTR("%S\n"), foo->title); // Worksv = *((uint8_t *)pgm_read_ptr(&foo->value)); // Worksf = (foo_func_t *)pgm_read_ptr(&foo->func); // Seems to be okayz = (*f)(&v /*pgm_read_ptr(&foo->value)*/); // Bad doggy, no biscuit!!}uint8_t yadda(const void *val) {uint8_t tmp = *((uint8_t *)val);return tmp + 10;}int main(void) {blah(&xyz);}The problem comes when executing the line withz = (*f)(&v)(ignore the commented-outpgm_read_ptr(&foo->value)- that's the ultimate goal, to pass a variable pointer also stored in flash, but I thought I'd eliminate that from the equation). I believe the C code I have that retrieves the function pointer (f = (foo_func_t *)pgm_read_ptr(&foo->func)) is probably correct, asfgets the value 0x00E4, which appears to correspond to the address ofyadda(). But execution simply doesnotpass to that function, and instead jumps off somewhere random, with the inevitable effect that the whole program just starts over.While fiddling around in the debugger, I discovered something new to me, the 'Disassembler' pane, which is an actual interactive representation of the assembler code that can be stepped through (as opposed to theavr-objdumpoutput generated at compile time). When I step the disassembly onwards from the point of the offending line of C code, I see the following:Code:000000D0 LDD R24,Y+13Load indirect with displacement000000D1 LDD R25,Y+14Load indirect with displacement000000D2 MOVW R30,R24Copy register pair000000D3 LDD R18,Z+0Load indirect with displacement000000D4 LDD R19,Z+1Load indirect with displacement000000D5 MOVW R24,R28Copy register pair000000D6 ADIW R24,0x10Add immediate to word000000D7 MOVW R30,R18Copy register pair000000D8 ICALLIndirect call to (Z)000000D9 STD Y+15,R24Store indirect with displacementOnce it hits theICALL, that's where it jumps off somewhere random. I presume the caption is correct, and it jumps to a location corresponding to the Z register's value. Interestingly, just before theMOVW R30,R18, the Z register actuallydoescontain the correct address: 0x00E4. But when that instruction executes, the Z reg ends up with the wrong value!WTF is going on here!? What's at fault? My code, the simulator, the compiler?

author: cv007
author_rank: Frequent Contributor
timestamp: January 12, 2019, 09:12:28 pm
content: First guess is you are stepping into a word address/byte address black hole of some kind.for instance-f = (foo_func_t *)pgm_read_ptr(&foo->func);the compiler is now 'out of the loop' with that pgm_read_ptr, and assumes f is now a function pointer, but maybe you retrieved a byte address and functions use word addresses (you could make f a word address with a simple /2, and try it)I'm just guessing, I don't know.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 12, 2019, 09:22:47 pm
content: By the way, forgot to mention, the code is compiled with -O0 - no optimisation.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: January 12, 2019, 11:25:15 pm
content: Don't dereference the pointer before executing it?Incidentally your "a string" is stored in RAM, you need to add a lineCode:myStrings1 = "a string";xyz = {...myStrings1,...}Tim

author: cv007
author_rank: Frequent Contributor
timestamp: January 13, 2019, 01:04:55 am
content: I could be wrong again, but...It appears the compiler deals with functions in word addresses, so is already storing/retrieving them correctly and no byte/word address mixups.I think you are simply using the function pointer wrong-foo_func_t *f; <-- this should simply be-> foo_func_t f; (its already a pointer, no need to call it a pointer to a pointer)and therefore-f = (foo_func_t)pgm_read_ptr(&foo->func);now you don't get the pointer to pointer problem you are having (you are reading the function pointer from flash, then also getting another read from its address, before you get the icall)QuoteWhy didn't you go one instruction further? you would have seen the Z address being loaded with a new value from R18/19.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 13, 2019, 01:37:05 pm
content: Quote from: T3sl4co1l on January 12, 2019, 11:25:15 pmNo, it does not appear to be. I can see the string in the flash data when examining the 'prog' section of the Memory pane in the AS debugger. And besides, if it wasn't, thesnprintf_P()call using a "%S" format string wouldn't work properly - it would simply insert garbage characters into the output.However, I do believe you're correct if that struct member were achar *. In that situation, yes, only the pointer to the string is stored in flash, not the string itself.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 13, 2019, 01:59:16 pm
content: Quote from: cv007 on January 13, 2019, 01:04:55 amI did, hence why I said after execution of that instruction, the Z register gets changed to an apparently incorrect value. At present, Z becomes 0x0000 after that instruction, although in fiddling around with the code, I had seen it be some other value. By the way, jumping to 0x0000 is effectively a reset, is it not? Which explains why the code starts over.Quote from: cv007 on January 13, 2019, 01:04:55 amAah, you're right! Thanks for pointing that out. If I change the code like that, it works now!Sometimes, working with pointers in C requires too much mental exercise. I totally forgot thatfoo_func_tis already a pointer to a function, so I must've went on autopilot writing those couple of lines of code, treating it just like thevaluemember of the struct.So, what's the deal with word versus byte addresses? I notice that the documentation for thepgm_read_*()functions has a note stating "The address is a byte address." that I never paid much mind to before, but perhaps I should?

author: cv007
author_rank: Frequent Contributor
timestamp: January 13, 2019, 05:03:42 pm
content: I guess I was trying to point out that you knew Z was correct, then wrong in one instruction before the icall, so no need to go any further. Then it becomes a matter of finding what was loaded into Z (r18/19) and work your way back to find out what is in r18/19, which would show that they were loaded by an indirect instruction with its address coming from the lpm read results. Then it would have dawned on you that you have a communication problem with the compiler (compiler is always right), and you would have found the problem.As a note, I don't think -O0 is very useful on an avr as you end up having to go through double the code compared to any optimization at all. I compiled your example in -Os so I could read the resulting assembly. Its also nice to know at the moment code is created what the compiler is doing with it- if its optimized away I would rather know right away rather than wait until I'm all done- then watch as half my code is optimized away when I decide to optimize.QuoteProgram counter deals in word addresses, so ultimately you will have some conflict when also having byte addresses (since you do have the ability to address bytes). Gcc deals in byte addresses pretty much exclusively (I think) and in the case of the avr compiler, it also seems to do the right thing when you want to store function addresses (stored as a word address). There may be times you may have to think about it (like maybe when doing self-programming), but the compiler wasn't written yesterday so they pretty much have a good handle on these things. You will also see non-gcc tools like your debugger (I assume) doing disassembly into word addresses- which gets a little confusing. As long as you know such a thing exists, that is probably as much as you have to worry about it.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 13, 2019, 05:45:20 pm
content: Quote from: cv007 on January 13, 2019, 05:03:42 pmNo worries, I only ever use -O0 when compiling code to run in the Atmel Studio simulator. It's a pain to do otherwise, as optimisation often makes a mess of stepping through the C code in a sensible manner, because the assembly no longer corresponds 'one-to-one' with the C source.Ah, I see now about word/byte addresses. Word addresses being most appropriate when directing program execution, as you only want to jump in increments of the instruction width (in the case of AVR, 16 bits IIRC). And byte addresses when describing a location in memory. But because program space is also arbitrarily readable as memory, you end up having to comprehend both. Gotcha.