From 6fa37325f1ac084dd06fdcdcacef8959e6da1edc Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Mon, 7 Apr 2025 21:05:31 +0200 Subject: [PATCH 1/3] Make MCE.py executable --- MCE.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 MCE.py diff --git a/MCE.py b/MCE.py old mode 100644 new mode 100755 From 445ad3b79e671b212e09ba77dac251a6e1b349e3 Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Mon, 7 Apr 2025 21:05:31 +0200 Subject: [PATCH 2/3] Fix crash when stdout is not a TTY on Linux Without this change, the following happened when piping/redirecting the output of MCE.py: ``` Error: MC Extractor v1.102.0 crashed, please report the following: Traceback (most recent call last): File "/root/MCExtractor/MCE.py", line 1093, in elif sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + mce_title + '\x07') ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/colorama/ansitowin32.py", line 47, in write self.__convertor.write(text) File "/usr/lib/python3/dist-packages/colorama/ansitowin32.py", line 177, in write self.write_and_convert(text) File "/usr/lib/python3/dist-packages/colorama/ansitowin32.py", line 199, in write_and_convert text = self.convert_osc(text) ^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/colorama/ansitowin32.py", line 272, in convert_osc winterm.set_title(params[1]) ^^^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'set_title' ``` This looks like a colorama bug, see https://github.com/tartley/colorama/issues/209. The workaround is to skip the title change if stdout is not a TTY. --- MCE.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MCE.py b/MCE.py index ba18fb6..4a193b7 100755 --- a/MCE.py +++ b/MCE.py @@ -1090,7 +1090,7 @@ def mass_scan(f_path) : # Set console/shell window title if sys_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(mce_title) -elif sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + mce_title + '\x07') +elif (sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1) and sys.stdout.isatty() : sys.stdout.write('\x1b]2;' + mce_title + '\x07') if not param.skip_intro : mce_hdr(mce_title) From 9eebd369d2016581d691a0b1ff9b697a61deee94 Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Mon, 7 Apr 2025 21:10:07 +0200 Subject: [PATCH 3/3] Fix segfault on exit when update check is enabled Before this patch, the script could often crash with segfaults on Debian 12/Python 3.11. It apparently when processing small files. In that case, there was likely a race condition where Python tried to terminate the update thread while it was already dead. It was easy to reproduce with: ```sh for i in {1..100}; do ./MCE.py -skip -exit /lib/firmware/amd-ucode/microcode_amd_fam16h.bin >& /dev/null || echo failed at iteration $i done ``` GDB showed: ``` Thread 1 "python3" received signal SIGABRT, Aborted. __pthread_kill_implementation (threadid=, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44 44 ./nptl/pthread_kill.c: No such file or directory. [Thread debugging using libthread_db enabled] ``` Using multiprocessing with daemon=True, there are no more crashes. Processes are probably easier to kill properly. The result of the update check is now shared using a queue. --- MCE.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/MCE.py b/MCE.py index 4a193b7..2ecae69 100755 --- a/MCE.py +++ b/MCE.py @@ -39,7 +39,8 @@ import hashlib import inspect import sqlite3 -import threading +import multiprocessing +import queue import traceback import urllib.request import importlib.util @@ -128,17 +129,6 @@ def __init__(self, source) : if self.mass_scan or self.search or self.build_repo or self.build_blob or self.get_last : self.skip_intro = True -# https://stackoverflow.com/a/65447493 by Shail-Shouryya -class Thread_With_Result(threading.Thread) : - def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None) : - self.result = None - if kwargs is None : kwargs = {} - - def function() : - self.result = target(*args, **kwargs) - - super().__init__(group=group, target=function, name=name, daemon=daemon) - class Intel_MC_Header(ctypes.LittleEndianStructure) : _pack_ = 1 _fields_ = [ @@ -688,7 +678,14 @@ def mc_print(self) : def mce_exit(code) : try : # Before exiting, print output of MCE & DB update check Thread, if completed/dead - if not thread_update.is_alive() and thread_update.result : print(thread_update.result) + if not update_process.is_alive(): + try: + update_process_result = update_result_queue.get(block=False) + if update_process_result is not None: + print(update_process_result) + # This should not happen if the update process ended + except queue.Empty: + pass # Remove temporary microcode files for temp_mc_path in temp_mc_paths: @@ -803,8 +800,8 @@ def date_check(year, month, day) : if month == 2 and day > 28 : return False return True - -def mce_upd_check(db_path) : + +def mce_upd_check(db_path, update_result_queue) : result = None try : @@ -842,9 +839,9 @@ def mce_upd_check(db_path) : elif not db_is_upd : result = col_m + '\nWarning: Outdated Database %s!' % db_print + git_link + col_e except : result = None - - return result - + + update_result_queue.put(result) + def mce_is_latest(ver_before, ver_after) : # ver_before/ver_after = [X.X.X] @@ -1028,9 +1025,11 @@ def mass_scan(f_path) : # Set temp location temp_path = os.path.join(mce_dir, 'Temporary') -# Initialize & Start background Thread for MCE & DB update check -thread_update = Thread_With_Result(target=mce_upd_check, args=(db_path,), daemon=True) -if not param.upd_dis : thread_update.start() # Start as soon as possible (mce_dir, db_path) +# Initialize & Start background process + queue for MCE & DB update check +update_result_queue = multiprocessing.Queue() +# Use daemon=True to make sure the update check process is terminated on exit +update_process = multiprocessing.Process(target=mce_upd_check, args=(db_path, update_result_queue), daemon=True) +if not param.upd_dis : update_process.start() # Start as soon as possible (mce_dir, db_path) # Set MCB location mcb_path = os.path.join(mce_dir, 'MCB.bin')