Repository: pyinstaller/pyinstaller

Issue:
Packaged app compressed with UPX crashes at startup in Windows x64
If I use UPX, my app crashes as soon as it starts (there's no console output when I use cmd.exe). Weird thing is, bundled app with --onefile works with no issues (files seem to be compressed, *.exe is smaller when I use UPX if I recall correctly). 
Everything works well in Windows 7 x86 too.

According to Windows 7 error message, "ucrtbase.dll" fails but according to Windows 10 (x64) error message, "vcruntime140.dll" does, so the workaround is easy: replacing each faulty *.dll file with its non-compressed version. It solves the problem but somewhat extensive testing is would be needed.
Have no tried Windows 8 yet.

**OS:** Windows 7 x64
**UPX:** 3.95 (x64 and x86), 3.94 and 3.93
**Python:** 3.7.3 x86 fresh install
**Pyinstaller:** 3.4 and 3.5dev0+b54a15d72e (no cache files)
**Test toy code:**
```
import os

print("hola")
os.system("pause")
```

A "hello world" app works though.

I'm new to GitHub so if there's something else I should do or test just tell me.
And thank you very very much for your hard work on PyInstaller!


Discussion:
I am experiencing the same issue on Windows 10, UPX 3.86. Using console and onefile. 

ucrtbase.dll is not designed to run on Windows...Error status: 0xc000007b



I suspect that this was fixed by #5382?
With Pyinstaller 4.2 I see the nice message `Disabling UPX for c:\program files\python39\VCRUNTIME140_1.dll due to CFG!` and no longer receive the error above! 

Still doesn't load when using UPX due to PyQT5 error of `qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""` but I am guessing that is something entirely different *sigh*
> Still doesn't load when using UPX due to PyQT5 error of qt.qpa.plugin: Could not find the Qt platform plugin "windows" in "" but I am guessing that is something entirely different sigh

That's due to upx/upx#107 (which is specific to Qt plugins and unrelated to CFG).

I suppose if we wanted to work around it, we'd need to exempt Qt5 plugins from UPX-processing. Either via hard-coded list, or by implementing a mechanism that would allow hooks to specify list(s) of files to exempt from UPX (and then have PySide2 and PyQt5 hooks exempt their Qt5 plugins).
Thanks for the link, will watch that one! UPX is not a make or break deal for me, but I am happy to help test my code with any changes around it.

For this ticket probably prudent to see if @MyDataIsReady responds soon, otherwise good to close from my point of view.  
Seems to be working! Just tried with Windows 10 x64, Python 3.8.7 and Pyinstaller 4.2. 

Thanks!
I'm just writing this so that this issue doesn't get buried (again). To properly fix this we need either of [Rok's solutions](https://github.com/pyinstaller/pyinstaller/issues/4178#issuecomment-771012901) (preferably the second IMO) or for the UPX issue to be fixed their end.

Resolving patch:
diff --git a/PyInstaller/building/utils.py b/PyInstaller/building/utils.py
--- a/PyInstaller/building/utils.py
+++ b/PyInstaller/building/utils.py
@@ -275,6 +275,9 @@ def checkCache(fnm, strip=False, upx=False, upx_exclude=None, dist_nm=None,
         # Flow Guard enabled, as it breaks them.
         if is_win and versioninfo.pefile_check_control_flow_guard(fnm):
             logger.info('Disabling UPX for %s due to CFG!', fnm)
+        elif misc.is_file_qt_plugin(fnm):
+            logger.info('Disabling UPX for %s due to it being a Qt plugin!',
+                        fnm)
         else:
             bestopt = "--best"
             # FIXME: Linux builds of UPX do not seem to contain LZMA
diff --git a/PyInstaller/utils/misc.py b/PyInstaller/utils/misc.py
--- a/PyInstaller/utils/misc.py
+++ b/PyInstaller/utils/misc.py
@@ -251,3 +251,44 @@ def module_parent_packages(full_modname):
         prefix += '.' + pkg if prefix else pkg
         parents.append(prefix)
     return parents
+
+
+def is_file_qt_plugin(filename):
+    """
+    Check if the given file is a Qt plugin file.
+    :param filename: Full path to file to check.
+    :return: True if given file is a Qt plugin file, False if not.
+    """
+
+    # Check the file contents; scan for QTMETADATA string
+    # The scan is based on the brute-force Windows codepath of
+    # findPatternUnloaded() from qtbase/src/corelib/plugin/qlibrary.cpp
+    # in Qt5.
+    with open(filename, 'rb') as fp:
+        fp.seek(0, os.SEEK_END)
+        end_pos = fp.tell()
+
+        SEARCH_CHUNK_SIZE = 8192
+        QTMETADATA_MAGIC = b'QTMETADATA '
+
+        magic_offset = -1
+        while end_pos >= len(QTMETADATA_MAGIC):
+            start_pos = max(end_pos - SEARCH_CHUNK_SIZE, 0)
+            chunk_size = end_pos - start_pos
+            # Is the remaining chunk large enough to hold the pattern?
+            if chunk_size < len(QTMETADATA_MAGIC):
+                break
+            # Read and scan the chunk
+            fp.seek(start_pos, os.SEEK_SET)
+            buf = fp.read(chunk_size)
+            pos = buf.rfind(QTMETADATA_MAGIC)
+            if pos != -1:
+                magic_offset = start_pos + pos
+                break
+            # Adjust search location for next chunk; ensure proper
+            # overlap
+            end_pos = start_pos + len(QTMETADATA_MAGIC) - 1
+        if magic_offset == -1:
+            return False
+
+        return True


Test patch: