Hi all,

I implemented some GDB pretty-printers for V8.  They use the debugging
information and the V8 run-time type information to print V8 objects in
a nice way, but without calling V8 code.  They are useable on core
files.  The pretty-printer is attached, and is also available from:

  http://wingolog.org/pub/d8-gdb.scm

It will print all kinds of strings (except externalized strings, as it
doesn't seem possible to call virtual methods from GDB extensions).  It
also prints all the oddballs, failures, smis, heap numbers, and
maybeobjects.  It can print the values contained by handles, too.

In this frame we can see some examples:

    #4  0x0000000000b903ae in 
v8::internal::MaterializeStackLocalsWithFrameInspector (isolate=0x1c9f030, 
target=Handle((v8::internal::JSObject *) 0x10edcd0a9479), 
        function=Handle((v8::internal::JSFunction *) 0x2d8ad3731ac9), 
frame_inspector=0x7fffffffc240) at ../src/runtime.cc:11485
            scope = {isolate_ = 0x1c9f030, prev_next_ = 0x1ce5e98, prev_limit_ 
= 0x1ce7af0}
            value = Handle(the-hole)
            name = Handle(0)
            i = 0
            shared = Handle((v8::internal::SharedFunctionInfo *) 0x2d8ad372e469)
            scope_info = Handle((v8::internal::ScopeInfo *) 
((v8::internal::FixedArray *) 0x2d8ad373cab9))

Here we see that "value" and "name" are handles to the hole and to Smi
0, respectively.  "scope_info" is an interesting case -- the declared
type (the part on the outside) is more refined than the type that is
computed from the map()->instance_type() (the part on the inside).  The
pretty printers will print both types if they differ.  There's no
problem here, of course; and admittedly it is useful to know when the
actual type is more refined, which does work but I don't have an example
here.

In this frame there's a string:

    (gdb) fr 4
    #4  0x0000000000b903ae in 
v8::internal::MaterializeStackLocalsWithFrameInspector (isolate=0x1c9f030, 
target=Handle((v8::internal::JSObject *) 0x651f90a9479), 
        function=Handle((v8::internal::JSFunction *) 0xa66ccc31ac9), 
frame_inspector=0x7fffffffc240) at ../src/runtime.cc:11485
    11485           ASSERT(!value->IsTheHole());
    (gdb) l
    11480           HandleScope scope(isolate);
    11481           Handle<Object> value(i < 
frame_inspector->GetParametersCount()
    11482                                    ? frame_inspector->GetParameter(i)
    11483                                    : 
isolate->heap()->undefined_value(),
    11484                                isolate);
    11485           ASSERT(!value->IsTheHole());
    11486           Handle<String> name(scope_info->ParameterName(i));
    11487       
    11488           RETURN_ON_EXCEPTION(
    11489               isolate,
    (gdb) p scope_info->ParameterName(i)
    $1 = "a"

Here we see that it extracts the string name correctly.

An uninitialized handle prints like this:

        details = Handle((v8::internal::JSObject *) 0x0)

To use these pretty printers, just run "source /path/to/d8-gdb.scm".  Or
if you put d8-gdb.scm right next to your d8, it will get sourced
automatically.  The caveat is that you will need a version of GDB that
allows for Guile extensions, and that's not common right now.  However
it is exceptionally easy to get this working.  Just install Guile
development libs:

    sudo apt-get install guile-2.0-dev

and then fetch and build GDB:

    git clone git://sourceware.org/git/binutils-gdb.git
    cd binutils-gdb
    mkdir +build
    cd +build
    ../configure --prefix=/opt/gdb
    make

Now to install it we make the /opt/gdb directory.

    sudo mkdir /opt/gdb; sudo chown `whoami` /opt/gdb

Note that since binutils and gdb are lumped into one repo for no good
reason, we should cd into the gdb/ subdir before making install:

    cd gdb
    make install

Now you have a GDB built from git runnable from /opt/gdb/bin/gdb that
doesn't interfere with anything else on your system, and which can load
up the V8 pretty-printers.

Happy hacking,

Andy

-- 
-- 
v8-users mailing list
[email protected]
http://groups.google.com/group/v8-users
--- 
You received this message because you are subscribed to the Google Groups 
"v8-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
;;; GDB debugging support for V8.
;;;
;;; Copyright 2014 Andy Wingo <[email protected]>
;;; Copyright 2014 Free Software Foundation, Inc.
;;;
;;; This program is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

(define-module (d8-gdb)
  #:use-module (ice-9 format)
  #:use-module (ice-9 rdelim)
  #:use-module ((gdb) #:hide (symbol?))
  #:use-module (gdb printing)
  #:use-module (ice-9 match))

;;; Commentary:
;;;
;;; This file defines GDB extensions to pretty-print V8 objects.
;;;
;;; This file is installed under a name that follows the convention that
;;; allows GDB to auto-load it anytime the user is debugging "d8".
;;; (info "(gdb) objfile-gdbdotext file").
;;;
;;; Code:


(define (v8-name name)
  (string-append "v8::internal::" name))

(define (v8-type name)
  (or (lookup-type (v8-name name))
      (error "type not found" name)))

(define (v8-handle-name name)
  (v8-name (string-append "Handle<" (v8-name name) ">")))

(define (v8-handle-type name)
  (or (lookup-type (v8-handle-name name))
      (error "type not found" name)))

(define (v8-pointer-type name)
  (type-pointer (lookup-type (v8-name name))))

(define (v8-symbol name)
  (or (and=> (lookup-symbol (v8-name name)) car)
      (error "symbol not found" name)))

(define (v8-constant name)
  (let ((sym (v8-symbol name)))
    (unless (symbol-constant? sym)
      (error "symbol not a constant" sym))
    (symbol-value sym)))

(define (v8-value-has-tag? val tag size)
  (value=? (value-logand (value-cast val (arch-uint-type (current-arch)))
                         (value-sub (value-lsh 1 (v8-constant size)) 1))
           (v8-constant tag)))

(define (v8-failure? maybe-obj)
  (v8-value-has-tag? maybe-obj "kFailureTag" "kFailureTagSize"))

(define (v8-smi? maybe-obj)
  (v8-value-has-tag? maybe-obj "kSmiTag" "kSmiTagSize"))

(define (->type type-or-string)
  (if (string? type-or-string)
      (v8-type type-or-string)
      type-or-string))

(define (v8-field-ptr obj offset type)
  (let* ((char* (type-pointer (arch-char-type (current-arch))))
         (offset (if (string? offset)
                     (v8-constant offset)
                     offset))
         (byte-ptr (value-sub (value-add (value-cast obj char*)
                                         offset)
                              (v8-constant "kHeapObjectTag"))))
    (value-cast byte-ptr type)))

(define (v8-field obj offset type)
  (let ((type (type-pointer (->type type))))
    (value-dereference (v8-field-ptr obj offset type))))

(define (v8-smi-field-ptr obj offset)
  (v8-field-ptr obj offset (type-pointer (v8-type "Smi"))))

(define (v8-smi-value obj)
  (value->integer
   (value-cast
    (value-rsh (value-cast obj
                           (lookup-type "intptr_t"))
               (value-add (v8-constant "kSmiTagSize")
                          (v8-constant "kSmiShiftSize")))
    (arch-int-type (current-arch)))))

(define (v8-smi-field obj offset)
  (v8-smi-value (value-dereference (v8-smi-field-ptr obj offset))))

(define (v8-pointer-field-ptr obj offset type)
  (v8-field-ptr obj offset (type-pointer (->type type))))

(define (v8-pointer-field obj offset type)
  (let ((type (type-pointer (->type type))))
    (value-dereference (v8-pointer-field-ptr obj offset type))))

(define (instance-type obj)
  (let* ((map (v8-pointer-field obj "HeapObject::kMapOffset" "Map")))
    (value-cast
     (v8-field map "Map::kInstanceTypeOffset" (arch-uint8-type (current-arch)))
     (v8-type "InstanceType"))))

(define (camel-cased-instance-type type)
  (define (enum-type-name type)
    (let lp ((fields (type-fields (value-type type))))
      (match fields
        (() (format #f "(Unknown type ~a)" (value->integer type)))
        ((f . fields)
         (if (value=? (field-enumval f) type)
             (match (string-split (field-name f) #\:)
               ((_ ... tail) tail))
             (lp fields))))))
  (match (string-split (enum-type-name type) #\_)
    ((word ... "TYPE")
     (string-join (map (lambda (str)
                         (if (string-suffix? "JS" str)
                             str
                             (string-titlecase str)))
                       word)
                  ""))))

(define-syntax v8-constant-case
  (lambda (x)
    (define (visit-clauses var clauses)
      (syntax-case clauses (else)
        ((((name ...) body ...) clause ...)
         #`(if (or (value=? var (v8-constant name)) ...)
               (let () body ...)
               #,(visit-clauses var #'(clause ...))))
        (((else body ...))
         #'(let () body ...))))
    (syntax-case x (else)
      ((_ exp clause ...)
       #`(let ((var exp))
           #,(visit-clauses #'var #'(clause ...)))))))
; (put 'v8-constant-case 'scheme-indent-function 1)

(define (v8-string? obj)
  (value<? (instance-type obj) (v8-constant "FIRST_NONSTRING_TYPE")))

(define (v8-string-length str)
  (v8-smi-field str "String::kLengthOffset"))

(define* (v8-seq-string-chars str start end #:key one-byte?)
  (let* ((ptr (v8-field-ptr str "SeqString::kHeaderSize"
                            (type-pointer (arch-uint8-type (current-arch)))))
         (elt-size (if one-byte? 1 2))
         (port (open-memory #:mode "r"
                            #:start (+ (value->integer ptr) (* elt-size start))
                            #:size (* elt-size (- end start)))))
    (set-port-encoding! port (if one-byte? "ISO-8859-1" "UTF-16"))
    (read-delimited "" port)))

(define* (print-default port val #:optional
                        (type-name (type-print-name (value-type val))))
  (format port "(~a) 0x~x" type-name (value->integer val)))

(define (print-v8-smi port smi)
  (format port "~a" (v8-smi-value smi)))

(define (print-v8-failure port failure)
  (let ((payload (value-rsh failure (v8-constant "kFailureTagSize"))))
    (v8-constant-case (value-logand payload
                                    (v8-constant "kFailureTypeTagMask"))
      (("Failure::RETRY_AFTER_GC")
       (let ((space (value-rsh payload (v8-constant "kFailureTypeTagSize"))))
         (format port "Failure(RetryAfterGC(~a))"
                 (v8-constant-case space
                   (("NEW_SPACE") "NEW_SPACE")
                   (("OLD_POINTER_SPACE") "OLD_POINTER_SPACE")
                   (("OLD_DATA_SPACE") "OLD_DATA_SPACE")
                   (("CODE_SPACE") "CODE_SPACE")
                   (("MAP_SPACE") "MAP_SPACE")
                   (("CELL_SPACE") "CELL_SPACE")
                   (("PROPERTY_CELL_SPACE") "PROPERTY_CELL_SPACE")
                   (("LO_SPACE") "LO_SPACE")
                   (else (value->integer space))))))
      (("Failure::EXCEPTION")
       (display "Failure(Exception)" port))
      (("Failure::INTERNAL_ERROR")
       (display "Failure(InternalError)" port))
      (("Failure::OUT_OF_MEMORY_EXCEPTION")
       (display "Failure(OutOfMemoryException)" port))
      (else
       (format port "Failure(~a)" (value->integer payload))))))

(define* (print-v8-string port str #:optional (start 0)
                          (end (v8-string-length str)))
  (let ((type (instance-type str)))
    (v8-constant-case type
      (("SLICED_STRING_TYPE" "SLICED_ASCII_STRING_TYPE")
       (print-v8-string
        port
        (v8-pointer-field str "SlicedString::kParentOffset" "String")
        (+ start (v8-smi-field str "SlicedString::kOffsetOffset"))
        (- end start)))

      (("CONS_STRING_TYPE" "CONS_ASCII_STRING_TYPE")
       (let* ((left (v8-pointer-field str "ConsString::kFirstOffset" "String"))
              (left-len (v8-string-length left))
              (right (v8-pointer-field str "ConsString::kSecondOffset" 
"String")))
         (when (< start left-len)
           (print-v8-string port left start (min end left-len)))
         (when (> end left-len)
           (print-v8-string port right 0 (- end left-len)))))

      (("STRING_TYPE" "INTERNALIZED_STRING_TYPE")
       (display (v8-seq-string-chars str start end #:one-byte? #f) port))
      (("ASCII_STRING_TYPE" "ASCII_INTERNALIZED_STRING_TYPE")
       (display (v8-seq-string-chars str start end #:one-byte? #t) port))

      (("EXTERNAL_STRING_TYPE" "EXTERNAL_ASCII_STRING_TYPE"
        "EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE"
        "SHORT_EXTERNAL_STRING_TYPE"
        "SHORT_EXTERNAL_ASCII_STRING_TYPE"
        "SHORT_EXTERNAL_STRING_WITH_ONE_BYTE_DATA_TYPE")
       (print-default port str (v8-type "ExternalString *")))

      (("EXTERNAL_INTERNALIZED_STRING_TYPE"
        "EXTERNAL_ASCII_INTERNALIZED_STRING_TYPE"
        "EXTERNAL_INTERNALIZED_STRING_WITH_ONE_BYTE_DATA_TYPE"
        "SHORT_EXTERNAL_INTERNALIZED_STRING_TYPE"
        "SHORT_EXTERNAL_ASCII_INTERNALIZED_STRING_TYPE"
        "SHORT_EXTERNAL_INTERNALIZED_STRING_WITH_ONE_BYTE_DATA_TYPE")
       (print-default port str (v8-type "ExternalInternalizedString *")))
      (else
       (format (current-warning-port) "warning: unknown string type ~a" type)
       (print-default port str (v8-type "String *"))))))

(define (print-v8-symbol port obj)
  (let ((name (v8-pointer-field obj "Symbol::kNameOffset" "Object")))
    (display "Symbol(" port)
    (if (v8-string? name)
        (print-v8-string port name)
        (format port "0x~x" (value->integer obj)))
    (display ")" port)))

(define (print-v8-oddball port obj)
  (let ((kind (v8-smi-field obj "Oddball::kKindOffset")))
    (format port "~a"
            (v8-constant-case kind
              (("Oddball::kFalse") "false")
              (("Oddball::kTrue") "true")
              (("Oddball::kTheHole") "the-hole")
              (("Oddball::kNull") "null")
              (("Oddball::kArgumentMarker") "argument-marker")
              (("Oddball::kUndefined") "undefined")
              (("Oddball::kUninitialized") "uninitialized")
              (else (string-append "Oddball(" (number->string kind ")")))))))

(define (print-v8-heap-number port obj)
  (let ((value (v8-field obj "HeapNumber::kValueOffset"
                         (arch-double-type (current-arch)))))
    (format port "~f" (value->real value))))

(define (print-v8-heap-object port obj)
  (cond
   ((v8-string? obj)
    (print-v8-string port obj))
   (else
    (let ((type (instance-type obj)))
      (v8-constant-case type
        (("SYMBOL_TYPE") (print-v8-symbol port obj))
        (("ODDBALL_TYPE") (print-v8-oddball port obj))
        (("HEAP_NUMBER_TYPE") (print-v8-heap-number port obj))
        (else
         (let ((actual-type (v8-name (string-append
                                      (camel-cased-instance-type type)
                                      " *")))
               (declared-type (type-print-name (value-type obj))))
           (if (equal? actual-type declared-type)
               (format port "(~a) 0x~x" actual-type (value->integer obj))
               (format port "(~a) ((~a) 0x~x)" declared-type actual-type
                       (value->integer obj))))))))))

(define (print-v8-object port obj)
  (if (v8-smi? obj)
      (print-v8-smi port obj)
      (print-v8-heap-object port obj)))

(define (print-v8-maybe-object port maybe-obj)
  (if (v8-failure? maybe-obj)
      (print-v8-failure port maybe-obj)
      (print-v8-object port maybe-obj)))

(define (print-v8-handle port handle)
  (let ((loc (value-field handle "location_")))
    (display "Handle(" port)
    (if (value=? loc 0)
        (print-default port loc
                       (type-print-name (type-target (value-type loc))))
        (print-v8-object port (value-dereference loc)))
    (display ")" port)))

(define* (install-pretty-printers #:optional (objfile (current-objfile)))
  (define (writer-worker printer value)
    (lambda (_)
      (call-with-output-string
       (lambda (port) (printer port value)))))
  (define (make-worker hint printer value)
    (make-pretty-printer-worker hint (writer-worker printer value) #f))
  (define (default-predicate name)
    (lambda (value)
      (equal? (type-print-name (value-type value))
              (v8-name (string-append name " *")))))
  (define* (register! name hint printer #:optional
                      (predicate (default-predicate name)))
    (define (handler pp value)
      (and (predicate value)
           (make-worker hint printer value)))
    (prepend-pretty-printer! objfile (make-pretty-printer name handler)))

  (register! "MaybeObject" #f print-v8-maybe-object)
  (register! "Object" #f print-v8-object)
  (register! "Smi" #f print-v8-smi)
  (register! "String" "string" print-v8-string)
  (register! "Handle" #f print-v8-handle
             (lambda (value)
               (let ((type (value-type value)))
                 (string-prefix? (v8-name "Handle<") (type-print-name type))))))

(install-pretty-printers)

Reply via email to