Author Topic: [AVR] Perplexed with PROGMEM-stored pointers to functions  (Read 2778 times)

0 Members and 1 Guest are viewing this topic.

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1456
  • Country: gb
[AVR] Perplexed with PROGMEM-stored pointers to functions
« on: January 12, 2019, 08:49:12 pm »
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: [Select]
#include <stdio.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

typedef uint8_t (*foo_func_t)(const void *val);

typedef struct {
char title[10];
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[11];
foo_func_t *f;

c = pgm_read_byte(&foo->count); // Works
snprintf_P(t, 11, PSTR("%S\n"), foo->title); // Works
v = *((uint8_t *)pgm_read_ptr(&foo->value)); // Works
f = (foo_func_t *)pgm_read_ptr(&foo->func); // Seems to be okay
z = (*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 with z = (*f)(&v) (ignore the commented-out pgm_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, as f gets the value 0x00E4, which appears to correspond to the address of yadda(). But execution simply does not pass to that function, and instead jumps off somewhere random, with the inevitable effect that the whole program just starts over. :rant:

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 the avr-objdump output 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: [Select]
000000D0  LDD R24,Y+13 Load indirect with displacement
000000D1  LDD R25,Y+14 Load indirect with displacement
000000D2  MOVW R30,R24 Copy register pair
000000D3  LDD R18,Z+0 Load indirect with displacement
000000D4  LDD R19,Z+1 Load indirect with displacement
000000D5  MOVW R24,R28 Copy register pair
000000D6  ADIW R24,0x10 Add immediate to word
000000D7  MOVW R30,R18 Copy register pair
000000D8  ICALL Indirect call to (Z)
000000D9  STD Y+15,R24 Store indirect with displacement

Once it hits the ICALL, 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 the MOVW R30,R18, the Z register actually does contain 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? |O
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 822
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #1 on: January 12, 2019, 09:12:28 pm »
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.
« Last Edit: January 12, 2019, 09:23:53 pm by cv007 »
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1456
  • Country: gb
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #2 on: January 12, 2019, 09:22:47 pm »
By the way, forgot to mention, the code is compiled with -O0 - no optimisation.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21608
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #3 on: January 12, 2019, 11:25:15 pm »
Don't dereference the pointer before executing it?

Incidentally your "a string" is stored in RAM, you need to add a line

Code: [Select]
myStrings1 = "a string";

xyz = {
...
myStrings1,
...
}

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 822
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #4 on: January 13, 2019, 01:04:55 am »
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)

Quote
just before the MOVW R30,R18, the Z register actually does contain the correct address: 0x00E4
Why didn't you go one instruction further? you would have seen the Z address being loaded with a new value from R18/19.
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1456
  • Country: gb
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #5 on: January 13, 2019, 01:37:05 pm »
Incidentally your "a string" is stored in RAM

No, 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, the snprintf_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 a char *. In that situation, yes, only the pointer to the string is stored in flash, not the string itself.
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1456
  • Country: gb
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #6 on: January 13, 2019, 01:59:16 pm »
Why didn't you go one instruction further? you would have seen the Z address being loaded with a new value from R18/19.

I 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.

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)

Aah, 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 that foo_func_t is already a pointer to a function, so I must've went on autopilot writing those couple of lines of code, treating it just like the value member of the struct. :palm:

So, what's the deal with word versus byte addresses? I notice that the documentation for the pgm_read_*() functions has a note stating "The address is a byte address." that I never paid much mind to before, but perhaps I should?
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 822
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #7 on: January 13, 2019, 05:03:42 pm »
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.

Quote
So, what's the deal with word versus byte addresses? I notice that the documentation for the pgm_read_*() functions has a note stating "The address is a byte address." that I never paid much mind to before, but perhaps I should?
Program 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.
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1456
  • Country: gb
Re: [AVR] Perplexed with PROGMEM-stored pointers to functions
« Reply #8 on: January 13, 2019, 05:45:20 pm »
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.

No 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. :-+
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf