Forum: Compiler & IDEs Wie Stackermittlung ?


von Peter D. (peda)


Lesenswert?

Ich benutze den ATmega8 unter WINAVR.

Ich will zur Laufzeit die Stackauslastung anzeigen lassen.
Dazu muß ich aber wissen, wo der Datenbereich endet.
Im Listfile habe ich nun gesehen, daß die Variablen aufsteigend nach
den Namen der Objektfiles plaziert werden.

Ich habe nun ein File "zzzzzzzz.c" erzeugt und dort eine globale
Variable plaziert. Nun kann ich mir anzeigen lassen, wieviel Bytes vom
Stack bis hinunter zu dieser Variable noch frei sind.

Allerdings scheint mir diese Lösung nicht sehr professionell.
Gibt es eine bessere Methode ?


Peter

von OldBug (Gast)


Lesenswert?

"RAMEND - Heap-Start" müsste doch die Lösung sein, oder?

von Jörg Wunsch (Gast)


Lesenswert?

Aber nur, wenn Du auch malloc() benutzt. ;-)

Ansonsten, ist schon x-mal diskutiert worden: den ganzen RAM (am
besten im .init1, also vor der Initialisierung von .data und .bss) mit
einem Muster füllen.  Später auswerten, bis wohin das Muster noch
existiert.

von Peter D. (peda)


Lesenswert?

Da tun sich ja Abgründe auf.

Ich selber benutze ja malloc() nicht, aber es gibt wohl einige
Bibliotheksfunktionen, die es einem heimlich unterjubeln.

Dann würde meine Methode ja nicht mehr funktionieren, da der von
malloc() belegte Platz mit als frei angesehen würde.
Und ich will den freien Platz auch immer mit 0x77 überschreiben, um die
maximale Stackbelegung zu ermitteln, d.h. der malloc() Speicher würde
mit plattgemacht.


Da muß man wohl 2 Funktionen für die Stackermittlung schreiben, eine
ohne malloc() und eine mit.


Gibt es vielleicht eine Möglichkeit, einen Linker-Fehler zu erzeugen,
wenn eine Funktion versucht, einem malloc() unterzujubeln ?


Beim 8051 war das ja so schön einfach: RAMEND-SP


Peter

von Peter D. (peda)


Lesenswert?

@Jörg,

"...ist schon x-mal diskutiert worden"

hast du dazu einen Link ?


Peter

von OldBug (Gast)


Lesenswert?

Aha, ich habe gerade mal ein wenig experimentiert!
Das Problem bei der Verwendung von malloc liegt darin, daß "brkval"
nicht existiert.
Wenn man kein malloc verwendet, dann ist die Sache recht einfach:

#include <avr/io.h>

extern unsigned int __bss_end;

volatile unsigned int sp;
volatile unsigned int bss = (unsigned int) &__bss_end;
volatile unsigned int sz = 0;

void foo(void);
void bar(void);

int
main(void)
{
        while(1)
        {
    sp = SP;
    sz = sp - bss;
    foo();
        }

        /* NEVEREACHED */
        return 0;
}

void
foo(void)
{
  sp = SP;
  sz = sp - bss;
  bar();
}

void
bar(void)
{
  volatile unsigned char c[32];

  sp = SP;
  sz = sp - bss;
  foo();
}

Das einfach mal im AVRStudio simulieren und ein paar watches anlegen:
sp zeigt den Stackpointer, bss das Ende der statischen Variablen (wer
hätte das gedacht? ;) und sz zeigt den verbleibenden Platz zwischen
__bss_end und SP.

von Jörg Wunsch (Gast)


Lesenswert?

Der einfachste Weg herauszufinden, wer malloc() benutzt, ist das Lesen
der Doku. ;-)  (OK, ich sehe gerade, da fehlt noch was... das werde
ich schnellstens nachholen.)

Der zweiteinfachste wäre das Lesen des Sourcecodes...

Im Ernst: derzeit wird malloc() ausschließlich von Mitgliedern der
stdio-Familie benutzt.  Insbesondere benutzt fdevopen() es, um die
struct __iob anzulegen, und die printf- und scanf-Familien benutzen es
in ihrer höchsten Ausbaustufe (floating point IO, bei scanf() dann
auch noch character class recognition), da m. E. die
Speicheranforderungen dieser Aufgaben das Maß, was man statisch
allozieren sollte, überschreiten.

Wenn Du eine eigene Mustererkennung machen willst, dann nimm ein
eigenes malloc().  Da kannst Du dann tun und lassen, was Du willst.
Auch die Bibliotheksroutinen würden in diesem Falle auf Dein malloc()
zugreifen.

