Go Down

Topic: (Solved: tablejump does it) Class constructor declaration does not call function (Read 6907 times) previous topic - next topic

FalconFour

Wow, here's a really big one for anyone using LiquidCrystal, or pretty much any library that relies on adding parameters to its declaration (e.g. "LiquidCrystal lcd(rs,enable,d4,d5,d6,d7);")... through the miracles of disassembly, it's revealed that these constructor functions *never get called at all*! They exist in the code, but nothing ever references or flows into them... they're just completely-avoided memory areas if the function isn't explicitly defined within a function. It makes sense, after all, if there's nowhere in the program flow that it's to be run...

Code:
Code: [Select]
LiquidTWI.h:
class LiquidTWI : public Print {
public:
LiquidTWI(uint8_t i2cAddr);
... }
LiquidTWI.cpp:
LiquidTWI::LiquidTWI(uint8_t i2cAddr) {
if (i2cAddr > 7) i2cAddr = 7;
_i2cAddr = i2cAddr; // transfer this function call's number into our internal class state
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; // in case they forget to call begin() at least we have something
}
i2c_perftest.pde:
LiquidTWI lcd(0);


And here's the disassembly from "avr-objdump -d -C i2c_perftest.cpp.elf > i2c_perftest.cpp.txt"...
Quote
000000ae <.do_global_ctors_start>:
 ae:   c8 36          cpi   r28, 0x68   ; 104
 b0:   d1 07          cpc   r29, r17
 b2:   c9 f7          brne   .-14        ; 0xa6 <.do_global_ctors_loop>
 b4:   0e 94 f5 05    call   0xbea   ; 0xbea <main>
 b8:   0c 94 61 07    jmp   0xec2   ; 0xec2 <_exit>
000000bc <__bad_interrupt> (omitted)...
000000c0 <global constructors keyed to lcd>:
 c0:   8a e4          ldi   r24, 0x4A   ; 74
 c2:   91 e0          ldi   r25, 0x01   ; 1
 c4:   60 e0          ldi   r22, 0x00   ; 0
 c6:   0e 94 4f 01    call   0x29e   ; 0x29e <LiquidTWI::LiquidTWI(unsigned char)>
 ca:   08 95          ret

Take a look at .do_global_ctors_start: it just jumps straight from initialization into main()! And main() never does anything with these constructors, neither do the first references to "lcd" functions either (in case of compiler optimization). The address 0xc0 is never even referenced in any way. Neither is 0x29e. The constructor function never, ever gets touched.

It may appear that constructors are doing their job, but most variables in constructors like LiquidCrystal equate to "0" in the code (like "_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;" = 0 | 0 | 0 == 0), and the pinMode is always set on every write operation in the original code, so this seems to have eluded Arduino coders for as long as LiquidCrystal has been around...

Although I still can't account for how on earth the pin-numbers actually get passed into the class when the constructor is responsible (in init()) for transferring those into the class's memory... that remains a mystery. But I spent a good hour or so trying to figure out why nothing was happening on the i2c GPIO chip when I had most of the init stuff in the constructor ;)

AWOL

Code: [Select]
class LiquidTWI : public Print {
public:
LiquidTWI(uint8_t i2cAddr);
... }

Semicolon?
"Pete, it's a fool (who) looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.
I speak for myself, not Arduino.

FalconFour

Yeah, that's how the original LiquidCrystal declares them in the header file:

Code: [Select]
class LiquidCrystal : public Print {
public:
  LiquidCrystal(uint8_t rs, uint8_t enable,
uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);
...
  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);

  void clear();
  void home();

  void noDisplay();
  void display();
  void noBlink();
...
};


... Oh, maybe the missing semicolon at the end of the bracket? Yeah, I left that out of the quoted version, sorry. It's in the original .h file I'm compiling that from. :)

davekw7x

For detective work, I suggest you use a really simple example.  Make a single change and compare object dump:

Code: [Select]

