Forum: Compiler & IDEs Initialisierung Statischer Funktionsliste


von Jürgen S. (jsachs)


Lesenswert?

Hallo,

ich muß ein Stück Code nachträglich an ein Programm anfügen. Mein 
Programm des AVR besteht aus zwei Teilen. Ein zweiter kann verändert 
werden.
Da ich nun das Problem habe, das ich nicht weis wo im Zweiten Teil meine 
Funktionen stehen, habe ich mir eine Structur erzeugt und diese 
dynamisch (zum Test) gefüllt. Klappt alles prima und die passenden 
Funktionen werden aufgerufen.
Nun muß ich dem Compiler beibringen, das er mir eine Statische 
Funktionsliste zur Compilezeit erzeugt und an den Anfang des Blocks 
schreibt.
Wie macht man sowas ?
Das ganze an den Anfang stellen geht ja mit Sections, nur wie fülle ich 
nun meine Structur zur Compilezeit ? Oder ist das der falsche Ansatz ?
Im Prinzip will ich eine ".o" Datei per Bootloader flashen und die 
Funktionen darin nutzen.


Mein Structur:
struct _sFuncTable
{
  void (*InitUA) (void);  // Funktion zum Init des Device
  void (*mainUA) (void);  // funktionen der UA die pro Mainloop einmal 
ausgeführt werden soll
  void (*setLevel) (uint8_t level, uint8_t value);  // Level setzen
  void (*setChannel) (uint8_t channel, uint8_t value);  // Channel 
setzen
  void (*processCommand) (char *cmd, uint8_t length);  // Verarbeite 
Kommando
  void (*processString) (char *str, uint8_t length);  // Verarbeitet 
String

  // funktionen für Managment _sFuncTable
  void InitFuncTable(void); // Setzt Dummy funktionen
  _sFuncTable() {InitFuncTable();};

};

Danke
Juergen

von Stefan (Gast)


Lesenswert?

Ich kenne keinen Weg, die Struktur zur Compilezeit mit den 
Funktionsadressen zu initialisieren. Hoffentlich bekommen wir da noch 
Tipps ;-)

Ich würde derzeit eine bekannte Adresse der Struktur einrichten 
(vielleicht unterhalb vom Stack und oberhalb der DATA und BSS Sektion in 
der Art wie man einen Heap einrichtet?) oder eine bekannte Adresse wo 
eine Funktion selbst oder ein Pointer darauf steht (Anfang/Ende vom 
Bootloaderbereich?), die dann aufgerufen wird und die übergebene 
Strukturadresse initialisiert.

von Jürgen S. (jsachs)


Lesenswert?

Hallo,

wie ist es dann mit diesem Ansatz  ? :
------------
#define Func void (*)(void)

// Die Funktionen die in die Tabelle sollen
void setLevel(uint8_t level, uint8_t value) {}  // Level setzen
void processCommand(char *cmd, uint8_t length) {}    // Verarbeite 
Kommando
void processString(char *str, uint8_t length) {}    // Verarbeitet 
String
void initUA(void);
void mainUA(void);
void setChannel(uint8_t ch, uint8_t val);

// Die Tabelle !
UA_SECTION void (*FC[])(void) = {initUA, mainUA, (Func)setLevel, 
(Func)setChannel, (Func)processCommand, (Func)processString};


------------
UA_SECTION ist wie folgt definiert:
#define UA_SECTION _attribute_ ((section (".uasection")))

und wird im Makefile mir "LDFLAGS += 
-Wl,--section-start=.uasection=$(0x1700)" dem Linker Parametern 
hinzugefügt

