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