| #!/bin/bash |
| |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| . /usr/lib/crosutils/common.sh || exit 1 |
| |
| # Flags |
| DEFINE_string attach "" \ |
| "pgrep for the given command - 'browser' finds the chrome browser process" |
| DEFINE_string board "${DEFAULT_BOARD}" \ |
| "The board to run debugger on." |
| DEFINE_string remote "localhost" \ |
| "The IP address of the remote device." |
| DEFINE_integer port 1234 \ |
| "The port number to use for connecting to remote device." |
| DEFINE_integer remote_pid 0 \ |
| "Process ID of the running process on the remote device to which to attach." |
| DEFINE_string remote_file "" \ |
| "Full pathname of the file to be debugged on the remote device." |
| DEFINE_string remote_args "" \ |
| "Command line arguments to pass to the executable on the remote device." |
| DEFINE_boolean cgdb ${FLAGS_FALSE} \ |
| "Use cgdb curses interface rather than plain gdb." |
| DEFINE_boolean ssh ${FLAGS_FALSE} \ |
| "Use ssh stdio forwarding instead of TCP to connect to gdbserver." |
| DEFINE_integer ssh_port 0 \ |
| "The ssh port number to use for ssh connections to the remote device." |
| |
| # Parse command line |
| FLAGS_HELP="usage: $0 [flags]" |
| FLAGS "$@" || exit 1 |
| eval set -- "${FLAGS_ARGV}" |
| check_flags_only_and_allow_null_arg "$@" && set -- |
| |
| BOARD=${FLAGS_board} |
| BOARD_ROOT=/build/${BOARD} |
| |
| if [ ${FLAGS_cgdb} -eq ${FLAGS_TRUE} ] && ! type -P cgdb >/dev/null; then |
| die "Please install cgdb first" |
| fi |
| |
| # Derive toolchain from $BOARD |
| CHOST=$(portageq-${BOARD} envvar CHOST) |
| |
| TMP_DIR="" |
| SSH_PID=0 |
| CLEAN=0 |
| GDBINIT_FILE=~/.gdbinit |
| |
| cleanup () |
| { |
| if [ ${CLEAN} -eq 0 ] ; then |
| rm -rf ${TMP_DIR} |
| if [ ${SSH_PID} -ne 0 ] ; then |
| kill ${SSH_PID} |
| fi |
| CLEAN=1 |
| fi |
| rm -rf ${GDBINIT_FILE} |
| } |
| |
| # Create a temporary location in which to copy testing_rsa file; make |
| # sure to clean it up when we exit. |
| |
| trap 'cleanup' EXIT INT TERM |
| TMP_DIR=$(mktemp -d) |
| |
| cp ${SCRIPTS_DIR}/mod_for_test_scripts/ssh_keys/testing_rsa \ |
| ${TMP_DIR}/testing_rsa |
| chmod 0600 ${TMP_DIR}/testing_rsa |
| |
| REMOTE_SSH_FLAGS=" -o StrictHostKeyChecking=no -o CheckHostIP=no\ |
| -o BatchMode=yes" |
| SSH_PORT="" |
| if [[ ${FLAGS_ssh_port} -ne 0 ]] ; then |
| SSH_PORT=" -p ${FLAGS_ssh_port}" |
| fi |
| |
| run_remote_command () |
| { |
| ssh -i ${TMP_DIR}/testing_rsa \ |
| ${REMOTE_SSH_FLAGS} root@${FLAGS_remote} ${SSH_PORT} \ |
| "$@" |
| } |
| |
| ssh_to_remote_machine () |
| { |
| local command=$1 |
| local error_msg=$2 |
| |
| if ! run_remote_command "${command}" ; then |
| die "${error_msg}" |
| fi |
| } |
| |
| validate_command_options () |
| { |
| # Verify we have at least a board, toolchain and remote file. |
| |
| if [[ -z "${BOARD}" ]] ; then |
| die "--board is required." |
| fi |
| |
| if [[ -z "${CHOST}" ]] ; then |
| die "Unable to determine correct toolchain from board." |
| fi |
| |
| if [[ -z "${FLAGS_remote_pid}" ]] ; then |
| if [[ -z "${FLAGS_remote_file}" ]] ; then |
| if [[ -z "${FLAGS_attach}" ]] ; then |
| die "--remote_file is required." |
| fi |
| fi |
| fi |
| |
| # Verify that the correct cross-gdb has been built first! |
| |
| if [[ ! -f /usr/bin/${CHOST}-gdb ]] ; then |
| die "${CHOST}-gdb does not exist. Please run setup_board." |
| fi |
| |
| # Verify that the IP Address is currently active. |
| |
| if [[ -z "${FLAGS_remote}" ]] ; then |
| die "No IP address specified." |
| fi |
| |
| echo "Verifying IP address ${FLAGS_remote} (this will take a few\ |
| seconds)..." |
| |
| if ! ping -c 3 -q ${FLAGS_remote} > /dev/null ; then |
| die "${FLAGS_remote} is not currently available." |
| fi |
| |
| if [[ -n "${FLAGS_attach}" ]]; then |
| if [[ "${FLAGS_attach}" == "browser" ]]; then |
| FLAGS_remote_pid=$(run_remote_command \ |
| "pstree -p|grep session_manager|cut -d\( -f3 | cut -d\) -f1") |
| if [ -z "${FLAGS_remote_pid}" ]; then |
| die "Unable to find browser process" |
| fi |
| else |
| FLAGS_remote_pid=$(run_remote_command "pgrep -f '${FLAGS_attach}'") |
| local count=$(echo ${FLAGS_remote_pid} | wc -w) |
| if [ ${count} -eq 0 ]; then |
| die "No process matching ${FLAGS_attach}" |
| elif [ ${count} -gt 1 ]; then |
| error "Multiple (${count}) processes matching \"${FLAGS_attach}\":" |
| local pids=$(echo "${FLAGS_remote_pid}" | tr '\n' ' ') |
| run_remote_command "ps ${pids}" |
| exit 1 |
| fi |
| fi |
| fi |
| |
| if [[ ${FLAGS_remote_pid} -ne 0 ]] ; then |
| local ssh_cmd="readlink -e /proc/${FLAGS_remote_pid}/exe" |
| local err_msg="${FLAGS_remote_pid} is not a valid PID on\ |
| ${FLAGS_remote}" |
| FLAGS_remote_file=$(run_remote_command "${ssh_cmd}") |
| if [[ $? -ne 0 ]] ; then |
| die "${err_msg}" |
| fi |
| fi |
| |
| if [[ ! -z "${FLAGS_remote_file}" ]] ; then |
| if [[ ${FLAGS_remote_file:0:1} != '/' ]] ; then |
| die "--remote_file must contain full pathname." |
| fi |
| fi |
| |
| # Verify that the debug version of the remote file exists. |
| |
| local local_file="${BOARD_ROOT}${FLAGS_remote_file}" |
| if [[ ! -x "${local_file}" ]]; then |
| echo |
| warn "${local_file} does not exist on your local machine or is not" |
| warn "executable. You may need to re-run build_packages before attempting to debug." |
| echo |
| read -p "Do you want to stop now? [y/N] " y_or_n |
| case "${y_or_n}" in |
| y|Y) exit 1 ;; |
| *) ;; |
| esac |
| fi |
| |
| local local_symbols="${BOARD_ROOT}/usr/lib/debug${FLAGS_remote_file}.debug" |
| local file_info="$(file "${local_file}")" |
| if [[ "${file_info}" != *"not stripped"* && ! -e "${local_symbols}" ]]; then |
| echo |
| local package="$(qfile-${BOARD} -C -q "${FLAGS_remote_file}")" |
| warn "${local_file} is stripped and ${local_symbols} does not exist" |
| warn "on your local machine. The debug symbols for that package may not be" |
| warn "installed." |
| warn "To install the debug symbols for ${package} only, run:" |
| warn " cros_install_debug_syms --board=${BOARD} ${package}" |
| warn "To install the debug symbols for all available packages, run:" |
| warn " cros_install_debug_syms --board=${BOARD} --all" |
| echo |
| read -p "Do you want to stop now? [y/N] " y_or_n |
| case "${y_or_n}" in |
| y|Y) exit 1 ;; |
| *) ;; |
| esac |
| fi |
| } |
| |
| setup_remote_iptable () |
| { |
| # Update the iptables on the remote device |
| |
| local ssh_cmd="/sbin/iptables -A INPUT -p tcp --dport ${FLAGS_port}\ |
| -j ACCEPT" |
| local err_msg="Unable to add port to iptables." |
| ssh_to_remote_machine "${ssh_cmd}" "${err_msg}" |
| } |
| |
| start_remote_gdbserver () |
| { |
| # Start gdbserver on the remote device |
| |
| local gdbserver_cmd="gdbserver :${FLAGS_port} ${FLAGS_remote_file} ${FLAGS_remote_args}" |
| if [[ ${FLAGS_remote_pid} -ne 0 ]] ; then |
| gdbserver_cmd="gdbserver --attach :${FLAGS_port} ${FLAGS_remote_pid}" |
| fi |
| |
| echo "Starting up gdbserver on your remote device." |
| local ssh_cmd="nohup ${gdbserver_cmd} > /tmp/gdbserver.out 2>&1 &" |
| local err_msg="Unable to ssh into root@${FLAGS_remote}." |
| ssh_to_remote_machine "${ssh_cmd}" "${err_msg}" |
| } |
| |
| generate_gdbinit_file () |
| { |
| # Create board-and-notebook-specific .gdbinit file. |
| |
| local remote_cmd='"target remote " + remote_ip_address + ":" + remote_port' |
| if [ ${FLAGS_ssh} -eq ${FLAGS_TRUE} ]; then |
| local gdbserver_cmd="gdbserver" |
| if [[ ${FLAGS_remote_pid} -ne 0 ]] ; then |
| gdbserver_cmd+=" --attach - ${FLAGS_remote_pid}" |
| else |
| gdbserver_cmd+=" - ${FLAGS_remote_file} ${FLAGS_remote_args}" |
| fi |
| remote_cmd="\"target remote | ssh -T -i ${TMP_DIR}/testing_rsa \ |
| ${REMOTE_SSH_FLAGS} -o TCPKeepAlive=no -o UserKnownHostsFile=/dev/null \ |
| root@${FLAGS_remote} ${SSH_PORT} ${gdbserver_cmd}\"" |
| fi |
| |
| cat <<-EOF > ${GDBINIT_FILE} |
| |
| define remote_connect |
| set \$file="${FLAGS_remote_file}" |
| python import os |
| python filename = str (gdb.parse_and_eval ("\$file")) |
| python fullname = os.path.join ("${BOARD_ROOT}", filename) |
| python file_command = "file " + fullname |
| python gdb.execute (file_command) |
| python remote_ip_address = "${FLAGS_remote}" |
| python remote_port = "${FLAGS_port}" |
| python remote_cmd = ${remote_cmd} |
| python gdb.execute (remote_cmd) |
| end |
| |
| set sysroot $BOARD_ROOT |
| set debug-file-directory $BOARD_ROOT/usr/lib/debug |
| remote_connect |
| EOF |
| } |
| |
| |
| # Some special set up is required for accessing the VM on a local machine... |
| |
| PORT_FORWARDING="" |
| |
| if [[ "${FLAGS_remote}" == "localhost" || |
| "${FLAGS_remote}" == "127.0.0.1" ]] ; then |
| if [[ -z ${SSH_PORT} ]] ; then |
| SSH_PORT=" -p 9222" |
| fi |
| PORT_FORWARDING=" -L ${FLAGS_port}:localhost:${FLAGS_port}" |
| elif [ ${FLAGS_ssh} -eq ${FLAGS_FALSE} ]; then |
| setup_remote_iptable |
| fi |
| |
| validate_command_options |
| |
| # If accessing the VM on the local machine, need a second ssh session open, |
| # for port forwarding, so gdb can find gdbserver... |
| |
| SSHD_PID=0 |
| if [[ -n "${PORT_FORWARDING}" ]] ; then |
| # Call ssh directly rather than using 'ssh_to_remote_machine' because |
| # too many things about this particular call are different. |
| ssh -i ${TMP_DIR}/testing_rsa -N \ |
| ${REMOTE_SSH_FLAGS} \ |
| root@${FLAGS_remote} ${SSH_PORT} ${PORT_FORWARDING} & |
| SSH_PID=$! |
| fi |
| |
| if [ ${FLAGS_ssh} -eq ${FLAGS_FALSE} ]; then |
| start_remote_gdbserver |
| echo "gdbserver is now running remotely. Output will be written to " |
| echo "/tmp/gdbserver.out on your remote device." |
| fi |
| |
| generate_gdbinit_file |
| |
| echo "Some helpful GDB commands:" |
| echo "directory <path> -- causes path to be searched for source files." |
| echo "info functions <regexp> -- find function matching given regexp." |
| echo |
| echo "Some helpful gdb_remote specific commands:" |
| echo "remote_connect -- reestablish remote connection." |
| echo |
| |
| # Start gdb on local machine. |
| if [ ${FLAGS_cgdb} -eq ${FLAGS_TRUE} ]; then |
| cgdb -d ${CHOST}-gdb |
| else |
| ${CHOST}-gdb |
| fi |
| |
| cleanup |