「hvf is not enabled on this aarch64 host」の原因は、avdmanager が書いた target=android-0 だった
Apple Silicon の Mac mini で Android 17 のエミュレータを起こしたら、hvf is not enabled on this aarch64 host と mprotect failed: Permission denied を延々と吐いて segfault した。ホストの Hypervisor.framework は生きている。原因はハイパーバイザーでも GPU でもなく、avdmanager が AVD の ini に書いた1行だった。
「hvf is not enabled」で segfault した
勤怠アプリのウィジェットがロック画面でどう出るかを見たくて、macOS 26.5.2 / Apple M2 Pro の Mac mini に Android 17 のシステムイメージを入れた。Linux の作業機から SSH で叩いているのでヘッドレス起動になる。作った AVD を起動すると、こうなった。
$ emulator -avd api37 -port 5556 -no-window -no-audio -no-snapshot -no-boot-anim qemu-system-aarch64-headless: qemu_mprotect__osdep: mprotect failed: Permission denied WARNING | hvf is not enabled on this aarch64 host. Segmentation fault: 11
mprotect failed は同じ行が何度も流れ、最後にプロセスごと落ちる。 警告はホストを名指ししているが、ホストは正常だった。sysctl kern.hv_support は 1 を返し、emulator -accel-check も Hypervisor.Framework OS X Version 26.5 と答える。
割れたのはエミュレータの版ではなくイメージの世代
疑わしいものが2つあった。イメージと、エミュレータ本体だ。イメージを入れるときに叩いた sdkmanager --install が、ログに Installing Android Emulator と出して本体まで 36.2.12 から 37.1.11 に上げていたからで、変数が同時に2つ動いていた。総当たりで潰した。
- API 36 のイメージは、エミュレータ 36.2.12 でも 37.1.11 でも起動した。
- API 37.0 のイメージは、36.2.12 でも 37.1.11 でも hvf is not enabled になった。
- API 37.2 のイメージも同じく、どちらのエミュレータでも hvf is not enabled になった。
軸はエミュレータではなくイメージだった。API 37 系を指しているときだけ HVF が落ちる。しかも自分で有効化することはできない。-accel on -feature HVF を渡すと Feature 'HVF' (4) is overridden to 'enabled' とは出るのに、そのあとに同じ警告が出て、mprotect failed が同じように続く。
途中でエミュレータ本体を 36.2.12 に戻す道を試して、捨てている。新しい本体が持ち込んだ退行だと思ったからだ。上の切り分けのとおり効かなかったうえ、37.2 イメージの package.xml には emulator への min-revision が 36.5.11 と書いてあった。戻した先は、そもそも Google が想定していない組み合わせだった。古い版に戻すのは、対象が依存の下限を宣言していないかを見てからにしたほうがいい。
API level: 3 と target=android-0
-verbose を付けて起動し直すと、警告の手前に見慣れない行があった。
DEBUG | Crash dump message: 'Deciding if GLDirectMem/Vulkan should be enabled. Selected GLES renderer: Angle Indirect Selected Vulkan renderer: Lavapipe API level: 3 ' ... DEBUG | CPU Acceleration: working DEBUG | CPU Acceleration status: Hypervisor.Framework OS X Version 26.5 DEBUG | handleCpuAcceleration: feature check for hvf WARNING | hvf is not enabled on this aarch64 host.
API level: 3。Android 17 のイメージを指しているのに、エミュレータは API レベル 3 の AVD だと思って機能を決めていた。直後には not enabling Vulkan because API level is < 29 も出ている。その数行あとの feature check for hvf も、同じ API レベルを見て落ちていたと読める。
AVD の root ini を見ると、こう書かれていた。
avd.ini.encoding=UTF-8 path=/Users/me/.android/avd/api37.avd path.rel=avd/api37.avd target=android-0
同じ手順で API 36 のイメージから作った AVD は target=android-36 になっている。イメージ側の source.properties を並べると差は1つで、API 36 のイメージが AndroidVersion.ApiLevel=36 と書くのに対し、37.2 のイメージは AndroidVersion.ApiLevel=37.2 と書く。整数ではない。avdmanager 19.0 がこれを整数として読めずに 0 を書いた、と考えるのが素直だ。ApiLevel=37.0 と書く 37.0 のイメージでも、起動の症状は同じだった。
AVD は普通に作られ、こちらが手を止めるような出力も無かった。読めなかった値は 0 に落とされ、その 0 が2層先でハイパーバイザーの警告として表に出た。エラーメッセージが名指しした相手(aarch64 ホスト)と、壊れていた相手(テキストファイル1行)は一致していない。
直すのは ini を書き換えるだけだった。root ini の target を実在する API レベルにし、AVD の config.ini に API を明示する。
sed -i '' 's/^target=.*/target=android-37/' ~/.android/avd/api37.ini echo "image.androidVersion.api=37" >> ~/.android/avd/api37.avd/config.ini
両方を入れて、エミュレータ 37.1.11 で起動し直すと、-verbose は API level: 37 を出し、hvf is not enabled は消えた。sys.boot_completed が 1 になり、ro.build.version.release は 17 を返す。なお、2行のうち片方だけで足りるかは試していない。
ロック画面のウィジェット面は、トグルだけでは出ない
そもそも Android 17 が要ったのは、ロック画面のウィジェットを見たかったからだ。Google の FAQ は、この体験が Android 16 QPR1 の次の版から AOSP に入ること、 「All widgets are compatible with the lock screen widget experience」であること、外れるのは not_keyguard を xml-36 フォルダに置いたときだけであることを書いている。つまり何もしなくても既存のウィジェットが並ぶ。手元に該当する端末が無いので、エミュレータで見るしかなかった。
設定の Lock screen にある「Widgets on lock screen」をオンにすると、settings list secure に glanceable_hub_enabled=1 が入る。ところがロック画面を左にスワイプしても何も出ない。SystemUI の側を見ると、フラグが立っていなかった。
$ adb shell dumpsys activity service com.android.systemui | grep communal
sysui_flag_communal_service_enabled: falseこのフラグは settings put secure で systemui/flags/communal_service_enabled に値を入れても変わらない。設定値そのものは書き込まれるのに、SystemUI を再起動しても false のままだった。効いたのはブロードキャストだった。
$ adb shell am broadcast -a com.android.systemui.action.SET_FLAG \
--es name communal_service_enabled --ez value true
$ adb shell cmd statusbar flag communal_service_enabled
Flag communal_service_enabled is trueこのブロードキャストは SystemUI を再起動させるので、直後に打った dumpsys は空で返る。失敗したように見えるが、少し待てばフラグは立っている。これでロック画面の左スワイプに「+ Add widgets」のカードが出た。この経路が使えるのは、エミュレータのイメージが userdebug で、SystemUI のフラグ一覧に can override: true と出る状態だからで、製品版の端末で同じ手が使えるという話ではない。
置いてみて分かったのは、枠が画面幅いっぱいの横長1段で、高さが約 134dp しかないことだった。FAQ の「approximately 4 cells wide by 3 cells tall」はランチャー上の寸法で、同じ文に「exact dimensions vary by device」とも添えてある。4 × 3 を前提に組んだ週表示のウィジェットをここに置くと、曜日が3行しか見えず、キャラクターの絵と重なった。ランチャーで整っていることは、ロック画面で整っていることの証明にならない。
まとめ
この件で時間を使ったのは、エラーメッセージが指す層と壊れている層が離れていたからだ。ハイパーバイザーの警告を読んで、macOS の設定・コード署名・エミュレータのバイナリを順に疑った。どれも正常で、実際に壊れていたのは生成されたテキストファイルの1行だった。読めない値を 0 に落として先へ進む設計は、失敗を遠くまで運ぶ。
抜けたのは、エラーの文面を追うのをやめて、ツールが生成したファイルを直接読んだときだった。-verbose と cat の2つで済んでいる。