;;;
;;; ptrace-hacking.lisp
;;;
;;; Trying to figure out how to use ptrace(2).
;;;

(cl:defpackage :ptrace-hacking (:use :cl :lh-debug-core))
(cl:in-package :ptrace-hacking)

;; TODO:
;;
;;   1.) Create lh-debug-core project and package.  [DONE]
;;
;;   2.) Create class hierarchy for ptrace process and amd64-mixin.
;;       [DONE]
;;
;;   3.) Set up existing functions as generic functions and methods.
;;       [DONE]
;;
;;   4.) Fill out register-access functions for amd64-mixin.  [DONE]
;;
;;   5.) Fix up exports from lh-debug-core.  [DONE]

;; In a departure from the precedent set with lh-usb, I propose to use
;; sb-posix for errno constants.

;; So, having come up with most of a usable model for ptrace, I
;; realize that it shouldn't be too difficult to create an interface
;; to allow for implementing the model by talking over a socket to a
;; debug slave written in C on another machine.  This is something for
;; the future, but it's well worth going out of our way to allow
;; (easier debugging when bringing up a new backend).

;; Some open questions I have:
;;
;;   1.) What is the interaction between the various ptrace "continue"
;;       requests, particularly with the "sysemu" variations?  What
;;       about the "singleblock" version?  It seems that there is some
;;       way to "cancel" a syscall even without "sysemu", and this
;;       mechanism is used in user-mode linux (arch/um/os-Linux) when
;;       there are no "sysemu" requests available.
;;
;;       System call cancellation is performed by changing the syscall
;;       being invoked to getpid(2), which takes no arguments, returns
;;       only one value and has no side-effects.
;;
;;       When continuing from a syscall entry trap the exit trap will
;;       only occur if a "syscall" continue option is used.  Other
;;       combinations not tested.
;;
;;   2.) Is there perhaps a better model for signal delivery that we
;;       can use than an optional or keyword parameter to our resume
;;       function?
;;
;;   3.) How best can we handle multiple threads/processes?
;;
;;   4.) What interface would be good for enabling/disabling the
;;       various "event" messages?
;;
;;   5.) What do we do about siginfo_t access functions?
;;
;;   6.) How do we support register access and/or other machine models
;;       such as the remote debug slave proposed above?
;;
;;       Register access is a bit of a mixin with cached register
;;       images, etc.  The current implementation will leak memory if
;;       we're not careful, so a redesign may be in order.
;;
;;   7.) How do we start a new child process of our own and use
;;       pthread-traceme?  What do we do about I/O from this process?
;;
;;       To a certain extent, this is the "run-program" issue all over
;;       again.
;;
;;   8.) There is a caveat about signal delivery and receiving event
;;       messages mentioned in the ptrace(2) man page.  How does this
;;       affect our interface or implementation?
;;
;;   9.) What do we want to do about error handling?
;;
;;   10.) ptrace-syscall stops on both entry to and exit from a
;;        syscall.  We need to know which is happening each time.
;;
;;        The obvious way to distinguish these cases is the use of a
;;        state model with events reported by waitpid(2) and events
;;        generated by ptrace(2) calls as the transitions.

;; My current plan for register access is an amd64-registers mixin.
;; This mixin would have a user-regs-struct to hold the register data,
;; a :before method on resume-process to invalidate its cached copy of
;; the registers, an accessor for registers by name, and some
;; introspective interface for exposing the register model to a
;; generic client (so that something like gdb's "info registers" could
;; be written).  This may be a subclass instead of a mixin until the
;; debug slave is implemented.

(defparameter *process* nil)