Ich sehe auch, das eine section mit 12 Byte angelegt wird, was ja meinen 
6 Funktionspointern a 2 Byte entspricht, nur ich finde sie nirgens. Ist 
diese nun Statisch beim Linken bereits vorhanden oder nicht ?

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .bootloader   0000005c  00001e00  00001e00  000016f6  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .uasection    0000000c  00001700  00001700  000016ea  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .text         000015ee  00000000  00000000  000000f4  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .data         00000008  00800060  000015ee  000016e2  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  4 .bss          0000002e  00800068  00800068  00001752  2**0
                  ALLOC
  5 .noinit       00000000  00800096  00800096  00001755  2**0
                  CONTENTS
  6 .eeprom       00000003  00810000  00810000  00001752  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  7 .stab         000046e0  00000000  00000000  00001758  2**2
                  CONTENTS, READONLY, DEBUGGING
  8 .stabstr      000036eb  00000000  00000000  00005e38  2**0
                  CONTENTS, READONLY, DEBUGGING

Danke
Juergen

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Warum probierst du es nicht einfach aus?

Es wird funktionieren, genau wie alle anderen initialisierten Daten.
Benötigt sowohl RAM (für die Variable) als auch ROM (für den
Initializer), aber das ist hier sicher nicht tragisch.

von Jürgen S. (jsachs)


Lesenswert?

Hallo,

hab ich schon versucht, nur funktioniert es nicht.
Dadurch das es zum Linkerzeitpunkt schon feststehen muß, kommt es wohl 
zu Problemen.
Wie gesagt es ist ja die Einsprungstabelle in die UA, damit das 
eigendliche Programm weis WO welche Routine steht. Da das eigendlich 
Programm (PA) und die Routinen(UA) nicht zusammen gelinkt werden, weis 
der Compiler nichts von den Funktionen.

Das PA prüft also nach einem Start ob die Adressen in der Sprungtabelle 
gültig sind (stehen an einer fixen Adresse). Wenn JA update ich meine 
Structur mit den Werten aus der Sprungtabelle. Wenn NEIN zeigt meine 
Structur auf Dummy Funktionen die sofort wieder zurückspringen. Dadurch 
erspare ich mir das Prüfen auf Gültigkeit während der Laufzeit.

Die PA wertet also das Protokoll des Buses aus und ruft dann Funktionen 
in der UA auf, die die Reaktion auslösen (z.B. Relais schalten).

Mann könnte den UA Bereich als "dynamisch Funktionsbibliothek" 
verstehen, die durch ein Flashupdate über den Bus die Aplikation 
verändert.

Gruss
Juergen Sachs

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dann ist wohl deine Prüfung daneben.  (Ich weiß nicht, was deine UA
und PA sein sollen.)

Funktionszeiger funktionieren mit Sicherheit im GCC, und sie
funktionieren auch etwa so, wie du das skizziert hast.  Die restliche
Magie dahinter darfst du gern dem C-Laufzeitsystem überlassen, dafür
ist das da.

von Jürgen S. (jsachs)


Lesenswert?

PA = Protokoll Area (Die Allgemeine Implementierung des Protokolls)
UA = User Area (Die Gerätespezifische Implementierung)

Ich habe folgendes gemacht:
#define Func void (*)(void)
UA_TABLE_SECTION void (*FuncTable[])(void) = {(Func)initUA, 
(Func)mainUA, (Func)setLevel, (Func)setChannel, (Func)processCommand, 
(Func)processString};

sehe ich mir nun das ".lss" File an, liegen die Funktion an folgenden 
Stellen:
initUA: 17aa
mainUA: 1a92
setLevel: 1b32
setChannel: 17ca
processCommand: 1b34
processString: 1b36
Der Abstand zu "1740" bei der ertsen Routine ergibt sich durch andere 
Funktionen in der gleichen Sektion, die aber nicht in der Sprungtabelle 
enthalten sind.

Sehe ich mir das Hex File für die Adressen der Sprungtabelle an, steht 
dort aber:
:10170000D50B490D990DE50B9A0D9B0D9B0D9B0D6E
:1017400080918B00809580938B0080918C00809598
:1017500080938C00F89492B3977080918C00887F6E
:10176000982B92BB789498B3937080918B008C7F68

