Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logcat output to JadX Debugger #1666

Merged
merged 26 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0d17bee
Adding logcatController class and writing adb / debugger panel inform…
jmlitfi Feb 27, 2022
7bcc2a5
Finished parsing logcat binary output and writing an arraylist contai…
jmlitfi Feb 27, 2022
1d8ef52
added highlighting of logcat output based on type. Added timestamp p…
jmlitfi Feb 28, 2022
1a5f590
Updated code to only get new log messages.
jmlitfi Feb 28, 2022
abd4058
Added additional code for select all
jmlitfi Mar 3, 2022
2be559b
Completed Check and uncheckall options.
jmlitfi Mar 8, 2022
6a718eb
Changed log highlighting to log color. Changed from JTextArea to JTe…
jmlitfi Mar 15, 2022
ae08e14
Moved labels into NLS rather than using hardcoded strings.
jmlitfi Mar 18, 2022
5c6395d
Implemented the ability to autoselect attached process. Changed the …
jmlitfi Mar 19, 2022
05bd41b
Moved labels into NLS rather than using hardcoded strings.
jmlitfi May 18, 2022
4c68bde
Merge branch 'skylot:master' into logcat3
jmlitfi Jul 10, 2022
d55bee4
Merge branch 'skylot:master' into logcat3
jmlitfi Sep 6, 2022
8fa71ef
updating to use info getter methods rather than directly accessing va…
Sep 6, 2022
d6a288d
Added Logcat Pause Button
Sep 6, 2022
afb4879
Added Clear button
Sep 8, 2022
3239035
Updated clear icon
Sep 8, 2022
e929191
Cleaning warnings
Sep 8, 2022
e7c9d13
cleaning
Sep 8, 2022
70e9eae
Changed behavior to only show logcat for debugged process to start with.
Sep 8, 2022
864867b
cleaning
Sep 8, 2022
64162d9
cleaning
Sep 8, 2022
051d243
cleaning
Sep 8, 2022
71c80f7
applying spotless
Sep 8, 2022
16093b6
Fixing bug with switch
Sep 8, 2022
d74c404
fixed formatting issue
Sep 8, 2022
b1fa980
add missing localization strings
skylot Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
390 changes: 390 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/device/debugger/LogcatController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
package jadx.gui.device.debugger;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.ui.panel.LogcatPanel;

public class LogcatController {
private static final Logger LOG = LoggerFactory.getLogger(LogcatController.class);

private final ADBDevice adbDevice;
private final LogcatPanel logcatPanel;
private Timer timer;
private final String timezone;
private LogcatInfo recent = null;
private ArrayList<LogcatInfo> events = new ArrayList<>();
private LogcatFilter filter = new LogcatFilter(null, null);
private String status = "null";

public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException {
this.adbDevice = adbDevice;
this.logcatPanel = logcatPanel;
this.timezone = adbDevice.getTimezone();
this.startLogcat();
}

public void startLogcat() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
getLog();
}
}, 0, 1000);
this.status = "running";
}

public void stopLogcat() {
timer.cancel();
this.status = "stopped";
}

public String getStatus() {
return this.status;
}

public void clearLogcat() {
try {
adbDevice.clearLogcat();
clearEvents();
} catch (IOException e) {
LOG.error("Failed to clear Logcat", e);
}
}

private void getLog() {
if (!logcatPanel.isReady()) {
return;
}
try {
byte[] buf;
if (recent == null) {
buf = adbDevice.getBinaryLogcat();
} else {
buf = adbDevice.getBinaryLogcat(recent.getAfterTimestamp());
}
if (buf == null) {
return;
}
ByteBuffer in = ByteBuffer.wrap(buf);
in.order(ByteOrder.LITTLE_ENDIAN);
while (in.remaining() > 20) {

LogcatInfo eInfo = null;
byte[] msgBuf;
short eLen = in.getShort();
short eHdrLen = in.getShort();
if (eLen + eHdrLen > in.remaining()) {
return;
}
switch (eHdrLen) {
case 20: // header length 20 == version 1
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
case 24: // header length 24 == version 2 / 3
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
case 28: // header length 28 == version 4
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(),
in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
default:

break;
}
if (eInfo == null) {
return;
}
if (recent == null) {
recent = eInfo;
} else if (recent.getInstant().isBefore(eInfo.getInstant())) {
recent = eInfo;
}

if (filter.doFilter(eInfo)) {
logcatPanel.log(eInfo);
}
events.add(eInfo);
}

} catch (Exception e) {
LOG.error("Failed to get logcat message", e);
}
}

