aboutsummaryrefslogtreecommitdiffstats
path: root/ddms/libs
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2011-10-09 14:44:55 -0700
committerSiva Velusamy <vsiva@google.com>2011-10-10 10:18:31 -0700
commit9d02c9ee4fa5a92420fa16e0e762d9cd079cee17 (patch)
tree42d2dc260fad452ec5bcb1fb84934ba38211a627 /ddms/libs
parent447dfafcabb6c30be5f21678f20301df9830a1cc (diff)
downloadsdk-9d02c9ee4fa5a92420fa16e0e762d9cd079cee17.zip
sdk-9d02c9ee4fa5a92420fa16e0e762d9cd079cee17.tar.gz
sdk-9d02c9ee4fa5a92420fa16e0e762d9cd079cee17.tar.bz2
Add a new implementation for symbol resolution.
The old implementation can still be used by setting the env variable ANDROID_DDMS_OLD_SYMRESOLVER. With the new one, all errors are collected, and displayed in the UI at the end, grouping similar errors together. Change-Id: Ibf2fa2122109b2eaf37579d65a3d0b18f475b436
Diffstat (limited to 'ddms/libs')
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/DdmUiPreferences.java12
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeHeapPanel.java132
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeSymbolResolverTask.java305
3 files changed, 436 insertions, 13 deletions
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DdmUiPreferences.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DdmUiPreferences.java
index f832a4e..db3642b 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DdmUiPreferences.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DdmUiPreferences.java
@@ -28,9 +28,9 @@ public final class DdmUiPreferences {
public static final int DEFAULT_THREAD_REFRESH_INTERVAL = 4; // seconds
private static int sThreadRefreshInterval = DEFAULT_THREAD_REFRESH_INTERVAL;
-
+
private static IPreferenceStore mStore;
-
+
private static String sSymbolLocation =""; //$NON-NLS-1$
private static String sAddr2LineLocation =""; //$NON-NLS-1$
private static String sTraceviewLocation =""; //$NON-NLS-1$
@@ -38,7 +38,7 @@ public final class DdmUiPreferences {
public static void setStore(IPreferenceStore store) {
mStore = store;
}
-
+
public static IPreferenceStore getStore() {
return mStore;
}
@@ -50,8 +50,8 @@ public final class DdmUiPreferences {
public static void setThreadRefreshInterval(int port) {
sThreadRefreshInterval = port;
}
-
- static String getSymbolDirectory() {
+
+ public static String getSymbolDirectory() {
return sSymbolLocation;
}
@@ -59,7 +59,7 @@ public final class DdmUiPreferences {
sSymbolLocation = location;
}
- static String getAddr2Line() {
+ public static String getAddr2Line() {
return sAddr2LineLocation;
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeHeapPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeHeapPanel.java
index 7bea050..e085f2c 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeHeapPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeHeapPanel.java
@@ -76,12 +76,26 @@ import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/** Panel to display native heap information. */
public class NativeHeapPanel extends BaseHeapPanel {
+ private static final boolean USE_OLD_RESOLVER;
+ static {
+ String useOldResolver = System.getenv("ANDROID_DDMS_OLD_SYMRESOLVER");
+ if (useOldResolver != null && useOldResolver.equalsIgnoreCase("true")) {
+ USE_OLD_RESOLVER = true;
+ } else {
+ USE_OLD_RESOLVER = false;
+ }
+ }
+ private final int MAX_DISPLAYED_ERROR_ITEMS = 5;
+
private static final String TOOLTIP_EXPORT_DATA = "Export Heap Data";
private static final String TOOLTIP_ZYGOTE_ALLOCATIONS = "Show Zygote Allocations";
private static final String TOOLTIP_DIFFS_ONLY = "Only show new allocations not present in previous snapshot";
@@ -151,7 +165,7 @@ public class NativeHeapPanel extends BaseHeapPanel {
}
/** {@inheritDoc} */
- public void clientChanged(Client client, int changeMask) {
+ public void clientChanged(final Client client, int changeMask) {
if (client != getCurrentClient()) {
return;
}
@@ -167,17 +181,121 @@ public class NativeHeapPanel extends BaseHeapPanel {
// We need to clone this list since getClientData().getNativeAllocationList() clobbers
// the list on future updates
- allocations = shallowCloneList(allocations);
+ final List<NativeAllocationInfo> nativeAllocations = shallowCloneList(allocations);
- addNativeHeapSnapshot(new NativeHeapSnapshot(allocations));
+ addNativeHeapSnapshot(new NativeHeapSnapshot(nativeAllocations));
updateDisplay();
// Attempt to resolve symbols in a separate thread.
// The UI should be refreshed once the symbols have been resolved.
- Thread t = new Thread(new SymbolResolverTask(allocations,
- client.getClientData().getMappedNativeLibraries()));
- t.setName("Address to Symbol Resolver");
- t.start();
+ if (USE_OLD_RESOLVER) {
+ Thread t = new Thread(new SymbolResolverTask(nativeAllocations,
+ client.getClientData().getMappedNativeLibraries()));
+ t.setName("Address to Symbol Resolver");
+ t.start();
+ } else {
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ resolveSymbols();
+ mDetailsTreeViewer.refresh();
+ mStackTraceTreeViewer.refresh();
+ }
+
+ public void resolveSymbols() {
+ Shell shell = Display.getDefault().getActiveShell();
+ ProgressMonitorDialog d = new ProgressMonitorDialog(shell);
+
+ NativeSymbolResolverTask resolver = new NativeSymbolResolverTask(
+ nativeAllocations,
+ client.getClientData().getMappedNativeLibraries(),
+ mSymbolSearchPathText.getText());
+
+ try {
+ d.run(true, true, resolver);
+ } catch (InvocationTargetException e) {
+ MessageDialog.openError(shell,
+ "Error Resolving Symbols",
+ e.getCause().getMessage());
+ return;
+ } catch (InterruptedException e) {
+ return;
+ }
+
+ MessageDialog.openInformation(shell, "Symbol Resolution Status",
+ getResolutionStatusMessage(resolver));
+ }
+ });
+ }
+ }
+
+ private String getResolutionStatusMessage(NativeSymbolResolverTask resolver) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Symbol Resolution Complete.\n\n");
+
+ // show addresses that were not mapped
+ Set<Long> unmappedAddresses = resolver.getUnmappedAddresses();
+ if (unmappedAddresses.size() > 0) {
+ sb.append(String.format("Unmapped addresses (%d): ",
+ unmappedAddresses.size()));
+ sb.append(getSampleForDisplay(unmappedAddresses));
+ sb.append('\n');
+ }
+
+ // show libraries that were not present on disk
+ Set<String> notFoundLibraries = resolver.getNotFoundLibraries();
+ if (notFoundLibraries.size() > 0) {
+ sb.append(String.format("Libraries not found on disk (%d): ",
+ notFoundLibraries.size()));
+ sb.append(getSampleForDisplay(notFoundLibraries));
+ sb.append('\n');
+ }
+
+ // show addresses that were mapped but not resolved
+ Set<Long> unresolvableAddresses = resolver.getUnresolvableAddresses();
+ if (unresolvableAddresses.size() > 0) {
+ sb.append(String.format("Unresolved addresses (%d): ",
+ unresolvableAddresses.size()));
+ sb.append(getSampleForDisplay(unresolvableAddresses));
+ sb.append('\n');
+ }
+
+ if (resolver.getAddr2LineErrorMessage() != null) {
+ sb.append("Error launching addr2line: ");
+ sb.append(resolver.getAddr2LineErrorMessage());
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Get the string representation for a collection of items.
+ * If there are more items than {@link #MAX_DISPLAYED_ERROR_ITEMS}, then only the first
+ * {@link #MAX_DISPLAYED_ERROR_ITEMS} items are taken into account,
+ * and an ellipsis is added at the end.
+ */
+ private String getSampleForDisplay(Collection<?> items) {
+ StringBuilder sb = new StringBuilder();
+
+ int c = 1;
+ Iterator<?> it = items.iterator();
+ while (it.hasNext()) {
+ Object item = it.next();
+ if (item instanceof Long) {
+ sb.append(String.format("0x%x", item));
+ } else {
+ sb.append(item);
+ }
+
+ if (c == MAX_DISPLAYED_ERROR_ITEMS && it.hasNext()) {
+ sb.append(", ...");
+ break;
+ } else if (it.hasNext()) {
+ sb.append(", ");
+ }
+
+ c++;
+ }
+ return sb.toString();
}
private void addNativeHeapSnapshot(NativeHeapSnapshot snapshot) {
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeSymbolResolverTask.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeSymbolResolverTask.java
new file mode 100644
index 0000000..5302fcb
--- /dev/null
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/heap/NativeSymbolResolverTask.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2011 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.heap;
+
+import com.android.ddmlib.NativeAllocationInfo;
+import com.android.ddmlib.NativeLibraryMapInfo;
+import com.android.ddmlib.NativeStackCallInfo;
+import com.android.ddmuilib.DdmUiPreferences;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * A symbol resolver task that can resolve a set of addresses to their corresponding
+ * source method name + file name:line number.
+ *
+ * It first identifies the library that contains the address, and then runs addr2line on
+ * the library to get the symbol name + source location.
+ */
+public class NativeSymbolResolverTask implements IRunnableWithProgress {
+ private static final String ADDR2LINE;
+ private static final String DEFAULT_SYMBOLS_FOLDER;
+
+ static {
+ String addr2lineEnv = System.getenv("ANDROID_ADDR2LINE");
+ ADDR2LINE = addr2lineEnv != null ? addr2lineEnv : DdmUiPreferences.getAddr2Line();
+
+ String symbols = System.getenv("ANDROID_SYMBOLS");
+ DEFAULT_SYMBOLS_FOLDER = symbols != null ? symbols : DdmUiPreferences.getSymbolDirectory();
+ }
+
+ private List<NativeAllocationInfo> mCallSites;
+ private List<NativeLibraryMapInfo> mMappedLibraries;
+ private List<String> mSymbolSearchFolders;
+
+ /** All unresolved addresses from all the callsites. */
+ private SortedSet<Long> mUnresolvedAddresses;
+
+ /** Set of all addresses that could were not resolved at the end of the resolution process. */
+ private Set<Long> mUnresolvableAddresses;
+
+ /** Map of library -> [unresolved addresses mapping to this library]. */
+ private Map<NativeLibraryMapInfo, Set<Long>> mUnresolvedAddressesPerLibrary;
+
+ /** Addresses that could not be mapped to a library, should be mostly empty. */
+ private Set<Long> mUnmappedAddresses;
+
+ /** Cache of the resolution for every unresolved address. */
+ private Map<Long, NativeStackCallInfo> mAddressResolution;
+
+ /** List of libraries that were not located on disk. */
+ private Set<String> mNotFoundLibraries;
+ private String mAddr2LineErrorMessage = null;
+
+ public NativeSymbolResolverTask(List<NativeAllocationInfo> callSites,
+ List<NativeLibraryMapInfo> mappedLibraries,
+ String symbolSearchPath) {
+ mCallSites = callSites;
+ mMappedLibraries = mappedLibraries;
+ mSymbolSearchFolders = new ArrayList<String>();
+ mSymbolSearchFolders.add(DEFAULT_SYMBOLS_FOLDER);
+ mSymbolSearchFolders.addAll(Arrays.asList(symbolSearchPath.split(":")));
+
+ mUnresolvedAddresses = new TreeSet<Long>();
+ mUnresolvableAddresses = new HashSet<Long>();
+ mUnresolvedAddressesPerLibrary = new HashMap<NativeLibraryMapInfo, Set<Long>>();
+ mUnmappedAddresses = new HashSet<Long>();
+ mAddressResolution = new HashMap<Long, NativeStackCallInfo>();
+ mNotFoundLibraries = new HashSet<String>();
+ }
+
+ public void run(IProgressMonitor monitor)
+ throws InvocationTargetException, InterruptedException {
+ monitor.beginTask("Resolving symbols", IProgressMonitor.UNKNOWN);
+
+ collectAllUnresolvedAddresses();
+ checkCancellation(monitor);
+
+ mapUnresolvedAddressesToLibrary();
+ checkCancellation(monitor);
+
+ resolveLibraryAddresses(monitor);
+ checkCancellation(monitor);
+
+ resolveCallSites(mCallSites);
+
+ monitor.done();
+ }
+
+ private void collectAllUnresolvedAddresses() {
+ for (NativeAllocationInfo callSite : mCallSites) {
+ mUnresolvedAddresses.addAll(callSite.getStackCallAddresses());
+ }
+ }
+
+ private void mapUnresolvedAddressesToLibrary() {
+ Set<Long> mappedAddresses = new HashSet<Long>();
+
+ for (NativeLibraryMapInfo lib : mMappedLibraries) {
+ SortedSet<Long> addressesInLibrary = mUnresolvedAddresses.subSet(lib.getStartAddress(),
+ lib.getEndAddress() + 1);
+ if (addressesInLibrary.size() > 0) {
+ mUnresolvedAddressesPerLibrary.put(lib, addressesInLibrary);
+ mappedAddresses.addAll(addressesInLibrary);
+ }
+ }
+
+ // unmapped addresses = unresolved addresses - mapped addresses
+ mUnmappedAddresses.addAll(mUnresolvedAddresses);
+ mUnmappedAddresses.removeAll(mappedAddresses);
+ }
+
+ private void resolveLibraryAddresses(IProgressMonitor monitor) throws InterruptedException {
+ for (NativeLibraryMapInfo lib : mUnresolvedAddressesPerLibrary.keySet()) {
+ String libPath = getLibraryLocation(lib);
+ Set<Long> addressesToResolve = mUnresolvedAddressesPerLibrary.get(lib);
+
+ if (libPath == null) {
+ mNotFoundLibraries.add(lib.getLibraryName());
+ markAddressesNotResolvable(addressesToResolve, lib);
+ } else {
+ monitor.subTask(String.format("Resolving addresses mapped to %s.", libPath));
+ resolveAddresses(lib, libPath, addressesToResolve);
+ }
+
+ checkCancellation(monitor);
+ }
+ }
+
+ private void resolveAddresses(NativeLibraryMapInfo lib, String libPath,
+ Set<Long> addressesToResolve) {
+ Process addr2line = null;
+ try {
+ addr2line = new ProcessBuilder(ADDR2LINE,
+ "-C", // demangle
+ "-f", // display function names in addition to file:number
+ "-e", libPath).start();
+ } catch (IOException e) {
+ // Since the library path is known to be valid, the only reason for an exception
+ // is that addr2line was not found. We just save the message in this case.
+ mAddr2LineErrorMessage = e.getMessage();
+ markAddressesNotResolvable(addressesToResolve, lib);
+ return;
+ }
+
+ BufferedReader resultReader = new BufferedReader(new InputStreamReader(
+ addr2line.getInputStream()));
+ BufferedWriter addressWriter = new BufferedWriter(new OutputStreamWriter(
+ addr2line.getOutputStream()));
+
+ long libStartAddress = isExecutable(lib) ? 0 : lib.getStartAddress();
+ try {
+ for (Long addr : addressesToResolve) {
+ long offset = addr.longValue() - libStartAddress;
+ addressWriter.write(Long.toHexString(offset));
+ addressWriter.newLine();
+ addressWriter.flush();
+ String method = resultReader.readLine();
+ String sourceFile = resultReader.readLine();
+
+ mAddressResolution.put(addr,
+ new NativeStackCallInfo(addr.longValue(),
+ lib.getLibraryName(),
+ method,
+ sourceFile));
+ }
+ } catch (IOException e) {
+ // if there is any error, then mark the addresses not already resolved
+ // as unresolvable.
+ for (Long addr : addressesToResolve) {
+ if (mAddressResolution.get(addr) == null) {
+ markAddressNotResolvable(lib, addr);
+ }
+ }
+ }
+
+ try {
+ resultReader.close();
+ addressWriter.close();
+ } catch (IOException e) {
+ // we can ignore these exceptions
+ }
+
+ addr2line.destroy();
+ }
+
+ private boolean isExecutable(NativeLibraryMapInfo object) {
+ // TODO: Use a tool like readelf or nm to determine whether this object is a library
+ // or an executable.
+ // For now, we'll just assume that any object present in the bin folder is an executable.
+ String devicePath = object.getLibraryName();
+ return devicePath.contains("/bin/");
+ }
+
+ private void markAddressesNotResolvable(Set<Long> addressesToResolve,
+ NativeLibraryMapInfo lib) {
+ for (Long addr : addressesToResolve) {
+ markAddressNotResolvable(lib, addr);
+ }
+ }
+
+ private void markAddressNotResolvable(NativeLibraryMapInfo lib, Long addr) {
+ mAddressResolution.put(addr,
+ new NativeStackCallInfo(addr.longValue(),
+ lib.getLibraryName(),
+ Long.toHexString(addr),
+ ""));
+ mUnresolvableAddresses.add(addr);
+ }
+
+ /**
+ * Locate on local disk the debug library w/ symbols corresponding to the
+ * library on the device. It searches for this library in the symbol path.
+ * @return absolute path if found, null otherwise
+ */
+ private String getLibraryLocation(NativeLibraryMapInfo lib) {
+ String pathOnDevice = lib.getLibraryName();
+ String libName = new File(pathOnDevice).getName();
+
+ for (String p : mSymbolSearchFolders) {
+ // try appending the full path on device
+ String fullPath = p + File.separator + pathOnDevice;
+ if (new File(fullPath).exists()) {
+ return fullPath;
+ }
+
+ // try appending basename(library)
+ fullPath = p + File.separator + libName;
+ if (new File(fullPath).exists()) {
+ return fullPath;
+ }
+ }
+
+ return null;
+ }
+
+ private void resolveCallSites(List<NativeAllocationInfo> callSites) {
+ for (NativeAllocationInfo callSite : callSites) {
+ List<NativeStackCallInfo> stackInfo = new ArrayList<NativeStackCallInfo>();
+
+ for (Long addr : callSite.getStackCallAddresses()) {
+ NativeStackCallInfo info = mAddressResolution.get(addr);
+
+ if (info != null) {
+ stackInfo.add(info);
+ }
+ }
+
+ callSite.setResolvedStackCall(stackInfo);
+ }
+ }
+
+ private void checkCancellation(IProgressMonitor monitor) throws InterruptedException {
+ if (monitor.isCanceled()) {
+ throw new InterruptedException();
+ }
+ }
+
+ public String getAddr2LineErrorMessage() {
+ return mAddr2LineErrorMessage;
+ }
+
+ public Set<Long> getUnmappedAddresses() {
+ return mUnmappedAddresses;
+ }
+
+ public Set<Long> getUnresolvableAddresses() {
+ return mUnresolvableAddresses;
+ }
+
+ public Set<String> getNotFoundLibraries() {
+ return mNotFoundLibraries;
+ }
+}