前言
PyInstaller是一個強大的工具,它可以分析我們的python腳本,發現腳本執行所依賴的模塊,並將他們打包到一個文件夾,或者封裝成一個可執行文件(exe或者binary)。然後,我們就可以將這個文件(文件夾)放到其他機器上去執行,目標機器甚至可以不用安裝python環境!這跟docker有異曲同工之妙。
問題描述
在Centos7上,用PyInstaller將py文件打成一個binary,把該可執行文件放到其他的Centos7上去執行,結果報如下的錯誤:
error while loading shared libraries: libz.so.1: failed to map segment from shared object: Operation not permitted
問題定位
一般Operation not permitted都是權限不足導致的,但是看了一下文件,是有執行(x)的權限的。
把這個文件放到其他的機器上去執行,發現有的機器可以跑,有的機器不能跑!所以肯定是機器配置上的差異。
回到PyInstaller官網,看到如下介紹:
The bootloader is the heart of the one-file bundle also. When started it creates a temporary folder in the appropriate temp-folder location for this OS. The folder is named _MEIxxxxxx, where xxxxxx is a random number.
In GNU/Linux and related systems, it is possible to mount the /tmp folder with a “no-execution” option. That option is not compatible with a PyInstaller one-file bundle. It needs to execute code out of /tmp.
可見,可執行文件在執行的時候,會去/tmp目錄下創建一個臨時文件夾_MEIxxxxxx,裏面包含了運行時需要的類庫。當程序執行完畢,臨時文件夾會自動被刪除。
所以,我們必須保證對/tmp目錄需要有可執行的權限。於是用mount命令查看了一下有問題的機器:
[root@localhost ~]# mount | grep noexec
/dev/sda1 on /boot type ext4 (rw,noexec,nosuid,nodev)
/dev/sda5 on /tmp type ext4 (rw,noexec,nosuid,nodev)
果不其然,/tmp目錄被打上了noexec的flag。這個flag的意思是,在此目錄下的所有文件都不能被執行。
一般/tmp目錄的權限是很大的,都是777,所以通常是很多黑客或者是惡意程序的攻擊對象。如果將/tmp目錄以noexec的形式來mount,可以很好的保護計算機。
問題解決
究其根源,是/tmp目錄缺少可執行權限,導致程序運行所需的lib庫沒法生成。這裏有兩個解決方案
- 用如下命令,將/tmp目錄的noexec改成exec:
mount -o remount,exec /tmp
- 如果/tmp目錄確實需要用noexec加以保護,那可以在PyInstaller打包的時候,加上–runtime-tmpdir參數,用以顯示指定臨時lib庫存放的目錄。這樣的話,程序就不會用/tmp目錄而改用顯示指定的目錄。
總結
當運行程序報Operation not permitted錯誤時,不僅需要考慮程序本身是否有可執行權限,還要查看看文件系統是否以noexec形式mount了。該問題不僅出現在PyInstaller中,在docker中也很常見。