| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ddmuilib; |
| |
| import com.android.ddmlib.Log; |
| import com.android.ddmlib.NativeLibraryMapInfo; |
| import com.android.ddmlib.NativeStackCallInfo; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * Represents an addr2line process to get filename/method information from a |
| * memory address.<br> |
| * Each process can only handle one library, which should be provided when |
| * creating a new process.<br> |
| * <br> |
| * The processes take some time to load as they need to parse the library files. |
| * For this reason, processes cannot be manually started. Instead the class |
| * keeps an internal list of processes and one asks for a process for a specific |
| * library, using <code>getProcess(String library)<code>.<br></br> |
| * Internally, the processes are started in pipe mode to be able to query them |
| * with multiple addresses. |
| */ |
| public class Addr2Line { |
| private static final String ANDROID_SYMBOLS_ENVVAR = "ANDROID_SYMBOLS"; |
| |
| private static final String LIBRARY_NOT_FOUND_MESSAGE_FORMAT = |
| "Unable to locate library %s on disk. Addresses mapping to this library " |
| + "will not be resolved. In order to fix this, set the the library search path " |
| + "in the UI, or set the environment variable " + ANDROID_SYMBOLS_ENVVAR + "."; |
| |
| /** |
| * Loaded processes list. This is also used as a locking object for any |
| * methods dealing with starting/stopping/creating processes/querying for |
| * method. |
| */ |
| private static final HashMap<String, Addr2Line> sProcessCache = |
| new HashMap<String, Addr2Line>(); |
| |
| /** |
| * byte array representing a carriage return. Used to push addresses in the |
| * process pipes. |
| */ |
| private static final byte[] sCrLf = { |
| '\n' |
| }; |
| |
| /** Path to the library */ |
| private NativeLibraryMapInfo mLibrary; |
| |
| /** the command line process */ |
| private Process mProcess; |
| |
| /** buffer to read the result of the command line process from */ |
| private BufferedReader mResultReader; |
| |
| /** |
| * output stream to provide new addresses to decode to the command line |
| * process |
| */ |
| private BufferedOutputStream mAddressWriter; |
| |
| private static final String DEFAULT_LIBRARY_SYMBOLS_FOLDER; |
| static { |
| String symbols = System.getenv(ANDROID_SYMBOLS_ENVVAR); |
| if (symbols == null) { |
| DEFAULT_LIBRARY_SYMBOLS_FOLDER = DdmUiPreferences.getSymbolDirectory(); |
| } else { |
| DEFAULT_LIBRARY_SYMBOLS_FOLDER = symbols; |
| } |
| } |
| |
| private static List<String> mLibrarySearchPaths = new ArrayList<String>(); |
| |
| /** |
| * Set the search path where libraries should be found. |
| * @param path search path to use, can be a colon separated list of paths if multiple folders |
| * should be searched |
| */ |
| public static void setSearchPath(String path) { |
| mLibrarySearchPaths.clear(); |
| mLibrarySearchPaths.addAll(Arrays.asList(path.split(":"))); |
| } |
| |
| /** |
| * Returns the instance of a Addr2Line process for the specified library. |
| * <br>The library should be in a format that makes<br> |
| * <code>$ANDROID_PRODUCT_OUT + "/symbols" + library</code> a valid file. |
| * |
| * @param library the library in which to look for addresses. |
| * @return a new Addr2Line object representing a started process, ready to |
| * be queried for addresses. If any error happened when launching a |
| * new process, <code>null</code> will be returned. |
| */ |
| public static Addr2Line getProcess(final NativeLibraryMapInfo library) { |
| String libName = library.getLibraryName(); |
| |
| // synchronize around the hashmap object |
| if (libName != null) { |
| synchronized (sProcessCache) { |
| // look for an existing process |
| Addr2Line process = sProcessCache.get(libName); |
| |
| // if we don't find one, we create it |
| if (process == null) { |
| process = new Addr2Line(library); |
| |
| // then we start it |
| boolean status = process.start(); |
| |
| if (status) { |
| // if starting the process worked, then we add it to the |
| // list. |
| sProcessCache.put(libName, process); |
| } else { |
| // otherwise we just drop the object, to return null |
| process = null; |
| } |
| } |
| // return the process |
| return process; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Construct the object with a library name. The library should be present |
| * in the search path as provided by ANDROID_SYMBOLS, ANDROID_OUT/symbols, or in the user |
| * provided search path. |
| * |
| * @param library the library in which to look for address. |
| */ |
| private Addr2Line(final NativeLibraryMapInfo library) { |
| mLibrary = library; |
| } |
| |
| /** |
| * Search for the library in the library search path and obtain the full path to where it |
| * is found. |
| * @return fully resolved path to the library if found in search path, null otherwise |
| */ |
| private String getLibraryPath(String library) { |
| // first check the symbols folder |
| String path = DEFAULT_LIBRARY_SYMBOLS_FOLDER + library; |
| if (new File(path).exists()) { |
| return path; |
| } |
| |
| for (String p : mLibrarySearchPaths) { |
| // try appending the full path on device |
| String fullPath = p + "/" + library; |
| if (new File(fullPath).exists()) { |
| return fullPath; |
| } |
| |
| // try appending basename(library) |
| fullPath = p + "/" + new File(library).getName(); |
| if (new File(fullPath).exists()) { |
| return fullPath; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Starts the command line process. |
| * |
| * @return true if the process was started, false if it failed to start, or |
| * if there was any other errors. |
| */ |
| private boolean start() { |
| // because this is only called from getProcess() we know we don't need |
| // to synchronize this code. |
| |
| String addr2Line = System.getenv("ANDROID_ADDR2LINE"); |
| if (addr2Line == null) { |
| addr2Line = DdmUiPreferences.getAddr2Line(); |
| } |
| |
| // build the command line |
| String[] command = new String[5]; |
| command[0] = addr2Line; |
| command[1] = "-C"; |
| command[2] = "-f"; |
| command[3] = "-e"; |
| |
| String fullPath = getLibraryPath(mLibrary.getLibraryName()); |
| if (fullPath == null) { |
| String msg = String.format(LIBRARY_NOT_FOUND_MESSAGE_FORMAT, mLibrary.getLibraryName()); |
| Log.e("ddm-Addr2Line", msg); |
| return false; |
| } |
| |
| command[4] = fullPath; |
| |
| try { |
| // attempt to start the process |
| mProcess = Runtime.getRuntime().exec(command); |
| |
| if (mProcess != null) { |
| // get the result reader |
| InputStreamReader is = new InputStreamReader(mProcess |
| .getInputStream()); |
| mResultReader = new BufferedReader(is); |
| |
| // get the outstream to write the addresses |
| mAddressWriter = new BufferedOutputStream(mProcess |
| .getOutputStream()); |
| |
| // check our streams are here |
| if (mResultReader == null || mAddressWriter == null) { |
| // not here? stop the process and return false; |
| mProcess.destroy(); |
| mProcess = null; |
| return false; |
| } |
| |
| // return a success |
| return true; |
| } |
| |
| } catch (IOException e) { |
| // log the error |
| String msg = String.format( |
| "Error while trying to start %1$s process for library %2$s", |
| DdmUiPreferences.getAddr2Line(), mLibrary); |
| Log.e("ddm-Addr2Line", msg); |
| |
| // drop the process just in case |
| if (mProcess != null) { |
| mProcess.destroy(); |
| mProcess = null; |
| } |
| } |
| |
| // we can be here either cause the allocation of mProcess failed, or we |
| // caught an exception |
| return false; |
| } |
| |
| /** |
| * Stops the command line process. |
| */ |
| public void stop() { |
| synchronized (sProcessCache) { |
| if (mProcess != null) { |
| // remove the process from the list |
| sProcessCache.remove(mLibrary); |
| |
| // then stops the process |
| mProcess.destroy(); |
| |
| // set the reference to null. |
| // this allows to make sure another thread calling getAddress() |
| // will not query a stopped thread |
| mProcess = null; |
| } |
| } |
| } |
| |
| /** |
| * Stops all current running processes. |
| */ |
| public static void stopAll() { |
| // because of concurrent access (and our use of HashMap.values()), we |
| // can't rely on the synchronized inside stop(). We need to put one |
| // around the whole loop. |
| synchronized (sProcessCache) { |
| // just a basic loop on all the values in the hashmap and call to |
| // stop(); |
| Collection<Addr2Line> col = sProcessCache.values(); |
| for (Addr2Line a2l : col) { |
| a2l.stop(); |
| } |
| } |
| } |
| |
| /** |
| * Looks up an address and returns method name, source file name, and line |
| * number. |
| * |
| * @param addr the address to look up |
| * @return a BacktraceInfo object containing the method/filename/linenumber |
| * or null if the process we stopped before the query could be |
| * processed, or if an IO exception happened. |
| */ |
| public NativeStackCallInfo getAddress(long addr) { |
| long offset = addr - mLibrary.getStartAddress(); |
| |
| // even though we don't access the hashmap object, we need to |
| // synchronized on it to prevent |
| // another thread from stopping the process we're going to query. |
| synchronized (sProcessCache) { |
| // check the process is still alive/allocated |
| if (mProcess != null) { |
| // prepare to the write the address to the output buffer. |
| |
| // first, conversion to a string containing the hex value. |
| String tmp = Long.toString(offset, 16); |
| |
| try { |
| // write the address to the buffer |
| mAddressWriter.write(tmp.getBytes()); |
| |
| // add CR-LF |
| mAddressWriter.write(sCrLf); |
| |
| // flush it all. |
| mAddressWriter.flush(); |
| |
| // read the result. We need to read 2 lines |
| String method = mResultReader.readLine(); |
| String source = mResultReader.readLine(); |
| |
| // make the backtrace object and return it |
| if (method != null && source != null) { |
| return new NativeStackCallInfo(addr, mLibrary.getLibraryName(), method, source); |
| } |
| } catch (IOException e) { |
| // log the error |
| Log.e("ddms", |
| "Error while trying to get information for addr: " |
| + tmp + " in library: " + mLibrary); |
| // we'll return null later |
| } |
| } |
| } |
| return null; |
| } |
| } |