//
// Blink sketch using static storage duration "global" struct.
// Not intended as an example of "good programming practices," but
// as a simple example to compare outpute from avr-objdump
//
// Run avr-objdump -d -C PathAndFileNameOfSketch.cpp.elf > BlinkDump13.txt
//
// Then change the object creation line to foo(12) or some such thing, and save
// the objdump to a file named BlinkDump12.txt
//
// Look at the difference: a single byte value 0x0D versus 0x0C in
// the line after
//  
//  00000xxx <global constructors keyed to foo>:
//
//
// Now, see if you can discover how the run-time code invokes the constructor
// with a parameter...
//
//  davekw7x
//

struct Foo
{
   int ledPin;
   Foo(int a) : ledPin(a) {}
};

// The constructor for Foo is invoked before the C++ main() program
Foo foo(13);

// C++ main() calls Arduino init() and then calls this setup() function.
void setup()
{                
   pinMode(foo.ledPin, OUTPUT); // Set led pin as OUTPUT
}

// This loop() function is called repeatedly from inside an infinite loop in C++ main()
void loop()                    
{
   digitalWrite(foo.ledPin, HIGH); // LED on
   delay(1000);                    // Wait for a second

   digitalWrite(foo.ledPin, LOW);  // LED off
   delay(1000);                    // Wait for a second
}



Regards,

Dave

FalconFour

Good example! It does indeed show that the value changes in the compiled code. However, the key issue still remains: that code is never executed in the final ASM program. Seriously, follow the code around and you can see where it goes...

Code: [Select]
Disassembly of section .text:
00000000 <__vectors>: (CPU starting point)
   0: 0c 94 62 00 jmp 0xc4 ; 0xc4 <__ctors_end>
---> 0xC4 -->
000000c4 <__ctors_end>: (skipping the actual code here...)
  da: 02 c0        rjmp .+4      ; 0xe0 <.do_copy_data_start>
---> 0xE0 -->
000000e0 <.do_copy_data_start>:
  ec: 01 c0        rjmp .+2      ; 0xf0 <.do_clear_bss_start>
---> 0xF0 -->
000000f0 <.do_clear_bss_start>:
  fc: 04 c0        rjmp .+8      ; 0x106 <.do_global_ctors_start>
---> 0x106 -->
00000106 <.do_global_ctors_start>:
10a: c9 f7        brne .-14      ; 0xfe <.do_global_ctors_loop>
---> 0xfe -->
000000fe <.do_global_ctors_loop>:
102: 0e 94 83 01 call 0x306 ; 0x306 <__tablejump__>
---> 0x306 -->
00000306 <__tablejump__>:
30c: 09 94        ijmp
---> ?? (I'm guessing this returns across a long jump) -->
10c: 0e 94 f7 00 call 0x1ee ; 0x1ee <main>
---> main() --> END

See? It just initializes the CPU using the standard set of commands, and never touches the "0000011a <global constructors keyed to foo>:" section that's right below that section (since main() never returns).

Now, I am curious about the "__tablejump__" subroutine... there's an "ijmp" command at the end that really makes me wonder...

FalconFour

Aha! OK, I'm the dummy. We figured as much, right?  :smiley-mr-green:

OK, so after hurting my head trying to follow the program and register-shifting around, for lack of any proper AVR debugger packaged with WinAVR (grr...), I finally figured it out.

  • do_global_ctors is what calls the "global ConsTructORS" (ctors) functions using a "jump table".

  • AVR uses word memory addresses, so a byte of "8d" in code is really byte address "11a" in the real world.

  • __ctors_start is a zero-terminated list of constructors to be called.

  • __tablejump__ is used to call each of these constructors; the constructors themselves have a "ret" that returns from the __tablejump__ subroutine.



So they do actually get executed as real code. Just in a very confusing, mind-bending sort of way. I thought it would be odd of the avr-gcc compiler to just leave unused segments lingering around!

Well, now I can use this knowledge to figure out why the heck, really, the constructor functions weren't doing their jobs...

PaulS

Quote
Well, now I can use this knowledge to figure out why the heck, really, the constructor functions weren't doing their jobs...

Since talking to yourself on the forum seems to have helped you figure out one issue, perhaps describing the real problem you were trying to solve, and posting some code, will lead you to figure out the real problem while we listen in.
The art of getting good answers lies in asking good questions.

Go Up