はじめに
x86_64のUbuntu 24.04環境で aarch64 向けにChromiumをクロスビルドし、さらにUPX (Ultimate Packer for Executables) で圧縮した Chromium の実行ファイルをaarch64のUbuntu 24.04環境で正常動作することを確認した。
まとめ
aarch64向けにクロスビルドした Chromium のバイナリサイズ (chromeファイルのサイズ) を次の表に示す
処理 | Chromiumのバイナリサイズ (MiB) |
---|---|
リリース向け設定でクロスビルド | 357 |
strip でシンボル削除 | 234 |
upx で圧縮 | 93 |
UPX (Ultimate Packer for Executables)
UPXとは、さまざまな形式の可実行ファイルを圧縮しファイルサイズを縮小するためのオープンソースのパッカーだ。バイナリファイルを圧縮することができ、圧縮後もそのまま実行可能である。実行ファイルだけでなく動的リンクされる共有ライブラリの圧縮にも対応している。
次のような特徴がある
- 高い圧縮率
- ファイルサイズを大幅に小さくできる
- 高速な解凍
- 圧縮された実行ファイルは実行時に自動でメモリ上に展開される。PC上であれば起動時の解凍にかかる時間は無視できるほど高速だと期待できる
- 組み込み機器の場合にはファイルをメモリにコピーする速度と解凍にかかる時間とのトレードオフ
- クロスプラットフォーム対応
- Windows、Linux、macOSなど、多くのオペレーティングシステム向けのバイナリをサポートする
-
Licenseは例外付きのGPL
- 自己解凍機能のため upx の一部が圧縮したソフトウェアに埋め込まれる
- ただし
SPECIAL EXCEPTION FOR COMPRESSED EXECUTABLES
という例外を認めているため、改造していない upx を使う限り、upx で圧縮したソフトウェアを配布しても GPL の影響を受けない
UPX のセットアップ
UPX をUbuntu24.04 環境にインストールする
sudo apt update
sudo apt-get install upx
インストールした upx のバージョンを確認
$ upx --version
upx 4.2.2
UCL data compression library 1.03
zlib data compression library 1.3.0.1-motley
LZMA SDK version 4.43
doctest C++ testing framework version 2.4.11
Copyright (C) 1996-2024 Markus Franz Xaver Johannes Oberhumer
Copyright (C) 1996-2024 Laszlo Molnar
Copyright (C) 2000-2024 John F. Reiser
Copyright (C) 2002-2024 Jens Medoch
Copyright (C) 1995-2023 Jean-loup Gailly and Mark Adler
Copyright (C) 1999-2006 Igor Pavlov
Copyright (C) 2016-2023 Viktor Kirilov
UPX comes with ABSOLUTELY NO WARRANTY; for details type 'upx -L'.
UPX の使用方法
UPX の使用方法と動作を確認する
ls コマンドを圧縮する
ls コマンドを適当なディレクトにコピーする
$ cp `which ls` .
コピーしたファイルサイズが 140KiB だと確認できる
$ du -h ./ls
140K ./ls
upx コマンドで ls を圧縮すると 64KiB (下のサイズの44.15%) まで圧縮される
$ upx ./ls
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
142312 -> 62824 44.15% linux/amd64 ls
Packed 1 file.
$ du -sh ./ls
64K ./ls
圧縮した ls コマンドの正常動作を確認できる
$ ./ls -l
total 64
-rwxr-xr-x 1 raiko raiko 62824 9月 30 12:07 ls
動的リンクするソフトウェアを upx で圧縮
upx は実行ファイルと同様に動的リンクされる共有ライブラリも圧縮可能だ。共有ライブラリを圧縮した場合にも正常動作することをサンプルプログラムを使って確認する
サンプルプログラム
- main.c : 動的リンクされた共有ライブラリのadd関数を呼び出す
- main_with_dlopen.c : 共有ライブラリを明示的に読み込んでadd関数を呼び出す
- add.c : add関数を提供する共有ライブラリになる
// main.c
#include <stdio.h>
extern int add(int a, int b);
int main() {
int a = 5;
int b = 3;
int result = add(a, b);
printf("main.c: Result of %d + %d = %d\n", a, b, result);
return 0;
}
// main_with_dlopen.c
#include <stdio.h>
#include <dlfcn.h>
int main() {
// 共有ライブラリをロード
#ifdef __aarch64__
void *handle = dlopen("./libadd_aarch64.so", RTLD_LAZY);
#else
void *handle = dlopen("./libadd.so", RTLD_LAZY);
#endif
if (!handle) {
fprintf(stderr, "Failed to load library: %s\n", dlerror());
return 1;
}
// 関数ポインタを取得
int (*add)(int, int) = (int (*)(int, int)) dlsym(handle, "add");
const char *error = dlerror();
if (error != NULL) {
fprintf(stderr, "Failed to load function: %s\n", error);
dlclose(handle);
return 1;
}
// 関数を呼び出し
int a = 5;
int b = 3;
int result = add(a, b);
printf("main_with_dlopen.c: Result of %d + %d = %d\n", a, b, result);
// ライブラリを閉じる
dlclose(handle);
return 0;
}
// add.c
int add(int a, int b) {
return a + b;
}
サンプルプログラムの動作確認
実行ファイル main, main_with_dlopen と、共有ライブラリ libadd.so を作成して実行結果を確認する
# ソースコードの取得
git clone https://github.com/aRaikoFunakami/upx_sample.git
cd upx_sample
# コンパイル
gcc -fPIC -shared -o libadd.so add.c
gcc -o main main.c -L. -ladd
gcc -o main_with_dlopen main_with_dlopen.c -ldl
# 実行
LD_LIBRARY_PATH=. ./main
LD_LIBRARY_PATH=. ./main_with_dlopen
期待出力
main と main_with_dlopen がそれぞれ正しく動作している
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
upx で圧縮
圧縮前のそれぞれのファイルサイズを確認しておく
$ ls -lh main main_with_dlopen libadd.so
-rwxrwxr-x 1 raiko raiko 15K 9月 30 12:50 libadd.so
-rwxrwxr-x 1 raiko raiko 16K 9月 30 12:50 main
-rwxrwxr-x 1 raiko raiko 16K 9月 30 12:50 main_with_dlopen
upx コマンドで実行ファイルと共有ライブラリを圧縮する。通常 strip したファイルを upx で圧縮するであろうから、ここでも strip した後に upx で圧縮する。
$ strip main main_with_dlopen libadd.so
$ upx main main_with_dlopen libadd.so
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
14464 -> 5400 37.33% linux/amd64 main
14464 -> 5872 40.60% linux/amd64 main_with_dlopen
14008 -> 8236 58.79% linux/amd64 libadd.so
-------------------- ------ ----------- -----------
42936 -> 19508 45.44% [ 3 files ]
Packed 3 files.
strip した後に upx で圧縮した場合、main, main_with_dlopen, libadd.so それぞれのバイナリサイズが大きく削減できた
$ ls -lh main main_with_dlopen libadd.so
-rwxrwxr-x 1 raiko raiko 8.1K 9月 30 12:43 libadd.so
-rwxrwxr-x 1 raiko raiko 5.3K 9月 30 12:43 main
-rwxrwxr-x 1 raiko raiko 5.8K 9月 30 12:43 main_with_dlopen
upx で圧縮したファイルが正常動作することを確認した
LD_LIBRARY_PATH=. ./main
LD_LIBRARY_PATH=. ./main_with_dlopen
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
クロスコンパイルへの対応状況
さきほどのサンプルプログラムを aarch64 向けにクロスコンパイルし、そのバイナリを upx で圧縮して実行を確認する
upx の対応状況
現時点で aarm64 (arm64) を含めて一般的に利用されるプラットフォーム向けの形式に対応している。現時点では残念ながらRISC-Vには未対応で開発中のようだ。
mipsel.r3000-ps1
のように初代PlayStationがサポートされていることも確認できる
$ upx --help
...
This version supports:
amd64-darwin.dylib dylib/amd64
amd64-darwin.macho macho/amd64
amd64-linux.elf linux/amd64
amd64-linux.kernel.vmlinux vmlinux/amd64
amd64-win64.pe win64/pe
arm-darwin.macho macho/arm
arm-linux.elf linux/arm
arm-linux.kernel.vmlinux vmlinux/arm
arm-linux.kernel.vmlinuz vmlinuz/arm
arm-wince.pe wince/arm
arm64-darwin.macho macho/arm64
arm64-linux.elf linux/arm64
armeb-linux.elf linux/armeb
armeb-linux.kernel.vmlinux vmlinux/armeb
fat-darwin.macho macho/fat
i086-dos16.com dos/com
i086-dos16.exe dos/exe
i086-dos16.sys dos/sys
i386-bsd.elf.execve bsd.exec/i386
i386-darwin.macho macho/i386
i386-dos32.djgpp2.coff djgpp2/coff
i386-dos32.tmt.adam tmt/adam
i386-dos32.watcom.le watcom/le
i386-freebsd.elf freebsd/i386
i386-linux.elf linux/i386
i386-linux.elf.execve linux.exec/i386
i386-linux.elf.shell linux.sh/i386
i386-linux.kernel.bvmlinuz bvmlinuz/i386
i386-linux.kernel.vmlinux vmlinux/i386
i386-linux.kernel.vmlinuz vmlinuz/i386
i386-netbsd.elf netbsd/i386
i386-openbsd.elf openbsd/i386
i386-win32.pe win32/pe
m68k-atari.tos atari/tos
mips-linux.elf linux/mips
mipsel-linux.elf linux/mipsel
mipsel.r3000-ps1 ps1/exe
powerpc-darwin.macho macho/ppc32
powerpc-linux.elf linux/ppc32
powerpc-linux.kernel.vmlinux vmlinux/ppc32
powerpc64-linux.elf linux/ppc64
powerpc64le-linux.elf linux/ppc64le
powerpc64le-linux.kernel.vmlinux vmlinux/ppc64le
クロスコンパイラのインストール
aarch64 環境向けのビルドツールと実行確認用に QEMU を用意する
sudo apt install -y g++-aarch64-linux-gnu
sudo apt install -y qemu-system-aarch64
クロスコンパイルしたプログラムの実行結果
クロスコンパイルしたファイルのファイルが aarch64 向けの実行ファイルもしくは共有ライブラリだと確認でき、またそれらが qemu 上で正常動作したことが確認できる。
aarch64-linux-gnu-gcc -fPIC -shared -o libadd_aarch64.so add.c
aarch64-linux-gnu-gcc -o main_aarch64 main.c -L. -ladd_aarch64
aarch64-linux-gnu-gcc -o main_with_dlopen_aarch64 main_with_dlopen.c -ldl
file main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_aarch64
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_with_dlopen_aarch64
main_aarch64: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=37c953d80fad00f01946b0e05ea320e7358daf19, for GNU/Linux 3.7.0, not stripped
main_with_dlopen_aarch64: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=441a7b9233ce75ecd06270698fa06ad39db30abc, for GNU/Linux 3.7.0, not stripped
libadd_aarch64.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=da4bcc02b42294218d7f60c0a01f2d5956bf5349, not stripped
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
upx で圧縮した実行結果を確認
upx で圧縮前のファイルサイズを確認しておく
ls -lh main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 68K 9月 30 13:01 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 69K 9月 30 13:01 main_aarch64
-rwxrwxr-x 1 raiko raiko 69K 9月 30 13:01 main_with_dlopen_aarch64
クロスコンパイルして作成した実行ファイルと共有ライブラリを strip した後に upx で圧縮する。 main_aarch64 と main_with_dlopen_aarch64 は圧縮できたが、 libadd_aarch64.so の圧縮には失敗した。このエラーには複数の要因があるようだが、今回は圧縮率が悪いため圧縮する必要なしと判定されていた。
$ aarch64-linux-gnu-strip main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
$ upx main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
67488 -> 6700 9.93% linux/arm64 main_aarch64
67488 -> 7232 10.72% linux/arm64 main_with_dlopen_aarch64
note: use --android-shlib if appropriate
upx: libadd_aarch64.so: NotCompressibleException
-------------------- ------ ----------- -----------
134976 -> 13932 10.32% [ 2 files ]
Packed 3 files: 2 ok, 1 error.
$ ls -lh main_aarch64 main_with_dlopen_aarch64 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 66K 9月 30 13:07 libadd_aarch64.so
-rwxrwxr-x 1 raiko raiko 6.6K 9月 30 13:07 main_aarch64
-rwxrwxr-x 1 raiko raiko 7.1K 9月 30 13:07 main_with_dlopen_aarch64
圧縮した実行ファイルをQEMU上で実行し正常動作することを確認した。
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_aarch64
LD_LIBRARY_PATH=. qemu-aarch64 -L /usr/aarch64-linux-gnu ./main_with_dlopen_aarch64
main.c: Result of 5 + 3 = 8
main_with_dlopen.c: Result of 5 + 3 = 8
upx で libadd_aarch64.so の圧縮に失敗した原因
圧縮率が 6.25% 以下であったため、最後の return false; でエラー判定となっていた
bool Packer::checkDefaultCompressionRatio(unsigned u_len, unsigned c_len) const {
assert((int) u_len > 0);
assert((int) c_len > 0);
if (c_len >= u_len)
return false;
unsigned gain = u_len - c_len;
if (gain < 512) // need at least 512 bytes gain
return false;
#if 1
if (gain >= 4096) // ok if we have at least 4096 bytes gain
return true;
#endif
if (gain >= u_len / 16) // ok if we have at least 6.25% gain
return true;
return false;
}
クロスビルドしたChromiumを圧縮
aarch64向け Chromium のビルド
x86_64版と同等の作業でChromiumのソースコードを取得する
# $HOMEディレクトリで作業した場合
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="${HOME}/depot_tools:$PATH"
mkdir ~/chromium && cd ~/chromium
fetch --nohooks --nohistory chromium
# chromium/src 以下で
cd src
# ビルドのセットアップ
./build/install-build-deps.sh
./build/linux/sysroot_scripts/install-sysroot.py --arch=arm64
gclient runhooks
# gn gen out/arm64 --args='target_os="linux" target_cpu="arm64"'
# リリース版のビルドセットアップ
gn gen out/aarch64 --args='target_os="linux" target_cpu="arm64" is_debug=false is_component_build=false symbol_level=0 enable_nacl=false blink_symbol_level=0 v8_symbol_level=0 is_official_build = true chrome_pgo_phase = 0'
autoninja -C out/aarch64 chrome
# クロスビルド開始
autoninja -C out/arm64 chrome
クロスビルドした chromium のバイナリを確認
クロスビルドした chrome ファイルのバイナリサイズを確認すると 375MiB である。
$ls -lSh out/aarch64/chrome out/aarch64/*.so
-rwxrwxr-x 1 raiko raiko 357M 9月 30 09:18 out/aarch64/chrome
-rwxrwxr-x 1 raiko raiko 24M 9月 29 17:11 out/aarch64/libvk_swiftshader.so
-rwxrwxr-x 1 raiko raiko 23M 9月 29 17:20 out/aarch64/libVkLayer_khronos_validation.so
-rwxrwxr-x 1 raiko raiko 8.9M 9月 29 17:13 out/aarch64/libGLESv2.so
-rwxrwxr-x 1 raiko raiko 595K 9月 29 17:12 out/aarch64/libVkICD_mock_icd.so
-rwxrwxr-x 1 raiko raiko 398K 9月 29 17:14 out/aarch64/libEGL.so
-rwxrwxr-x 1 raiko raiko 42K 9月 29 19:26 out/aarch64/libqt6_shim.so
-rwxrwxr-x 1 raiko raiko 38K 9月 29 19:26 out/aarch64/libqt5_shim.so
strip後は chrome ファイルのバイナリサイズは 234MiB になり
$ aarch64-linux-gnu-strip out/aarch64/chrome out/aarch64/*.so
$ ls -lSh out/aarch64/chrome out/aarch64/*.so
-rwxrwxr-x 1 raiko raiko 234M 9月 30 11:16 out/aarch64/chrome
-rwxrwxr-x 1 raiko raiko 17M 9月 30 11:16 out/aarch64/libvk_swiftshader.so
-rwxrwxr-x 1 raiko raiko 16M 9月 30 11:16 out/aarch64/libVkLayer_khronos_validation.so
-rwxrwxr-x 1 raiko raiko 6.2M 9月 30 11:16 out/aarch64/libGLESv2.so
-rwxrwxr-x 1 raiko raiko 356K 9月 30 11:16 out/aarch64/libVkICD_mock_icd.so
-rwxrwxr-x 1 raiko raiko 255K 9月 30 11:16 out/aarch64/libEGL.so
-rwxrwxr-x 1 raiko raiko 29K 9月 30 11:16 out/aarch64/libqt6_shim.so
-rwxrwxr-x 1 raiko raiko 27K 9月 30 11:16 out/aarch64/libqt5_shim.so
upxで圧縮すると 93MiB まで chrome ファイルのバイナリサイズを削減できた。
upx out/aarch64/chrome out/aarch64/*.so
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
244518080 -> 96672028 39.54% linux/arm64 chrome
note: use --android-shlib if appropriate
260400 -> 192292 73.84% linux/arm64 libEGL.so
note: use --android-shlib if appropriate
6489272 -> 3668652 56.53% linux/arm64 libGLESv2.so
note: use --android-shlib if appropriate
364008 -> 230364 63.29% linux/arm64 libVkICD_mock_icd.so
note: use --android-shlib if appropriate
16675304 -> 10577884 63.43% linux/arm64 libVkLayer_khronos_validation.so
note: use --android-shlib if appropriate
26936 -> 24740 91.85% linux/arm64 libqt5_shim.so
note: use --android-shlib if appropriate
29096 -> 26900 92.45% linux/arm64 libqt6_shim.so
note: use --android-shlib if appropriate
16900528 -> 10540892 62.37% linux/arm64 libvk_swiftshader.so
-------------------- ------ ----------- -----------
285263624 -> 121933752 42.74% [ 8 files ]
圧縮後の chrome ファイルと全ての共有ライブラリの合計は116MiBであった
$ ls -l out/aarch64/chrome out/aarch64/*.so | awk '{sum+=$5} END {print sum/1024/1024 " MiB"}'
116.285 MiB
圧縮した Chromium の動作確認
Chromiumはとても重いプログラムなのでQEMUでCPUをエミュレートした場合にはパフォーマンス的に苦しいため、M3 MacBook上で Tart VM で動作させたaarch64版のUbuntu24.04で動作確認を行った。
$ uname -a
Linux ubuntu 6.8.0-45-generic #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:26:41 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux
aarch64版のUbuntu24.04にビルドしたライブラリとリソースをコピーする
REMOTE_HOST=raiko@192.168.1.113 \
scp -r $REMOTE_HOST:~/chromium/src/out/aarch64/chrome \
$REMOTE_HOST:~/chromium/src/out/aarch64/resources \
$REMOTE_HOST:~/chromium/src/out/aarch64/locales \
$REMOTE_HOST:~/chromium/src/out/aarch64/icudtl.dat \
$REMOTE_HOST:~/chromium/src/out/aarch64/chrome_crashpad_handler \
$REMOTE_HOST:~/chromium/src/out/aarch64/v8_context_snapshot.bin \
$REMOTE_HOST:~/chromium/src/out/aarch64/*.pak \
$REMOTE_HOST:~/chromium/src/out/aarch64/*.so .
Chromiumの起動する
./chrome --no-sandbox
YouTubeで動画が再生されることを確認した
蛇足
Ubuntu24.04などAppArmorが利用されている環境で--no-sandbox
をつけずに実行したい場合は下記を参考に設定を行うことで--no-sandbox
を付けずに実行可能となる。