public boolean reload() {
stopLogcat();
boolean ok = logcatPanel.clearLogcatArea();
if (ok) {
events.forEach((eInfo) -> {
if (filter.doFilter(eInfo)) {
logcatPanel.log(eInfo);
}
});
startLogcat();
}
return true;
}

public void clearEvents() {
this.recent = null;
this.events = new ArrayList<>();
}

public void exit() {
stopLogcat();
filter = new LogcatFilter(null, null);
recent = null;
}

public LogcatFilter getFilter() {
return this.filter;
}

public class LogcatFilter {
private final ArrayList<Integer> pid;
private ArrayList<Byte> msgType = new ArrayList<Byte>() {
{
add((byte) 1);
add((byte) 2);
add((byte) 3);
add((byte) 4);
add((byte) 5);
add((byte) 6);
add((byte) 7);
add((byte) 8);
}
};

public LogcatFilter(ArrayList<Integer> pid, ArrayList<Byte> msgType) {
if (pid != null) {
this.pid = pid;
} else {
this.pid = new ArrayList<>();
}

if (msgType != null) {
this.msgType = msgType;
}
}

public void addPid(int pid) {

if (!this.pid.contains(pid)) {
this.pid.add(pid);
}
}

public void removePid(int pid) {
int pidPos = this.pid.indexOf(pid);
if (pidPos >= 0) {
this.pid.remove(pidPos);
}
}

public void togglePid(int pid, boolean state) {
if (state) {
addPid(pid);
} else {
removePid(pid);
}
}

public void addMsgType(byte msgType) {
if (!this.msgType.contains(msgType)) {
this.msgType.add(msgType);
}
}

public void removeMsgType(byte msgType) {
int typePos = this.msgType.indexOf(msgType);
if (typePos >= 0) {
this.msgType.remove(typePos);
}
}

public void toggleMsgType(byte msgType, boolean state) {
if (state) {
addMsgType(msgType);
} else {
removeMsgType(msgType);
}
}

public boolean doFilter(LogcatInfo inInfo) {
if (pid.contains(inInfo.getPid())) {
return msgType.contains(inInfo.getMsgType());
}
return false;
}

public ArrayList<LogcatInfo> getFilteredList(ArrayList<LogcatInfo> inInfoList) {
ArrayList<LogcatInfo> outInfoList = new ArrayList<LogcatInfo>();
inInfoList.forEach((inInfo) -> {
if (doFilter(inInfo)) {
outInfoList.add(inInfo);
}
});
return outInfoList;
}
}

public class LogcatInfo {
private String msg;
private final byte msgType;
private final int nsec;
private final int pid;
private final int sec;
private final int tid;
private final short hdrSize;
private final short len;
private final short version;
private int lid;
private int uid;

public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.version = 1;
}

// Version 2 and 3 both have the same arguments
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.lid = lid;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.version = 3;
}

public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, int uid, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.lid = lid;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.uid = uid;
this.version = 4;
}

public void setMsg(byte[] msg) {
this.msg = new String(msg);
}

public short getVersion() {
return this.version;
}

public short getLen() {
return this.len;
}

public short getHeaderLen() {
return this.hdrSize;
}

public int getPid() {
return this.pid;
}

public int getTid() {
return this.tid;
}

public int getSec() {
return this.sec;
}

public int getNSec() {
return this.nsec;
}

public int getLid() {
return this.lid;
}

public int getUid() {
return this.uid;
}

public Instant getInstant() {
return Instant.ofEpochSecond(getSec(), getNSec());
}

public String getTimestamp() {
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
return dtFormat.format(getInstant());
}

public String getAfterTimestamp() {
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
return dtFormat.format(getInstant().plusMillis(1));
}

public byte getMsgType() {
return this.msgType;
}

public String getMsgTypeString() {
switch (getMsgType()) {
case 0:
return "Unknown";
case 1:
return "Default";
case 2:
return "Verbose";
case 3:
return "Debug";
case 4:
return "Info";
case 5:
return "Warn";
case 6:
return "Error";
case 7:
return "Fatal";
case 8:
return "Silent";
default:
return "Unknown";
}
}

public String getMsg() {
return this.msg;
}
}
}
Loading