|
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). |
| 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