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 | |
---|
38 | Available disassemblers are configured by pushing a strategy to SYSTEM:*DISASSEMBLERS*. |
---|
39 | |
---|
40 | SYSTEM: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 | |
---|
46 | The pairs (keyword function) contain a keyword identifying this |
---|
47 | particulat disassembler, and a symbol designating function takes a |
---|
48 | object to disassemble. |
---|
49 | |
---|
50 | Use SYS:CHOOSE-DISASSEMBLER to install a given disassembler as the one |
---|
51 | used by CL:DISASSEMBLE. Additional disassemblers/decompilers are |
---|
52 | packaged in the ABCL-INTROSPECT contrib. |
---|
53 | |
---|
54 | The intial default is :javap using the javap command line tool which |
---|
55 | is 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 | |
---|
61 | With optional keyword NAME, select the associated disassembler from |
---|
62 | SYS:*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 | |
---|