[PATCH 09/10] vmcore_tasks: Add main entry point and backtrace parser
Pnina Feder
pnina.feder at mobileye.com
Mon Jun 22 13:54:47 PDT 2026
- vmcore_tasks.c: Main entry point that orchestrates vmcore
analysis. Opens vmcore ELF, parses headers and vmcoreinfo
note, initializes architecture and memory subsystems, collects
all tasks with per-thread register state and backtraces,
extracts kernel dmesg, and writes combined output to
tasks_buffer.txt. Supports debug verbosity levels via -d flag.
- vmcore_tasks_backtrace_parser.py: Post-processing Python tool
that parses tasks_buffer.txt and resolves raw addresses to
function names and source locations using addr2line and readelf.
Handles shared library load address calculation, ELF link base
detection for non-PIE binaries, C++ symbol demangling, and
classifies tasks into user/kernel/running/crashed categories.
Signed-off-by: Pnina Feder <pnina.feder at mobileye.com>
---
vmcore_tasks/vmcore_tasks.c | 182 ++++++++
vmcore_tasks/vmcore_tasks_backtrace_parser.py | 393 ++++++++++++++++++
2 files changed, 575 insertions(+)
create mode 100644 vmcore_tasks/vmcore_tasks.c
create mode 100644 vmcore_tasks/vmcore_tasks_backtrace_parser.py
diff --git a/vmcore_tasks/vmcore_tasks.c b/vmcore_tasks/vmcore_tasks.c
new file mode 100644
index 00000000..68785b4e
--- /dev/null
+++ b/vmcore_tasks/vmcore_tasks.c
@@ -0,0 +1,182 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "elf_info.h"
+#include "vmcore_info.h"
+#include "memory.h"
+#include "maple_tree.h"
+
+#include "vmcore_tasks_defs.h"
+#include "tasks.h"
+
+extern const char *fname;
+struct arch_deps arch;
+int debug_level = 0;
+
+#define OUT_BUF_SIZE (64 * 1024) // Larger buffer for full dmesg
+
+static char dmesg_buf[OUT_BUF_SIZE];
+static size_t dmesg_len = 0;
+static bool dmesg_truncated = false;
+
+void dmesg_handler(char *buf, unsigned int len)
+{
+ if (dmesg_len + len < OUT_BUF_SIZE) {
+ memcpy(dmesg_buf + dmesg_len, buf, len);
+ dmesg_len += len;
+ dmesg_buf[dmesg_len] = '\0';
+ } else if (!dmesg_truncated) {
+ fprintf(stderr, "Warning: dmesg buffer full, output truncated\n");
+ dmesg_truncated = true;
+ }
+}
+
+/* Write dmesg to file */
+void save_dmesg_formatted(FILE *fp, const char *dmesg)
+{
+ if (dmesg && dmesg[0]) {
+ fprintf(fp, "\ndmesg: (size: %zu)\n%s\n", dmesg_len, dmesg);
+ }
+}
+
+void cleanup_minicore(struct system_minicore *sys_mc)
+{
+ if (!sys_mc || !sys_mc->tasks)
+ return;
+
+ for (int i = 0; i < sys_mc->task_count; i++) {
+ struct task_data *task = &sys_mc->tasks[i];
+
+ /* Free VMA metadata */
+ if (task->vmas.vma_meta) {
+ free(task->vmas.vma_meta);
+ task->vmas.vma_meta = NULL;
+ }
+
+ /* Free threads array */
+ if (task->threads) {
+ free(task->threads);
+ task->threads = NULL;
+ }
+ }
+
+ free(sys_mc->tasks);
+ sys_mc->tasks = NULL;
+ sys_mc->task_count = 0;
+}
+
+int main(int argc, char **argv)
+{
+ int fd = -1, ret = 0;
+ struct system_minicore sys_mc;
+ // Initialize vmcoreinfo tables from raw data
+ const char *vmci_data;
+ size_t vmci_size;
+ const char *corefile = NULL;
+ FILE *out_fp = NULL;
+
+ memset(&sys_mc, 0, sizeof(sys_mc));
+
+ /* Parse command line arguments */
+ for (int i = 1; i < argc; i++) {
+ if (strncmp(argv[i], "-d", 2) == 0) {
+ if (argv[i][2] != '\0') {
+ /* -d<level> form, e.g. -d1, -d3 */
+ debug_level = atoi(&argv[i][2]);
+ } else if (i + 1 < argc && argv[i + 1][0] != '-') {
+ /* -d <level> form with space */
+ debug_level = atoi(argv[++i]);
+ } else {
+ /* -d alone — default to TRACE */
+ debug_level = DBG_TRACE;
+ }
+ if (debug_level < DBG_NONE || debug_level > DBG_MEM) {
+ fprintf(stderr, "Invalid debug level %d (valid: %d-%d)\n",
+ debug_level, DBG_NONE, DBG_MEM);
+ ret = -1;
+ goto out_close;
+ }
+ } else if (argv[i][0] != '-') {
+ corefile = argv[i];
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ fprintf(stderr, "usage: %s [-d[0-4]] <kernel core file>\n", argv[0]);
+ ret = -1;
+ goto out_close;
+ }
+ }
+ if (!corefile) {
+ fprintf(stderr, "usage: %s [-d[0-4]] <kernel core file>\n", argv[0]);
+ ret = -1;
+ goto out_close;
+ }
+
+ fname = corefile;
+
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s: %s\n",
+ fname, strerror(errno));
+ ret = -1;
+ goto out_close;
+ }
+
+ pr_info("Reading vmcore ELF and initializing vmcoreinfo\n");
+ ret = read_elf(fd);
+ if (ret != 0) {
+ fprintf(stderr, "Unable to read ELF information"
+ " from vmcore\n");
+ ret = -1;
+ goto out_close;
+ }
+
+ if (elf_get_raw_vmcoreinfo(&vmci_data, &vmci_size) == 0) {
+ vmcoreinfo_init(vmci_data, vmci_size);
+ vmcore_set_fd(fd);
+ } else {
+ fprintf(stderr, "Warning: no VMCOREINFO note found\n");
+ }
+
+ /* Dump vmcoreinfo for debugging */
+ if (is_trace()) vmcoreinfo_dump();
+
+ pr_info("Initializing architecture specific dependencies\n");
+ arch_init();
+ mem_vtop = arch.vtop;
+ memory_config_init();
+
+ /* Open output file for writing */
+ out_fp = fopen("tasks_buffer.txt", "w");
+ if (!out_fp) {
+ fprintf(stderr, "Failed to open output file: %s\n", strerror(errno));
+ ret = -1;
+ goto out_close;
+ }
+ pr_info("os release: %s\n", vmcoreinfo_osrelease());
+
+ pr_info("Collecting tasks data\n");
+ ret = collect_tasks(&sys_mc);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to collect tasks\n");
+ ret = -1;
+ goto out_close;
+ }
+ save_minicore_formatted(out_fp, &sys_mc);
+
+ pr_info("Dumping dmesg log\n");
+ dump_dmesg(fd, dmesg_handler);
+ save_dmesg_formatted(out_fp, dmesg_buf);
+
+ printf("Tasks data saved to tasks_buffer.txt\n");
+
+out_close:
+ if (out_fp)
+ fclose(out_fp);
+ cleanup_minicore(&sys_mc);
+ if (fd >= 0)
+ close(fd);
+ vmcoreinfo_cleanup();
+ return ret;
+}
\ No newline at end of file
diff --git a/vmcore_tasks/vmcore_tasks_backtrace_parser.py b/vmcore_tasks/vmcore_tasks_backtrace_parser.py
new file mode 100644
index 00000000..5abea4ef
--- /dev/null
+++ b/vmcore_tasks/vmcore_tasks_backtrace_parser.py
@@ -0,0 +1,393 @@
+#!/usr/bin/env python
+
+import argparse
+import subprocess
+import os
+import sys
+import logging
+import re
+
+g_output_file = 'tasks_output.txt'
+g_readelf_file_dict = {'name':'readelf','path':'./'}
+g_addr2line_file_dict = {'name':'addr2line','path':'./'}
+g_syms_default = ""
+g_task_list = []
+g_user_task_list = []
+g_kernel_task_list = []
+g_running_task_list = []
+g_crash_task_list = []
+g_libs_address_so_dict = {}
+g_libs_load_address_dict = {}
+
+
+class MyFormatter(logging.Formatter):
+ err_fmt = "ERR: %(msg)s"
+ dbg_fmt = "DBG: %(module)s: %(lineno)d: %(msg)s"
+ info_fmt = "%(msg)s"
+
+ def __init__(self, fmt="%(levelno)s: %(msg)s"):
+ logging.Formatter.__init__(self, fmt)
+
+ def format(self, record):
+ format_orig = self._fmt
+
+ if record.levelno == logging.DEBUG:
+ self._fmt = MyFormatter.dbg_fmt
+
+ elif record.levelno == logging.INFO:
+ self._fmt = MyFormatter.info_fmt
+
+ elif record.levelno == logging.ERROR:
+ self._fmt = MyFormatter.err_fmt
+
+ result = logging.Formatter.format(self, record)
+
+ self._fmt = format_orig
+
+ return result
+
+# create logger
+fmt = MyFormatter()
+logger = logging.getLogger('mcd_bt_parser_logger')
+logger.setLevel(logging.INFO)
+
+# create console handler
+console_handler = logging.StreamHandler()
+console_handler.setFormatter(fmt)
+logger.addHandler(console_handler)
+
+class ArgumentParser:
+ def __init__(self):
+ self.mandatory_arg = ['buffer']
+ self.mandatory_toolchain_tools = [g_readelf_file_dict, g_addr2line_file_dict]
+ self.parser = argparse.ArgumentParser(description='Mini codedump backtrace parser')
+
+ self.parser.add_argument('--buffer', '-b', metavar='<path to tasks buffer>', required=True, help='tasks buffer')
+ self.parser.add_argument('--syms', '-s', '--path', '-p', metavar='<path to libs/apps syms>', required=True, help='libs/apps path. can be separated by :')
+ self.parser.add_argument('--toolchain', '-t', metavar='<path to toolchain>', help='toolchain bin directory, or full path including prefix (e.g. /path/bin/riscv64-mti-linux-gnu-)')
+ self.parser.add_argument('--toolchain-prefix', metavar='<toolchain prefix>', help='toolchain tool prefix (e.g. mips64el-linux-musl-)')
+
+ self.activate()
+
+ # Update tool names if --toolchain-prefix is given
+ if self.args.toolchain_prefix:
+ g_readelf_file_dict['name'] = self.args.toolchain_prefix + 'readelf'
+ g_addr2line_file_dict['name'] = self.args.toolchain_prefix + 'addr2line'
+ elif self.args.toolchain and not os.path.isdir(self.args.toolchain):
+ # Support combined path+prefix, e.g. /path/bin/riscv64-mti-linux-gnu-
+ toolchain_dir = os.path.dirname(self.args.toolchain)
+ toolchain_prefix = os.path.basename(self.args.toolchain)
+ if toolchain_dir and os.path.isdir(toolchain_dir):
+ self.args.toolchain = toolchain_dir
+ g_readelf_file_dict['name'] = toolchain_prefix + 'readelf'
+ g_addr2line_file_dict['name'] = toolchain_prefix + 'addr2line'
+ logger.info("auto-detected toolchain dir: {} prefix: {}".format(toolchain_dir, toolchain_prefix))
+
+ if g_syms_default:
+ self.args.syms="{}:{}".format(self.args.syms,g_syms_default)
+ else:
+ self.args.syms=self.args.syms
+
+ # Normalize syms paths: if a path is a file, use its parent directory
+ normalized_paths = []
+ for spath in self.args.syms.split(':'):
+ if os.path.isfile(spath):
+ parent = os.path.dirname(spath) or '.'
+ logger.info("syms path '{}' is a file, using parent directory '{}'".format(spath, parent))
+ normalized_paths.append(parent)
+ else:
+ normalized_paths.append(spath)
+ self.args.syms = ':'.join(normalized_paths)
+ self.check_mandatory_args()
+ self.check_dependency_tools()
+
+ def activate(self):
+ self.args = self.parser.parse_args()
+
+ def args(self):
+ return self.args
+
+ def check_mandatory_args(self):
+ logger.info("checking mandatory input args")
+ for arg in vars(self.args):
+ logger.debug("parsing arguments: arg={} value={}".format(arg, getattr(self.args, arg)))
+
+ if arg in self.mandatory_arg and not os.path.isfile(getattr(self.args, arg)):
+ logger.error("missing mandatory input arg: --{}. {} is invalid file path".format(arg, getattr(self.args, arg)))
+ sys.exit()
+
+ logger.info("Done!")
+
+ def check_dependency_tools(self):
+ # got toolchain input flag
+ if (self.args.toolchain) != None and os.path.isdir(self.args.toolchain):
+ logger.debug("got toolchain flag: {}".format(self.args.toolchain))
+ # go over the toolchain dir and verify the mandatory tools are there
+ for file_dict in self.mandatory_toolchain_tools:
+ path = "{}/{}".format(self.args.toolchain, file_dict['name'])
+ if (not os.path.isfile(path)):
+ logger.error("toolchain path is missing tool: {}".format(path))
+ sys.exit()
+ else:
+ # update the toolchain tool path
+ path = "{}/{}".format(self.args.toolchain, file_dict['name'])
+ file_dict['path'] = self.args.toolchain
+ logger.debug("updating toolchain tools path to: {}".format(file_dict['path']))
+
+ # didn't get toolchain flag - tools must be in current working directory of parser
+ else:
+ for file_dict in self.mandatory_toolchain_tools:
+ if (not os.path.isfile("{}/{}".format(file_dict['path'], file_dict['name']))):
+ logger.error("toolchain path is missing tool: {}".format(file_dict))
+ sys.exit()
+
+class BacktraceParser:
+ def __init__(self, user_params):
+ self.addr2line = "{}/{}".format(g_addr2line_file_dict['path'], g_addr2line_file_dict['name'])
+ self.readelf_path = "{}/{}".format(g_readelf_file_dict['path'], g_readelf_file_dict['name'])
+ self.syms = user_params.args.syms
+ self.buffer = user_params.args.buffer
+ self.PF_DUMPCORE = 0x00000200 # Dumped core
+ self.PF_SIGNALED = 0x00000400 # Killed by a signal
+
+ self.parse_buffer_txt()
+
+ self.print_tasks_summary()
+
+ def write_to_file(self, content, write_handle):
+ write_handle.write(content)
+
+ def get_task_flags(self, line):
+ return int(line.split("flags: ")[1].rstrip(), 16)
+
+ def is_crashed_task(self, flag):
+ return flag & self.PF_SIGNALED != 0
+
+ def get_text_address_from_so(self, readelf_path, so_file):
+ logger.debug("input params: readelf_path={} so_file={}".format(readelf_path, so_file))
+
+ if not so_file in g_libs_address_so_dict:
+ logger.debug("{} not found in libs offset dictionary, trying to get .text section from {}".format(so_file, so_file))
+ cmd = '{} -S {} | grep -F .text'.format(readelf_path, so_file)
+ value = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().split()[3]
+ logger.debug("value={}".format(value))
+ logger.debug("text address in {} is: {}".format(so_file, str(value)))
+ g_libs_address_so_dict[so_file] = value
+
+ return g_libs_address_so_dict.get(so_file)
+
+ def check_func_name(self, func_name):
+ out_func_name = func_name
+
+ if func_name.startswith('_Z'):
+ cmd = '{} {}'.format('c++filt -n -p', func_name)
+ out_func_name = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().rstrip().decode("utf-8")
+ logger.debug("func name {} mangled to {}".format(func_name, out_func_name))
+
+ return out_func_name
+
+ def print_tasks_summary(self):
+ logger.info("writing tasks summary into {}".format(g_output_file))
+
+ with open(g_output_file, 'a') as writer:
+ # summary of all tasks
+ writer.write("\n{}\nuser tasks:\n{}\n".format(11 * "=", 11 * "="))
+ for task in g_user_task_list:
+ writer.write(task)
+
+ writer.write("\n{}\nkernel tasks:\n{}\n".format(13 * "=", 13 * "="))
+ for task in g_kernel_task_list:
+ writer.write(task)
+
+ writer.write("\n{}\nrunning tasks:\n{}\n".format(14 * "=", 14 * "="))
+ for task in g_running_task_list:
+ writer.write(task)
+
+ writer.write("\n{}\ncrash tasks:\n{}\n".format(12 * "=", 12 * "="))
+ for task in g_crash_task_list:
+ writer.write(task)
+ logger.info("Done!")
+
+ def handle_so_file(self, line, filename, bt_index, addr):
+ func_name = "<unknown function>"
+ file_line = "<.so binary not found>"
+ for path in self.syms.split(':'):
+ full_path = path + "/" + os.path.basename(filename)
+ if os.path.isfile(full_path):
+ logger.debug("found .so file at: {}".format(full_path))
+
+ so_address_offset = int(self.get_text_address_from_so(self.readelf_path, full_path), 16)
+ so_virtual_base = int(g_libs_load_address_dict [filename], 16)
+ text_virtual_addr = so_virtual_base + so_address_offset
+ bt_offset = addr - text_virtual_addr
+
+ cmd = '{} -e {} -f -s -p -i -j .text {}'.format(self.addr2line, full_path, hex(bt_offset))
+ logger.debug("addr: " + str(hex(addr)) + " text_virtual_addr: " + str(hex(text_virtual_addr)) + " so_virtual_base: " + str(hex(so_virtual_base)) + " so_address_offset: " + str(hex(so_address_offset)) + " cmd: " + cmd)
+ bt = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].decode("utf-8")
+
+ # in case of inline function
+ if "(inlined by)" in bt:
+ cmd = '{} -e {} -f -s -p -i -j .text {}'.format(self.addr2line, full_path, hex(bt_offset + 4))
+ bt = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].decode("utf-8").splitlines()[-1].replace(" (inlined by) ", "")
+
+ logger.debug("backtrace cmd: {}\noutput:{}".format(cmd, bt))
+
+ try:
+ func_name = self.check_func_name(bt.split()[0])
+ file_line = bt.split('at ')[1].rstrip()
+ except:
+ logger.error("in getting function name/line number")
+ logger.error("backtrace cmd: {}\n".format(cmd))
+ func_name = "<error in getting function>"
+ file_line = "<error in getting file line>"
+
+ break
+
+ return "{} {} in {} at {} [{}]\n".format(bt_index, str(hex(addr)),
+ func_name, file_line,filename)
+
+ def find_binary(self, filename):
+ """Find binary in syms paths, preferring _unstripped variant."""
+ basename = os.path.basename(filename)
+ for path in self.syms.split(':'):
+ # Prefer _unstripped variant
+ unstripped = path + "/" + basename + "_unstripped"
+ if os.path.isfile(unstripped):
+ return unstripped
+ regular = path + "/" + basename
+ if os.path.isfile(regular):
+ return regular
+ return None
+
+ def get_elf_load_base(self, readelf_path, elf_file):
+ """Get the ELF link base address (first LOAD segment p_vaddr).
+ For non-PIE executables this is the fixed link address (e.g. 0x120000000).
+ For PIE executables / shared libraries this is typically 0.
+ """
+ cmd = '{} -l {} 2>/dev/null | grep LOAD | head -1'.format(readelf_path, elf_file)
+ try:
+ out = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().decode("utf-8").strip()
+ if out:
+ # LOAD segment line: " LOAD 0x000000 0x1000000000 0x1000000000 ..."
+ # p_vaddr is the 3rd field (index 2)
+ parts = out.split()
+ return int(parts[2], 16)
+ except Exception as e:
+ logger.warning("Failed to get ELF load base for {}: {}".format(elf_file, e))
+ return 0
+
+ def handle_bin_file(self, line, filename, bt_index, addr, vm_start=0):
+ func_name = "<unknown function>"
+ file_line = "<binary file not found>"
+ full_path = self.find_binary(filename)
+ if full_path:
+ logger.debug("found binary app at: {}".format(full_path))
+ elf_link_base = self.get_elf_load_base(self.readelf_path, full_path)
+ bt_offset = (addr - vm_start) + elf_link_base
+ cmd = '{} -e {} -f -s -p {}'.format(self.addr2line, full_path, str(hex(bt_offset)))
+ bt = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].decode("utf-8").rstrip()
+ logger.debug("backtrace cmd: {}\noutput:{}".format(cmd, bt))
+
+ try:
+ func_name = self.check_func_name(bt.split()[0])
+ file_line = bt.split('at ')[1].rstrip()
+ except:
+ logger.error("in getting function name/line number")
+ logger.error("backtrace cmd: {}\n".format(cmd))
+ func_name = "<error in getting function>"
+ file_line = "<error in getting file line>"
+
+ return "{} {} in {} at {} [{}]\n".format(bt_index, str(hex(addr)),
+ func_name, file_line, filename)
+
+ def parse_buffer_txt(self):
+ # open buffer txt for read, open output file for write
+ with open(self.buffer, 'r', errors='replace') as buffer_txt:
+ with open(g_output_file, 'w') as writer:
+ logger.info("reading from {} into {}".format(self.buffer, g_output_file))
+
+ # go over each line
+ line = buffer_txt.readline()
+ while line:
+ line_len = len(line)
+ logger.debug("current line is: {}".format(line))
+
+ # start of task
+ if line.startswith('name:'):
+ task_name = line.split('name:')[1].split(',')[0]
+ logger.debug("current task name is: {}".format(task_name))
+
+ # if the header task is user
+ if "is_user_task: Y" in line:
+ logger.debug("task {} is user task".format(task_name))
+ g_user_task_list.append("{}".format(line))
+
+ # if the header task is kernel
+ elif "is_user_task: N" in line:
+ logger.debug("task {} is kernel task".format(task_name))
+ g_kernel_task_list.append("{}".format(line))
+
+ if "state: 0" in line:
+ logger.debug("task {} is in running mode".format(task_name))
+ g_running_task_list.append("{}".format(line))
+
+ if self.is_crashed_task(self.get_task_flags(line)):
+ logger.debug("task {} is crashed".format(task_name))
+ g_crash_task_list.append("{}".format(line))
+
+ # write the header task to file
+ self.write_to_file("\n{}\n{}{}\n".format('=' * line_len, line, '=' * line_len), writer)
+
+ #/proc/map libs load address parsing
+ elif line.startswith("vm_nr"):
+ g_libs_load_address_dict.clear()
+ self.write_to_file(line, writer)
+
+ elif line.strip() and line.split()[0].isdigit():
+ if len(line.split()) > 4:
+ buffer_libname = os.path.basename(line.split()[2].rstrip())
+ if not buffer_libname in g_libs_load_address_dict:
+ value = line.split()[1].split('-')[0][1:]
+ g_libs_load_address_dict[buffer_libname] = value
+ self.write_to_file(line, writer)
+
+ # backtrace parsing
+ elif re.match(r'#.*?addr=',line):
+ bt_index = line.split(' ')[0]
+ addr = int(line.split(', ')[0].split('=')[1].rstrip(), 16)
+ vm_start = int(line.split(', ')[1].split('=')[1].rstrip(), 16)
+ lib_name = os.path.basename(line.split(', ')[3].split('=')[1].rstrip())
+
+ if addr == 0:
+ self.write_to_file(("{}: {}\n".format(line.split(':')[0], str(hex(addr)))), writer)
+
+ else:
+ # in case of SHARED library
+ if '.so' in lib_name:
+ output = self.handle_so_file(line, lib_name, bt_index, addr)
+ else:
+ output = self.handle_bin_file(line, lib_name, bt_index, addr, vm_start)
+
+ self.write_to_file(output, writer)
+
+ # print the dmesg header
+ elif line.startswith("dmesg:"):
+ self.write_to_file("\n{}\n{}{}\n".format('=' * line_len, line, '=' * line_len), writer)
+
+ # line is not backtrace and not header - just print the line
+ else:
+ self.write_to_file(line, writer)
+
+ # read the next line
+ line = buffer_txt.readline()
+
+ # end of tasks buffer
+ self.write_to_file("\n{} END OF TASKS BUFFER {}\n".format(30 * "@", 30 * "@"), writer)
+
+def main():
+ user_params = ArgumentParser()
+ BacktraceParser(user_params)
+
+if __name__ == '__main__':
+ main()
+
--
2.43.0
More information about the kexec
mailing list