mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®