[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