source: trunk/abcl/src/org/armedbear/lisp/disassemble.lisp @ 15578

Last change on this file since 15578 was 15578, checked in by Mark Evenson, 10 months ago

Fix DISASSEMBLE in some situations by falling back to funcall

Originally part of <https://github.com/armedbear/abcl/pull/437/>

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 10.5 KB
Line 
1;;; disassemble.lisp
2;;;
3;;; Copyright (C) 2005 Peter Graves
4;;; $Id: disassemble.lisp 15578 2022-05-23 06:23:38Z mevenson $
5;;;
6;;; This program is free software; you can redistribute it and/or
7;;; modify it under the terms of the GNU General Public License
8;;; as published by the Free Software Foundation; either version 2
9;;; of the License, or (at your option) any later version.
10;;;
11;;; This program is distributed in the hope that it will be useful,
12;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with this program; if not, write to the Free Software
18;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19;;;
20;;; As a special exception, the copyright holders of this library give you
21;;; permission to link this library with independent modules to produce an
22;;; executable, regardless of the license terms of these independent
23;;; modules, and to copy and distribute the resulting executable under
24;;; terms of your choice, provided that you also meet, for each linked
25;;; independent module, the terms and conditions of the license of that
26;;; module.  An independent module is a module which is not derived from
27;;; or based on this library.  If you modify this library, you may extend
28;;; this exception to your version of the library, but you are not
29;;; obligated to do so.  If you do not wish to do so, delete this
30;;; exception statement from your version.
31
32(in-package :system)
33(require :clos)
34
35(defvar *disassembler-function* nil
36  "The currently used function for CL:DISASSEMBLE. 
37
38Available disassemblers are configured by pushing a strategy to SYSTEM:*DISASSEMBLERS*. 
39
40SYSTEM:CHOOSE-DISASSEMBLER selects a current strategy from this list .")
41
42(defvar *disassemblers*
43  `((:system-javap . disassemble-class-bytes))
44  "Methods of invoking CL:DISASSEMBLE consisting of a enumeration of (keyword function) pairs
45
46The pairs (keyword function) contain a keyword identifying this
47particulat disassembler, and a symbol designating function takes a
48object to disassemble.
49
50Use SYS:CHOOSE-DISASSEMBLER to install a given disassembler as the one
51used by CL:DISASSEMBLE.  Additional disassemblers/decompilers are
52packaged in the ABCL-INTROSPECT contrib.
53
54The intial default is :javap using the javap command line tool which
55is part of the Java Developement Kit.
56")
57
58(defun choose-disassembler (&optional name)
59  "Report current disassembler that would be used by CL:DISASSEMBLE
60
61With optional keyword NAME, select the associated disassembler from
62SYS:*DISASSEMBLERS*."
63  (flet ((sane-disassembler-p (disassembler)
64           (and disassembler
65                (fboundp disassembler))))
66    (setf *disassembler-function*
67          (if name
68              (let ((disassembler (cdr (assoc name *disassemblers*))))
69                (if (sane-disassembler-p disassembler)
70                    disassembler
71                    (error "Disassembler ~a doesn't appear to work." name)))
72              (if (sane-disassembler-p *disassembler-function*)
73                  *disassembler-function*
74                  ;; simplest strategy: choose the first working one
75                  (loop
76                    :for (nil . disassembler) in *disassemblers*
77                    :when (sane-disassembler-p disassembler)
78                      :do (return disassembler)
79                    :finally (warn "Can't find suitable disassembler.")))))))
80
81(eval-when (:compile-toplevel :load-toplevel :execute)
82  (defmacro with-open ((name value) &body body)
83    `(let ((,name ,value))
84       (unwind-protect
85           (progn ,@body)
86         (java:jcall-raw "close" ,name)))))
87
88(defun read-byte-array-from-stream (stream)
89  (let ((buffer (java:jnew-array (java:jclass "byte") 4096)))
90    (with-open (output (java:jnew "java.io.ByteArrayOutputStream"))
91      (loop
92        for length = (java:jcall "read" stream buffer)
93        until (eql length -1)
94        do (java:jcall-raw "write" output buffer 0 length))
95      (java:jcall-raw "flush" output)
96      (java:jcall-raw "toByteArray" output))))
97
98(defun class-resource-path (class)
99  (format NIL "~A.class" (substitute #\/ #\. (java:jcall "getName" class))))
100
101(defun class-bytes (class)
102  (with-open (stream (java:jcall-raw
103                      "getResourceAsStream"
104                      (java:jcall-raw "getClassLoader" class)
105                      (class-resource-path class)))
106    (read-byte-array-from-stream stream)))
107
108(defun disassemble-bytes (bytes)
109  "Disassemble jvm code BYTES returning a string."
110  (funcall (or *disassembler-function* (choose-disassembler))
111           bytes))
112
113(defun disassemble-function (arg)
114  (let ((function (cond ((java::java-object-p arg) 
115                         (cond ((java::jinstance-of-p arg "java.lang.Class")
116                                arg)
117                               ((java::jinstance-of-p arg "java.lang.reflect.Method")
118                                (java::jmethod-declaring-class arg))
119                               ))
120                        ((functionp arg)
121                         arg)
122                        ((symbolp arg)
123                         (or (macro-function arg) (symbol-function arg)))
124                        (t arg))))
125    (when (typep function 'generic-function)
126      (setf function (mop::funcallable-instance-function function)))
127    ;; use isInstance instead of jinstance-of-p
128    ;; because the latter checked java-object-p
129    ;; which fails since its a lisp object
130    (when (and (java:jcall "isInstance"  (java:jclass "org.armedbear.lisp.Closure") function)
131               (not (java:jcall "isInstance"  (java:jclass "org.armedbear.lisp.CompiledClosure") function)))
132      (return-from disassemble-function 
133        (with-output-to-string (s)
134          (format s "Not a compiled function: ~%")
135          (pprint (java:jcall "getBody" function) s))))
136    (let ((bytes (or (and (java:jcall "isInstance" (java:jclass "org.armedbear.lisp.Function") function)
137                          (ignore-errors (getf (function-plist function))) 'class-bytes)
138                     (and (java:jcall "isInstance" (java:jclass "org.armedbear.lisp.CompiledClosure") function)
139                          (equalp (java::jcall "getName" (java::jobject-class 
140                                                          (java:jcall "getClassLoader" (java::jcall "getClass" function))))
141                                  "org.armedbear.lisp.FaslClassLoader")
142                          (fasl-compiled-closure-class-bytes function)))))
143      ;; we've got bytes here then we've covered the case that the disassembler already handled
144      ;; If not then we've either got a primitive (in function) or we got passed a method object as arg.
145      (if bytes
146          (disassemble-bytes bytes)
147          (let ((class (if (java:java-object-p function) function (java:jcall "getClass" function))))
148            (let ((classloader (java:jcall "getClassLoader" class)))
149              (if (or (java:jinstance-of-p classloader "org.armedbear.lisp.MemoryClassLoader")
150                      (java:jinstance-of-p classloader "org.armedbear.lisp.FaslClassLoader"))
151                  (disassemble-bytes
152                   (or
153                    (ignore-errors
154                     (java:jcall "getFunctionClassBytes" classloader class))
155                    ;;; alanr found that in certain situations (under
156                    ;;; OSGI?) that one has to explicitly FUNCALL the
157                    ;;; function slot, so we fall back to that strategy.
158                    (ignore-errors
159                     (funcall (java:jfield "org.armedbear.lisp.Function" "FUNCTION_CLASS_BYTES") function))))
160                  (disassemble-bytes
161                   (read-byte-array-from-stream
162                    (java:jcall-raw
163                     "getResourceAsStream"
164                     (java:jcall-raw "getClassLoader" class)
165                     (class-resource-path class)))))))))))
166
167(defparameter +propertyList+ 
168  (load-time-value
169   (let ((it (find "propertyList" (java::jcall "getDeclaredFields" (java::jclass "org.armedbear.lisp.Function")) :key (lambda(e)(java::jcall "getName" e)) :test 'equal)))
170     (java::jcall "setAccessible" it t)
171     it)))
172
173(defun function-plist (function)
174  (java::jcall "get" +propertylist+ function))
175
176(defun (setf function-plist) (new function)
177  (java::jcall "set" +propertylist+ function new))
178
179;; PITA. make loadedFrom public
180;;; TODO Java9 work out a sensible story to preserve existing values if required
181(defun get-loaded-from (function)
182  (let* ((jfield (find "loadedFrom" (java:jcall "getDeclaredFields" (java:jclass "org.armedbear.lisp.Function")) 
183                       :key 'java:jfield-name :test 'equal)))
184    (java:jcall "setAccessible" jfield java:+true+)
185    (java:jcall "get" jfield function)))
186
187(defun set-loaded-from (function value)
188  (let* ((jfield (find "loadedFrom" (java:jcall "getDeclaredFields" (java:jclass "org.armedbear.lisp.Function")) 
189                       :key 'java:jfield-name :test 'equal)))
190    (java:jcall "setAccessible" jfield java:+true+)
191    (java:jcall "set" jfield function value)))
192
193;; because getFunctionClassBytes gets a null pointer exception
194(defun fasl-compiled-closure-class-bytes (function)
195  (let* ((loaded-from (get-loaded-from function))
196         (class-name (subseq (java:jcall "getName" (java:jcall "getClass" function)) (length "org.armedbear.lisp.")))
197         (url (if (not (eq (pathname-device loaded-from) :unspecific))
198                  ;; we're loading from a jar
199                  (java:jnew "java.net.URL" 
200                             (namestring (make-pathname :directory (pathname-directory loaded-from)
201                                                               :device (pathname-device loaded-from)
202                                                               :name class-name :type "cls")))
203                  ;; we're loading from a fasl file
204                  (java:jnew "java.net.URL" (namestring (make-pathname :device (list loaded-from)
205                                                                       :name class-name :type "cls"))))))
206    (read-byte-array-from-stream (java:jcall "openStream" url))))
207
208;; closure bindings
209;; (get-java-field (elt (#"get" (elt (#"getFields" (#"getClass" #'foo)) 0) #'foo) 0) "value")
210
211(defun disassemble (arg)
212  (print-lines-with-prefix (disassemble-function arg)))
213
214(defun print-lines-with-prefix (string)
215  (with-input-from-string (stream string)
216    (loop
217      (let ((line (read-line stream nil)))
218        (unless line (return))
219        (write-string "; ")
220        (write-string line)
221        (terpri)))))
222
Note: See TracBrowser for help on using the repository browser.