最近, 我需要把一个x86平台上的程序移植到ARM device上去。那是一个带C extension的python的程序。我琢磨了很久终于明白该怎么做了。
第一步,准备toolchain
首先,一个平台是四部分定义组成:
1. CPU architecture。 如arm, mips, powerpc, i386, i686...
2. ABI。如EABI, EABIhf
3. operating system
4. C library, 如glibc,uclibc...
这四个当中有任何一个对不上都跑不起来。比如,给ubuntu编译的程序就没办法在raspbian上跑,因为ELF的interpreter的路径不一样。
如何挑选gcc toolchain:
在传统的GNU的世界里有个build/host/target概念。大概是从autoconf中继承来的。
build:the type of system on which the package is being configured and compiled
host: the type of system on which the package runs.
target: the type of system for which any compiler tools in the package produce code
对于native compiler来说,这个三个都是一样的。对于cross compiler来说, build == host != target.
比如说,假如我要在x86_64的平台上编译aarch64的程序,那么我就需要build arch=x86_64, host arch=x86_64, target arch=aarch64的gcc。
另外,不同的device可能需要不同的cflags。比如pi, 编译的时候需要加上--with-arch=armv6 --with-tune=arm1176jz-s --with-fpu=vfp --with-float=hard。因为它很特别,它是带hard float point的armv6。
刚才只是说arch, gcc完整的target一般是下面这样的形式
1. <arch>-<vendor>-<os>-<libc/abi>
2. <arch>-<os>-<libc/abi>
比如:arm-linux-gnueabihf
arch: arm
os: linux
libc: gnu
abi: eabihf
如果手上拿到的是,arm-none-gnueabihf,那么说明它是为bare-metal做的,一般是用来编译操作系统kernel的。
至于toolchain从哪来,那就选择很多了。可以自己编译,也可以apt-get install,也可以从linaro这样的网站上下载。但是要注意,gcc的版本还是要跟target os里面的默认gcc的版本要match。
第二步:准备sysroot
如果你的程序不需要c/c++标准库以外的东西,那就不需要sysroot。如果你的程序依赖于某个库,但是你打算自己编译那个库并且静态链接,那也不需要sysroot。
但有些东西天生必须是动态库,比如python的C extension,它编译时需要使用python的头文件,并且这些头文件在不同平台上还不一样。所以你不能用自己build machine上的/usr/include下的东西。需要把target os的那些头文件、库文件copy出来放到build machine上。那个目录就叫sysroot。
举个例子,假如我们的target os是raspbian-buster。那么就去raspbian的网站上把raspbian-buster的raw image下载下来(不是安装用的iso)。然后运行
$ fdisk -l 2020-02-13-raspbian-buster.img
Disk 2020-02-13-raspbian-buster.img: 3.54 GiB, 3787456512 bytes, 7397376 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xea7d04d6
Device Boot Start End Sectors Size Id Type
2020-02-13-raspbian-buster.img1 8192 532479 524288 256M c W95 FAT32 (LBA)
2020-02-13-raspbian-buster.img2 532480 7397375 6864896 3.3G 83 Linux
一眼可以看出,第二个分区是rootfs,从第532480个扇区开始。一个扇区是512字节,所以是从532480*512=272629760字节开始。
然后
$ mkdir /mnt/pi
$ mount -r -o loop,offset=272629760 2020-02-13-raspbian-buster.img /mnt/pi
然后/mnt/pi就是我们的sysroot了。
如果target是ubuntu,那么可以下载它的cloud image。比如
https://mirrors.cloud.tencent.com/ubuntu-cloud-images/server/releases/bionic/release-20200317/ubuntu-18.04-server-cloudimg-arm64.img。
但是这个不是raw image,是qcow2格式的。需要先转换成raw image格式。
qemu-img convert -p -O raw ubuntu-18.04-server-cloudimg-arm64.img ubuntu.raw
然后再运行fdisk和mount.
另外,还有很重要的一步,检查sysroot下的符号链接,如果有失效的,需要视情况修复。
比如,pi的image中,/usr/lib/libdl.so指向了/lib/libdl.so。但是这是一个绝对路径,直接就指向了build os的文件,这是万万不行的。
可以用下面这条命令找死链:
find . -type l -exec realpath {} \; |grep 'No such file'
然后我琢磨出一条神奇的命令来修复它:
sudo tar -C /mnt/pi -cf - . | sudo tar --transform 'flags=s;s,^/,/mnt/pi2/,' -xf -
意思是说,把/mnt/pi目录复制到/mnt/pi2,在/mnt/pi2下找所有指向绝对路径的软链接,把根换成/mnt/pi2。
第三步:写toolchain file
如下所示:
SET(CMAKE_SYSTEM_NAME Linux)
#The next line doesn't matter
SET(CMAKE_SYSTEM_VERSION 1)
SET(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
SET(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
SET(CMAKE_FIND_ROOT_PATH /mnt/pi)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
然后跑cmake的时候加上-DCMAKE_TOOLCHAIN_FILE=my-tool-chain.cmake就行了
第四步:创建python package
创建python package的一般方式是:
python setup.py bdist_wheel
但是呢,这个python程序是build os的,不是target os的。于是打出来的包就乱套了。
我的做法是,先这么错下去,然后手动解开那个包,找到dist-info下面的WHEEL文件,把Tag改对,然后用"wheel pack ."命令重新打包。
但是遗憾的是,manylinux的auditwheel工具还是没法用。如果你不在乎manylinux标准,只是想生个package自己用,不往pypi上push,那么这样也足够了。
--
FROM 73.189.34.*