An overview of the
Debugging is inevitable in Android development, and with the help of the IDE, it can be done with just two clicks on the IDE button. This makes debugging so simple and convenient that developers only need to memorize the debugging techniques of various ides and do not need to understand the debugging principles.
When debugging, developers can break point debugging, observe or modify running parameters, observe virtual machine stack information dump, remote debugging, etc. This article will take you through the principles of debugging on Android.
To learn Adb debugging principles, start with the slightly simpler Java debugging principles. So let’s start with the principles of Java debugging.
Manually debugging Java
In the formal introduction of Java debugging principles, first to manually debug Java program debugging.
Step 1: Write a Java file:
public class TestMain {
public static void main(String[] args) throws InterruptedException {
while (true) {
Thread.sleep(1000);
String hello = hello("" + new Random().nextInt(100)); System.out.println(hello); }}private static String hello(String hello) {
returnhello; }}Copy the code
The second step is to compile the class file from the Java file:
$javac -g src/com/example/www/TestMain.java -d class
Copy the code
Step 3: In debug mode, run the class file and listen for port 8000, and mount JDWP:
$java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 -cp class/ com.example.www.TestMain
Copy the code
Step 4: Use debugging tool JDB to communicate with port 8000 to start debugging:
$jdb -attach localhost:8000
Copy the code
Step 5: Break a dot on line 10 of testMain.java:
> stop at com.example.www.TestMain:10
Copy the code
The effect is as follows:
sxxxx0@wxxxxxeMBP test % JDB -attach localhost:8000 set uncaptured java.lang.Throwable set delayed uncaptured java.lang.Throwable is initializing JDB...> stop at com.example.www.TestMain:10Set a breakpoint com. Example. www.TestMain:10> Breakpoint hit: thread = "main", com. Example. www.TestMain.main (), line = 10 bci = 6 main [1] the run> Breakpoint hit: thread = "main", com. Example. www.TestMain.main (), line = 10 bci = 6 main [1] the clear com. Example. www.TestMain:10 has been deleted: Breakpoint com. Example. www.TestMain:10 main [1] stop in com. Example. www.TestMain.hello set breakpoints com. Example. www.TestMain.hello main [1] run> Breakpoint hit: thread = "main", com. Example. www.TestMain.hello (), line 16 bci = = 0 main [1]Copy the code
At this point, manual debugging is enabled, leaving debugging at a breakpoint in the TestMain#hello method (line 16 of the source code).
In addition to the line breakpoint that can be set with stop at, as above, method breakpoints can also be set with stop in. You can also specify the source path using -source, and the IDE default source path is $PROJECT_ROOT. With debug Config enabled, you can manually set the source path in the IDE to tell JDB where to find the source:
More Java debug commands are availableThe official documentation.
In the above five steps of debugging, from the third step, most readers may be unfamiliar, because in the debug program debugging, press the IDE debug button, the IDE automatically run the class file in the background, and use JDB to help us on the interface buried point into buried point instructions, no developer manual debugging.
Now, when I turn on Debug, I will output the following prompts 🙂
-agentlib: JDWP =transport=dt_socket,server=y,suspend=n –
JDPA
Let’s take a look at how JVM debugging works. Java Platform Debugger Architecture (JDPA) defines the process of debugging the JVM. Learning how to debug the JVM is actually learning JDPA.
As shown in the figure above, the composition of JDPA is divided into three parts:
- Java Virtual Machine Tool Interface (JVMTI) : By implementing JVMTI, you can obtain the running state of the debugger virtual machine or change the running parameters. JVMTI is the foundation of debugging and provides a tool interface for external intervention in the RUNNING of the JVM. JVMTI is provided by the JVM itself
- Java Debug Wire Protocol (JDWP) : The Java Debug Wire Protocol (JDWP) defines The communication Protocol between The Debugger and The Debuggee
- Java Debug Interface (JDI) : The implementation of JDI allows the debugger to send debugging commands to the JVM. JDI is also responsible for receiving JVM state information from JVMTI (for example, JDB is an implementation of JDI).
The essence of debugging is the communication between the Debugger (JDI) and the Debuggee(JVMTI), and JDWP is the protocol used for the communication.
JDPA workflow
The following figure illustrates the JDPA workflow:
- Debugger
Implement JDI directly or indirectly and send or receive data using the communication rules defined by JDWP. For example: JDB, IDE built-in debugging tools.
- JDWP Agent
The specific implementation of JVMTI, development is generally used to establish an Agent to use JVMTI, it uses the JVMTI function, set some callback functions, “accept the data from JDI, and through these data and commands to obtain or operate the debug virtual machine running state, and will return the running state to JDI”.
The JDWP Agent can be selected to load when the Java virtual machine starts. For example, to debug remotely, we need to specify that the JDWP is loaded:
$java -agentlib:jdwp=transport=dt_socket Copy the code
The preceding parameters not only specify that the JDWP Agent needs to be loaded, but also specify that the JDWP Agent uses socket to communicate with the Debugger.
- JDWP
JDWP specifies the communication protocol between JDI and JVMTI. JDWP does not include the implementation of the transport layer, so JDWP data can be transmitted in any transmission mode, as long as the data format meets the requirements of JDWP.
- Target JVM
The debugged VIRTUAL machine.
The JDWP protocol
Like the Http protocol, the JDWP protocol has a handshake and a reply.
Communication to shake hands
The communication process of JDWP protocol starts with a simple handshake, as shown in the figure below:
The Debugger sends the string “jDWP-handshake” to the Target Java VIRTUAL machine and the Target Java virtual machine replies with “jDWP-handshake”. The Handshake succeeds
When the Target JVM starts, you can either select “listen on specified debug port” or “connect directly to an existing debug port.” Now look at the long command above
$java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 -cp class/ com.example.www.TestMain
Copy the code
The server parameter controls startup options. Y means to listen on the specified debug port, and n means to connect to an existing debug port. The suspend parameter is whether to suspend the virtual machine before debugging begins.
So, the above instruction says:
Run testmain. class and listen for debug port 8000. When external (e.g., JDB) sends a “jDWP-handshake” to this port, it means that the other party is requesting to serve as debug end for the current running virtual machine. If you want to know more about this, please refer to the official documentation
Communication packet
Once the handshake is complete, the debugger can send data to and from the Target Java virtual machine. There are two types of packets in JDWP: command packets (CmdPacket) and Reply packets (ReplyPacket).
CmdPacket
First look at the structure of CmdPacket:
typedef struct {
jint len; // packet length
jint id; // packet id
jbyte flags; // value is 0
jbyte cmdSet; // command set
jbyte cmd; // command in specific command set
jbyte *data; // data carried by packet
} jdwpCmdPacket;
Copy the code
- Length: indicates the Length of the entire packet, including the Length part. Since the length of the packet header is fixed at 11bytes, if a command packet has no data part, the value of length is 11.
- Id: is a unique value that identifies and identifies the command to which reply belongs. The Reply packet and the command packet that it replies to have the same Id. Asynchronous messages are identified by matching ids.
- Flags: Currently the value is always 0 for command packet.
- Command Set: is equivalent to a group of commands. Some commands with similar functions are grouped in the same Command Set.
The value of the Command Set is divided into three parts:
0-63: commands sent from the debugger to the Target Java VM 64-127: commands sent from the Target Java VM to the debugger 128-256: reserved custom and extended commands
ReplyPacket
Look again at ReplyPacket:
typedef struct {
jint len; // packet length
jint id; // packet id
jbyte flags; // value 0x80
jshort errorCode; // error code
jbyte *data; // data carried by packet
} jdwpReplyPacket;
Copy the code
Flags: The current value for reply packet is always 0x80. We can judge whether the received packet is command or reply by the value of Flags.
Error Code: Indicates whether the returned command was executed correctly. Zero indicates correct execution, and non-zero indicates incorrect execution.
Data: The content and structure vary depending on the command and reply. For example, a command that requests the value of an object’s member variable will have the id of the object and the id of the member variable in its data. Reply contains the value of the member variable.
Readers who want to learn more about the JDWP protocol are recommended to read: JDWP Protocol and Implementation.
Android Debugging Principles
After analyzing the Java debugging principle, the following analysis of Android debugging principle. Android debugging is a little more complicated than Java debugging.
Above is the structure of ADB:
Host is on the PC side, where Adb Server and Adb Clients are running, and mobile Emulator is running.
Target Device is a mobile phone, whether it is a mobile phone or a mobile emulator, running Adbd (Adb Daemon) and virtual machine (yellow oval).
In Java debugging, JDWP protocol communication is used between JDI and JVMTI to complete debugging work. In the debugging of Android, Adbd and virtual machine is also using JDWP communication, so “Adbd and JDB are similar to the concrete implementation of JDI”.
A introduction
Adb Server will always listen on local port 5037 after starting. Adb Client connects to port 5037 through a local random port. A PC can connect to multiple mobile devices or virtual machines, and a mobile phone can connect to multiple PCS at the same time. Connection management for these devices is done by Adb Server.
Adb Clients can be viewed as a shell window (when using the Adb shell command, a client can be created). When entering adb shell, the client opens a random port to communicate with port 5037, completing the connection to the local server program. If Adb Server is not started, an Adb Server server program is started.
As shown in the figure, adb Server is started when adb Devices command is run, and port 5037 is monitored. After adb shell command is run, port 5037 is connected to port 53094. When the ADB Shell window is closed, port 53094 is closed. This port is the random port generated by adb Client as described above.
Adb Daemon (ADBD) A background service running on an emulator or mobile device. When the Android system starts, adBD is started by the init program. If ADBD dies, then ADBD is restarted by init. In other words, as long as the Android system is running, adBD is “deathless” and is always on the servo state
Communication is introduced
- Adb Clients sends various commands to the Adb Server using data in specific formats
The format of this data is: Length (4 bytes) + commend
For example, 000Chost:version 000C indicates the length of the command. The actual command is host:version
After receiving the request from the Client, the Server returns data in the following format:
If it is successful, the four-byte string “OKAY” is returned. If it is failed, the four-byte string “FAIL” and the error cause are returned. If it is not, the error code is returned
When the Adb Client sends the command and receives the “OKAY” reply from the Adb Server, it can continue to initiate the operation command.
- A message structure is used to communicate data between Adb Server and Adb Daemon. Adb Daemon will always listen on port 5555 of the phone (or emulator) once it is started. When Adb Server starts, it attempts to communicate with port 5555, using either wireless (TCP) or USB to transfer data.
The structure of the data message between Adb Server and Adb Daemon is:
struct message {
unsigned command; A_CNXN,A_SYNC... * /
unsigned arg0; The first argument to the /* directive is */
unsigned arg1; The second argument to the /* directive is */
unsigned data_length; /* Length of the data body */
unsigned data_crc32; /* Data body */
unsigned magic; /* command ^ 0xffffffff */
};
Copy the code
The protocol defines seven command types:
define A_SYNC 0x434e5953
define A_CNXN 0x4e584e43
define A_AUTH 0x48545541
define A_OPEN 0x4e45504f
define A_OKAY 0x59414b4f
define A_CLSE 0x45534c43
define A_WRTE 0x45545257
Copy the code
conclusion
So far, the principle of Android debugging is completed. In the debugging process, it is worth noting that:
- Development can set the breakpoint location, but also set the source path. Therefore, the program running during debugging may not be the actual source code, as long as the breakpoint information is the same, it can be debugged.
- Breakpoint information: information about the line breakpoint (consisting of package name, class name, line number); Method breakpoint information (consisting of package name, class name, method name)
- Method breakpoints are more important than line breakpoints. If you find your program running very slowly or unresponsive during debugging, you can remove all method breakpoints. (Java Method Breakpoints shown below)
(The mistakes and shortcomings in the article also please the majority of readers to comment, discuss together, progress together: -)
Recommended reading
Java Platform, Standard Edition Tools Reference Java: Slow Performance or Hangups when starting debugger and Stepping ADB Principle JDWP protocol and implementation JPDA system overview of Android virtual machine debugger principle and implementation ADB Client, ADB Server, AND ADBD in Android