Also stehen komplett wirre Adressen in Sprungtabelle....

Die Function Table ist auf 0x1700 und die nachfolgenden Funktionen auf 
0x1740 per Sections gelegt, was nachfolgende Tabelle ja auch bestätigt:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .bootloader   00000000  00001e00  00001e00  00001739  2**0
                  CONTENTS
  1 .uatablesection 00000010  00001700  00001700  0000132e  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .uasection    000003f8  00001740  00001740  0000133e  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .text         00001232  00000000  00000000  000000f4  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  4 .data         00000008  00800060  00001232  00001326  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  5 .bss          0000002e  00800068  00800068  00001736  2**0
                  ALLOC
  6 .noinit       00000000  00800096  00800096  00001739  2**0
                  CONTENTS
  7 .eeprom       00000003  00810000  00810000  00001736  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  8 .stab         00004500  00000000  00000000  0000173c  2**2
                  CONTENTS, READONLY, DEBUGGING
  9 .stabstr      00003670  00000000  00000000  00005c3c  2**0
                  CONTENTS, READONLY, DEBUGGING

Aber vielleicht mache ich hier auch einen Auswerte Fehler. Es 
Funktioniert jedenfalls nicht wenn ich es in den Controller lade.

Ich mache mal ein kleines Beispiel Projekt zur Verdeutlichung und Poste 
das dann hier.

Gruss
Juergen Sachs

von Jürgen S. (jsachs)


Angehängte Dateien:

Lesenswert?

So,
hier nun das Beispiel.

Komplett schnell Runterprogrammiert. Also nicht "sauberster Stiel"
Das Programm sollte erst alle LEDs an Port B blinken lassen, dann Bit 7, 
dann Bit 6, in initUA() Bit 5, in mainUA() Bit 4, und am Ende Bit 3.

Bit 4 und Bit 5 bleiben Dunkel. die Funktionen initUA() und mainUA() 
werden also nicht ausgeführt.

Ich hab mal die Quellcodes, Makefile , das .lss file usw mit eingepackt. 
Das ganze ist jetzt eben für einen atmega8515. Was anderes hab ich im 
Moment nicht zur Hand für mein STK200.

Achja, die Sprungtabelle muß zur Compilezeit angelegt werden, nicht zur 
Laufzeit !

Gruss
Juergen Sachs

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jürgen Sachs wrote:

> Ich habe folgendes gemacht:
1
> #define Func void (*)(void)
2
> UA_TABLE_SECTION void (*FuncTable[])(void) = {(Func)initUA,
3
> (Func)mainUA, (Func)setLevel, (Func)setChannel, (Func)processCommand,
4
> (Func)processString};

Lass die Typecasts weg.  Sie riskieren nur, dass du dir
Programmierfehler verbergen lässt.

> Sehe ich mir das Hex File für die Adressen der Sprungtabelle an, steht
> dort aber:

Aua.  Guck dir doch keine Hexfiles an, das ist doch Masochismus!

Es gibt so viele andere Varianten dafür, einschließlich diverser
Optionen von Disassemblern...

Ich bin zu faul das nachzurechnen, aber ich vermute mal, dass du
einfach nur drüber stolperst, dass beim AVR alle Codeadressen die
durch 2 geteilten Byteadressen sind, d. h. es wird in 16-bit-Worten
adressiert.

> Es Funktioniert jedenfalls nicht wenn ich es in den Controller lade.

Dann musst du das debuggen, statt den Fehler beim Compiler zu suchen.

von Martin Thomas (Gast)


Lesenswert?