(defun simple-event-function (process event &rest args)
  (format t "Process ~D -- ~S ~S~%"
	  (slot-value process 'lh-debug-core::pid) event args))

(defun simple-attach (pid)
  (setf *process* (attach-to-process pid))
  (setf (event-function *process*) 'simple-event-function))

;; Okay, new discoveries:
;;
;;  1.) When resuming a process :until-syscall, we get stopped both
;;      before and after the syscall runs.  If it's before, the :rax
;;      register-value is always -ENOSYS, and the :orig-rax
;;      register-value is the syscall number.  We may want to do
;;      something to cover this inconsistency up.
;;
;;  2.) I have yet to see an obvious way to distinguish between before
;;      and after statuses for :syscall stops, save monitoring state
;;      transitions.
;;
;;  3.) A debugger can rewrite all of the parameters to a syscall
;;      between catching syscall entry and allowing the process to
;;      continue towards syscall exit.  All of the parameters,
;;      including the sycall number (UML uses this to rewrite syscalls
;;      to getpid on platforms without PTRACE_SYSEMU, effectively
;;      cancelling them).
;;
;;  4.) When a process is resumed with mode :normal when it was
;;      stopped in a syscall it will not stop on exit from that
;;      syscall (not unexpected, but good to know).  Exploring the
;;      rest of the interactions surrounding different stop types and
;;      continue modes would be a good idea, along with checking the
;;      behavior of other ptrace requests (certain event stops
;;      disallow signal delivery, apparently).


;; So, a simple debugger.  We set up a dummy event-function to just
;; store the event data where our outer loop can get at it.  Then, we
;; "just" stop on signals, and reflect all sigsegvs back to the
;; process being debugged sight unseen.

;; Obvious problems with this debugger:
;;
;;  1.) Can't change the resume-mode (no stop-on-syscall, no
;;      singlestep).
;;
;;  2.) Can't access registers (should be simple enough to fix).
;;
;;  3.) Only stops on signals.
;;
;;  4.) Can't pass signals to the process.
;;
;;  5.) No access to the process object itself from the prompt.
;;
;;  6.) No way to detach from or kill the process.

;; Possible future extensions:
;;
;;  1.) Memory access via /proc/<pid>/mem.
;;
;;  2.) Viewing memory map information from /proc/<pid>/maps.
;;
;;  3.) Backtraces (for Lisp, need to be able to call C runtime
;;      functions from debugger; for C, need to parse ELF/DWARF
;;      information and /proc/<pid>/maps; for both, need to access
;;      memory).
;;
;;  4.) Breakpoints (need to be able to find symbols, either C or
;;      Lisp, in order to make this reasonable).
;;
;;  5.) A disassembler (requres memory access, possibly backtrace).
;;
;;  6.) SIGTRAP identification for Lisp code.  Pick off
;;      internal-errors by name, identify the arguments involved, etc.
;;
;;  7.) Show active catch-blocks and handler-binds in Lisp frames.

(defun simple-debug (process)
  (let (last-event
	(resume-mode :normal))
    (labels ((event-function (process &rest event)
	       (declare (ignore process))
	       (setf last-event event)))
      (setf (event-function process) #'event-function)
      (tagbody
	 :get-next-event
	 (update-process-status process :wait t)

	 (cond
	   ((and (eq (car last-event) :stopped)
		 (= (cadr last-event) sb-posix:sigsegv))
	    ;; Ideally, forward all heap faults to the GC and trap the
	    ;; others.  In practice, forward all faults to the process.
	    (resume-process process
			    :send-signal sb-posix:sigsegv
			    :resume-mode resume-mode)
	    (go :get-next-event))
	   (t
	    (format t "Event: ~S~%" last-event)))

	 :evaluate
	 (format t "~S> " (slot-value process 'lh-debug-core::status))
	 (finish-output)
	 (let ((input (read)))
	   (case input
	     ((:cont :continue)
	      (resume-process process :resume-mode resume-mode)
	      (go :get-next-event))
	     (:quit
	      (go :done))
	     (t
	      (let ((results (sb-ext:interactive-eval input)))
		(format t "~&~X~%" results))
	      (go :evaluate))))

	 :done))))

(defun simple-attach-and-debug (pid)
  (let ((process (attach-to-process pid)))
    (simple-debug process)))

;;; EOF
