|
| 1 | +# Debugging Android apps using lldb and ds2 |
| 2 | + |
| 3 | +ds2 can run as a debug server on an Android device or emulator to enable remote debugging of native |
| 4 | +code in Android applications. While ds2 is capable of connecting to gdb, this guide will focus on |
| 5 | +using [lldb](https://lldb.llvm.org/). |
| 6 | + |
| 7 | +## Setup |
| 8 | + |
| 9 | +### Installing adb |
| 10 | +Debugging Android requires the [Android Debug Bridge (adb)](https://developer.android.com/tools/adb) |
| 11 | +be installed on your workstation. It comes as part of the Android SDK Platform Tools, which can be |
| 12 | +downloaded [here](https://developer.android.com/tools/releases/platform-tools#downloads) without |
| 13 | +installing Android Studio. |
| 14 | + |
| 15 | +### Enable Device Debugging |
| 16 | +If you are using a physical Android device, debugging must be enabled via the device's |
| 17 | +[developer options](https://developer.android.com/studio/debug/dev-options). |
| 18 | + |
| 19 | +This step is unnecessary if you are using an Android emulator. |
| 20 | + |
| 21 | +### Make the Application Debuggable |
| 22 | +The Android application you intend to debug must have `andriod:debuggable="true"` in its |
| 23 | +`AndroidManifest.xml` file: |
| 24 | +```xml |
| 25 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| 26 | + ... |
| 27 | + <application |
| 28 | + android:debuggable="true" |
| 29 | + ... |
| 30 | + |
| 31 | +``` |
| 32 | +While the property can be set directly, it is typically set via a debug |
| 33 | +[build variant](https://developer.android.com/build/build-variants). |
| 34 | + |
| 35 | +### Grant the Application Network Permission |
| 36 | +The application you intend to debug must have the `android.permission.INTERNET` permission declared |
| 37 | +in its `AndroidManifest.xml` file: |
| 38 | +```xml |
| 39 | + <uses-permission android:name="android.permission.INTERNET" /> |
| 40 | +``` |
| 41 | +If your application does not have this permission, you cannot debug it using ds2. |
| 42 | + |
| 43 | +This requirement is necessary because: |
| 44 | +1. ds2 must run in the context of an application's sandbox to debug the application |
| 45 | +2. When running in an application's sandbox, ds2 is limited to the set of permissions granted to |
| 46 | +that application |
| 47 | +3. ds2 connects to the debugger via a TCP connection, so it requires network access |
| 48 | + |
| 49 | +There are no other permissions required for ds2 to debug an Android application. |
| 50 | + |
| 51 | +## Running ds2 |
| 52 | + |
| 53 | +### Deploying ds2 to the Android device |
| 54 | +Use adb to deploy the ds2 binary from your workstation to the `/data/local/tmp` directory on your |
| 55 | +Android device: |
| 56 | + |
| 57 | +```bash |
| 58 | +$ adb push /path/to/ds2 /data/local/tmp |
| 59 | +``` |
| 60 | +> **_NOTE:_** For the Android emulator, copy the `x86_64` version of ds2. For an Android device, |
| 61 | +copy the `arm64-v8a` version. |
| 62 | + |
| 63 | +Ensure the binary is executable using `chmod +x` in the device shell: |
| 64 | +```bash |
| 65 | +$ adb shell chmod +x /data/local/tmp/ds2 |
| 66 | +``` |
| 67 | +### Copying ds2 into your application's sandbox |
| 68 | +ds2 can be executed directly from its `/data/local/tmp` location to debug programs launched via |
| 69 | +`adb shell`. However, to debug processes in an Android application, ds2 must be run in the context |
| 70 | +of that application's sandbox using `run-as`. |
| 71 | + |
| 72 | +Android applications can read from the `/data/local/tmp` directory, but, per security policy, they |
| 73 | +cannot execute progams from this location. To work-around this restriction, you must first copy the |
| 74 | +ds2 binary to the application's private storage using `run-as cp`: |
| 75 | +```bash |
| 76 | +$ adb shell run-as com.example.TestApp cp /data/local/tmp/ds2 ./ |
| 77 | +``` |
| 78 | +This command copies the ds2 executable to root of the application's working directory (e.g. |
| 79 | +`/data/user/0/com.example.TestApp`). Once copied, you can execute ds2 from this location to debug |
| 80 | +the application. |
| 81 | + |
| 82 | +To confirm ds2 can run in the sandbox, execute it with no arguments using `run-as`: |
| 83 | +``` |
| 84 | +$ adb shell run-as com.example.TestApp ./ds2 |
| 85 | +Usage: |
| 86 | + ./ds2 [v]ersion |
| 87 | + ./ds2 [g]dbserver [options] |
| 88 | + ./ds2 [p]latform [options] |
| 89 | +``` |
| 90 | +### Port forwarding |
| 91 | +To connect the debugger from your workstation to an instance of ds2 running on your Android device, |
| 92 | +use adb's port forwarding to forward a TCP port to use for the connection: |
| 93 | +```bash |
| 94 | +$ adb forward tpc:5432 tcp:5432 |
| 95 | +``` |
| 96 | +The exact port number you choose doesn't really matter as long as it is not already in use. Make |
| 97 | +note of the port number since you will need it later. |
| 98 | +### Running ds2 |
| 99 | +Launch ds2 on your Android device in "platform" mode. Tell it to listen on the same port number that |
| 100 | +you forwarded with adb. |
| 101 | +```bash |
| 102 | +$ adb shell run-as com.example.TestApp ./ds2 platform --server --listen *:5432 |
| 103 | +``` |
| 104 | +ds2 will now block waiting for an incoming connection from a debugger. If this command fails, make |
| 105 | +sure the application has network permission (see above) and that there isn't already an instance of |
| 106 | +ds2 running with the same port number. |
| 107 | + |
| 108 | +## Debugging with lldb |
| 109 | + |
| 110 | +### Connecting |
| 111 | +You are now ready to connect the debugger. Launch lldb from the command line: |
| 112 | +```bash |
| 113 | +$ lldb |
| 114 | +``` |
| 115 | +From the `(lldb)` prompt, run `platform select remote-android`: |
| 116 | +```bash |
| 117 | +(lldb) platform select remote-android |
| 118 | + Platform: remote-android |
| 119 | + Connected: no |
| 120 | +``` |
| 121 | +Connenct to the running ds2 instance using `platform connect connect://localhost:5432`, specifying |
| 122 | +the same port number that ds2 is listening on in the connect URI: |
| 123 | +```bash |
| 124 | +(lldb) platform connect connect://localhost:5432 |
| 125 | + Platform: remote-android |
| 126 | + Triple: aarch64-unknown-linux-android |
| 127 | +OS Version: 34 (5.10.198-android13-4-00050-g12f3388846c3-ab11920634) |
| 128 | + Hostname: localhost |
| 129 | + Connected: yes |
| 130 | +WorkingDir: /data/user/0/com.example.TestApp |
| 131 | + Kernel: #1 SMP PREEMPT Mon Jun 3 20:51:42 UTC 2024 |
| 132 | +``` |
| 133 | +Note the `WorkingDir` value: it should be the root of your application's data directory. |
| 134 | +### Attaching to a process |
| 135 | +With a platform connection established between lldb and ds2, you can now attach the debugger to a |
| 136 | +running process using its process ID (pid). To determine the pid for the process you wish to debug, |
| 137 | +list the processes running in your applications' sandbox with `platform process list`: |
| 138 | +```bash |
| 139 | +(lldb) platform process list |
| 140 | +2 matching processes were found on "remote-android" |
| 141 | + |
| 142 | +PID PARENT USER TRIPLE NAME |
| 143 | +====== ====== ========== ============================== ============================ |
| 144 | +8298 8296 u0_a284 aarch64-unknown-linux-android ds2 |
| 145 | +8883 1139 u0_a284 aarch64-unknown-linux-android app_process64 |
| 146 | +``` |
| 147 | +Because ds2 is running in your application's sandbox, this command will list only processes that |
| 148 | +are also running in the sandbox. You should see both the ds2 process and an application process for |
| 149 | +each of your application's running processes. If you only see ds2, make sure your application is |
| 150 | +is running and try again. |
| 151 | +
|
| 152 | +Once you know the pid for the process you wish to debug, use the `attach --pid` command to attach |
| 153 | +the debugger to it: |
| 154 | +``` |
| 155 | +(lldb) attach --pid 8883 |
| 156 | +Process 8883 stopped |
| 157 | +* thread #1, name = 'example.TestApp', stop reason = signal SIGSTOP |
| 158 | + frame #0: 0x00000072cc1cad28 libc.so`__epoll_pwait + 8 |
| 159 | +libc.so`__epoll_pwait: |
| 160 | +-> 0x72cc1cad28 <+8>: cmn x0, #0x1, lsl #12 ; =0x1000 |
| 161 | + 0x72cc1cad2c <+12>: cneg x0, x0, hi |
| 162 | + 0x72cc1cad30 <+16>: b.hi 0xc6530 ; __set_errno_internal |
| 163 | + 0x72cc1cad34 <+20>: ret |
| 164 | +Executable module set to "/home/user/.lldb/module_cache/remote-android/.cache/00418409-0550-60A6-0094-DA0030D00989/app_process64". |
| 165 | +Architecture set to: aarch64-unknown-linux-android0 |
| 166 | +(lldb) |
| 167 | +``` |
| 168 | +This command spawns an additional ds2 instance (running in gdbserver mode) which attaches to the |
| 169 | +process and sets-up the debug session with lldb. |
| 170 | + |
| 171 | +Once attached, you can debug the process with standard gdb and lldb commands. An lldb tutorial can |
| 172 | +be found at [llvm.org](https://lldb.llvm.org/use/tutorial.html). |
0 commit comments