#!/usr/bin/env bash # mainly based on https://www.sh-zam.com/2019/05/debugging-krita-on-android.html set -euo pipefail IFS=$'\n\t' function get_buildoption() { jq -r '.[] | select(.name == "'$1'") | .value' < meson-info/intro-buildoptions.json } function get_cpp_compiler() { jq -r '.[] | select(.name == "'$1'") | .target_sources.[] | select(.language == "cpp") | .compiler.[]' < meson-info/intro-targets.json } # customize default_app_id=uk.co.powdertoy.tpt default_app_exe=powder default_lldb_server=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/17/lib/linux/aarch64/lldb-server default_lldb_client=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/lldb.sh in_build_site=no if which jq >/dev/null && [[ -f meson-info/intro-buildoptions.json ]]; then >&2 echo "[+] pwd is a build site, auto-detecting parameters" in_build_site=yes default_app_id=$(get_buildoption app_id) default_app_exe=$(get_buildoption app_exe) found_lldb=no compiler_path=$(get_cpp_compiler $default_app_exe) for line in $compiler_path; do # iterate over the command array (might be for example [ "ccache", "aarch64-linux-android21-clang++" ]) if (echo $line | grep toolchains && basename $line | grep android | grep clang++) >/dev/null; then arch=$(basename $line | cut -d '-' -f 1) default_lldb_server=$(realpath $(dirname $line)/../lib/clang/*/lib/linux/$arch/lldb-server) default_lldb_client=$(realpath $(dirname $line)/lldb.sh) found_lldb=yes fi done if [[ $found_lldb != yes ]]; then >&2 echo "[-] cannot determine LLDB paths from compiler command array:" for line in $compiler_path; do >&2 echo "[-] - $line" done exit 1 fi >&2 echo "[+] APP_ID: $default_app_id" >&2 echo "[+] APP_EXE: $default_app_exe" >&2 echo "[+] LLDB_SERVER: $default_lldb_server" >&2 echo "[+] LLDB_CLIENT: $default_lldb_client" else >&2 echo "[+] pwd is not a build site, not auto-detecting parameters" fi app_id=${APP_ID:-$default_app_id} app_exe=${APP_EXE:-$default_app_exe} lldb_server=${LLDB_SERVER:-$default_lldb_server} lldb_server_port=${LLDB_SERVER_PORT:-9998} jdb_port=${JDB_PORT:-13456} lldb_client=${LLDB_CLIENT:-$default_lldb_client} meson=${MESON:-meson} adb=${ADB:-adb} jdb=${JDB:-jdb} # don't customize unless necessary app_activity=${APP_ACTIVITY:-PowderActivity} lldb_server_staging=${LLDB_SERVER_STAGING:-/data/local/tmp/lldb-server} lldb_server_remote=${LLDB_SERVER_REMOTE:-lldb-server} pidof_retry_count=${PIDOF_RETRY_COUNT:-20} pidof_retry_delay=${PIDOF_RETRY_DELAY:-0.1} function check_which() { if ! which $1 >/dev/null; then >&2 echo "[-] can't run $1" return 1 fi } function check_env() { if ! [[ -f $lldb_server ]]; then >&2 echo "[-] $lldb_server doesn't exist" return 1 fi check_which $lldb_client check_which $adb check_which $jdb } function check_adb() { $adb shell whoami >/dev/null } function maybe_install_app() { if [[ $in_build_site != yes ]]; then return 0 fi android_keystore=$(get_buildoption android_keystore) android_keyalias=$(get_buildoption android_keyalias) if [[ -z ${ANDROID_KEYSTORE_PASS-} ]]; then >&2 echo "[-] ANDROID_KEYSTORE_PASS not set" >&2 echo >&2 cat << HELP The current directory seems to be a build site, but ANDROID_KEYSTORE_PASS is not set, so android/$app_exe.apk cannot be invoked. If you don't have a keystore yet, create one with: ANDROID_KEYSTORE_PASS=bagelsbagels keytool -genkey \\ -keystore $android_keystore \\ -alias $android_keyalias \\ -storepass:env ANDROID_KEYSTORE_PASS \\ -keypass:env ANDROID_KEYSTORE_PASS \\ -dname CN=bagels Then try again with: ANDROID_KEYSTORE_PASS=bagelsbagels $0 Naturally, replace bagelsbagels with an appropriate password. HELP exit 1 fi >&2 echo "[+] meson compiling android/$app_exe.apk" if ! $meson compile sign-apk; then >&2 echo "[-] failed" return 1 fi >&2 echo "[+] adb installing android/$app_exe.apk" if ! $adb install android/$app_exe.apk; then >&2 echo "[-] failed" return 1 fi } function check_debuggable() { $adb shell run-as $app_id whoami >/dev/null } function find_lldb_server() { $adb shell run-as $app_id pgrep $lldb_server_remote >/dev/null } function kill_lldb_server() { if ! $adb shell run-as $app_id pkill $lldb_server_remote; then >&2 echo "[-] failed" return 1 fi } function adb_forward() { >&2 echo "[+] adb forwarding tcp:$jdb_port jdwp:$pid" if ! ($adb forward tcp:$jdb_port jdwp:$pid | grep $jdb_port >/dev/null); then >&2 echo "[+] failed" return 1 fi } function adb_unforward() { $adb forward --remove tcp:$jdb_port } function undo_current_adb_forward() { >&2 echo "[+] adb un-forwarding orphaned tcp:$jdb_port" adb_unforward } function maybe_undo_previous_adb_forward() { if $adb forward --list | grep tcp:$jdb_port; then >&2 echo "[+] adb un-forwarding orphaned tcp:$jdb_port" adb_unforward fi } function maybe_kill_previous_lldb_server() { if find_lldb_server; then >&2 echo "[+] killing orphaned $lldb_server_remote" kill_lldb_server fi } function kill_current_lldb_server() { >&2 echo "[+] killing $lldb_server_remote" kill_lldb_server } function start_app() { >&2 echo "[+] starting $app_id/.$app_activity" set +e $adb shell am start -D -n "$app_id/.$app_activity" >/dev/null set -e local i local maybe_pid local pidof_result for ((i = 0; i <= $pidof_retry_count; i++)); do set +e maybe_pid=$($adb shell pidof $app_id) pidof_result=$? set -e if [[ $pidof_result == 0 ]]; then pid=$maybe_pid break fi sleep $pidof_retry_delay done if [[ -z ${pid-} ]]; then >&2 echo "[-] failed" return 1 fi echo $pid } function jdb_attach() { >&2 echo "[+] attaching jdb in the background" $jdb -attach localhost:$jdb_port >/dev/null 2>/dev/null & disown $! # at some point jdb exits because it doesn't have an stdin... fine by me } function maybe_deploy_lldb_server() { if ! $adb shell [[ -f $lldb_server_staging ]]; then >&2 echo "[+] $lldb_server_remote not present on host, deploying" if ! ($adb push $lldb_server $lldb_server_staging && $adb shell chmod +x $lldb_server_staging); then >&2 echo "[-] failed" fi fi } function start_lldb_server() { if ! $adb shell run-as $app_id pgrep $lldb_server_remote >/dev/null; then >&2 echo "[+] $lldb_server_remote not running on host, starting" $adb shell run-as $app_id cp $lldb_server_staging /data/data/$app_id/$lldb_server_remote $adb shell run-as $app_id ./$lldb_server_remote platform --server --listen "*:$lldb_server_port" >/dev/null 2>/dev/null & disown $! if ! $adb shell run-as $app_id pgrep $lldb_server_remote >/dev/null; then >&2 echo "[-] failed" return 1 fi fi } function start_lldb() { local pid=$1 >&2 echo "[+] starting $lldb_client" local lldb_init=$(mktemp) cat - << LLDB_INIT > $lldb_init platform select remote-android platform connect connect://localhost:$lldb_server_port attach $pid continue LLDB_INIT local lldb_status set +e $lldb_client --source $lldb_init lldb_status=$? set -e >&2 echo "[+] $lldb_client exited with status $lldb_status" rm $lldb_init } check_env check_adb maybe_install_app check_debuggable maybe_kill_previous_lldb_server maybe_undo_previous_adb_forward if [[ ${1-} == clean ]]; then >&2 echo "[+] done" exit 0 fi maybe_deploy_lldb_server start_lldb_server pid=$(start_app) adb_forward jdb_attach start_lldb $pid kill_current_lldb_server undo_current_adb_forward >&2 echo "[+] done"