Habe nur oberflächlich über den Code geschaut. Wie von Jörg bereits 
angemerkt, kann es ein Problem zwischen Byte- und Word-Adressen sein. Es 
lohnt sich allerdings auch ein Blick darauf, welche Daten von welcher 
Adresse aus welchem Speicherbereich geladen werden. Falls die Addressen 
der Funktionen im Flash-Speicher liegen, dann werden die lds vor icall 
wie im disassembly nicht helfen, lpm wird dann benoetigt, oder man 
kopiert den Inhalt der Tabelle um (ist vielleicht schon so, kann ich auf 
Anhieb aber nicht am Code nachvollziehen). Einfach mal AVR-Studio 
Simulator anwerfen mit der elf-Datei und passendem Target. Sollte 
helfen, das nochzuvollziehen.

Martin Thomas

von Jürgen S. (jsachs)


Lesenswert?

Jörg Wunsch wrote:
> Lass die Typecasts weg.  Sie riskieren nur, dass du dir
> Programmierfehler verbergen lässt.
Die sind deshlab drinn, weil nicht alle Funktionen Parameterlos sind 
alos void (*)(void). Da geht ohne Cast nichts.
>
>> Sehe ich mir das Hex File für die Adressen der Sprungtabelle an, steht
>> dort aber:
> Aua.  Guck dir doch keine Hexfiles an, das ist doch Masochismus!
Ich begnüge mich normalerweise mit dem ".lss" File :-) Nur wenn ich 100% 
Wissen will was an der Adresse steht, hilft nur das HEX File ?!

> Es gibt so viele andere Varianten dafür, einschließlich diverser
> Optionen von Disassemblern...
> Ich bin zu faul das nachzurechnen, aber ich vermute mal, dass du
> einfach nur drüber stolperst, dass beim AVR alle Codeadressen die
> durch 2 geteilten Byteadressen sind, d. h. es wird in 16-bit-Worten
> adressiert.
Hmm, sollte das der Compiler nicht berücksichtigen ?? Ok, dann sollte 
ich z.B nicht 0x1740 für die erste funktion im Speicher stehen haben, 
wie ich es für die Section vorgebe, sondern 0xBA0 (die Hälfte eben)

>> Es Funktioniert jedenfalls nicht wenn ich es in den Controller lade.
Interessanter Weise verhält sich der Proz so als gäbe es die Zeilen 
nicht. Rennt nicht ins Nirvana, oder sonstiges.... uff.

> Dann musst du das debuggen, statt den Fehler beim Compiler zu suchen.
Das ist so meine schwache Seite. Keine Ahnung wie ich das unter Linux 
mache.... Noch keine Einfache Anleitung gefunden, hat jemand einen Tip ? 
Oder ist es das beste doch noch das ganze unter Windows zu Debugen ?

Danke
Juergen

von Jürgen S. (jsachs)


Lesenswert?

Martin Thomas wrote:
> angemerkt, kann es ein Problem zwischen Byte- und Word-Adressen sein. Es
> lohnt sich allerdings auch ein Blick darauf, welche Daten von welcher
> Adresse aus welchem Speicherbereich geladen werden. Falls die Addressen
> der Funktionen im Flash-Speicher liegen, dann werden die lds vor icall
> wie im disassembly nicht helfen, lpm wird dann benoetigt, oder man
> kopiert den Inhalt der Tabelle um (ist vielleicht schon so, kann ich auf
> Anhieb aber nicht am Code nachvollziehen). Einfach mal AVR-Studio
> Simulator anwerfen mit der elf-Datei und passendem Target. Sollte
> helfen, das nochzuvollziehen.
Wie ich oben schon schrieb, bin ich in Sachen Debuging ein blutigster 
Anfänger. Und die ganze Entwicklerumgebung auch noch unter Windows zu 
Installieren hätte ich mir gerne verkniffen. Unter Linux läuft das so 
schön rund. Da muß ich vermutlich mein Makefile usw. anpassen.

Wenn ich das Richtig verstehe, muß ich die Funktionsadressen / 2 teilen 
um die Adressen zu haben, die Letztendlich im Speicher der Sprungtabelle 
stehen müssen. Das wusste ich jetzt noch nicht. Aber man lernt ja nie 
aus.

