* [RFC PATCH 0/4] scripts: convert four small Perl utilities to Python
@ 2026-10-18 10:12 Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 1/4] scripts: convert bootgraph.pl " Breno Rodrigues Alves
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-18 10:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
Breno Rodrigues Alves
This series converts four small Perl utilities in scripts/ to
Python 3, following the precedent of kernel-doc.py, spdxcheck.py,
checkkconfigsymbols.py, and bpf_doc.py.
Each conversion is a faithful 1:1 port: same CLI, same output,
same behavior (including pre-existing bugs, documented in the
individual commit messages). All use only the Python standard
library.
The four scripts are:
- bootgraph.pl -> bootgraph.py (SVG boot timeline)
- checkincludes.pl -> checkincludes.py (duplicate #include finder)
- markup_oops.pl -> markup_oops.py (oops annotation)
- profile2linkerlist.pl -> profile2linkerlist.py (readprofile to linker list)
Tested by running the Perl and Python versions side by side on
the same input; output is byte-for-byte identical for the three
scripts that have verifiable output (bootgraph, checkincludes,
profile2linkerlist). markup_oops requires a live oops and
matching vmlinux to fully test.
Breno Rodrigues Alves (4):
scripts: convert bootgraph.pl to Python
scripts: convert checkincludes.pl to Python
scripts: convert markup_oops.pl to Python
scripts: convert profile2linkerlist.pl to Python
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 1/4] scripts: convert bootgraph.pl to Python
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities to Python Breno Rodrigues Alves
@ 2026-10-18 10:12 ` Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 2/4] scripts: convert checkincludes.pl " Breno Rodrigues Alves
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-18 10:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
Breno Rodrigues Alves
Faithful re-implementation of bootgraph.pl in Python 3 (stdlib only).
Same CLI (-h, -header), same SVG output, same error handling.
One intentional change: the Perl version has an unreachable
'exit 1' after 'help(1); exit 1;' (help() already calls exit 0),
so it always returned 0 even on error. The Python version
returns exit 1 when no data is found in dmesg.
Tested with a synthetic dmesg containing three initcalls;
generated SVG is byte-for-byte identical to the Perl output.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
scripts/bootgraph.py | 194 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 194 insertions(+)
create mode 100755 scripts/bootgraph.py
diff --git a/scripts/bootgraph.py b/scripts/bootgraph.py
new file mode 100755
index 000000000..6839d2824
--- /dev/null
+++ b/scripts/bootgraph.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright 2008, Intel Corporation
+# Authors: Arjan van de Ven <arjan@linux.intel.com>
+#
+# Turn dmesg output into an SVG graphic showing which initcalls take
+# how much time. The kernel must be compiled with CONFIG_PRINTK_TIME
+# and booted with "initcall_debug" on the command line.
+#
+# usage:
+# dmesg | python3 scripts/bootgraph.py > output.svg
+
+import argparse
+import re
+import subprocess
+import sys
+
+RE_CALLING = re.compile(r'([0-9.]+)\] calling ([a-zA-Z0-9_.]+)\+')
+RE_PID = re.compile(r'@ ([0-9]+)')
+RE_ASYNC_WAIT = re.compile(r'([0-9.]+)\] async_waiting @ ([0-9]+)')
+RE_RETURNED = re.compile(r'([0-9.]+)\] initcall ([a-zA-Z0-9_.]+)\+.*returned')
+RE_ASYNC_CONT = re.compile(r'([0-9.]+)\] async_continuing @ ([0-9]+)')
+RE_STOP = re.compile(r'(Write protecting the|Freeing unused kernel memory)')
+
+STYLES = [
+ 'fill:rgb(0,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,255,0);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,0,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,255,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,0,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(128,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+]
+STYLE_WAIT = 'fill:rgb(128,128,128);fill-opacity:0.5;stroke-width:0;stroke:rgb(0,0,0)'
+
+HEADERSTEP = 20
+XHEADER = 15
+YHEADER = 25
+
+
+def usage(exit_code=1):
+ stream = sys.stdout if exit_code == 0 else sys.stderr
+ print('Usage:', file=stream)
+ print('1) dmesg | python3 scripts/bootgraph.py [OPTION] > output.svg',
+ file=stream)
+ print('2) python3 scripts/bootgraph.py -h', file=stream)
+ print('', file=stream)
+ print('Options:', file=stream)
+ print('\t-header\tInsert kernel version and date', file=stream)
+ sys.exit(exit_code)
+
+
+def parse_dmesg(stream):
+ """Return (start, end, types, pids, count, first, last)."""
+ start, end, types, pids, pidctr = {}, {}, {}, {}, {}
+ done = False
+ first, last, count = 99999.0, 0.0, 0
+
+ for line in stream:
+ m = RE_CALLING.search(line)
+ if m:
+ func = m.group(2)
+ if not done:
+ start[func] = float(m.group(1))
+ types[func] = 0
+ first = min(first, start[func])
+ mp = RE_PID.search(line)
+ if mp:
+ pids[func] = int(mp.group(1))
+ count += 1
+
+ m = RE_ASYNC_WAIT.search(line)
+ if m:
+ pid = int(m.group(2))
+ pidctr[pid] = pidctr.get(pid, 0) + 1
+ func = f'wait_{pid}_{pidctr[pid]}'
+ if not done:
+ start[func] = float(m.group(1))
+ types[func] = 1
+ first = min(first, start[func])
+ pids[func] = pid
+ count += 1
+
+ m = RE_RETURNED.search(line)
+ if m and not done:
+ end[m.group(2)] = float(m.group(1))
+ last = end[m.group(2)]
+
+ m = RE_ASYNC_CONT.search(line)
+ if m:
+ pid = int(m.group(2))
+ func = f'wait_{pid}_{pidctr.get(pid, "")}'
+ end[func] = float(m.group(1))
+ last = end[func]
+
+ if RE_STOP.search(line):
+ done = True
+
+ return start, end, types, pids, count, first, last
+
+
+def emit_svg(out, start, end, types, pids, first, last, header):
+ out.write('<?xml version="1.0" standalone="no"?> \n')
+ out.write('<svg width="2000" height="100%" version="1.1" '
+ 'xmlns="http://www.w3.org/2000/svg">\n')
+
+ if header:
+ version = subprocess.check_output(['uname', '-a'], text=True).rstrip()
+ date = subprocess.check_output(['date'], text=True).rstrip()
+ out.write(f'<text transform="translate({XHEADER},{YHEADER})">'
+ f'Kernel version: {version}</text>\n')
+ cyheader = YHEADER + HEADERSTEP
+ out.write(f'<text transform="translate({XHEADER},{cyheader})">'
+ f'Date: {date}</text>\n')
+
+ mult = 1950.0 / (last - first)
+ threshold2 = (last - first) / 120.0
+ threshold = threshold2 / 10
+
+ stylecounter = 0
+ rows, rowscount = {}, 1
+
+ for key in sorted(start.keys(), key=lambda k: start[k]):
+ duration = end[key] - start[key]
+ if duration < threshold:
+ continue
+ pid = pids.get(key)
+ if pid not in rows:
+ rows[pid] = rowscount
+ rowscount += 1
+ s = (start[key] - first) * mult
+ s2, s3 = s + 6, s + 1
+ e = (end[key] - first) * mult
+ w = e - s
+ y = rows[pid] * 150
+ y2 = y + 4
+ style = STYLES[stylecounter]
+ stylecounter = (stylecounter + 1) % 12
+ if types[key] == 1:
+ y += 15
+ out.write(f'<rect x="{s:.15g}" width="{w:.15g}" y="{y}" '
+ f'height="115" style="{STYLE_WAIT}"/>\n')
+ else:
+ out.write(f'<rect x="{s:.15g}" width="{w:.15g}" y="{y}" '
+ f'height="145" style="{style}"/>\n')
+ if duration >= threshold2:
+ out.write(f'<text transform="translate({s2:.15g},{y2}) '
+ f'rotate(90)">{key}</text>\n')
+ else:
+ out.write(f'<text transform="translate({s3:.15g},{y2}) '
+ f'rotate(90)" font-size="3pt">{key}</text>\n')
+
+ # top timeline
+ time = first
+ step = (last - first) / 15.0
+ while time < last:
+ s3 = (time - first) * mult
+ tm = int(time * 100) / 100.0
+ out.write(f'<text transform="translate({s3:.15g},89) rotate(90)">{tm:.15g}</text>\n')
+ time += step
+
+ out.write('</svg>\n')
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('-h', '--help', action='store_true')
+ parser.add_argument('-header', '--header', action='store_true')
+ args = parser.parse_args(argv)
+
+ if args.help:
+ usage(0)
+
+ start, end, types, pids, count, first, last = parse_dmesg(sys.stdin)
+
+ if count == 0:
+ print("No data found in the dmesg. Make sure that 'printk.time=1' and",
+ file=sys.stderr)
+ print("'initcall_debug' are passed on the kernel command line.",
+ file=sys.stderr)
+ usage(1)
+
+ emit_svg(sys.stdout, start, end, types, pids, first, last, args.header)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
\ No newline at end of file
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 2/4] scripts: convert checkincludes.pl to Python
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities to Python Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 1/4] scripts: convert bootgraph.pl " Breno Rodrigues Alves
@ 2026-10-18 10:12 ` Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 3/4] scripts: convert markup_oops.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 4/4] scripts: convert profile2linkerlist.pl " Breno Rodrigues Alves
3 siblings, 0 replies; 5+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-18 10:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
Breno Rodrigues Alves
Faithful re-implementation of checkincludes.pl in Python 3
(stdlib only). Same -r behavior, same duplicate detection,
same error messages.
Tested with a C file containing a duplicate stdio.h include;
output is byte-for-byte identical to the Perl version.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
scripts/checkincludes.py | 101 +++++++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100755 scripts/checkincludes.py
diff --git a/scripts/checkincludes.py b/scripts/checkincludes.py
new file mode 100755
index 000000000..7e85e4bda
--- /dev/null
+++ b/scripts/checkincludes.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# checkincludes: find/remove files included more than once
+#
+# Copyright abandoned, 2000, Niels Kristian Bech Jensen <nkbj@image.dk>.
+# Copyright 2009 Luis R. Rodriguez <mcgrof@gmail.com>
+#
+# Checks for duplicate #include directives. With -r, removes duplicates
+# in place. Does not account for macros or #ifdef branches; run only when
+# you know the duplicates are real.
+
+import re
+import sys
+
+RE_INCLUDE = re.compile(r'^\s*#\s*include\s*[<"](\S*)[>"]')
+
+
+def usage():
+ print("Usage: checkincludes.py [-r]", file=sys.stderr)
+ print("By default we just warn of duplicates", file=sys.stderr)
+ print("To remove duplicated includes in place use -r", file=sys.stderr)
+ sys.exit(1)
+
+
+def process_file(path, remove):
+ """Return (dup_count, error_msg). error_msg is None on success."""
+ try:
+ with open(path, 'r') as f:
+ lines = f.readlines()
+ except OSError as e:
+ return 0, f"Cannot open {path}: {e}"
+
+ included = {}
+ for line in lines:
+ m = RE_INCLUDE.match(line)
+ if m:
+ included[m.group(1)] = included.get(m.group(1), 0) + 1
+
+ if not remove:
+ dups = 0
+ for filename, n in included.items():
+ if n > 1:
+ print(f"{path}: {filename} is included more than once.")
+ dups += 1
+ return dups, None
+
+ # Removal mode: keep the last occurrence, remove earlier duplicates.
+ # Same semantics as the Perl original (first N-1 occurrences removed).
+ dups = 0
+ out_lines = []
+ for line in lines:
+ m = RE_INCLUDE.match(line)
+ if m and included.get(m.group(1), 0) > 1:
+ included[m.group(1)] -= 1
+ dups += 1
+ else:
+ out_lines.append(line)
+
+ # Match original: always rewrite, even when nothing was removed.
+ try:
+ with open(path, 'w') as f:
+ f.writelines(out_lines)
+ except OSError as e:
+ return 0, f"Cannot write to {path}: {e}"
+
+ if dups > 0:
+ print(f"{path}: removed {dups} duplicate includes")
+ return dups, None
+
+
+def main(argv):
+ remove = False
+ args = list(argv)
+
+ if not args:
+ usage()
+
+ # Original only parses "-r" when there are >= 2 args.
+ if len(args) >= 2 and args[0].startswith('-'):
+ if args[0] == '-r':
+ remove = True
+ args.pop(0)
+ else:
+ usage()
+
+ dup_counter = 0
+ for path in args:
+ n, err = process_file(path, remove)
+ if err:
+ print(err, file=sys.stderr)
+ sys.exit(1)
+ dup_counter += n
+
+ if dup_counter == 0:
+ print("No duplicate includes found.")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
\ No newline at end of file
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 3/4] scripts: convert markup_oops.pl to Python
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities to Python Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 1/4] scripts: convert bootgraph.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 2/4] scripts: convert checkincludes.pl " Breno Rodrigues Alves
@ 2026-10-18 10:12 ` Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 4/4] scripts: convert profile2linkerlist.pl " Breno Rodrigues Alves
3 siblings, 0 replies; 5+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-18 10:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
Breno Rodrigues Alves
Faithful re-implementation of markup_oops.pl in Python 3
(stdlib only). Same CLI (-c, -m, -h), same oops parsing,
same objdump invocation, same register tracking logic.
Uses subprocess with argument lists instead of shell
backticks for objdump/modinfo calls; no behavior change.
Tested with -h; full oops annotation requires a live oops
and matching vmlinux.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
scripts/markup_oops.py | 324 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 324 insertions(+)
create mode 100755 scripts/markup_oops.py
diff --git a/scripts/markup_oops.py b/scripts/markup_oops.py
new file mode 100755
index 000000000..e9bafcb42
--- /dev/null
+++ b/scripts/markup_oops.py
@@ -0,0 +1,324 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright 2008, Intel Corporation
+# Authors: Arjan van de Ven <arjan@linux.intel.com>
+#
+# Turn an oops output into an annotated disassembly of the faulting
+# instruction and surrounding code, tracking register values backwards
+# from the faulting point.
+#
+# usage:
+# dmesg | python3 scripts/markup_oops.py [OPTION] [VMLINUX]
+
+import argparse
+import re
+import subprocess
+import sys
+
+REGS = {}
+
+RE_32_REGS_A = re.compile(r'EAX: ([0-9a-f]+) EBX: ([0-9a-f]+) '
+ r'ECX: ([0-9a-f]+) EDX: ([0-9a-f]+)')
+RE_32_REGS_B = re.compile(r'ESI: ([0-9a-f]+) EDI: ([0-9a-f]+) '
+ r'EBP: ([0-9a-f]+) ESP: ([0-9a-f]+)')
+RE_64_REGS_A = re.compile(r'RAX: ([0-9a-f]+) RBX: ([0-9a-f]+) '
+ r'RCX: ([0-9a-f]+)')
+RE_64_REGS_B = re.compile(r'RDX: ([0-9a-f]+) RSI: ([0-9a-f]+) '
+ r'RDI: ([0-9a-f]+)')
+RE_64_REGS_C = re.compile(r'RBP: ([0-9a-f]+) R08: ([0-9a-f]+) '
+ r'R09: ([0-9a-f]+)')
+RE_64_REGS_D = re.compile(r'R10: ([0-9a-f]+) R11: ([0-9a-f]+) '
+ r'R12: ([0-9a-f]+)')
+RE_64_REGS_E = re.compile(r'R13: ([0-9a-f]+) R14: ([0-9a-f]+) '
+ r'R15: ([0-9a-f]+)')
+
+RE_EIP_32 = re.compile(r'EIP: 0060:\[\<([a-z0-9]+)\>\]')
+RE_RIP_64 = re.compile(r'RIP: 0010:\[\<([a-z0-9]+)\>\]')
+RE_FUNC_32 = re.compile(r'EIP is at ([a-zA-Z0-9_]+)\+0x([0-9a-f]+)/0x[a-f0-9]')
+RE_FUNC_64 = re.compile(
+ r'RIP: 0010:\[\<[0-9a-f]+\>\] \[\<[0-9a-f]+\>\] '
+ r'([a-zA-Z0-9_]+)\+0x([0-9a-f]+)/0x[a-f0-9]')
+RE_MOD_32 = re.compile(
+ r'EIP is at ([a-zA-Z0-9_]+)\+(0x[0-9a-f]+)/0x[a-f0-9]+\W\[([a-zA-Z0-9_-]+)\]')
+RE_MOD_64 = re.compile(
+ r'RIP: 0010:\[\<[0-9a-f]+\>\] \[\<[0-9a-f]+\>\] '
+ r'([a-zA-Z0-9_]+)\+(0x[0-9a-f]+)/0x[a-f0-9]+\W\[([a-zA-Z0-9_-]+)\]')
+
+
+def parse_x86_regs(line):
+ """Extract register values from one oops line into REGS."""
+ m = RE_32_REGS_A.search(line)
+ if m:
+ REGS['%eax'] = m.group(1)
+ REGS['%ebx'] = m.group(2)
+ REGS['%ecx'] = m.group(3)
+ REGS['%edx'] = m.group(4)
+ m = RE_32_REGS_B.search(line)
+ if m:
+ REGS['%esi'] = m.group(1)
+ REGS['%edi'] = m.group(2)
+ REGS['%esp'] = m.group(4)
+ m = RE_64_REGS_A.search(line)
+ if m:
+ REGS['%eax'] = m.group(1)
+ REGS['%ebx'] = m.group(2)
+ REGS['%ecx'] = m.group(3)
+ m = RE_64_REGS_B.search(line)
+ if m:
+ REGS['%edx'] = m.group(1)
+ REGS['%esi'] = m.group(2)
+ REGS['%edi'] = m.group(3)
+ m = RE_64_REGS_C.search(line)
+ if m:
+ REGS['%r08'] = m.group(2)
+ REGS['%r09'] = m.group(3)
+ m = RE_64_REGS_D.search(line)
+ if m:
+ REGS['%r10'] = m.group(1)
+ REGS['%r11'] = m.group(2)
+ REGS['%r12'] = m.group(3)
+ m = RE_64_REGS_E.search(line)
+ if m:
+ REGS['%r13'] = m.group(1)
+ REGS['%r14'] = m.group(2)
+ REGS['%r15'] = m.group(3)
+
+
+def reg_name(reg):
+ """Normalize a 64-bit register name to its 32-bit form."""
+ reg = re.sub(r'r(.)x', r'e\1x', reg)
+ reg = re.sub(r'r(.)i', r'e\1i', reg)
+ reg = re.sub(r'r(.)p', r'e\1p', reg)
+ return reg
+
+
+def process_x86_regs(line, cntr):
+ """Annotate a disassembly line with live register values."""
+ if len(line) < 40:
+ return ''
+ m = re.search(r'([0-9a-zA-Z,%()\-\+]+)$', line)
+ if not m:
+ return ''
+
+ lastword = m.group(1)
+ clobber = lastword
+ clobber = re.sub(r'\([a-z0-9%,]+\)', '', clobber)
+ clobber = re.sub(r'.*,', '', clobber)
+ if cntr == 0:
+ clobber = ''
+
+ clobberprime = reg_name(clobber)
+ lastwordprime = reg_name(lastword)
+ result = ''
+
+ for reg in list(REGS.keys()):
+ val = REGS[reg]
+ if re.match(r'^0+$', val):
+ val = '0'
+ else:
+ val = re.sub(r'^0*', '', val)
+
+ if reg in clobber or reg in clobberprime:
+ if val:
+ result += f' {reg} => {val} '
+ REGS[reg] = ''
+ val = ''
+
+ if reg in lastword or reg in lastwordprime:
+ if val:
+ result += f' {reg} = {val} '
+
+ return result
+
+
+def in_range(address, target):
+ delta = int('0x' + address, 16) - int('0x' + target, 16)
+ return -4096 < delta < 4096
+
+
+def usage():
+ print('Usage:')
+ print(' dmesg | python3 markup_oops.py [OPTION] [VMLINUX]')
+ print()
+ print('OPTION:')
+ print(' -c, --cross-compile CROSS_COMPILE\t'
+ 'Specify the prefix used for toolchain.')
+ print(' -m, --module MODULE_DIRNAME\t\t'
+ 'Specify the module filename.')
+ print(' -h, --help\t\t\t\tHelp.')
+ sys.exit(0)
+
+
+def run_cmd(cmd):
+ """Run a command, return stdout text or None on error."""
+ try:
+ return subprocess.check_output(cmd, text=True)
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ return None
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('-c', '--cross-compile', default='',
+ metavar='CROSS_COMPILE')
+ parser.add_argument('-m', '--module', default='', metavar='MODULE_DIRNAME')
+ parser.add_argument('-h', '--help', action='store_true')
+ parser.add_argument('vmlinux', nargs='?', default=None)
+ args = parser.parse_args(argv)
+
+ if args.help:
+ usage()
+
+ cross = args.cross_compile
+ vmlinux = args.vmlinux
+ modulefile = args.module
+
+ if vmlinux is None:
+ kerver = subprocess.check_output(['uname', '-r'], text=True).rstrip()
+ vmlinux = f'/lib/modules/{kerver}/build/vmlinux'
+ print(f'No vmlinux specified, assuming {vmlinux}')
+
+ filename = vmlinux
+ target = '0'
+ function = ''
+ module = ''
+ func_offset = '0'
+ vmaoffset = 0
+
+ for line in sys.stdin:
+ m = RE_EIP_32.search(line) or RE_RIP_64.search(line)
+ if m:
+ target = m.group(1)
+ m = RE_FUNC_32.search(line) or RE_FUNC_64.search(line)
+ if m:
+ function = m.group(1)
+ func_offset = m.group(2)
+ m = RE_MOD_32.search(line) or RE_MOD_64.search(line)
+ if m:
+ module = m.group(3)
+ parse_x86_regs(line)
+
+ if target == '0':
+ print('No oops found!')
+ usage()
+
+ target_int = int('0x' + target, 16)
+ offset_int = int('0x' + func_offset, 16)
+ decodestart = target_int - offset_int
+ decodestop = target_int + 8192
+
+ # Module case: find .ko, compute VMA offset relative to text start.
+ if module:
+ if not modulefile:
+ out = run_cmd(['modinfo', '-F', 'filename', module])
+ modulefile = out.rstrip() if out else ''
+ filename = modulefile
+ if not filename:
+ print(f'Module .ko file for {module} not found. Aborting')
+ sys.exit(1)
+ out = run_cmd([cross + 'objdump', '-dS', filename])
+ if out is None:
+ print('Cannot start objdump', file=sys.stderr)
+ sys.exit(1)
+ pat = re.compile(rf'^([0-9a-f]+) \<{re.escape(function)}\>:')
+ for line in out.splitlines():
+ m = pat.match(line)
+ if m:
+ vmaoffset = (target_int
+ - int('0x' + m.group(1), 16)
+ - offset_int)
+
+ # Disassemble the region around the faulting instruction.
+ out = run_cmd([
+ cross + 'objdump', '-dS',
+ f'--adjust-vma={vmaoffset}',
+ f'--start-address={decodestart}',
+ f'--stop-address={decodestop}',
+ filename,
+ ])
+ if out is None:
+ print('Cannot start objdump', file=sys.stderr)
+ sys.exit(1)
+
+ counter = 0
+ state = 0
+ center = -1
+ lines = []
+
+ for line in out.splitlines():
+ if state == 0:
+ m = re.match(r'^([a-f0-9]+):', line)
+ if m and in_range(m.group(1), target):
+ state = 1
+ if state == 1:
+ m = re.match(r'^([a-f0-9]{6,}):', line)
+ if m:
+ val = m.group(1)
+ if not in_range(val, target):
+ break
+ if val == target:
+ center = counter
+ lines.append(line)
+ counter += 1
+
+ if counter == 0:
+ print('No matching code found')
+ sys.exit(1)
+ if center == -1:
+ print('No matching code found')
+ sys.exit(1)
+
+ # Find start: scan backwards up to 10 source lines or 20 asm lines.
+ start = center
+ codelines = binarylines = 0
+ while start > 1:
+ start -= 1
+ if re.match(r'^([a-f0-9]+):', lines[start]):
+ binarylines += 1
+ else:
+ codelines += 1
+ if codelines > 10 or binarylines > 20:
+ break
+
+ # Find finish: scan forwards.
+ finish = center
+ codelines = binarylines = 0
+ while finish < counter:
+ finish += 1
+ line = '' if finish >= counter else lines[finish]
+ if re.match(r'^([a-f0-9]+):', line):
+ binarylines += 1
+ else:
+ codelines += 1
+ if codelines > 10 or binarylines > 20:
+ break
+
+ # Annotate registers backwards from the faulting instruction.
+ reglines = [None] * len(lines)
+ i = center
+ while i >= start:
+ reglines[i] = process_x86_regs(lines[i], center - i)
+ i -= 1
+
+ # Print annotated disassembly.
+ i = start
+ while i < finish:
+ if i == center:
+ prefix = f'*{lines[i]} '
+ else:
+ prefix = f' {lines[i]} '
+ out_line = prefix
+ if reglines[i] and len(reglines[i]) > 0:
+ pad = 60 - len(prefix)
+ out_line += ' ' * max(0, pad) + f'| {reglines[i]}'
+ if i == center:
+ out_line += '<--- faulting instruction'
+ print(out_line)
+ i += 1
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
\ No newline at end of file
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 4/4] scripts: convert profile2linkerlist.pl to Python
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities to Python Breno Rodrigues Alves
` (2 preceding siblings ...)
2026-10-18 10:12 ` [RFC PATCH 3/4] scripts: convert markup_oops.pl " Breno Rodrigues Alves
@ 2026-10-18 10:12 ` Breno Rodrigues Alves
3 siblings, 0 replies; 5+ messages in thread
From: Breno Rodrigues Alves @ 2026-10-18 10:12 UTC (permalink / raw)
To: linux-kernel
Cc: Andy Whitcroft, Joe Perches, Dwaipayan Ray, Lukas Bulwahn,
Breno Rodrigues Alves
Faithful re-implementation of profile2linkerlist.pl in Python 3
(stdlib only).
Note: the original Perl script has a regex that expects the
historical readprofile format '<num> <name> <num>'. Modern
readprofile outputs '<num>\t<name>', so the script produces
incorrect output for modern input (e.g. '*(.text.3)' instead
of '*(.text.foo)'). This behavior is preserved faithfully;
a follow-up patch can fix the regex separately.
Tested with the same input on both versions; output is
byte-for-byte identical.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
scripts/profile2linkerlist.py | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
create mode 100755 scripts/profile2linkerlist.py
diff --git a/scripts/profile2linkerlist.py b/scripts/profile2linkerlist.py
new file mode 100755
index 000000000..fb7a6def3
--- /dev/null
+++ b/scripts/profile2linkerlist.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Takes a (sorted) output of readprofile and turns it into a list suitable
+# for linker scripts.
+#
+# usage:
+# readprofile | sort -rn | python3 profile2linkerlist.py > functionlist
+
+import re
+import sys
+
+RE_PROFILE = re.compile(r'\W*[0-9]+\W*([a-zA-Z_0-9]+)\W*[0-9]+')
+
+
+def main():
+ for line in sys.stdin:
+ if 'unknown' in line or 'total' in line:
+ continue
+ m = RE_PROFILE.search(line)
+ if m:
+ print(f'*(.text.{m.group(1)})')
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
\ No newline at end of file
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-18 10:12 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities to Python Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 1/4] scripts: convert bootgraph.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 2/4] scripts: convert checkincludes.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 3/4] scripts: convert markup_oops.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 4/4] scripts: convert profile2linkerlist.pl " Breno Rodrigues Alves
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®