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 7/7] scripts: convert recordmcount.pl to Python
Date: Sun, 18 Oct 2026 08:06:38 -0300	[thread overview]
Message-ID: <20261018110638.9947-8-breno3011alves@gmail.com> (raw)
In-Reply-To: <20261018110638.9947-1-breno3011alves@gmail.com>

Faithful re-implementation of recordmcount.pl in Python 3
(stdlib only). Same per-architecture regexes, same 9-step
pipeline (nm -> objdump -> assemble -> objcopy/ld).

Tested byte-for-byte for x86_64 and arm64; other architectures
use the same parsing rules as the Perl version.

Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
 scripts/recordmcount.py | 335 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 335 insertions(+)
 create mode 100755 scripts/recordmcount.py

diff --git a/scripts/recordmcount.py b/scripts/recordmcount.py
new file mode 100755
index 000000000..b4e1728a3
--- /dev/null
+++ b/scripts/recordmcount.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+# (c) 2008, Steven Rostedt <srostedt@redhat.com>
+#
+# recordmcount.py - makes a section __mcount_loc holding all offsets
+# to calls to mcount. See recordmcount.pl header for detailed design.
+#
+# usage: recordmcount.py arch endian bits objdump objcopy cc ld nm rm mv \
+#                       is_module inputfile
+
+import os
+import re
+import subprocess
+import sys
+
+TEXT_SECTIONS = {
+    '.text', '.init.text', '.ref.text', '.sched.text',
+    '.spinlock.text', '.irqentry.text', '.softirqentry.text',
+    '.kprobes.text', '.cpuidle.text', '.text.unlikely',
+}
+TEXT_SECTION_PREFIXES = ('.text.',)
+
+MCOUNT_SECTION = '__mcount_loc'
+
+
+class Config:
+    """Per-architecture parsing configuration."""
+
+    def __init__(self):
+        self.local_re   = re.compile(r'^[0-9a-fA-F]+\s+t\s+(\S+)')
+        self.weak_re    = re.compile(r'^[0-9a-fA-F]+\s+([wW])\s+(\S+)')
+        self.section_re = re.compile(r'Disassembly of section\s+(\S+):')
+        self.func_re    = re.compile(r'^([0-9a-fA-F]+)\s+<([^^]*?)>:')
+        self.mcount_re  = re.compile(
+            r'^\s*([0-9a-fA-F]+):.*\s(mcount|__fentry__)$')
+        self.section_type   = '@progbits'
+        self.mcount_adjust  = 0
+        self.alignment      = None
+        self.type           = '.long'
+        self.ld_flags       = ''
+        self.objdump_flags  = ''
+        self.objcopy_flags  = ''
+        self.cc_flags       = ''
+
+
+def config_for(arch, endian, bits, cc):
+    cfg = Config()
+    if arch in ('x86_64', 'i386', 'x86'):
+        arch = 'x86_64' if bits == 64 else 'i386'
+    if arch == 'x86_64':
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+):.*\s(mcount|__fentry__)'
+            r'([+-]0x[0-9a-zA-Z]+)?$')
+        cfg.type = '.quad'
+        cfg.alignment = 8
+        cfg.mcount_adjust = -1
+        cfg.ld_flags      = '-m elf_x86_64'
+        cfg.objdump_flags = '-M x86-64'
+        cfg.objcopy_flags = '-O elf64-x86-64'
+        cfg.cc_flags      = '-m64'
+    elif arch == 'i386':
+        cfg.alignment = 4
+        cfg.mcount_adjust = -1
+        cfg.ld_flags      = '-m elf_i386'
+        cfg.objdump_flags = '-M i386'
+        cfg.objcopy_flags = '-O elf32-i386'
+        cfg.cc_flags      = '-m32'
+    elif arch == 's390' and bits == 64:
+        if '-DCC_USING_HOTPATCH' in cc:
+            cfg.mcount_re = re.compile(
+                r'^\s*([0-9a-fA-F]+):\s*c0 04 00 00 00 00\s*'
+                r'(brcl\s*0,|jgnop\s*)[0-9a-f]+ <([^\+]*)>$')
+            cfg.mcount_adjust = 0
+        cfg.alignment = 8
+        cfg.type = '.quad'
+        cfg.ld_flags = '-m elf64_s390'
+        cfg.cc_flags = '-m64'
+    elif arch == 'sh':
+        cfg.alignment = 2
+        cfg.ld_flags = '-m shlelf_linux'
+        cfg.objcopy_flags = '-O elf32-shbig-linux' if endian == 'big' \
+            else '-O elf32-sh-linux'
+    elif arch == 'powerpc':
+        cfg.local_re = re.compile(r'^[0-9a-fA-F]+\s+t\s+(\.?\S+)')
+        cfg.func_re  = re.compile(r'^([0-9a-fA-F]+)\s+<(\.?\w*?)>:')
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+):.*\s\.?_mcount$')
+        emu = 'ppc'
+        if endian == 'big':
+            cfg.cc_flags      = '-mbig-endian'
+            cfg.ld_flags      = '-EB'
+        else:
+            cfg.cc_flags      = '-mlittle-endian'
+            cfg.ld_flags      = '-EL'
+            emu = 'lppc'
+        if bits == 64:
+            cfg.type = '.quad'
+            cfg.cc_flags += ' -m64'
+            cfg.ld_flags += f' -m elf64{emu}'
+        else:
+            cfg.cc_flags += ' -m32'
+            cfg.ld_flags += f' -m elf32{emu}'
+    elif arch == 'arm':
+        cfg.alignment = 2
+        cfg.section_type = '%progbits'
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+):\s*R_ARM_(CALL|PC24|THM_CALL)'
+            r'\s+(__gnu_mcount_nc|mcount)$')
+    elif arch == 'arm64':
+        cfg.alignment = 3
+        cfg.section_type = '%progbits'
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+):\s*R_AARCH64_CALL26\s+_mcount$')
+        cfg.type = '.quad'
+    elif arch == 'sparc64':
+        cfg.func_re = re.compile(r'^([0-9a-fA-F]+)\s+<(\w*?)>:')
+        cfg.mcount_re = re.compile(r'^\s*([0-9a-fA-F]+):.*\s_mcount$')
+        cfg.alignment = 8
+        cfg.type = '.xword'
+        cfg.ld_flags      = '-m elf64_sparc'
+        cfg.cc_flags      = '-m64'
+        cfg.objcopy_flags = '-O elf64-sparc'
+    elif arch == 'mips':
+        if_bits = bits
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+): R_MIPS_26\s+_mcount$')
+        cfg.objdump_flags = f'-Melf-trad{endian}mips'
+        if endian == 'big':
+            cfg.ld_flags = f'-melf{bits}btsmip -EB'
+        else:
+            cfg.ld_flags = f'-melf{bits}ltsmip -EL'
+        cfg.cc_flags = f'-mno-abicalls -fno-pic -mabi={bits}'
+        if bits == 64:
+            cfg.func_re = re.compile(
+                r'^([0-9a-fA-F]+)\s+<(.|[^\$]L.*?|\$[^L].*?'
+                r'|[^\$][^L].*?)>:')
+            cfg.type = '.dword'
+    elif arch == 'microblaze':
+        cfg.mcount_re = re.compile(r'^\s*([0-9a-fA-F]+):.*\s_mcount$')
+    elif arch == 'riscv':
+        cfg.func_re = re.compile(
+            r'^([0-9a-fA-F]+)\s+<([^.0-9][0-9a-zA-Z_\.]+)>:')
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+):\sR_RISCV_CALL(_PLT)?\s_mcount$')
+        cfg.type = '.quad'
+        cfg.alignment = 2
+    elif arch == 'csky':
+        cfg.mcount_re = re.compile(
+            r'^\s*([0-9a-fA-F]+):\s*R_CKCORE_PCREL_JSR_IMM26BY2\s+'
+            r'_mcount$')
+        cfg.alignment = 2
+    else:
+        sys.exit(f"Arch {arch} is not supported with "
+                 f"CONFIG_DYNAMIC_FTRACE")
+    return cfg, arch
+
+
+def run(cmd):
+    try:
+        return subprocess.check_output(cmd, text=True,
+                                        stderr=subprocess.DEVNULL)
+    except (subprocess.CalledProcessError, FileNotFoundError):
+        return ''
+
+
+def main(argv):
+    if len(argv) != 12:
+        print("usage: recordmcount.py arch endian bits objdump objcopy cc "
+              "ld nm rm mv is_module inputfile", file=sys.stderr)
+        sys.exit(1)
+
+    (arch, endian, bits_s, objdump, objcopy, cc,
+     ld, nm, rm, mv, is_module, inputfile) = argv
+    bits = int(bits_s)
+
+    if re.search(r'kernel/trace/ftrace\.o$', inputfile):
+        sys.exit(0)
+
+    if not objdump:  objdump = 'objdump'
+    if not objcopy:  objcopy = 'objcopy'
+    if not cc:       cc = 'gcc'
+    if not ld:       ld = 'ld'
+    if not nm:       nm = 'nm'
+    if not rm:       rm = 'rm'
+    if not mv:       mv = 'mv'
+
+    cfg, arch = config_for(arch, endian, bits, cc)
+
+    dirname, filename = os.path.split(inputfile)
+    dirname = dirname or '.'
+    base, ext = os.path.splitext(filename)
+    mcount_s = os.path.join(dirname, f'.tmp_mc_{base}.s')
+    mcount_o = os.path.join(dirname, f'.tmp_mc_{base}.o')
+
+    # Step 1: collect locals and weaks
+    locals_set = set()
+    weak = {}
+    nm_out = run([nm, inputfile])
+    for line in nm_out.splitlines():
+        m = cfg.local_re.match(line)
+        if m:
+            locals_set.add(m.group(1))
+            continue
+        m = cfg.weak_re.match(line)
+        if m:
+            weak[m.group(2)] = m.group(1)
+
+    offsets = []
+    ref_func = None
+    offset = 0
+    state = {'opened': False, 'fh': None}
+
+    def open_out():
+        if state['opened']:
+            return
+        state['fh'] = open(mcount_s, 'w')
+        state['fh'].write(f'\t.section {MCOUNT_SECTION},"a",'
+                          f'{cfg.section_type}\n')
+        if cfg.alignment is not None:
+            state['fh'].write(f'\t.align {cfg.alignment}\n')
+        state['opened'] = True
+
+    def update_funcs():
+        if not ref_func or not offsets:
+            return
+        if ref_func in weak:
+            sys.exit(f"{inputfile}: ERROR: referencing weak function "
+                     f"{ref_func} for mcount")
+        if ref_func in locals_set:
+            convert_set.add(ref_func)
+        open_out()
+        for cur in offsets:
+            state['fh'].write(f"\t{cfg.type} {ref_func} + "
+                              f"{cur - offset}\n")
+
+    convert_set = set()
+
+    # Step 2: parse objdump output
+    objdump_cmd = ['env', 'LC_ALL=C', objdump, '-hdr', inputfile]
+    if cfg.objdump_flags:
+        objdump_cmd[3:3] = cfg.objdump_flags.split()
+    try:
+        proc = subprocess.Popen(objdump_cmd, stdout=subprocess.PIPE,
+                                stderr=subprocess.DEVNULL, text=True)
+    except FileNotFoundError:
+        sys.exit(f"error running {objdump}")
+
+    read_headers = True
+    read_function = False
+    text_found = False
+    text = ''
+
+    for raw in proc.stdout:
+        line = raw.rstrip('\n')
+
+        if read_headers and MCOUNT_SECTION in line:
+            print(f"ERROR: {MCOUNT_SECTION} already in {inputfile}\n"
+                  f"\tDelete {inputfile} and try again.",
+                  file=sys.stderr)
+            sys.exit(-1)
+
+        m = cfg.section_re.search(line)
+        if m:
+            read_headers = False
+            sec = m.group(1)
+            read_function = sec in TEXT_SECTIONS or any(
+                sec.startswith(p) for p in TEXT_SECTION_PREFIXES)
+            update_funcs()
+            text_found = False
+            ref_func = None
+            offsets.clear()
+            continue
+
+        if read_function:
+            m = cfg.func_re.match(line)
+            if m:
+                text_found = True
+                text = m.group(2)
+                if text not in locals_set and text not in weak:
+                    ref_func = text
+                    read_function = False
+                    offset = int(m.group(1), 16)
+                elif ref_func is None and text not in weak \
+                        and not text.startswith('.L'):
+                    ref_func = text
+                    offset = int(m.group(1), 16)
+
+        if text_found:
+            m = cfg.mcount_re.search(line)
+            if m:
+                offsets.append(int(m.group(1), 16) + cfg.mcount_adjust)
+
+    proc.wait()
+    update_funcs()
+
+    if not state['opened']:
+        sys.exit(0)
+    state['fh'].close()
+
+    # Step 3: assemble
+    subprocess.call([cc] + cfg.cc_flags.split() +
+                    ['-o', mcount_o, '-c', mcount_s])
+
+    if convert_set:
+        glob = ' '.join(f'--globalize-symbol {s}' for s in convert_set)
+        loc = ' '.join(f'--localize-symbol {s}' for s in convert_set)
+        globalobj = os.path.join(dirname, f'.tmp_gl_{filename}')
+        globalmix = os.path.join(dirname, f'.tmp_mx_{filename}')
+        subprocess.call([objcopy] + cfg.objcopy_flags.split() +
+                        glob.split() + [inputfile, globalobj])
+        subprocess.call([ld] + cfg.ld_flags.split() +
+                        ['-r', globalobj, mcount_o, '-o', globalmix])
+        subprocess.call([objcopy] + cfg.objcopy_flags.split() +
+                        loc.split() + [globalmix, inputfile])
+        for p in (globalobj, globalmix):
+            try:
+                os.unlink(p)
+            except OSError:
+                pass
+    else:
+        mix = os.path.join(dirname, f'.tmp_mx_{filename}')
+        subprocess.call([ld] + cfg.ld_flags.split() +
+                        ['-r', inputfile, mcount_o, '-o', mix])
+        os.replace(mix, inputfile)
+
+    for p in (mcount_o, mcount_s):
+        try:
+            os.unlink(p)
+        except OSError:
+            pass
+    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 11:07 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-18 11:06 [RFC PATCH 0/7] scripts: convert seven more Perl utilities " Breno Rodrigues Alves
2026-10-18 11:06 ` [RFC PATCH 1/7] scripts: convert checkdeclares.pl " Breno Rodrigues Alves
2026-10-18 11:06 ` [RFC PATCH 2/7] scripts: convert checkversion.pl " Breno Rodrigues Alves
2026-10-18 11:06 ` [RFC PATCH 3/7] scripts: convert extract_xc3028.pl " Breno Rodrigues Alves
2026-10-18 11:06 ` [RFC PATCH 4/7] scripts: convert generate_initcall_order.pl " Breno Rodrigues Alves
2026-10-18 11:06 ` [RFC PATCH 5/7] scripts: convert get_maintainer.pl " Breno Rodrigues Alves
2026-10-18 11:06 ` [RFC PATCH 6/7] scripts: convert leaking_addresses.pl " Breno Rodrigues Alves
2026-10-18 11:06 ` Breno Rodrigues Alves [this message]
2026-10-18 20:59 ` [RFC PATCH 0/7] scripts: convert seven more Perl utilities " Jonathan Corbet
2026-10-18 23:19   ` Breno Rodrigues Alves
2026-10-19 10:05     ` Heiko Carstens

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=20261018110638.9947-8-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®