Skip to content

Latest commit

 

History

History
172 lines (151 loc) · 7.38 KB

Android.md

File metadata and controls

172 lines (151 loc) · 7.38 KB

Debugging Android apps using lldb and ds2

ds2 can run as a debug server on an Android device or emulator to enable remote debugging of native code in Android applications. While ds2 is capable of connecting to gdb, this guide will focus on using lldb.

Setup

Installing adb

Debugging Android requires the Android Debug Bridge (adb) be installed on your workstation. It comes as part of the Android SDK Platform Tools, which can be downloaded here without installing Android Studio.

Enable Device Debugging

If you are using a physical Android device, debugging must be enabled via the device's developer options.

This step is unnecessary if you are using an Android emulator.

Make the Application Debuggable

The Android application you intend to debug must have andriod:debuggable="true" in its AndroidManifest.xml file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        android:debuggable="true"
        ...

While the property can be set directly, it is typically set via a debug build variant.

Grant the Application Network Permission

The application you intend to debug must have the android.permission.INTERNET permission declared in its AndroidManifest.xml file:

    <uses-permission android:name="android.permission.INTERNET" />

If your application does not have this permission, you cannot debug it using ds2.

This requirement is necessary because:

  1. ds2 must run in the context of an application's sandbox to debug the application
  2. When running in an application's sandbox, ds2 is limited to the set of permissions granted to that application
  3. ds2 connects to the debugger via a TCP connection, so it requires network access

There are no other permissions required for ds2 to debug an Android application.

Running ds2

Deploying ds2 to the Android device

Use adb to deploy the ds2 binary from your workstation to the /data/local/tmp directory on your Android device:

$ adb push /path/to/ds2 /data/local/tmp

NOTE: For the Android emulator, copy the x86_64 version of ds2. For an Android device, copy the arm64-v8a version.

Ensure the binary is executable using chmod +x in the device shell:

$ adb shell chmod +x /data/local/tmp/ds2

Copying ds2 into your application's sandbox

ds2 can be executed directly from its /data/local/tmp location to debug programs launched via adb shell. However, to debug processes in an Android application, ds2 must be run in the context of that application's sandbox using run-as.

Android applications can read from the /data/local/tmp directory, but, per security policy, they cannot execute progams from this location. To work-around this restriction, you must first copy the ds2 binary to the application's private storage using run-as cp:

$ adb shell run-as com.example.TestApp cp /data/local/tmp/ds2 ./

This command copies the ds2 executable to root of the application's working directory (e.g. /data/user/0/com.example.TestApp). Once copied, you can execute ds2 from this location to debug the application.

To confirm ds2 can run in the sandbox, execute it with no arguments using run-as:

$ adb shell run-as com.example.TestApp ./ds2
Usage:
  ./ds2 [v]ersion
  ./ds2 [g]dbserver [options]
  ./ds2 [p]latform [options]

Port forwarding

To connect the debugger from your workstation to an instance of ds2 running on your Android device, use adb's port forwarding to forward a TCP port to use for the connection:

$ adb forward tpc:5432 tcp:5432

The exact port number you choose doesn't really matter as long as it is not already in use. Make note of the port number since you will need it later.

Running ds2

Launch ds2 on your Android device in "platform" mode. Tell it to listen on the same port number that you forwarded with adb.

$ adb shell run-as com.example.TestApp ./ds2 platform --server --listen *:5432

ds2 will now block waiting for an incoming connection from a debugger. If this command fails, make sure the application has network permission (see above) and that there isn't already an instance of ds2 running with the same port number.

Debugging with lldb

Connecting

You are now ready to connect the debugger. Launch lldb from the command line:

$ lldb

From the (lldb) prompt, run platform select remote-android:

(lldb) platform select remote-android
  Platform: remote-android
 Connected: no

Connenct to the running ds2 instance using platform connect connect://localhost:5432, specifying the same port number that ds2 is listening on in the connect URI:

(lldb) platform connect connect://localhost:5432
  Platform: remote-android
    Triple: aarch64-unknown-linux-android
OS Version: 34 (5.10.198-android13-4-00050-g12f3388846c3-ab11920634)
  Hostname: localhost
 Connected: yes
WorkingDir: /data/user/0/com.example.TestApp
    Kernel: #1 SMP PREEMPT Mon Jun 3 20:51:42 UTC 2024

Note the WorkingDir value: it should be the root of your application's data directory.

Attaching to a process

With a platform connection established between lldb and ds2, you can now attach the debugger to a running process using its process ID (pid). To determine the pid for the process you wish to debug, list the processes running in your applications' sandbox with platform process list:

(lldb) platform process list
2 matching processes were found on "remote-android"

PID    PARENT USER       TRIPLE                         NAME
====== ====== ========== ============================== ============================
8298   8296   u0_a284    aarch64-unknown-linux-android  ds2
8883   1139   u0_a284    aarch64-unknown-linux-android  app_process64

Because ds2 is running in your application's sandbox, this command will list only processes that are also running in the sandbox. You should see both the ds2 process and an application process for each of your application's running processes. If you only see ds2, make sure your application is is running and try again.

Once you know the pid for the process you wish to debug, use the attach --pid command to attach the debugger to it:

(lldb) attach --pid 8883
Process 8883 stopped
* thread #1, name = 'example.TestApp', stop reason = signal SIGSTOP
    frame #0: 0x00000072cc1cad28 libc.so`__epoll_pwait + 8
libc.so`__epoll_pwait:
->  0x72cc1cad28 <+8>:  cmn    x0, #0x1, lsl #12 ; =0x1000
    0x72cc1cad2c <+12>: cneg   x0, x0, hi
    0x72cc1cad30 <+16>: b.hi   0xc6530        ; __set_errno_internal
    0x72cc1cad34 <+20>: ret
Executable module set to "/home/user/.lldb/module_cache/remote-android/.cache/00418409-0550-60A6-0094-DA0030D00989/app_process64".
Architecture set to: aarch64-unknown-linux-android0
(lldb)

This command spawns an additional ds2 instance (running in gdbserver mode) which attaches to the process and sets-up the debug session with lldb.

Once attached, you can debug the process with standard gdb and lldb commands. An lldb tutorial can be found at llvm.org.