mirror of
https://github.com/littlefs-project/littlefs.git
synced 2025-10-17 16:01:42 +08:00

This provides 2 things: 1. perf integration with the bench/test runners - This is a bit tricky with perf as it doesn't have its own way to combine perf measurements across multiple processes. perf.py works around this by writing everything to a zip file, using flock to synchronize. As a plus, free compression! 2. Parsing and presentation of perf results in a format consistent with the other CSV-based tools. This actually ran into a surprising number of issues: - We need to process raw events to get the information we want, this ends up being a lot of data (~16MiB at 100Hz uncompressed), so we paralellize the parsing of each decompressed perf file. - perf reports raw addresses post-ASLR. It does provide sym+off which is very useful, but to find the source of static functions we need to reverse the ASLR by finding the delta the produces the best symbol<->addr matches. - This isn't related to perf, but decoding dwarf line-numbers is really complicated. You basically need to write a tiny VM. This also turns on perf measurement by default for the bench-runner, but at a low frequency (100 Hz). This can be decreased or removed in the future if it causes any slowdown.
450 lines
14 KiB
Python
Executable File
450 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Preprocessor that makes asserts easier to debug.
|
|
#
|
|
# Example:
|
|
# ./scripts/prettyasserts.py -p LFS_ASSERT lfs.c -o lfs.a.c
|
|
#
|
|
# Copyright (c) 2022, The littlefs authors.
|
|
# Copyright (c) 2020, Arm Limited. All rights reserved.
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
#
|
|
|
|
import re
|
|
import sys
|
|
|
|
# NOTE the use of macros here helps keep a consistent stack depth which
|
|
# tools may rely on.
|
|
#
|
|
# If compilation errors are noisy consider using -ftrack-macro-expansion=0.
|
|
#
|
|
|
|
LIMIT = 16
|
|
|
|
CMP = {
|
|
'==': 'eq',
|
|
'!=': 'ne',
|
|
'<=': 'le',
|
|
'>=': 'ge',
|
|
'<': 'lt',
|
|
'>': 'gt',
|
|
}
|
|
|
|
LEXEMES = {
|
|
'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'],
|
|
'assert': ['assert'],
|
|
'arrow': ['=>'],
|
|
'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
|
|
'paren': ['\(', '\)'],
|
|
'cmp': CMP.keys(),
|
|
'logic': ['\&\&', '\|\|'],
|
|
'sep': [':', ';', '\{', '\}', ','],
|
|
}
|
|
|
|
|
|
def openio(path, mode='r'):
|
|
if path == '-':
|
|
if mode == 'r':
|
|
return os.fdopen(os.dup(sys.stdin.fileno()), 'r')
|
|
else:
|
|
return os.fdopen(os.dup(sys.stdout.fileno()), 'w')
|
|
else:
|
|
return open(path, mode)
|
|
|
|
def write_header(f, limit=LIMIT):
|
|
f.writeln("// Generated by %s:" % sys.argv[0])
|
|
f.writeln("//")
|
|
f.writeln("// %s" % ' '.join(sys.argv))
|
|
f.writeln("//")
|
|
f.writeln()
|
|
|
|
f.writeln("#include <stdbool.h>")
|
|
f.writeln("#include <stdint.h>")
|
|
f.writeln("#include <inttypes.h>")
|
|
f.writeln("#include <stdio.h>")
|
|
f.writeln("#include <string.h>")
|
|
f.writeln("#include <signal.h>")
|
|
# give source a chance to define feature macros
|
|
f.writeln("#undef _FEATURES_H")
|
|
f.writeln()
|
|
|
|
# write print macros
|
|
f.writeln("__attribute__((unused))")
|
|
f.writeln("static void __pretty_assert_print_bool(")
|
|
f.writeln(" const void *v, size_t size) {")
|
|
f.writeln(" (void)size;")
|
|
f.writeln(" printf(\"%s\", *(const bool*)v ? \"true\" : \"false\");")
|
|
f.writeln("}")
|
|
f.writeln()
|
|
f.writeln("__attribute__((unused))")
|
|
f.writeln("static void __pretty_assert_print_int(")
|
|
f.writeln(" const void *v, size_t size) {")
|
|
f.writeln(" (void)size;")
|
|
f.writeln(" printf(\"%\"PRIiMAX, *(const intmax_t*)v);")
|
|
f.writeln("}")
|
|
f.writeln()
|
|
f.writeln("__attribute__((unused))")
|
|
f.writeln("static void __pretty_assert_print_mem(")
|
|
f.writeln(" const void *v, size_t size) {")
|
|
f.writeln(" const uint8_t *v_ = v;")
|
|
f.writeln(" printf(\"\\\"\");")
|
|
f.writeln(" for (size_t i = 0; i < size && i < %d; i++) {" % limit)
|
|
f.writeln(" if (v_[i] >= ' ' && v_[i] <= '~') {")
|
|
f.writeln(" printf(\"%c\", v_[i]);")
|
|
f.writeln(" } else {")
|
|
f.writeln(" printf(\"\\\\x%02x\", v_[i]);")
|
|
f.writeln(" }")
|
|
f.writeln(" }")
|
|
f.writeln(" if (size > %d) {" % limit)
|
|
f.writeln(" printf(\"...\");")
|
|
f.writeln(" }")
|
|
f.writeln(" printf(\"\\\"\");")
|
|
f.writeln("}")
|
|
f.writeln()
|
|
f.writeln("__attribute__((unused))")
|
|
f.writeln("static void __pretty_assert_print_str(")
|
|
f.writeln(" const void *v, size_t size) {")
|
|
f.writeln(" __pretty_assert_print_mem(v, size);")
|
|
f.writeln("}")
|
|
f.writeln()
|
|
f.writeln("__attribute__((unused, noinline))")
|
|
f.writeln("static void __pretty_assert_fail(")
|
|
f.writeln(" const char *file, int line,")
|
|
f.writeln(" void (*type_print_cb)(const void*, size_t),")
|
|
f.writeln(" const char *cmp,")
|
|
f.writeln(" const void *lh, size_t lsize,")
|
|
f.writeln(" const void *rh, size_t rsize) {")
|
|
f.writeln(" printf(\"%s:%d:assert: assert failed with \", file, line);")
|
|
f.writeln(" type_print_cb(lh, lsize);")
|
|
f.writeln(" printf(\", expected %s \", cmp);")
|
|
f.writeln(" type_print_cb(rh, rsize);")
|
|
f.writeln(" printf(\"\\n\");")
|
|
f.writeln(" fflush(NULL);")
|
|
f.writeln(" raise(SIGABRT);")
|
|
f.writeln("}")
|
|
f.writeln()
|
|
|
|
# write assert macros
|
|
for op, cmp in sorted(CMP.items()):
|
|
f.writeln("#define __PRETTY_ASSERT_BOOL_%s(lh, rh) do { \\"
|
|
% cmp.upper())
|
|
f.writeln(" bool _lh = !!(lh); \\")
|
|
f.writeln(" bool _rh = !!(rh); \\")
|
|
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
|
f.writeln(" __pretty_assert_fail( \\")
|
|
f.writeln(" __FILE__, __LINE__, \\")
|
|
f.writeln(" __pretty_assert_print_bool, \"%s\", \\"
|
|
% cmp)
|
|
f.writeln(" &_lh, 0, \\")
|
|
f.writeln(" &_rh, 0); \\")
|
|
f.writeln(" } \\")
|
|
f.writeln("} while (0)")
|
|
for op, cmp in sorted(CMP.items()):
|
|
f.writeln("#define __PRETTY_ASSERT_INT_%s(lh, rh) do { \\"
|
|
% cmp.upper())
|
|
f.writeln(" __typeof__(lh) _lh = lh; \\")
|
|
f.writeln(" __typeof__(lh) _rh = rh; \\")
|
|
f.writeln(" if (!(_lh %s _rh)) { \\" % op)
|
|
f.writeln(" __pretty_assert_fail( \\")
|
|
f.writeln(" __FILE__, __LINE__, \\")
|
|
f.writeln(" __pretty_assert_print_int, \"%s\", \\"
|
|
% cmp)
|
|
f.writeln(" &(intmax_t){_lh}, 0, \\")
|
|
f.writeln(" &(intmax_t){_rh}, 0); \\")
|
|
f.writeln(" } \\")
|
|
f.writeln("} while (0)")
|
|
for op, cmp in sorted(CMP.items()):
|
|
f.writeln("#define __PRETTY_ASSERT_MEM_%s(lh, rh, size) do { \\"
|
|
% cmp.upper())
|
|
f.writeln(" const void *_lh = lh; \\")
|
|
f.writeln(" const void *_rh = rh; \\")
|
|
f.writeln(" if (!(memcmp(_lh, _rh, size) %s 0)) { \\" % op)
|
|
f.writeln(" __pretty_assert_fail( \\")
|
|
f.writeln(" __FILE__, __LINE__, \\")
|
|
f.writeln(" __pretty_assert_print_mem, \"%s\", \\"
|
|
% cmp)
|
|
f.writeln(" _lh, size, \\")
|
|
f.writeln(" _rh, size); \\")
|
|
f.writeln(" } \\")
|
|
f.writeln("} while (0)")
|
|
for op, cmp in sorted(CMP.items()):
|
|
f.writeln("#define __PRETTY_ASSERT_STR_%s(lh, rh) do { \\"
|
|
% cmp.upper())
|
|
f.writeln(" const char *_lh = lh; \\")
|
|
f.writeln(" const char *_rh = rh; \\")
|
|
f.writeln(" if (!(strcmp(_lh, _rh) %s 0)) { \\" % op)
|
|
f.writeln(" __pretty_assert_fail( \\")
|
|
f.writeln(" __FILE__, __LINE__, \\")
|
|
f.writeln(" __pretty_assert_print_str, \"%s\", \\"
|
|
% cmp)
|
|
f.writeln(" _lh, strlen(_lh), \\")
|
|
f.writeln(" _rh, strlen(_rh)); \\")
|
|
f.writeln(" } \\")
|
|
f.writeln("} while (0)")
|
|
f.writeln()
|
|
f.writeln()
|
|
|
|
def mkassert(type, cmp, lh, rh, size=None):
|
|
if size is not None:
|
|
return ("__PRETTY_ASSERT_%s_%s(%s, %s, %s)"
|
|
% (type.upper(), cmp.upper(), lh, rh, size))
|
|
else:
|
|
return ("__PRETTY_ASSERT_%s_%s(%s, %s)"
|
|
% (type.upper(), cmp.upper(), lh, rh))
|
|
|
|
|
|
# simple recursive descent parser
|
|
class ParseFailure(Exception):
|
|
def __init__(self, expected, found):
|
|
self.expected = expected
|
|
self.found = found
|
|
|
|
def __str__(self):
|
|
return "expected %r, found %s..." % (
|
|
self.expected, repr(self.found)[:70])
|
|
|
|
class Parser:
|
|
def __init__(self, in_f, lexemes=LEXEMES):
|
|
p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
|
|
for n, l in lexemes.items())
|
|
p = re.compile(p, re.DOTALL)
|
|
data = in_f.read()
|
|
tokens = []
|
|
line = 1
|
|
col = 0
|
|
while True:
|
|
m = p.search(data)
|
|
if m:
|
|
if m.start() > 0:
|
|
tokens.append((None, data[:m.start()], line, col))
|
|
tokens.append((m.lastgroup, m.group(), line, col))
|
|
data = data[m.end():]
|
|
else:
|
|
tokens.append((None, data, line, col))
|
|
break
|
|
self.tokens = tokens
|
|
self.off = 0
|
|
|
|
def lookahead(self, *pattern):
|
|
if self.off < len(self.tokens):
|
|
token = self.tokens[self.off]
|
|
if token[0] in pattern or token[1] in pattern:
|
|
self.m = token[1]
|
|
return self.m
|
|
self.m = None
|
|
return self.m
|
|
|
|
def accept(self, *patterns):
|
|
m = self.lookahead(*patterns)
|
|
if m is not None:
|
|
self.off += 1
|
|
return m
|
|
|
|
def expect(self, *patterns):
|
|
m = self.accept(*patterns)
|
|
if not m:
|
|
raise ParseFailure(patterns, self.tokens[self.off:])
|
|
return m
|
|
|
|
def push(self):
|
|
return self.off
|
|
|
|
def pop(self, state):
|
|
self.off = state
|
|
|
|
def p_assert(p):
|
|
state = p.push()
|
|
|
|
# assert(memcmp(a,b,size) cmp 0)?
|
|
try:
|
|
p.expect('assert') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
p.expect('memcmp') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
lh = p_expr(p) ; p.accept('ws')
|
|
p.expect(',') ; p.accept('ws')
|
|
rh = p_expr(p) ; p.accept('ws')
|
|
p.expect(',') ; p.accept('ws')
|
|
size = p_expr(p) ; p.accept('ws')
|
|
p.expect(')') ; p.accept('ws')
|
|
cmp = p.expect('cmp') ; p.accept('ws')
|
|
p.expect('0') ; p.accept('ws')
|
|
p.expect(')')
|
|
return mkassert('mem', CMP[cmp], lh, rh, size)
|
|
except ParseFailure:
|
|
p.pop(state)
|
|
|
|
# assert(strcmp(a,b) cmp 0)?
|
|
try:
|
|
p.expect('assert') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
p.expect('strcmp') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
lh = p_expr(p) ; p.accept('ws')
|
|
p.expect(',') ; p.accept('ws')
|
|
rh = p_expr(p) ; p.accept('ws')
|
|
p.expect(')') ; p.accept('ws')
|
|
cmp = p.expect('cmp') ; p.accept('ws')
|
|
p.expect('0') ; p.accept('ws')
|
|
p.expect(')')
|
|
return mkassert('str', CMP[cmp], lh, rh)
|
|
except ParseFailure:
|
|
p.pop(state)
|
|
|
|
# assert(a cmp b)?
|
|
try:
|
|
p.expect('assert') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
lh = p_expr(p) ; p.accept('ws')
|
|
cmp = p.expect('cmp') ; p.accept('ws')
|
|
rh = p_expr(p) ; p.accept('ws')
|
|
p.expect(')')
|
|
return mkassert('int', CMP[cmp], lh, rh)
|
|
except ParseFailure:
|
|
p.pop(state)
|
|
|
|
# assert(a)?
|
|
p.expect('assert') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
lh = p_exprs(p) ; p.accept('ws')
|
|
p.expect(')')
|
|
return mkassert('bool', 'eq', lh, 'true')
|
|
|
|
def p_expr(p):
|
|
res = []
|
|
while True:
|
|
if p.accept('('):
|
|
res.append(p.m)
|
|
while True:
|
|
res.append(p_exprs(p))
|
|
if p.accept('sep'):
|
|
res.append(p.m)
|
|
else:
|
|
break
|
|
res.append(p.expect(')'))
|
|
elif p.lookahead('assert'):
|
|
state = p.push()
|
|
try:
|
|
res.append(p_assert(p))
|
|
except ParseFailure:
|
|
p.pop(state)
|
|
res.append(p.expect('assert'))
|
|
elif p.accept('string', None, 'ws'):
|
|
res.append(p.m)
|
|
else:
|
|
return ''.join(res)
|
|
|
|
def p_exprs(p):
|
|
res = []
|
|
while True:
|
|
res.append(p_expr(p))
|
|
if p.accept('cmp', 'logic', ','):
|
|
res.append(p.m)
|
|
else:
|
|
return ''.join(res)
|
|
|
|
def p_stmt(p):
|
|
ws = p.accept('ws') or ''
|
|
|
|
# memcmp(lh,rh,size) => 0?
|
|
if p.lookahead('memcmp'):
|
|
state = p.push()
|
|
try:
|
|
p.expect('memcmp') ; p.accept('ws')
|
|
p.expect('(') ; p.accept('ws')
|
|
lh = p_expr(p) ; p.accept('ws')
|
|
p.expect(',') ; p.accept('ws')
|
|
rh = p_expr(p) ; p.accept('ws')
|
|
p.expect(',') ; p.accept('ws')
|
|
size = p_expr(p) ; p.accept('ws')
|
|
p.expect(')') ; p.accept('ws')
|
|
p.expect('=>') ; p.accept('ws')
|
|
p.expect('0') ; p.accept('ws')
|
|
return ws + mkassert('mem', 'eq', lh, rh, size)
|
|
except ParseFailure:
|
|
p.pop(state)
|
|
|
|
# strcmp(lh,rh) => 0?
|
|
if p.lookahead('strcmp'):
|
|
state = p.push()
|
|
try:
|
|
p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
|
lh = p_expr(p) ; p.accept('ws')
|
|
p.expect(',') ; p.accept('ws')
|
|
rh = p_expr(p) ; p.accept('ws')
|
|
p.expect(')') ; p.accept('ws')
|
|
p.expect('=>') ; p.accept('ws')
|
|
p.expect('0') ; p.accept('ws')
|
|
return ws + mkassert('str', 'eq', lh, rh)
|
|
except ParseFailure:
|
|
p.pop(state)
|
|
|
|
# lh => rh?
|
|
lh = p_exprs(p)
|
|
if p.accept('=>'):
|
|
rh = p_exprs(p)
|
|
return ws + mkassert('int', 'eq', lh, rh)
|
|
else:
|
|
return ws + lh
|
|
|
|
def main(input=None, output=None, pattern=[], limit=LIMIT):
|
|
with openio(input or '-', 'r') as in_f:
|
|
# create parser
|
|
lexemes = LEXEMES.copy()
|
|
lexemes['assert'] += pattern
|
|
p = Parser(in_f, lexemes)
|
|
|
|
with openio(output or '-', 'w') as f:
|
|
def writeln(s=''):
|
|
f.write(s)
|
|
f.write('\n')
|
|
f.writeln = writeln
|
|
|
|
# write extra verbose asserts
|
|
write_header(f, limit=limit)
|
|
if input is not None:
|
|
f.writeln("#line %d \"%s\"" % (1, input))
|
|
|
|
# parse and write out stmt at a time
|
|
try:
|
|
while True:
|
|
f.write(p_stmt(p))
|
|
if p.accept('sep'):
|
|
f.write(p.m)
|
|
else:
|
|
break
|
|
except ParseFailure as f:
|
|
pass
|
|
|
|
for i in range(p.off, len(p.tokens)):
|
|
f.write(p.tokens[i][1])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
import sys
|
|
parser = argparse.ArgumentParser(
|
|
description="Preprocessor that makes asserts easier to debug.",
|
|
allow_abbrev=False)
|
|
parser.add_argument(
|
|
'input',
|
|
help="Input C file.")
|
|
parser.add_argument(
|
|
'-o', '--output',
|
|
required=True,
|
|
help="Output C file.")
|
|
parser.add_argument(
|
|
'-p', '--pattern',
|
|
action='append',
|
|
help="Regex patterns to search for starting an assert statement. This"
|
|
" implicitly includes \"assert\" and \"=>\".")
|
|
parser.add_argument(
|
|
'-l', '--limit',
|
|
type=lambda x: int(x, 0),
|
|
default=LIMIT,
|
|
help="Maximum number of characters to display in strcmp and memcmp. "
|
|
"Defaults to %r." % LIMIT)
|
|
sys.exit(main(**{k: v
|
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
|
if v is not None}))
|