tools/gdb: fallback to parse elf to get macro

For LTO optimization, we may not be able to parse the macro value. Parse .debug_macro section from elf manually.

Signed-off-by: xuxingliang <xuxingliang@xiaomi.com>
This commit is contained in:
xuxingliang
2024-08-16 20:36:39 +08:00
committed by Xiang Xiao
parent fccd908114
commit 7daecfb10c
3 changed files with 75 additions and 24 deletions

View File

@@ -40,7 +40,7 @@
import os
import re
import subprocess
import tempfile
import time
PUNCTUATORS = [
"\[",
@@ -128,35 +128,39 @@ def fetch_macro_info(file):
# it's broken on some GDB distribution :(, I haven't
# found a solution to it.
with tempfile.NamedTemporaryFile(delete=False) as f1:
cache = os.path.splitext(file)[0] + ".macro"
if not os.path.isfile(cache):
start = time.time()
with open(cache, "w") as f:
# # os.system(f"readelf -wm {file} > {output}")
process = subprocess.Popen(
f"readelf -wm {file}", shell=True, stdout=f, stderr=subprocess.STDOUT
)
# # os.system(f"readelf -wm {file} > {output}")
process = subprocess.Popen(
f"readelf -wm {file}", shell=True, stdout=f1, stderr=subprocess.STDOUT
)
process.communicate()
errcode = process.returncode
process.communicate()
errcode = process.returncode
f.close()
f1.close()
if errcode != 0:
return {}
print(f"readelf took {time.time() - start:.1f} seconds")
if errcode != 0:
return {}
p = re.compile(".*macro[ ]*:[ ]*([\S]+\(.*?\)|[\w]+)[ ]*(.*)")
macros = {}
p = re.compile(".*macro[ ]*:[ ]*([\S]+\(.*?\)|[\w]+)[ ]*(.*)")
macros = {}
start = 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
with open(f1.name, "rb") as f2:
for line in f2.readlines():
line = line.decode("utf-8")
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}")
if not parse_macro(line, macros, p):
print(f"Failed to parse {line}")
print(f"Parse macro took {time.time() - start:.1f} seconds")
return macros

View File

@@ -25,7 +25,9 @@ import traceback
import gdb
import utils
STACK_COLORATION_PATTERN = utils.get_symbol_value("STACK_COLOR")
STACK_COLORATION_PATTERN = utils.get_symbol_value(
"STACK_COLOR", locspec="up_create_stack"
)
class Stack(object):

View File

@@ -25,8 +25,11 @@ from typing import List
import gdb
from macros import fetch_macro_info, try_expand
g_symbol_cache = {}
g_type_cache = {}
g_macro_ctx = None
def backtrace(addresses: List[gdb.Value]) -> List[str]:
@@ -102,6 +105,35 @@ class ContainerOf(gdb.Function):
ContainerOf()
class MacroCtx:
"""
This is a singleton class which only initializes once to
cache a context of macro definition which can be queried later
TODO: we only deal with single ELF at the moment for simplicity
If you load more object files while debugging, only the first one gets loaded
will be used to retrieve macro information
"""
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "instance"):
cls.instance = super(MacroCtx, cls).__new__(cls)
return cls.instance
def __init__(self, filename):
self._macro_map = {}
self._file = filename
self._macro_map = fetch_macro_info(filename)
@property
def macro_map(self):
return self._macro_map
@property
def objfile(self):
return self._file
def gdb_eval_or_none(expresssion):
"""Evaluate an expression and return None if it fails"""
try:
@@ -158,6 +190,19 @@ def get_symbol_value(name, locspec="nx_start", cacheable=True):
gdb.execute("inferior 2", to_string=True)
gdb.execute(f"list {locspec}", to_string=True)
value = gdb_eval_or_none(name)
if not value:
# Try to expand macro by reading elf
global g_macro_ctx
if not g_macro_ctx:
gdb.write("No macro context found, trying to load from ELF\n")
if len(gdb.objfiles()) > 0:
g_macro_ctx = MacroCtx(gdb.objfiles()[0].filename)
else:
raise gdb.GdbError("An executable file must be provided")
expr = try_expand(name, g_macro_ctx.macro_map)
value = gdb_eval_or_none(expr)
if cacheable:
g_symbol_cache[(name, locspec)] = value