2024-07-09 11:26:27 +08:00
|
|
|
############################################################################
|
2024-09-09 18:36:20 +08:00
|
|
|
# tools/gdb/nuttxgdb/macros.py
|
2024-07-09 11:26:27 +08:00
|
|
|
#
|
2024-11-20 14:47:04 +08:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
#
|
2024-07-09 11:26:27 +08:00
|
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
|
|
# this work for additional information regarding copyright ownership. The
|
|
|
|
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
|
|
# "License"); you may not use this file except in compliance with the
|
|
|
|
# License. You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
# License for the specific language governing permissions and limitations
|
|
|
|
# under the License.
|
|
|
|
#
|
|
|
|
############################################################################
|
|
|
|
|
|
|
|
# NOTE: GDB stores macro information based on the current stack frame's scope,
|
|
|
|
# including the source file and line number. Therefore, there may be missing
|
|
|
|
# macro definitions when you are at different stack frames.
|
|
|
|
#
|
|
|
|
# To resolve this issue, we need to retrieve all macro information from the ELF file
|
|
|
|
# then parse and evaluate it by ourselves.
|
|
|
|
#
|
|
|
|
# There might be two ways to achieve this, one is to leverage the C preprocessor
|
2024-11-20 14:47:04 +08:00
|
|
|
# to directly preprocess all the macros interpreted into python constants
|
2024-07-09 11:26:27 +08:00
|
|
|
# gcc -E -x c -P <file_with_macros> -I/path/to/nuttx/include
|
|
|
|
#
|
|
|
|
# While the other way is to leverage the dwarf info stored in the ELF file,
|
|
|
|
# with -g3 switch, we have a `.debug_macro` section containing all the information
|
|
|
|
# about the macros.
|
|
|
|
#
|
2024-11-20 14:47:04 +08:00
|
|
|
# Currently, we are using the second method.
|
2024-07-09 11:26:27 +08:00
|
|
|
|
2024-09-23 12:07:07 +08:00
|
|
|
import hashlib
|
2024-10-31 14:15:06 +08:00
|
|
|
import json
|
2024-07-09 11:26:27 +08:00
|
|
|
import os
|
|
|
|
import re
|
2024-08-16 20:36:39 +08:00
|
|
|
import time
|
2024-09-23 12:07:07 +08:00
|
|
|
from os import path
|
2024-07-09 11:26:27 +08:00
|
|
|
|
|
|
|
PUNCTUATORS = [
|
2024-11-19 09:45:04 +08:00
|
|
|
"\[",
|
|
|
|
"\]",
|
|
|
|
"\(",
|
|
|
|
"\)",
|
|
|
|
"\{",
|
|
|
|
"\}",
|
|
|
|
"\?",
|
|
|
|
";",
|
|
|
|
",",
|
|
|
|
"~",
|
|
|
|
"\.\.\.",
|
|
|
|
"\.",
|
|
|
|
"\-\>",
|
|
|
|
"\-\-",
|
|
|
|
"\-\=",
|
|
|
|
"\-",
|
|
|
|
"\+\+",
|
|
|
|
"\+\=",
|
|
|
|
"\+",
|
|
|
|
"\*\=",
|
|
|
|
"\*",
|
|
|
|
"\!\=",
|
|
|
|
"\!",
|
|
|
|
"\&\&",
|
|
|
|
"\&\=",
|
|
|
|
"\&",
|
|
|
|
"\/\=",
|
|
|
|
"\/",
|
|
|
|
"\%\>",
|
|
|
|
"%:%:",
|
|
|
|
"%:",
|
|
|
|
"%=",
|
|
|
|
"%",
|
|
|
|
"\^\=",
|
|
|
|
"\^",
|
|
|
|
"\#\#",
|
|
|
|
"\#",
|
|
|
|
"\:\>",
|
|
|
|
"\:",
|
|
|
|
"\|\|",
|
|
|
|
"\|\=",
|
|
|
|
"\|",
|
|
|
|
"<<=",
|
|
|
|
"<<",
|
|
|
|
"<=",
|
|
|
|
"<:",
|
|
|
|
"<%",
|
|
|
|
"<",
|
|
|
|
">>=",
|
|
|
|
">>",
|
|
|
|
">=",
|
|
|
|
">",
|
|
|
|
"\=\=",
|
|
|
|
"\=",
|
2024-07-09 11:26:27 +08:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def parse_macro(line, macros, pattern):
|
|
|
|
# grep name, value
|
|
|
|
# the first group matches the token, the second matches the replacement
|
|
|
|
m = pattern.match(line)
|
|
|
|
if not m:
|
|
|
|
return False
|
|
|
|
|
|
|
|
name, value = m.group(1), m.group(2)
|
|
|
|
|
|
|
|
if name in macros:
|
|
|
|
# FIXME: what should we do if we got a redefinition/duplication here?
|
|
|
|
# for now I think it's ok just overwrite the old value
|
|
|
|
pass
|
|
|
|
|
|
|
|
# emplace, for all undefined macros we evalute it to zero
|
|
|
|
macros[name] = value if value else "0"
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_macro_info(file):
|
2024-09-23 12:07:07 +08:00
|
|
|
if not path.isfile(file):
|
2024-07-09 11:26:27 +08:00
|
|
|
raise FileNotFoundError("No given ELF target found")
|
|
|
|
|
|
|
|
# FIXME: we don't use subprocess here because
|
|
|
|
# it's broken on some GDB distribution :(, I haven't
|
|
|
|
# found a solution to it.
|
|
|
|
|
2024-09-23 12:07:07 +08:00
|
|
|
with open(file, "rb") as f:
|
|
|
|
hash = hashlib.md5(f.read()).hexdigest()
|
|
|
|
|
2024-10-31 14:15:06 +08:00
|
|
|
macros = {}
|
|
|
|
p = re.compile(".*macro[ ]*:[ ]*([\S]+\(.*?\)|[\w]+)[ ]*(.*)")
|
|
|
|
cache = path.join(path.dirname(path.abspath(file)), f"{hash}.json")
|
|
|
|
print(f"Load macro: {cache}")
|
2024-09-23 12:07:07 +08:00
|
|
|
if not path.isfile(cache):
|
2024-10-31 14:15:06 +08:00
|
|
|
t = time.time()
|
2024-09-23 11:01:06 +08:00
|
|
|
os.system(f'readelf -wm "{file}" > "{cache}"')
|
2024-10-31 14:15:06 +08:00
|
|
|
print(f"readelf took {time.time() - t:.1f} seconds")
|
2024-07-09 11:26:27 +08:00
|
|
|
|
2024-10-31 14:15:06 +08:00
|
|
|
t = time.time()
|
|
|
|
with open(cache, "r") as f2:
|
|
|
|
for line in f2.readlines():
|
|
|
|
if not line.startswith(" DW_MACRO_define") and not line.startswith(
|
|
|
|
" DW_MACRO_undef"
|
|
|
|
):
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not parse_macro(line, macros, p):
|
|
|
|
print(f"Failed to parse {line}")
|
|
|
|
|
|
|
|
print(f"Parse macro took {time.time() - t:.1f} seconds")
|
2024-07-09 11:26:27 +08:00
|
|
|
|
2024-10-31 14:15:06 +08:00
|
|
|
with open(cache, "w") as f2:
|
|
|
|
dump = json.dumps(macros, indent=4, sort_keys=True)
|
|
|
|
f2.write(dump)
|
2024-07-09 11:26:27 +08:00
|
|
|
|
2024-10-31 14:15:06 +08:00
|
|
|
print(f"Cache macro info to {cache}")
|
|
|
|
else:
|
|
|
|
with open(cache, "r") as f2:
|
|
|
|
macros = json.load(f2)
|
2024-07-09 11:26:27 +08:00
|
|
|
|
|
|
|
return macros
|
|
|
|
|
|
|
|
|
|
|
|
def split_tokens(expr):
|
|
|
|
p = "(" + "|".join(PUNCTUATORS) + ")"
|
2024-11-19 09:45:04 +08:00
|
|
|
res = list(
|
|
|
|
filter(lambda e: e != "", map(lambda e: e.rstrip().lstrip(), re.split(p, expr)))
|
|
|
|
)
|
2024-07-09 11:26:27 +08:00
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def do_expand(expr, macro_map):
|
|
|
|
if expr in PUNCTUATORS:
|
|
|
|
return expr
|
|
|
|
|
|
|
|
tokens = split_tokens(expr)
|
|
|
|
|
|
|
|
res = []
|
|
|
|
|
|
|
|
for t in tokens:
|
|
|
|
if t not in macro_map:
|
|
|
|
res.append(t)
|
|
|
|
continue
|
|
|
|
res += do_expand(macro_map[t], macro_map)
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
# NOTE: Implement a fully functional parser which can
|
2024-11-20 14:47:04 +08:00
|
|
|
# preprocess all the C macros according to ISO 9899 standard
|
2024-07-09 11:26:27 +08:00
|
|
|
# may be an overkill, what we really care about are those
|
2024-11-20 14:47:04 +08:00
|
|
|
# macros that can be evaluated to a constant value.
|
2024-07-09 11:26:27 +08:00
|
|
|
#
|
|
|
|
# #define A (B + C + D)
|
|
|
|
# #define B 1
|
|
|
|
# #define C 2
|
|
|
|
# #define D 3
|
|
|
|
# invoking try_expand('A', macro_map) will give you "(1 + 2 + 3)"
|
|
|
|
#
|
|
|
|
# However,
|
|
|
|
# #define SUM(B,C,D) (B + C + D)
|
|
|
|
# invoking try_expand('SUM(1,2,3)', macro_map) will give you "SUM(1,2,3)"
|
|
|
|
#
|
|
|
|
# We have not implemented this feature as we have not found a practical
|
|
|
|
# use case for it in our GDB plugin.
|
|
|
|
#
|
|
|
|
# However, you can switch to the correct stack frame that has this macro defined
|
2024-11-20 14:47:04 +08:00
|
|
|
# and let GDB expand and evaluate it for you if you really want to evaluate some very
|
2024-07-09 11:26:27 +08:00
|
|
|
# complex macros.
|
|
|
|
|
|
|
|
|
|
|
|
def try_expand(expr, macro):
|
|
|
|
res = []
|
|
|
|
|
|
|
|
res += do_expand(expr, macro)
|
|
|
|
|
|
|
|
return "".join(res)
|