Eine alternative Implementierung für fdevopen(), die ohne malloc()
auskommt, ist übrigens auch im Gespräch, bin ich aber noch nicht dazu
gekommen.  Das Problem ist dabei weniger, dass die Leute der
zusätzlich allozierte RAM stören würde, vielmehr ist oft genug der
ROM-Overhead dafür zu viel.  (Wenn man kein fdevopen() benutzt,
sondern nur die sprintf/sscanf-Varianten, und kein floating point
braucht, kommt man auch jetzt bereits komplett ohne malloc() aus.)

Nein, Link zu den Threads habe ich nicht parat, aber das Thema ist
schon paarmal angefragt worden.  Auch Patrick `OldBug' hatte da wohl
schon seinen Senf dazu gegeben, soweit ich mich erinnern kann.

von Peter D. (peda)


Lesenswert?

Vielen Dank,

das

extern unsigned int __bss_end;

ist genau das, was ich gesucht habe.
Sieht ja doch irgendwie blöd aus, wenn ein File "zzzzzzzz.c" heißt.


Und das malloc() ist wohl nicht so kritisch, wie ich erst dachte, werds
wohl nicht brauchen.

Bzw. die Idee mit dem selber schreiben klingt auch gut, es sollte ja
eine Tiefe von 1 bis max 2 Aufrufen ausreichen.


Peter

von OldBug (Gast)


Angehängte Dateien:

Lesenswert?

Ich hab mal weiter gemacht.

Es würde ausreichen, "brkval" aus malloc.c global Verfügbar zu
machen.
Ich habe mal das modifizierte malloc.c angehangen (source war malloc
aus der avr-libc 1.0.4).

Hier das "Tesprogramm":

--8<--
#include <avr/io.h>
#include <stdlib.h>

volatile unsigned int heapsz;

void *p;

void foo(void);
void bar(void);

unsigned int freemem(void);

int
main(void)
{
        while(1)
        {
                heapsz = freemem();
                foo();
        }

        /* NEVEREACHED */
        return 0;
}

void
foo(void)
{
        heapsz = freemem();
        bar();
}

void
bar(void)
{
        volatile unsigned char c[32];

        p = malloc(32);
        heapsz = freemem();
        //free(p);
        foo();
}

unsigned int
freemem(void)
{
        extern char *brkval;

        return (unsigned int) SP - (unsigned int) brkval;
}
-->8--

Was ist zu tun?

1. main.c und (angehängtes) malloc.c in ein Verzeichnis
2. Makefile erstellen, und eigenes malloc.c als src angeben
3. Testen...

Man müsste jetzt noch beide Varianten (malloc/kein malloc) vereinen und
schon könnte man sich genüsslich die Größe des Verbleibenden Speichers
ansehen.

von Peter D. (peda)


Lesenswert?

Anbei mein Code (ohne Berücksichtigung von malloc). Ich habs aber noch
nicht getestet.
Nach dem Reset führt man ihn einmal aus.
Bei jedem Aufruf gibt er den minimal verfügbaren Speicher seit dem
letzten Aufruf aus.

Man kann damit z.B. testen, ob es Funktionen gibt, die besonders
hungrig sind, indem man diese zwischen 2 Aufrufen ausführt und dann die
Differenz bildet.


#include "main.h"

extern u8 __bss_end;


u16 stack_use( void )
{
  u16 i;
  u8 *mp;

  for( i = 0, mp = &__bss_end; (u16)mp <= SP; *mp++ = 0x77 )
    if( (*mp != 0x77) || i )
      i++;                              // count used stack

  return SP - (u16)&__bss_end - i;
}


Peter

von OldBug (Gast)


Lesenswert?

Moment mal...wolltest Du "nur" die Größe des Stacks wissen?
Dann müsste doch (RAMEND - SP) das gewünschte Ergebnis liefern.

von Jörg Wunsch (Gast)


Lesenswert?

Btw., brkval heißt mittlerweile (CVS-HEAD von avr-libc) __brkval und
ist in der Tat global.  Ist natürlich kein garantiertes Interface.
Die Öffnung wurde notwendig, um realloc() implementieren zu können.

von OldBug (Gast)


Lesenswert?

Ja, das hatte ich auch schon gesehen, deswegen auch der Hinweis auf den
Source der v1.0.4. :-)

von Peter D. (peda)


Lesenswert?

So, ich habs nochmal überarbeitet, funktioniert super:


#include "main.h"


#define FREE_MARK 0x77

extern u8 __bss_end;                    // lowest stack address

extern u8 __stack;                      // highest stack address


u16 stack_size( void )                  // available stack
{
  return (u16)&__stack - (u16)&__bss_end + 1;
}


u16 stack_free( void )                  // unused stack after last
call
{
  u8 flag = 1;
  u16 i, free = 0;
  u8 * mp = &__bss_end;

  for( i = SP - (u16)&__bss_end + 1; i; i--){
    if( *mp != FREE_MARK )
      flag = 0;
    free += flag;
    *mp++ = FREE_MARK;
  }
  return free;
}


Nebenbei kann man damit auch feststellen, ob seit dem letzten Aufruf
ein Reset erfolgte.
Der erste Aufruf liefert nämlich immer 0 zurück, da der freie Stack ja
noch nicht markiert wurde.


Peter

von Veit D. (devil-elec)


Lesenswert?

Hallo Peter,

ich habe das mal auf meinem Arduino getestet und bekomme sich dynamisch 
verändernde Werte. Mal weniger mal wieder mehr und wieder weniger usw.

Ich rufe 3 Funktionen auf und danach immer jeweils immer deine 
stack_free()

Ich dachte die Funktion gibt den bis dahin minimalen freien 
Stackspeicher aus. Weil das Muster 0x77 ja immer weiter rückt, weniger 
wird, im Stack. Also dürfte doch der Wert nie wieder größer werden. 
Oder?

von Peter (Gast)


Lesenswert?

Leichenschänder!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

@Peter:  Du kannst folgendes verwenden; dürfte der Ursprung von vielen 
Versionen sein, die im Netz flottieren.

Ist nicht kompatibel mit malloc et al.

*mem-check.h*
1
#ifndef MEM_CHECK_H
2
#define MEM_CHECK_H
3
4
#include <stdint.h>
5
6
extern uint16_t get_mem_unused (void);
7
8
#endif /* MEM_CHECK_H */

*mem-check.c*
1
#include <avr/io.h> // RAMEND
2
#include "mem-check.h"
3
4
// Mask to init SRAM and check against
5
#define MASK 0xaa
6
7
// __heap_start is defined in the linker script
8
extern uint8_t __heap_start;
9
10
uint16_t get_mem_unused (void)
11
{
12
    uint16_t unused = -1u;
13
14
    // Get end of static allocated RAM space (.data, .bss, .noinit, ...)
15
    uint8_t *p = &__heap_start;
16
17
    while (1)
18
    {
19
        unused++;
20
        // Mask written in __init_mem still intact?
21
        if (*p++ != MASK)
22
            return unused;
23
    }
24
}
25
26
// Init RAM space after static allocated RAM with MASK.
27
// Write accesses to the stack will overwrite this mask, so
28
// we can probe later on and detect memory usage.
29
// !!! Do never call this function         !!!
30
// !!! The Linker knows what to do with it !!!
31
static void __attribute__ ((naked, used, unused, section (".init8")))
32
memcheck_init_mem (void);
33
34
static void memcheck_init_mem (void)
35
{
36
    // We investigate inline assembly to be independent
37
    // of optimization flags
38
        __asm__ __volatile (
39
        "ldi r30, lo8 (%[start])"      "\n\t"
40
        "ldi r31, hi8 (%[start])"      "\n\t"
41
        "ldi r24, lo8 (%[mask])"       "\n\t"
42
        "ldi r25, hi8 (%[end])"        "\n\t"
43
        "0:"                           "\n\t"
44
        "st  Z+,  r24"                 "\n\t"
45
        "cpi r30, lo8 (%[end])"        "\n\t"
46
        "cpc r31, r25"                 "\n\t"
47
        "brlo 0b"
48
        :
49
        : [mask]  "i" (MASK),
50
          [end]   "i" (RAMEND+1),
51
          [start] "i" (&__heap_start)
52
    );
53
}

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@Peter: ich dachte du freust dich wenn es jemand verwendet.   :-)
Meine Frage haste aber nicht beantwortet. Soll dein Code den freien 
Stackbereich dynamisch ermitteln oder doch nicht?

@Johann: der Code ist von rn-wissen.de, kenne ich, habs aber nicht zum 
laufen bekommen. Deshalb habe ich Peters verwendet.

http://rn-wissen.de/wiki/index.php?title=Speicherverbrauch_bestimmen_mit_avr-gcc

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


Lesenswert?

Veit D. schrieb:
> @Peter: ich dachte du freust dich wenn es jemand verwendet.   :-)

Es soll wohl mehr als einen „Peter“ geben auf der Welt.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

dachte der richtige Peter hätte sich nur als Gast kurz gemeldet. Das ist 
der Nachteil vom Gastlogin.

von Mitlesa (Gast)


Lesenswert?

Peter D. schrieb:
> So, ich habs nochmal überarbeitet, funktioniert super:

Das sind die (klitze)kleinen Juwelen dieses Forums. Dinge die ich
schon immer wissen wollte aber zu faul war diese selbst zu erforschen
oder zu erarbeiten. Bin ja noch nicht so lang präsent hier im
Forum, daher findet man so etwas nur durch Zufall beim Aufwärmen
eines Threads. Also danke an den peda. Kaum 11 Jahre später ....

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.