Skip to content

Commit 828baf1

Browse files
committed
Support of non-GCC based compilers for static code analysis (platformio platformio#5040)
1 parent 4d4f5a2 commit 828baf1

File tree

2 files changed

+81
-61
lines changed

2 files changed

+81
-61
lines changed

docs

platformio/builder/tools/piosize.py

Lines changed: 80 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,61 +16,82 @@
1616

1717
import json
1818
import sys
19-
from os import environ, makedirs, remove
20-
from os.path import isdir, join, splitdrive
19+
from os import environ
20+
from os.path import join, splitdrive
21+
from pathlib import Path
2122

2223
from elftools.elf.descriptions import describe_sh_flags
2324
from elftools.elf.elffile import ELFFile
2425

2526
from platformio.compat import IS_WINDOWS
26-
from platformio.proc import exec_command
2727

2828

29-
def _run_tool(cmd, env, tool_args):
30-
sysenv = environ.copy()
31-
sysenv["PATH"] = str(env["ENV"]["PATH"])
29+
def _get_source_path(top_DIE):
30+
src_file_dir = (
31+
top_DIE.attributes["DW_AT_name"].value.decode("utf-8").replace("\\", "/")
32+
if ("DW_AT_name" in top_DIE.attributes)
33+
else "?"
34+
)
35+
comp_dir = (
36+
top_DIE.attributes["DW_AT_comp_dir"].value.decode("utf-8").replace("\\", "/")
37+
if ("DW_AT_comp_dir" in top_DIE.attributes)
38+
else ""
39+
)
3240

33-
build_dir = env.subst("$BUILD_DIR")
34-
if not isdir(build_dir):
35-
makedirs(build_dir)
36-
tmp_file = join(build_dir, "size-data-longcmd.txt")
41+
return str(Path.resolve(Path(comp_dir) / Path(src_file_dir)).as_posix())
3742

38-
with open(tmp_file, mode="w", encoding="utf8") as fp:
39-
fp.write("\n".join(tool_args))
4043

41-
cmd.append("@" + tmp_file)
42-
result = exec_command(cmd, env=sysenv)
43-
remove(tmp_file)
44+
def _collect_dwarf_info(elffile):
45+
dwarf_list = []
46+
dwarfinfo = elffile.get_dwarf_info()
47+
src_path = ""
4448

45-
return result
49+
for CU in dwarfinfo.iter_CUs():
50+
top_DIE = CU.get_top_DIE()
4651

52+
if top_DIE.tag == "DW_TAG_compile_unit":
53+
src_path = _get_source_path(top_DIE)
4754

48-
def _get_symbol_locations(env, elf_path, addrs):
49-
if not addrs:
50-
return {}
51-
cmd = [env.subst("$CC").replace("-gcc", "-addr2line"), "-e", elf_path]
52-
result = _run_tool(cmd, env, addrs)
53-
locations = [line for line in result["out"].split("\n") if line]
54-
assert len(addrs) == len(locations)
55+
for DIE in top_DIE.iter_children():
56+
die_name_attr = DIE.attributes.get("DW_AT_name", None)
57+
die_name = die_name_attr.value.decode("utf-8") if die_name_attr else ""
5558

56-
return dict(zip(addrs, [loc.strip() for loc in locations]))
59+
if die_name != "":
60+
if (DIE.tag == "DW_TAG_subprogram") or (
61+
DIE.tag == "DW_TAG_variable" and "DW_AT_location" in DIE.attributes
62+
):
63+
src_line_attr = DIE.attributes.get("DW_AT_decl_line", None)
64+
src_line = src_line_attr.value if src_line_attr else ""
5765

66+
dwarf_list.append(
67+
{
68+
"dw_name": die_name,
69+
"src_path": src_path,
70+
"src_line": src_line,
71+
}
72+
)
5873

59-
def _get_demangled_names(env, mangled_names):
60-
if not mangled_names:
61-
return {}
62-
result = _run_tool(
63-
[env.subst("$CC").replace("-gcc", "-c++filt")], env, mangled_names
64-
)
65-
demangled_names = [line for line in result["out"].split("\n") if line]
66-
assert len(mangled_names) == len(demangled_names)
67-
68-
return dict(
69-
zip(
70-
mangled_names,
71-
[dn.strip().replace("::__FUNCTION__", "") for dn in demangled_names],
72-
)
74+
sorted_dwarf_list = sorted(
75+
dwarf_list, key=lambda x: len(x["dw_name"]), reverse=False
7376
)
77+
return sorted_dwarf_list
78+
79+
80+
def _get_dwarf_info(symbol, dwarfs):
81+
location = "?"
82+
line = ""
83+
demangled_name = ""
84+
85+
for d in dwarfs:
86+
if d["dw_name"] in symbol["name"]:
87+
location = d["src_path"]
88+
line = d["src_line"]
89+
demangled_name = d["dw_name"]
90+
91+
if demangled_name == "":
92+
demangled_name = symbol["name"]
93+
94+
return location, line, demangled_name
7495

7596

7697
def _collect_sections_info(env, elffile):
@@ -98,7 +119,7 @@ def _collect_sections_info(env, elffile):
98119
return sections
99120

100121

101-
def _collect_symbols_info(env, elffile, elf_path, sections):
122+
def _collect_symbols_info(env, elffile, sections):
102123
symbols = []
103124

104125
symbol_section = elffile.get_section_by_name(".symtab")
@@ -110,14 +131,13 @@ def _collect_symbols_info(env, elffile, elf_path, sections):
110131
sysenv["PATH"] = str(env["ENV"]["PATH"])
111132

112133
symbol_addrs = []
113-
mangled_names = []
114134
for s in symbol_section.iter_symbols():
115135
symbol_info = s.entry["st_info"]
116136
symbol_addr = s["st_value"]
117137
symbol_size = s["st_size"]
118138
symbol_type = symbol_info["type"]
119139

120-
if not env.pioSizeIsValidSymbol(s.name, symbol_type, symbol_addr):
140+
if not env.pioSizeIsValidSymbol(s.name, symbol_size, symbol_type, symbol_addr):
121141
continue
122142

123143
symbol = {
@@ -129,31 +149,26 @@ def _collect_symbols_info(env, elffile, elf_path, sections):
129149
"section": env.pioSizeDetermineSection(sections, symbol_addr),
130150
}
131151

132-
if s.name.startswith("_Z"):
133-
mangled_names.append(s.name)
134-
135152
symbol_addrs.append(hex(symbol_addr))
136153
symbols.append(symbol)
137154

138-
symbol_locations = _get_symbol_locations(env, elf_path, symbol_addrs)
139-
demangled_names = _get_demangled_names(env, mangled_names)
140-
for symbol in symbols:
141-
if symbol["name"].startswith("_Z"):
142-
symbol["demangled_name"] = demangled_names.get(symbol["name"])
143-
location = symbol_locations.get(hex(symbol["addr"]))
155+
sorted_symbols = sorted(symbols, key=lambda x: len(x["name"]), reverse=True)
156+
sorted_dwarf = _collect_dwarf_info(elffile)
157+
158+
for symbol in sorted_symbols:
159+
160+
location, line, demangled_name = _get_dwarf_info(symbol, sorted_dwarf)
161+
symbol["demangled_name"] = demangled_name
162+
144163
if not location or "?" in location:
145164
continue
146165
if IS_WINDOWS:
147166
drive, tail = splitdrive(location)
148167
location = join(drive.upper(), tail)
149168
symbol["file"] = location
150-
symbol["line"] = 0
151-
if ":" in location:
152-
file_, line = location.rsplit(":", 1)
153-
if line.isdigit():
154-
symbol["file"] = file_
155-
symbol["line"] = int(line)
156-
return symbols
169+
symbol["line"] = line if (line != "") else 0
170+
171+
return sorted_symbols
157172

158173

159174
def pioSizeDetermineSection(_, sections, symbol_addr):
@@ -165,8 +180,13 @@ def pioSizeDetermineSection(_, sections, symbol_addr):
165180
return "unknown"
166181

167182

168-
def pioSizeIsValidSymbol(_, symbol_name, symbol_type, symbol_address):
169-
return symbol_name and symbol_address != 0 and symbol_type != "STT_NOTYPE"
183+
def pioSizeIsValidSymbol(_, symbol_name, symbol_size, symbol_type, symbol_address):
184+
return (
185+
symbol_name
186+
and symbol_size != 0
187+
and symbol_address != 0
188+
and symbol_type != "STT_NOTYPE"
189+
)
170190

171191

172192
def pioSizeIsRamSection(_, section):
@@ -224,7 +244,7 @@ def DumpSizeData(_, target, source, env): # pylint: disable=unused-argument
224244
}
225245

226246
files = {}
227-
for symbol in _collect_symbols_info(env, elffile, elf_path, sections):
247+
for symbol in _collect_symbols_info(env, elffile, sections):
228248
file_path = symbol.get("file") or "unknown"
229249
if not files.get(file_path, {}):
230250
files[file_path] = {"symbols": [], "ram_size": 0, "flash_size": 0}

0 commit comments

Comments
 (0)