diff options
author | Siva Velusamy <vsiva@google.com> | 2011-10-09 14:44:55 -0700 |
---|---|---|
committer | Siva Velusamy <vsiva@google.com> | 2011-10-10 10:18:31 -0700 |
commit | 9d02c9ee4fa5a92420fa16e0e762d9cd079cee17 (patch) | |
tree | 42d2dc260fad452ec5bcb1fb84934ba38211a627 /ddms/libs | |
parent | 447dfafcabb6c30be5f21678f20301df9830a1cc (diff) | |
download | sdk-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')
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; + } +} |