mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Breno Rodrigues Alves <breno3011alves@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Whitcroft <apw@canonical.com>, Joe Perches <joe@perches.com>,
	Dwaipayan Ray <dwaipayanray1@gmail.com>,
	Lukas Bulwahn <lukas.bulwahn@gmail.com>,
	Breno Rodrigues Alves <breno3011alves@gmail.com>
Subject: [RFC PATCH 3/4] scripts: convert markup_oops.pl to Python
Date: Sun, 18 Oct 2026 07:12:26 -0300	[thread overview]
Message-ID: <20261018101227.7680-4-breno3011alves@gmail.com> (raw)
In-Reply-To: <20261018101227.7680-1-breno3011alves@gmail.com>

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


  parent reply	other threads:[~2026-10-18 10:12 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities " 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 [this message]
2026-10-18 10:12 ` [RFC PATCH 4/4] scripts: convert profile2linkerlist.pl " Breno Rodrigues Alves

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261018101227.7680-4-breno3011alves@gmail.com \
    --to=breno3011alves@gmail.com \
    --cc=apw@canonical.com \
    --cc=dwaipayanray1@gmail.com \
    --cc=joe@perches.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lukas.bulwahn@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®