Last active
March 15, 2021 18:15
-
-
Save jigpu/b7b47e4bede584cec8562f666fd84af4 to your computer and use it in GitHub Desktop.
Simultaneously capture XI2, evdev, and HID data from a tablet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Usage: capture.sh [limit [count]] | |
# Simultaneously capture HID, evdev, and Xinput events from a device. | |
# | |
# limit Maximum output file size in bytes (default: 0 [unlimited]) | |
# count Number of output files to rotate through (default: 1) | |
# | |
# This script must be run as root in order to read all events. | |
set -e | |
trap cleanup INT QUIT TERM | |
function cleanup { | |
echo | |
echo "Archiving capture data..." | |
sleep 0.1 | |
jobs -p | xargs kill 2>/dev/null || true | |
wait | |
tar cvzf record_$NOW.tar.gz record_$NOW.*.log* | |
rm record_$NOW.*.log* | |
echo | |
echo "Files have been archived to $(pwd)/record_$NOW.tar.gz" | |
echo "Please upload this file for analysis." | |
} | |
function kernelver_int { | |
awk -F'[-.]' '{print $1*1000000+$2*1000+$3}' <<<$1 | |
} | |
function hid_available { | |
test $(kernelver_int $(uname -r)) -ge $(kernelver_int "3.17") || grep -o 7.[56789] /etc/redhat-release >/dev/null 2>/dev/null | |
return $? | |
} | |
function logger { | |
DEST=$1 | |
LOGSIZE=$2 | |
LOGCOUNT=$3 | |
NBYTES=0 | |
T=$(date +%s.%N) | |
while IFS='' read -r TEXT; do | |
if [[ "$DEST" == *".xi2.log" && "$TEXT" == "EVENT "* ]]; then | |
NOW=$(date +%s.%N) | |
awk -vA="$T" -vB="$NOW" 'BEGIN {print B " [" (B-A)*1000 " ms]"; exit}' >> "$DEST" | |
T=$NOW | |
fi | |
echo "$TEXT" >> "$DEST" | |
if [[ $LOGSIZE -gt 0 ]]; then | |
# Calling stat to get the size every time we write a line is very slow... | |
# Lets do our own accounting instead. | |
NBYTES=$(($NBYTES + ${#TEXT} + 2)) | |
if [[ $NBYTES -gt $LOGSIZE ]]; then | |
echo "Rotating $DEST..." | |
for I in $(seq $(($LOGCOUNT-1)) -1 0); do | |
mv -f "$DEST.$I" "$DEST.$(($I+1))" 2> /dev/null || true | |
done | |
mv "$DEST" "$DEST.0" || true | |
rm -f "$DEST.$LOGCOUNT" 2> /dev/null || true | |
NBYTES=0 | |
fi | |
fi | |
done | |
} | |
if [[ "$EUID" -ne 0 ]]; then | |
echo "This command must be run as root to capture hardware and kernel logs." | |
exit 1 | |
fi | |
LOGLIMIT=0 | |
LOGCOUNT=1 | |
if [[ $# -ge 1 ]]; then | |
LOGLIMIT=$1 | |
fi | |
if [[ $# -ge 2 ]]; then | |
LOGCOUNT=$2 | |
fi | |
REC_XI2=$(command -v xinput 2> /dev/null || true) | |
REC_KRN=$(command -v evemu-record 2> /dev/null || command -v evtest 2>/dev/null || true) | |
REC_HID=$(command -v hid-recorder 2> /dev/null || true) | |
REC_USB="" | |
REC_TTY=$(command -v isdv4-serial-debugger 2>/dev/null || true) | |
if ! hid_available ; then | |
# Can't capture HID data prior to Linux 3.17... | |
REC_USB="grep" | |
if ! modprobe usbmon; then | |
echo "Unable to load usbmon kernel module." | |
exit 1 | |
fi | |
fi | |
ERRMSG="" | |
if [[ -z "$REC_XI2" ]]; then | |
ERRMSG="${ERRMSG}\n * xinput [Try: \`apt-get install xinput\` or \`yum install xorg-x11-server-utils\`]" | |
fi | |
if [[ -z "$REC_KRN" ]]; then | |
ERRMSG="${ERRMSG}\n * evemu-record [Try: \`apt-get install evemu-tools\` or \`yum install evemu\` (may require EPEL)]" | |
fi | |
if hid_available && [[ -z "$REC_HID" ]]; then | |
ERRMSG="${ERRMSG}\n * hid-recorder [Try: \`yum install hid-replay\` (may require EPEL) or https://bentiss.github.io/hid-replay-docs/ ]" | |
fi | |
if [[ -z "$REC_TTY" ]]; then | |
ERRMSG="${ERRMSG}\n * isdv4-serial-debugger [Try: \`apt-get install xserver-xorg-input-wacom\` \`yum install xorg-x11-wacom-devel\` ]" | |
fi | |
if [[ -n "$ERRMSG" ]] ; then | |
echo "The following utilities are missing and must be installed to capture logs:" | |
echo "$ERRMSG" | |
exit 1 | |
fi | |
DEV_X11="" | |
DEV_EVDEV="" | |
DEV_HID="" | |
DEV_USB="" | |
DEV_TTY="" | |
xinput list | |
while [[ "x$DEV_X11" == "x" ]]; do | |
echo -ne "\nID number of the device to record: " | |
read DEV_X11 | |
if ! xinput list $DEV_X11 > /dev/null 2>&1; then | |
echo "Unknown device ID." | |
DEV_X11="" | |
fi | |
done | |
DEV_EVDEV=$(xinput list-props "$DEV_X11" | awk 'BEGIN {OFS=FS="\t"} /^\tDevice Node/ {$1=$2="";print substr($0,4,length()-4)}') | |
if [[ -c "$DEV_EVDEV" ]]; then | |
echo "Found evdev device node: $DEV_EVDEV" | |
else | |
echo "Unknown device node: $DEV_EVDEV" | |
exit 1 | |
fi | |
SYSFS_PATH=$(readlink -e "/sys/$(udevadm info -q path "$DEV_EVDEV")" 2>/dev/null || true) | |
if [[ -n "$SYSFS_PATH" ]]; then | |
echo "Found evdev sysfs at $SYSFS_PATH" | |
else | |
echo "Unable to locate sysfs path from $(udevadm info -q path "$DEV_EVDEV")" | |
exit 1 | |
fi | |
HIDRAW_PATH=$(readlink -e "$SYSFS_PATH/device/device/hidraw" 2>/dev/null || true) | |
USBBUS_PATH=$(readlink -e "$SYSFS_PATH/../../../../../busnum" 2>/dev/null || true) | |
USBDEV_PATH=$(readlink -e "$SYSFS_PATH/../../../../../devnum" 2>/dev/null || true) | |
TTYDEV_PATH=$(ls "$SYSFS_PATH/../../../../serio*" 2>/dev/null || true) | |
if [[ -n "$HIDRAW_PATH" ]]; then | |
echo "Found hidraw sysfs at $HIDRAW_PATH" | |
DEV_HID="/dev/$(ls $HIDRAW_PATH)" | |
if [[ -c "$DEV_HID" ]]; then | |
echo "Found hidraw device node: $DEV_HID" | |
else | |
echo "Unknown hidraw device node: $DEV_HID" | |
exit 1 | |
fi | |
elif [[ -n "$USBBUS_PATH" && -n "$USBDEV_PATH" ]]; then | |
echo "Found usb bus sysfs at $USBBUS_PATH" | |
echo "Found usb dev sysfs at $USBDEV_PATH" | |
BUS=$(cat "$USBBUS_PATH") | |
DEV=$(cat "$USBDEV_PATH") | |
DEV_USB=$(printf "%d:%03d" $BUS $DEV) | |
echo "Found USB bus/device: $DEV_USB" | |
if ! test -f /sys/kernel/debug/usb/usbmon/0u; then | |
echo "Unable to locate usbmon character device" | |
exit 1 | |
fi | |
elif [[ -n "$TTYDEV_PATH" ]]; then | |
echo "Found tty sysfs at $TTYDEV_PATH" | |
DEV_TTY="/dev/"$(basename $(readlink -f "$SYSFS_PATH/../../../..")) | |
if [[ -c "$DEV_TTY" ]]; then | |
echo "Found TTY device node: $DEV_TTY" | |
else | |
echo "Unknown TTY device node: $DEV_TTY" | |
exit 1 | |
fi | |
else | |
echo "Unable to find a hardware device from $SYSFS_PATH" | |
exit 1 | |
fi | |
cd /tmp | |
NOW=$(date +%s) | |
echo | |
echo "Capturing events into temporary files..." | |
if [[ $LOGLIMIT -gt 0 ]]; then | |
echo "(Limiting to $LOGLIMIT bytes across $LOGCOUNT files)" | |
fi | |
$REC_XI2 test-xi2 --root "$DEV_X11" | logger record_$NOW.xi2.log $LOGLIMIT $LOGCOUNT & | |
echo " XI2: $(pwd)/record_$NOW.xi2.log" | |
$REC_KRN "$DEV_EVDEV" | logger record_$NOW.evemu.log $LOGLIMIT $LOGCOUNT & | |
echo " evdev: $(pwd)/record_$NOW.evemu.log" | |
if [[ -n "$DEV_HID" ]] ; then | |
$REC_HID "$DEV_HID" | logger record_$NOW.hid.log $LOGLIMIT $LOGCOUNT & | |
echo " HW: $(pwd)/record_$NOW.hid.log" | |
elif [[ -n "$DEV_USB" ]] ; then | |
$REC_USB ":$DEV_USB:" /sys/kernel/debug/usb/usbmon/0u | logger record_$NOW.usb.log $LOGLIMIT $LOGCOUNT & | |
echo " HW: $(pwd)/record_$NOW.usb.log" | |
elif [[ -n "$DEV_TTY" ]] ; then | |
$REC_TTY "$DEV_TTY" | logger record_$NOW.tty.log $LOGLIMIT $LOGCOUNT & | |
echo " HW: $(pwd)/record_$NOW.tty.log" | |
fi | |
echo | |
echo "Press CTRL+C to stop recording." | |
wait |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment