• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ddmuilib;
18 
19 import com.android.ddmlib.*;
20 
21 import java.io.BufferedOutputStream;
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.util.Collection;
26 import java.util.HashMap;
27 
28 /**
29  * Represents an addr2line process to get filename/method information from a
30  * memory address.<br>
31  * Each process can only handle one library, which should be provided when
32  * creating a new process.<br>
33  * <br>
34  * The processes take some time to load as they need to parse the library files.
35  * For this reason, processes cannot be manually started. Instead the class
36  * keeps an internal list of processes and one asks for a process for a specific
37  * library, using <code>getProcess(String library)<code>.<br></br>
38  * Internally, the processes are started in pipe mode to be able to query them
39  * with multiple addresses.
40  */
41 public class Addr2Line {
42 
43     /**
44      * Loaded processes list. This is also used as a locking object for any
45      * methods dealing with starting/stopping/creating processes/querying for
46      * method.
47      */
48     private static final HashMap<String, Addr2Line> sProcessCache =
49             new HashMap<String, Addr2Line>();
50 
51     /**
52      * byte array representing a carriage return. Used to push addresses in the
53      * process pipes.
54      */
55     private static final byte[] sCrLf = {
56         '\n'
57     };
58 
59     /** Path to the library */
60     private String mLibrary;
61 
62     /** the command line process */
63     private Process mProcess;
64 
65     /** buffer to read the result of the command line process from */
66     private BufferedReader mResultReader;
67 
68     /**
69      * output stream to provide new addresses to decode to the command line
70      * process
71      */
72     private BufferedOutputStream mAddressWriter;
73 
74     /**
75      * Returns the instance of a Addr2Line process for the specified library.
76      * <br>The library should be in a format that makes<br>
77      * <code>$ANDROID_PRODUCT_OUT + "/symbols" + library</code> a valid file.
78      *
79      * @param library the library in which to look for addresses.
80      * @return a new Addr2Line object representing a started process, ready to
81      *         be queried for addresses. If any error happened when launching a
82      *         new process, <code>null</code> will be returned.
83      */
getProcess(final String library)84     public static Addr2Line getProcess(final String library) {
85         // synchronize around the hashmap object
86         if (library != null) {
87             synchronized (sProcessCache) {
88                 // look for an existing process
89                 Addr2Line process = sProcessCache.get(library);
90 
91                 // if we don't find one, we create it
92                 if (process == null) {
93                     process = new Addr2Line(library);
94 
95                     // then we start it
96                     boolean status = process.start();
97 
98                     if (status) {
99                         // if starting the process worked, then we add it to the
100                         // list.
101                         sProcessCache.put(library, process);
102                     } else {
103                         // otherwise we just drop the object, to return null
104                         process = null;
105                     }
106                 }
107                 // return the process
108                 return process;
109             }
110         }
111         return null;
112     }
113 
114     /**
115      * Construct the object with a library name.
116      * <br>The library should be in a format that makes<br>
117      * <code>$ANDROID_PRODUCT_OUT + "/symbols" + library</code> a valid file.
118      *
119      * @param library the library in which to look for address.
120      */
Addr2Line(final String library)121     private Addr2Line(final String library) {
122         mLibrary = library;
123     }
124 
125     /**
126      * Starts the command line process.
127      *
128      * @return true if the process was started, false if it failed to start, or
129      *         if there was any other errors.
130      */
start()131     private boolean start() {
132         // because this is only called from getProcess() we know we don't need
133         // to synchronize this code.
134 
135         // get the output directory.
136         String symbols = System.getenv("ANDROID_SYMBOLS");
137         if (symbols == null) {
138             symbols = DdmUiPreferences.getSymbolDirectory();
139         }
140 
141         // build the command line
142         String[] command = new String[5];
143         command[0] = DdmUiPreferences.getAddr2Line();
144         command[1] = "-C";
145         command[2] = "-f";
146         command[3] = "-e";
147         command[4] = symbols + mLibrary.replaceAll("libc\\.so", "libc_debug\\.so");
148 
149         try {
150             // attempt to start the process
151             mProcess = Runtime.getRuntime().exec(command);
152 
153             if (mProcess != null) {
154                 // get the result reader
155                 InputStreamReader is = new InputStreamReader(mProcess
156                         .getInputStream());
157                 mResultReader = new BufferedReader(is);
158 
159                 // get the outstream to write the addresses
160                 mAddressWriter = new BufferedOutputStream(mProcess
161                         .getOutputStream());
162 
163                 // check our streams are here
164                 if (mResultReader == null || mAddressWriter == null) {
165                     // not here? stop the process and return false;
166                     mProcess.destroy();
167                     mProcess = null;
168                     return false;
169                 }
170 
171                 // return a success
172                 return true;
173             }
174 
175         } catch (IOException e) {
176             // log the error
177             String msg = String.format(
178                     "Error while trying to start %1$s process for library %2$s",
179                     DdmUiPreferences.getAddr2Line(), mLibrary);
180             Log.e("ddm-Addr2Line", msg);
181 
182             // drop the process just in case
183             if (mProcess != null) {
184                 mProcess.destroy();
185                 mProcess = null;
186             }
187         }
188 
189         // we can be here either cause the allocation of mProcess failed, or we
190         // caught an exception
191         return false;
192     }
193 
194     /**
195      * Stops the command line process.
196      */
stop()197     public void stop() {
198         synchronized (sProcessCache) {
199             if (mProcess != null) {
200                 // remove the process from the list
201                 sProcessCache.remove(mLibrary);
202 
203                 // then stops the process
204                 mProcess.destroy();
205 
206                 // set the reference to null.
207                 // this allows to make sure another thread calling getAddress()
208                 // will not query a stopped thread
209                 mProcess = null;
210             }
211         }
212     }
213 
214     /**
215      * Stops all current running processes.
216      */
stopAll()217     public static void stopAll() {
218         // because of concurrent access (and our use of HashMap.values()), we
219         // can't rely on the synchronized inside stop(). We need to put one
220         // around the whole loop.
221         synchronized (sProcessCache) {
222             // just a basic loop on all the values in the hashmap and call to
223             // stop();
224             Collection<Addr2Line> col = sProcessCache.values();
225             for (Addr2Line a2l : col) {
226                 a2l.stop();
227             }
228         }
229     }
230 
231     /**
232      * Looks up an address and returns method name, source file name, and line
233      * number.
234      *
235      * @param addr the address to look up
236      * @return a BacktraceInfo object containing the method/filename/linenumber
237      *         or null if the process we stopped before the query could be
238      *         processed, or if an IO exception happened.
239      */
getAddress(long addr)240     public NativeStackCallInfo getAddress(long addr) {
241         // even though we don't access the hashmap object, we need to
242         // synchronized on it to prevent
243         // another thread from stopping the process we're going to query.
244         synchronized (sProcessCache) {
245             // check the process is still alive/allocated
246             if (mProcess != null) {
247                 // prepare to the write the address to the output buffer.
248 
249                 // first, conversion to a string containing the hex value.
250                 String tmp = Long.toString(addr, 16);
251 
252                 try {
253                     // write the address to the buffer
254                     mAddressWriter.write(tmp.getBytes());
255 
256                     // add CR-LF
257                     mAddressWriter.write(sCrLf);
258 
259                     // flush it all.
260                     mAddressWriter.flush();
261 
262                     // read the result. We need to read 2 lines
263                     String method = mResultReader.readLine();
264                     String source = mResultReader.readLine();
265 
266                     // make the backtrace object and return it
267                     if (method != null && source != null) {
268                         return new NativeStackCallInfo(mLibrary, method, source);
269                     }
270                 } catch (IOException e) {
271                     // log the error
272                     Log.e("ddms",
273                             "Error while trying to get information for addr: "
274                                     + tmp + " in library: " + mLibrary);
275                     // we'll return null later
276                 }
277             }
278         }
279         return null;
280     }
281 }
282