Forum: Mikrocontroller und Digitale Elektronik vprintf: region rom overflowed by 6932 bytes


von Alexander M. (a_lexander)


Lesenswert?

Hallo Zusammen,

Ich bin aktuell dabei, mir eine print-Ausgabe mit variablen Argumenten 
zu erstellen.

Mein print-Funktion sieht folgendermaßen aus:
1
void log_log(int level, const char *file, int line, const char *fmt, ...) {
2
  if (level < L.level) {
3
    return;
4
  }
5
6
  /* Acquire lock */
7
  lock();
8
  
9
  char buffer[256];
10
  if (!L.quiet) {
11
  va_list args;
12
  va_start(args, fmt);
13
  snprintf(buffer, sizeof(buffer), "%s:%d: %s: %s\r\n", file, line, level_name(level), fmt);
14
  vprintf(buffer, args);
15
  fflush(stdout);
16
  va_end(args);
17
  }
18
  /* Release lock */
19
  unlock();
20
}

Mein Code ist in Bootloader & Application unterteilt, in der Application 
funktioniert diese Ausgabe auch ohne Problem, beim Compilieren des 
Bootloader erscheint jedoch folgende Fehlermeldung:

Bootloader.elf section `.text' will not fit in region `rom'
region `rom' overflowed by 6932 bytes

Wenn ich nun vprintf(buffer,args) auskommentiere, erscheint dieser 
Fehler nicht mehr; jedoch habe ich dann natürlich auch keine Ausgabe 
mehr...
Die Größe des Bootloader ist leider im Maximum, somit kann ich diesen 
nicht mehr erweitern.

Die Frage lautet also:
Wie bekomme ich eine Print-Ausgabe im Bootloader Teil hin?
Warum ergibt sich ein so großer Overflow von 6932 bytes bei einfügen der 
vprintf-Funktion, ist diese so speicherintensiv?

Vielleicht will mir jemand helfen bei dem Thema ;)

Danke & Grüße

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


Lesenswert?

Alexander M. schrieb:
> Warum ergibt sich ein so großer Overflow von 6932 bytes bei einfügen der
> vprintf-Funktion, ist diese so speicherintensiv?

Wäre natürlich gut, du würdest die Architektur und vielleicht auch deine 
Bootloader-Größe mal dazu schreiben.

Generell: printf muss Vorkehrungen für alle möglichen Konvertierungen 
stets vollständig mitschleppen. Schließlich entscheidet sich erst zur 
Laufzeit anhand der Format-Strings, welche davon wirklich benötigt 
werden.

In größenlimitierten Umgebungen (AVR, aber auch kleine ARMs) gibt es 
daher zuweilen mehrere Varianten von printf & Co., bei denen man vorab 
unterschiedliche Formatierungsmöglichkeiten (bspw. Gleitkomma) 
weggelassen hat. Für eine solche Variante kann man sich entscheiden, 
wenn man die entsprechenden Datentypen nicht braucht, aber trotzdem 
printf benutzen will.

Ansonsten: halt die Ausgaben auf irgendwas ohne printf umbauen. Weniger 
komfortabel, aber meist braucht ein Bootloader ja auch nicht unbedingt 
so viel davon.

von Alexander M. (a_lexander)


Lesenswert?

Na klar:

Bootloader Größe: 0x8000
uC: ATSAMG55 
http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11289-32-bit-Cortex-M4-Microcontroller-SAM-G55_Summary-Datasheet.pdf

Was meinst du denn mit "es gibt mehrere Varianten"? Hast du da 
vielleicht ein gutes Beispiel?

Danke ;)

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


Lesenswert?

Alexander M. schrieb:
> Was meinst du denn mit "es gibt mehrere Varianten"?

Habe nur noch mit so großen ARMs zu tun, dass mich das dann irgendwann 
nicht mehr richtig interessiert hat … ;-)

Ich glaube mich zu erinnern, dass es "i"-Varianten gibt, iprintf und 
dergleichen, die habe keine Gleitkommakonvertierungen. Versuch mal, 
danach zu gugeln.

Deine Benutzung von snprintf und danach vprintf ist aber etwas seltsam. 
Du solltest nur eins brauchen.

von Alexander M. (a_lexander)


Lesenswert?

Du hast's schön ;)...

Interessant mit dem iprintf, das werde ich mir mal genauer anschauen, 
ansonsten gibt's einfach Ausgaben ohne variable Argumente...

- snprintf hab ich verwendet, um einen Buffer-Overflow abzufangen
- vprintf für die Ausgabe

Wie wäre denn dein Vorschlag?

Grüße

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


Lesenswert?

Alexander M. schrieb:
> Wie wäre denn dein Vorschlag?

Was genau musst du denn wie ausgeben im Bootloader?

von Alexander M. (a_lexander)


Lesenswert?

Ich will lediglich einen Wrapper bauen für eine printf-Ausgabe.

Dazu soll der das Argument fmt mein String sein, und anschließend eben 
die variablen Argumente. Alles andere Parameter werden "im Hintergrund" 
bereits eingefügt.

Beispiel:
1
#define log_info(...)    log_log(LOG_INFO,    __FILE__, __LINE__, ##__VA_ARGS__)

vprintf benötigt aber auch einen eigenen fmt & Argumente, deshalb muss 
ich m.M.n. im snprintf diesen neuen fmt "neu konfigurieren". Das 2. 
Argument sind dann eben die variablen Argumente vom 1. fmt.

von Stefan F. (Gast)


Lesenswert?

Die Zeichenketten kannst du einzeln ausgeben, dann brauchst du keinen so 
großen Buffer.

Die Integer Zahl kannst du mit atoi() in eine Zeichenkette umwandeln. 
atoi() ist wesentlich schlanker, als printf().

Du hast da auch noch einen beträchtlichen Overhead durch die Verwendung 
der File/Stream Funktionen. Kannst du nicht direkt (z.B. auf einen 
seriellen port) ausgeben?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Auf buffer kann man auch ganz verzichten.

Alt:
1
  snprintf(buffer, sizeof(buffer), "%s:%d: %s: %s\r\n", file, line, level_name(level), fmt);
2
  vprintf(buffer, args);

Neu:
1
  printf ("%s:%d: %s: ", file, line, level_name(level));
2
  vprintf (fmt, args);
3
  putchar ('\r');
4
  putchar ('\n');

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


Lesenswert?

Wenn du das schon so machen willst wie oben skizziert (jetzt habe ich's 
einigermaßen verstanden ;), dann würde ich zwei getrennte Ausgaben 
machen:
1
  printf("%s:%d: %s: ", file, line, level_name(level));
2
  vprintf(fmt, args);
3
  printf("\r\n");

stdio puffert ja sowieso alles bis zum \n.

Die Frage ist halt, was deine Aufrufer alles an tatsächlichen Argumenten 
übergeben können müssen. Ggf. lässt es sich ja ohne printf erledigen.

ps: die Umwandlung von _LINE_ in einen String sollte sich mit 
Präprozessormitteln erledigen lassen.

von John Doe (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Die Integer Zahl kannst du mit atoi() in eine Zeichenkette umwandeln.
> atoi() ist wesentlich schlanker, als printf().
>


Nee, ist klar...

von Stefan F. (Gast)


Lesenswert?

John Doe schrieb:
> Stefan ⛄ F. schrieb:
>> Die Integer Zahl kannst du mit atoi() in eine Zeichenkette umwandeln.
>> atoi() ist wesentlich schlanker, als printf().
> Nee, ist klar...

Ach Scheiße. Ich meinte natürlich itoa().

von Alexander M. (a_lexander)


Lesenswert?

Danke euch ;) Das sind sehr lehrreiche Informationen ;)

Bootloader sieht so aus, die Argumente werden einfach nicht ausgewertet:
1
void log_log(int level, const char *file, int line, const char *fmt, ...) {
2
  if (level < L.level) {
3
    return;
4
  }
5
6
  /* Acquire lock */
7
  lock();
8
9
  if (!L.quiet) {
10
  printf("%s:%d: %s: %s", file, line, level_name(level), fmt);
11
  }
12
  /* Release lock */
13
  unlock();
14
}

Application sieht bisher so aus:
1
void log_log(int level, const char *file, int line, const char *fmt, ...) {
2
  if (level < L.level) {
3
    return;
4
  }
5
6
  /* Acquire lock */
7
  lock();
8
  
9
  if (!L.quiet) {
10
  va_list args;
11
  va_start(args, fmt);
12
  
13
  printf("%s:%d: %s: ", file, line, level_name(level));
14
  vprintf(fmt, args);
15
  printf("\r\n");
16
  
17
  va_end(args);
18
  }
19
  /* Release lock */
20
  unlock();
21
}

Was meintet ihr mit itoa / Umwandlung von _LINE_, das hab ich ehrlich 
gesagt noch nicht verstanden..
Warum sollte ich das machen? Verringert sich dadurch die Laufzeit?
Wie soll das dann gehen? Bei mir klappt's aktuell noch nicht wirklich

von Stefan F. (Gast)


Lesenswert?

itoa() wandelt Dir Zahlen in Text um, belegt dabei aber viel weniger 
Flash und RAM, als printf().

Zum zweiten Teil der Frage schau Dir mal das an: 
http://www.decompile.com/cpp/faq/file_and_line_error_string.htm

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


Lesenswert?

Beispiel:

foo.c:
1
#define STRINGIFY(x) #x
2
#define STR(x) STRINGIFY(x)
3
#define LINENO  STR(__LINE__)
4
5
#define LOG_IT(msg) log_it(__FILE__, LINENO, msg)
6
7
extern void uart_puts(const char *);
8
9
void log_it(const char *file, const char *lineno, const char *msg)
10
{
11
        uart_puts(file);
12
        uart_puts(" ");
13
        uart_puts(lineno);
14
        uart_puts(": ");
15
        uart_puts(msg);
16
        uart_puts("\r\n");
17
}
18
19
20
        LOG_IT("mumble");
21
22
        LOG_IT("doodle");

Schickst du diese Datei jetzt mit gcc -E durch den Präprozessor, dann 
bekommst du:
1
# 1 "foo.c"
2
# 1 "<built-in>"
3
# 1 "<command-line>"
4
# 31 "<command-line>"
5
# 1 "/usr/include/stdc-predef.h" 1 3 4
6
# 32 "<command-line>" 2
7
# 1 "foo.c"
8
9
10
11
12
13
14
extern void uart_puts(const char *);
15
16
void log_it(const char *file, const char *lineno, const char *msg)
17
{
18
 uart_puts(file);
19
 uart_puts(" ");
20
 uart_puts(lineno);
21
 uart_puts(": ");
22
 uart_puts(msg);
23
 uart_puts("\r\n");
24
}
25
26
27
        log_it("foo.c", "20", "mumble");
28
29
 log_it("foo.c", "22", "doodle");

Dein numerisches Argument "Nummer der Zeile in der Datei" wurde also 
schon durch den Präprozessor in einen String umgewandelt, und alles, was 
du als Backend zur UART noch brauchst, ist eine Funktion, die einen 
String ausgibt.

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Beispiel: ...

Eben.

Ist mein Reden seit Ewigkeit:
printf sollte man beim µC durch aufgelöste Funktionen ersetzen - und 
zwar bereits zur Quellcode-Schreibzeit. Soviel Fleiß sollte sein. Genau 
deshalb gibt's bei mir Char_Out, String_Out, Dezi_Out, FF_Out, LongToStr 
usw. Spart nicht nur Platz, sondern ist auch schneller, als wenn erstmal 
irgend ein Textinterpreter werkeln muß.

Da haste ja tatsächlich was aus der L..... gelernt.

W.S.

von foobar (Gast)


Lesenswert?

Irgendwas stimmt nicht.  Die vararg-Versionen (printf, sprintf, etc) 
basieren üblicherweise auf den stdarg-Versionen (vprintf, vsprintf, 
etc).  In anderen Worten: wenn du printf benutzt, benutzt du eh vprintf.

Einige Platformen haben für die String-Varianten (sprintf, vsprintf, 
etc) eigene Implementation; die sparsameren aber nicht (z.B. AVR-libc, 
da wird ein temporärer "String-Stream" erstellt und dann vfprintf etc 
aufgerufen).

Schau dir mal an, was alles dazugelinkt wird - evtl ist es ja auch das 
Stream-handling von stdio (Buffering, Ausgabegerätabstraktion, etc) das 
durch irgendwas getriggert und dann dazugelinkt wird.  In einem 
Bootloader braucht man üblicherweise nicht das gesamte Spektrum von 
stdio-Features...

von foobar (Gast)


Lesenswert?

[leicht OT und wohl schon oft diskutiert, aber mir fällt es schwer, so 
etwas unkommentiert stehen zu lassen ;-)]

> printf sollte man beim µC durch aufgelöste Funktionen ersetzen

Jain.  In der Tat gibt es extrem aufgeblähte stdio-Routinen, bei denen 
sich die Nutzung auf einem Mikrocontroller so gut wie verbietet.  Aber 
wenn du z.B. mal einen Blick in die AVR-libc-Implementation wirfst, 
wirst du feststellen, dass es auch ziemlich gute und sehr kompakte 
Implementation gibt, die man ziemlich bedenkenlos nutzen kann.

> Genau deshalb gibt's bei mir Char_Out, String_Out, Dezi_Out, FF_Out,
> LongToStr usw. Spart nicht nur Platz, sondern ist auch schneller,

Sobald man mehr als nur String-Konstanten ausgibt (also etwas wie 
Dezi_Out, FF_Out, LongToStr braucht), sollte man sich das mal genauer 
anschauen.  Evtl stellst du dann sogar fest, dass du mehr Platz brauchst 
als printf & Co, insb wenn du die für mehrere Ausgabegeräte brauchst[1]. 
Was den Platz angeht: bei den Leuten, die um printf herumprogrammieren, 
seh ich ständig irgendwelche buffer[200] in denen erstmal alles (mit 
viel Kode) zusammengebastelt wird.  Was meinst du da zu sparen?  RAM 
bestimmt nicht und beim ROM hat sich das nach ein paar Ausgaben auch 
erledigt - die String-Representation der Ausgabe (z.B. "%d/%x") ist 
kompakter als der vergleichbare Maschinenkode.

Geschwindigkeit ist meist eh irrelevant - schnell in dem Buffer 
schreiben und dann per Busy-Loop auf die UART schieben?!?



[1] Um die UART beim AVR-libc stdio-fähig zu machen, braucht es gerade 
mal dieses bisschen Kode:
1
static int put(char c, FILE *str)
2
{
3
    uart_putc(c);
4
    return 0;
5
}
6
7
static int get(FILE *str)
8
{
9
    return uart_getc();
10
}
11
12
FILE uart[1] = { FDEV_SETUP_STREAM(put, get, _FDEV_SETUP_RW) };
Und schon hat man einen Stream namens "uart" den man wie stdin/stdout 
benutzen kann.  Wenn gewünscht reicht ein weiteres "stdin = stdout = 
uart;" aus, um diese UART als Standardein- und ausgabe zu setzen.  Kein 
Gehampel mit uart_puts, uart_DeziOut, lcd_puts, lcd_DeziOut, LongToStr, 
LongToHexStr, IntToHexStr, UShortToStr, ...

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


Lesenswert?

W.S. schrieb:
> printf sollte man beim µC

Nein.

Nicht mit dieser Absolutheit: „sollte man“.

Hier in diesem ganz konkreten Fall mag das Sinn haben – wenngleich mir 
immer noch nicht ganz klar ist, warum das bisschen printf ihm den 
vergleichsweise großzügigen Bootloaderbereich des SAMG55 überlaufen 
lässt. Aber wenn es gerade mal grenzwertig ist, und ansonsten nicht 
viel Funktionalität gebraucht wird, dann ist das eine aufwandsarme 
Ausweichvariante.

Wenn auf unseren 2 MiB großen ARMs der Speicher mal überläuft, dann ganz 
bestimmt nicht, weil wir printf benutzen. Wenn wir dein „sollte man“ 
dort dagegen von vornherein verwirklicht hätten, dann hätten wir wohl 
zahllose Entwicklerstunden zusätzlich verplempert – in 
Alternativimplementierungen einschließlich der Reparatur der dabei 
entstandenen neuen Bugs.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Und wenn man nur ein minimal printf ohne Formatierungen möchte, dann 
baut man das mal kurz selber.
Diese Formatierungen verbrauchen den Platz.
Sowie die floatlib, daher gibts in zB der nanolibc ein printf ohne 
float.
Aber rumgehampel mit einzelnen Ausgabefunktionen ist einfach nur 
krebsig.
Sowas hab ich mit 14 noch gemacht, aber ab dann wird man schlauer.

... aber von sowas hat W.S. natürlich mal wieder keinen Ansatz von 
Ahnung.

W.S. schrieb:
> Da haste ja tatsächlich was aus der L..... gelernt.
Kommt dein Alzheimer wieder durch?
Jörg W. hat doch nun schon mehrfach geschrieben, dass er sich den Code 
nicht angesehen hat und es auch nie ansehen wird.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> W.S. schrieb:
>> printf sollte man beim µC
>
> Nein.
>
> Nicht mit dieser Absolutheit: „sollte man“.

Sehe ich genauso. Man sollte wirklich keine Angst vor printf() haben - 
schon gar nicht auf 32-Bit-Prozessoren.

Thema: Speicherplatz

Es ist Quatsch, mit Speicherplatz zu argumentieren. Wenn man printf() 
nur einmal aufruft, sind die weiteren 999 Aufrufe absolut kostenlos. Und 
man spart sich eine ganze Menge Drumherum-Programmiererei. Warum meinen 
immer bestimmte Leute, man müsse sich selbst drangsalieren? Und nein: 
Mit Speicherplatzverschwendung hat das nichts zu tun.

Thema: Geschwindigkeit

Formatierte Ausgaben sind meist für den Menschen gedacht - auch hier in 
diesem Thread. Der Mensch kann sowieso nicht schnell lesen. Von daher 
sind diese Argumente "printf() ist lahm" vollkommen am Thema vorbei. 
Wenn man effeziente Kommunikation mit anderen Geräten machen will, 
braucht man in den seltensten Fällen dafür printf, denn dann ist eine 
Formatierung der Ausgabe - wie sie printf() bietet - meist gar nicht 
gefragt.

von Johannes S. (Gast)


Lesenswert?

und printf ist einfach Standard. Wenn man mehrere Libraries benutzt und 
jeder seine eigene Ausgabe bastelt nervt das auch. Aber gut, unser Chuck 
Norris der Programmierer benutzt keine Libs.
Da muss ich nochmal Mbed loben, da zieht gerade eine minimal_printf 
Version ein die konfigurierbar ist und bis zu ca. 15 kB flash spart. Die 
wird per Linker option gewrappt und alle Funktionen benutzen automatisch 
die Sparversion. Atmel SAM benutze ich nicht, wird aber auch von Mbed 
unterstützt.
Wobei, das minimal_printf nutzt nix Mbed spezifisches und kann 
vielleicht auch in das Projekt vom TO eingebaut werden:
https://github.com/ARMmbed/mbed-os/tree/master/platform/source/minimal-printf

von Alexander M. (a_lexander)


Lesenswert?

Danke :)

von S. R. (svenska)


Lesenswert?

Jörg W. schrieb:
> Ansonsten: halt die Ausgaben auf irgendwas ohne printf umbauen.

Oder man baut sich ein entsprechend schlankeres printf-Äquivalent. Hier 
fliegt eins in AVR-Assembler rum, was ich für meinen i8080-Emulator 
gebaut habe. Kann 8- und 16-bittige Hexadezimalzahlen auf der UART 
ausgeben und besteht aus sagenhaften 37 Befehlen für printf, 16 für die 
Hexformatierung und 22 für die UART-Ausgabe inklusive \n nach 
\r\n-Konvertierung. Lässt sich sicherlich auch noch weiter optimieren.

Ist zwar unglaublich primitiv, aber trotzdem wesentlich komfortabler 
als der Vorschlag von W.S.

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.