Max stack usage for C program

Wednesday, 10 April 2019
|
Écrit par
Grégory Soutadé

Another day, another script. This one helps to compute the maximum stack usage of a C program. In facts, it combines the output of cflow and GCC GNAT to find the heaviest path used (which is not necessary the deepest). The first one compute target software call graph while option -fstack-usage of GCC creates .su files containing stack usage of all functions.

Targets software are simple embedded software. This script is a simple base not intended to run on all cases, handle dynamic stack nor recursive functions (if you wish to add it...).

A file version is available here.

#!/usr/bin/env python

import os
import re
import argparse

class SUInfo:
    def __init__(self, filename, line, func_name, stack_size):
        self.filename = filename
        self.line = line
        self.func_name = func_name
        self.stack_size = stack_size

    def __str__(self):
        s = '%s() <%s:%s> %d' % (self.func_name, self.filename, self.line, self.stack_size)
        return s

class FlowElement:
    def __init__(self, root, depth, stack_size, suinfo):
        self.root = root
        self.depth = depth
        self.stack_size = stack_size
        self.suinfo = suinfo
        self.childs = []

    def append(self, suinfo):
        self.childs.append(suinfo)

    def __str__(self):
        spaces = '    ' * self.depth
        su = self.suinfo
        res = '%s-> %s() %d <%s:%d>' % (spaces, su.func_name, su.stack_size,
                                        su.filename, su.line)
        return res

def display_max_path(element):
    print('Max stack size %d' % (element.stack_size))
    print('Max path :')
    res = ''
    while element:
        res = str(element) + '\n' + res
        element = element.root
    print(res)

cflow_re = re.compile(r'([ ]*).*\(\) \<.* at (.*)\>[:]?')

def parse_cflow_file(path, su_dict):
    root = None
    cur_root = None
    current = None
    cur_depth = 0
    max_stack_size = 0
    max_path = None
    with open(path) as f:
        while True:
            line = f.readline()
            if not line: break
            match = cflow_re.match(line)
            if not match: continue

            spaces = match.group(1)
            # Convert tab into 4 spaces
            spaces = spaces.replace('\t', '    ')
            depth = len(spaces)/4
            filename = match.group(2)
            (filename, line) = filename.split(':')
            filename = '%s:%s' % (os.path.basename(filename), line)

            suinfo = su_dict.get(filename, None)
            # Some functions may have been inlined
            if not suinfo:
                # print('WARNING: Key %s not found in su dict"' % (filename))
                continue

            if not root:
                root = FlowElement(None, 0, suinfo.stack_size, suinfo)
                cur_root = root
                current = root
                max_path = root
                max_stack_size = suinfo.stack_size
            else:
                # Go back
                if depth < cur_depth:
                    while cur_root.depth > (depth-1):
                        cur_root = cur_root.root
                # Go depth
                elif depth > cur_depth:
                    cur_root = current
                cur_depth = depth
                stack_size = cur_root.stack_size + suinfo.stack_size
                element = FlowElement(cur_root, cur_depth,
                                      stack_size,
                                      suinfo)
                current = element
                if stack_size > max_stack_size:
                    max_stack_size = stack_size
                    max_path = current
                cur_root.append(element)
    display_max_path(max_path)

su_re = re.compile(r'(.*)\t([0-9]+)\t(.*)')

def parse_su_files(path, su_dict):
    for root, dirs, files in os.walk(path):
        for sufile in files:
            if sufile[-2:] != 'su': continue
            with open(os.path.join(path, sufile)) as f:
                while True:
                    line = f.readline()
                    if not line: break
                    match = su_re.match(line)
                    if not match:
                        # print('WARNING no match for "%s"' % (line))
                        continue
                    infos = match.group(1)
                    (filename, line, size, function) = infos.split(':')
                    stack_size = int(match.group(2))
                    key = '%s:%s' % (filename, line)
                    su_info = SUInfo(filename, int(line), function, stack_size)
                    su_dict[key] = su_info


if __name__ == '__main__':
    optparser = argparse.ArgumentParser(description='Max static stack size computer')
    optparser.add_argument('-f', '--cflow-file', dest='cflow_file',
                           help='cflow generated file')
    optparser.add_argument('-d', '--su-dir', dest='su_dir',
                           default='.',
                           help='Directory where GNAT .su files are generated')
    options = optparser.parse_args()

    su_dict = {}

    parse_su_files(options.su_dir, su_dict)
    parse_cflow_file(options.cflow_file, su_dict)

Usage & example

Let's take this simple software as example.

First, compile your software using -fstack-usage options in CFLAGS. It will creates an .su file for each object file. Then, launch cflow on your software. Finally, call my script.

mkdir test
cd test
gcc -fstack-usage gget.c -lpthread -lcurl
cflow gget.c > cflow.res
./cflow.py -f cflow.res

Result:

Max stack size 608
Max path :
-> main() 352 <gget.c:493>
    -> do_transfert() 160 <gget.c:228>
        -> progress_cb() 96 <gget.c:214>
#
1
De
Edward
, le
11 August 2020 03:08
Hi, many thanks for uploading your script! I have done a test with the script with the following result:
"cat test.c
int doSomething(void);
int main(void){
int i = doSomething();
return 0;
}

int doSomething(void){
int count = 0;
int i[100] = {0};
for(; count < 100 ; count++){
i[count+1] = i[count]+1;
}
return i[99];
}

cat test.su
test.c:2:5:main 0 static
test.c:7:5:doSomething 408 static

cflow test.c > test.cflow

cat test.cflow
main() <int main (void) at test.c:2>:
doSomething() <int doSomething (void) at test.c:7>

./cflow.py -f test.cflow
Max stack size 0
Max path :
-> main() 0 <test.c:2>"

May you help me understand where I have gone wrong?

Many thanks,

Edward
Répondre
Auteur :


e-mail* :


Le commentaire :


#
2
De
Greg
, le
11 August 2020 11:08
You're right. There was an error in cflow_re, last semicolon is not present for leaves. Fixed, thanks !
Répondre
Auteur :


e-mail* :


Le commentaire :


#
3
De
Edward
, le
12 August 2020 03:08
Hi, thanks for your reply. I don't really follow what was wrong and how to fix the script. Do you mind if you and point out how to make it work?

Many thanks,
Edward
Répondre
Auteur :


e-mail* :


Le commentaire :


#
4
De
Greg
, le
12 August 2020 06:08
I fixed the bug in the script, just follow instructions and all should be OK now.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
5
De
Edward
, le
12 August 2020 08:08
It is fixed and working now, much appreciated, thanks!
Répondre
Auteur :


e-mail* :


Le commentaire :


#
6
De
shiping
, le
17 June 2021 14:06
Hi Greg,

Many thanks for sharing this script, I am working at Bestbuy and trying to solve the problem your script solves here. I didn't see any open source license on your script, wondering if you mind us using this script at work?

Thanks!
Shiping
Répondre
Auteur :


e-mail* :


Le commentaire :


#
7
De
Greg
, le
19 June 2021 18:06
Hi,

Thanks. This script is simple and was made to help people. I consider it as free to use (something like WTFPL license). Feel free to keep a reference to your source (I do it a lot with some snippets from stackoverflow for example) and improve it !


Greg
Répondre
Auteur :


e-mail* :


Le commentaire :


#
8
De
shiping
, le
21 June 2021 15:06
Great, thank you!
Répondre
Auteur :


e-mail* :


Le commentaire :


Auteur :


e-mail* :


Le commentaire :




* Seulement pour être notifié d'une réponse à cet article
* Only for email notification