Wie gesagt einer nen guten link für ein Debuging HOWTO. Wäre ich Dankbar 
für.

Gruss
Juergen

von Karl H. (kbuchegg)


Lesenswert?

Unter Linux kann ich dir auch nicht weiterhelfen.

> Und die ganze Entwicklerumgebung auch noch unter Windows zu
> Installieren hätte ich mir gerne verkniffen

Das geht ziemlich problemlos.

Von Atmel das AVR-Studio holen.
Von http://winavr.sourceforge.net/ das Compiler-Packet holen.

AVR-Studio installieren
WinAvr installieren
Fertig.

AVR-Studio aufmachen. Neues Projekt anlegen.
Alle C und H Files auf das Projekt-Verzeichnis kopieren.
Dann im AVR-Studio die Files zum Projekt hinzufügen.
F7 drücken und warten bis alles kompiliert und gelinkt
ist (merkst du was? Du brauchst kein Makefile erstellen,
macht AVR-Studio für dich).

Ist das Pgm soweit fertig, mit Strg-Alt-Shift-F5  (kein Witz,
die Kombination ist wirklich so! Wer sich das ausgedacht hat,
müsste an die W...) den Debugger starten und mit F10 bzw. F11
im Singlestep durch das Pgm durchgehen. Variablen-Inhalte werden
angezeigt, wenn man mit der Maus auf den Variablennamen zeigt.
Mit F9 wird ein Breakpoint gesetzt, bzw. gelöscht.
Im Kontextmenü, bzw. im Debug-Menü gibts noch ne Menge
mehr zu entdecken.

Der springende Punkt ist, dass sich WinAvr relativ nahtlos
in AVR-Studio integriert. Ein paar Stolpersteine gibt es,
allerdings nichts was dich im Moment berührt.

von Jürgen S. (jsachs)


Lesenswert?

Ok,

sehe ich mir demnächst an.
Aber: Ein Hinweis hat mich doch Stuzig gemacht, Zitat:
> Falls die Addressen
> der Funktionen im Flash-Speicher liegen, dann werden die lds vor icall
> wie im disassembly nicht helfen, lpm wird dann benoetigt.....

Argh, Ja tun Sie natürlich. Testprogramm wie folgt abgeändert:
void (*initUA)(void) = (Func)pgm_read_word(UA_TABLE_START_ADR);
void (*mainUA)(void) = (Func)pgm_read_word(UA_TABLE_START_ADR+2);//( 
_sFuncTable *)UA_TABLE_START_ADR;

also mit "pgm_read_word()" auf die Adressen zugegriffen, und siehe da es 
geht.

Danke für den Hinweis. Zigmal schon Strings aus dem Flash gelesen.... 
Naja, der Fehler passiert einem Vermutlich nur einmal :-)

Danke für den Wink mit dem "LPM" (Auch wenn ich erst nachschauen mußte 
was der macht :-) )

Ich werde mal das in meinem Großes Projekt übernehmen und lasse mich 
überraschen.

Gruss
Juergen

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jürgen Sachs wrote:

>> Lass die Typecasts weg.  Sie riskieren nur, dass du dir
>> Programmierfehler verbergen lässt.

> Die sind deshlab drinn, weil nicht alle Funktionen Parameterlos sind
> alos void (*)(void). Da geht ohne Cast nichts.

Das ist doch aber ziemlicher Quatsch dann, wie sollen die Funktionen,
die Parameter erwarten, denn dann zu ihren Parametern kommen?

Dann kannst du auch gleich alles auf (void *) casten.  Das ist
zumindest (sogar im Sinne des C-Standards) sauber: ein beliebiger
Zeiger darf in einen void * umgewandelt werden und von dort wieder in
den ursprünglichen Zeiger.  Die Information, welchen Typ der
ursprüngliche Zeiger genau hatte, muss dann natürlich irgendwo
,,außerhalb'' ermittelt werden.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.