Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/tools/android/OWNERS b/tools/android/OWNERS
new file mode 100644
index 0000000..f8370f6
--- /dev/null
+++ b/tools/android/OWNERS
@@ -0,0 +1,4 @@
+digit@chromium.org
+michaelbai@chromium.org
+wangxianzhu@chromium.org
+yfriedman@chromium.org
diff --git a/tools/android/adb_reboot/adb_reboot.c b/tools/android/adb_reboot/adb_reboot.c
new file mode 100644
index 0000000..d414dd5
--- /dev/null
+++ b/tools/android/adb_reboot/adb_reboot.c
@@ -0,0 +1,43 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+int main(int argc, char ** argv) {
+ int i = fork();
+ struct stat ft;
+ time_t ct;
+
+ if (i < 0) {
+ printf("fork error");
+ return 1;
+ }
+ if (i > 0)
+ return 0;
+
+ /* child (daemon) continues */
+ int j;
+ for (j = 0; j < sysconf(_SC_OPEN_MAX); j++)
+ close(j);
+
+ setsid(); /* obtain a new process group */
+
+ while (1) {
+ sleep(120);
+
+ stat("/sdcard/host_heartbeat", &ft);
+ time(&ct);
+ if (ct - ft.st_mtime > 120) {
+ /* File was not touched for some time. */
+ system("su -c reboot");
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/android/adb_reboot/adb_reboot.gyp b/tools/android/adb_reboot/adb_reboot.gyp
new file mode 100644
index 0000000..85134b9
--- /dev/null
+++ b/tools/android/adb_reboot/adb_reboot.gyp
@@ -0,0 +1,14 @@
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+{
+ 'targets': [
+ {
+ 'target_name': 'adb_reboot',
+ 'type': 'executable',
+ 'sources': [
+ 'adb_reboot.c',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/adb_remote_setup.sh b/tools/android/adb_remote_setup.sh
new file mode 100755
index 0000000..87c6601
--- /dev/null
+++ b/tools/android/adb_remote_setup.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# URL from which the latest version of this script can be downloaded.
+script_url="http://src.chromium.org/svn/trunk/src/tools/android/adb_remote_setup.sh"
+
+# Replaces this file with the latest version of the script and runs it.
+update-self() {
+ local script="${BASH_SOURCE[0]}"
+ local new_script="${script}.new"
+ local updater_script="${script}.updater"
+ curl -sSf -o "$new_script" "$script_url" || return
+ chmod +x "$new_script" || return
+
+ # Replace this file with the newly downloaded script.
+ cat > "$updater_script" << EOF
+#!/bin/bash
+if mv "$new_script" "$script"; then
+ rm -- "$updater_script"
+else
+ echo "Note: script update failed."
+fi
+ADB_REMOTE_SETUP_NO_UPDATE=1 exec /bin/bash "$script" $@
+EOF
+ exec /bin/bash "$updater_script" "$@"
+}
+
+if [[ "$ADB_REMOTE_SETUP_NO_UPDATE" -ne 1 ]]; then
+ update-self "$@" || echo 'Note: script update failed'
+fi
+
+if [[ $# -ne 1 && $# -ne 2 ]]; then
+ cat <<'EOF'
+Usage: adb_remote_setup.sh REMOTE_HOST [REMOTE_ADB]
+
+Configures adb on a remote machine to communicate with a device attached to the
+local machine. This is useful for installing APKs, running tests, etc while
+working remotely.
+
+Arguments:
+ REMOTE_HOST hostname of remote machine
+ REMOTE_ADB path to adb on the remote machine (you can omit this if adb is in
+ the remote host's path)
+EOF
+ exit 1
+fi
+
+remote_host="$1"
+remote_adb="${2:-adb}"
+
+# Ensure adb is in the local machine's path.
+if ! which adb >/dev/null; then
+ echo "error: adb must be in your local machine's path."
+ exit 1
+fi
+
+if which kinit >/dev/null; then
+ # Allow ssh to succeed without typing your password multiple times.
+ kinit -R || kinit
+fi
+
+# Ensure local and remote versions of adb are the same.
+remote_adb_version=$(ssh "$remote_host" "$remote_adb version")
+local_adb_version=$(adb version)
+if [[ "$local_adb_version" != "$remote_adb_version" ]]; then
+ echo >&2
+ echo "WARNING: local adb is not the same version as remote adb." >&2
+ echo "This should be fixed since it may result in protocol errors." >&2
+ echo " local adb: $local_adb_version" >&2
+ echo " remote adb: $remote_adb_version" >&2
+ echo >&2
+ sleep 5
+fi
+
+# Kill the adb server on the remote host.
+ssh "$remote_host" "$remote_adb kill-server"
+
+# Start the adb server locally.
+adb start-server
+
+# Forward various ports from the remote host to the local host:
+# 5037: adb
+# 8001: http server
+# 9031: sync server
+# 10000: net unittests
+# 10201: net unittests
+ssh -C \
+ -R 5037:localhost:5037 \
+ -L 8001:localhost:8001 \
+ -L 9031:localhost:9031 \
+ -R 10000:localhost:10000 \
+ -R 10201:localhost:10201 \
+ "$remote_host"
diff --git a/tools/android/android_tools.gyp b/tools/android/android_tools.gyp
new file mode 100644
index 0000000..84de85c
--- /dev/null
+++ b/tools/android/android_tools.gyp
@@ -0,0 +1,52 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ # Intermediate target grouping the android tools needed to run native
+ # unittests and instrumentation test apks.
+ {
+ 'target_name': 'android_tools',
+ 'type': 'none',
+ 'dependencies': [
+ 'adb_reboot/adb_reboot.gyp:adb_reboot',
+ 'file_poller/file_poller.gyp:file_poller',
+ 'forwarder2/forwarder.gyp:forwarder2',
+ 'md5sum/md5sum.gyp:md5sum',
+ 'purge_ashmem/purge_ashmem.gyp:purge_ashmem',
+ 'run_pie/run_pie.gyp:run_pie',
+ '../../tools/telemetry/telemetry.gyp:*#host',
+ ],
+ },
+ {
+ 'target_name': 'heap_profiler',
+ 'type': 'none',
+ 'dependencies': [
+ 'heap_profiler/heap_profiler.gyp:heap_dump',
+ 'heap_profiler/heap_profiler.gyp:heap_profiler',
+ ],
+ },
+ {
+ 'target_name': 'memdump',
+ 'type': 'none',
+ 'dependencies': [
+ 'memdump/memdump.gyp:memdump',
+ ],
+ },
+ {
+ 'target_name': 'memconsumer',
+ 'type': 'none',
+ 'dependencies': [
+ 'memconsumer/memconsumer.gyp:memconsumer',
+ ],
+ },
+ {
+ 'target_name': 'ps_ext',
+ 'type': 'none',
+ 'dependencies': [
+ 'ps_ext/ps_ext.gyp:ps_ext',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/asan/asan_device_setup.sh b/tools/android/asan/asan_device_setup.sh
new file mode 100755
index 0000000..5948f2b
--- /dev/null
+++ b/tools/android/asan/asan_device_setup.sh
@@ -0,0 +1,194 @@
+#!/bin/bash -e
+#===- lib/asan/scripts/asan_device_setup.py -----------------------------------===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+# Prepare Android device to run ASan applications.
+#
+#===------------------------------------------------------------------------===#
+
+
+HERE="$(cd "$(dirname "$0")" && pwd)"
+
+revert=no
+extra_options=
+device=
+lib=
+
+function usage {
+ echo "usage: $0 [--revert] [--device device-id] [--lib path] [--extra_options options]"
+ echo " --revert: Uninstall ASan from the device."
+ echo " --lib: Path to ASan runtime library."
+ echo " --extra_options: Extra ASAN_OPTIONS."
+ echo " --device: Install to the given device. Use 'adb devices' to find"
+ echo " device-id."
+ echo
+ exit 1
+}
+
+while [[ $# > 0 ]]; do
+ case $1 in
+ --revert)
+ revert=yes
+ ;;
+ --extra-options)
+ shift
+ if [[ $# == 0 ]]; then
+ echo "--extra-options requires an argument."
+ exit 1
+ fi
+ extra_options="$1"
+ ;;
+ --lib)
+ shift
+ if [[ $# == 0 ]]; then
+ echo "--lib requires an argument."
+ exit 1
+ fi
+ lib="$1"
+ ;;
+ --device)
+ shift
+ if [[ $# == 0 ]]; then
+ echo "--device requires an argument."
+ exit 1
+ fi
+ device="$1"
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ shift
+done
+
+ADB=${ADB:-adb}
+if [[ x$device != x ]]; then
+ ADB="$ADB -s $device"
+fi
+
+ASAN_RT="libclang_rt.asan-arm-android.so"
+
+if [[ x$revert == xyes ]]; then
+ echo '>> Uninstalling ASan'
+ $ADB root
+ $ADB wait-for-device
+ $ADB remount
+ $ADB shell mv /system/bin/app_process.real /system/bin/app_process
+ $ADB shell rm /system/bin/asanwrapper
+ $ADB shell rm /system/lib/$ASAN_RT
+
+ echo '>> Restarting shell'
+ $ADB shell stop
+ $ADB shell start
+
+ echo '>> Done'
+ exit 0
+fi
+
+if [[ -d "$lib" ]]; then
+ ASAN_RT_PATH="$lib"
+elif [[ -f "$lib" && "$lib" == *"$ASAN_RT" ]]; then
+ ASAN_RT_PATH=$(dirname "$lib")
+elif [[ -f "$HERE/$ASAN_RT" ]]; then
+ ASAN_RT_PATH="$HERE"
+elif [[ $(basename "$HERE") == "bin" ]]; then
+ # We could be in the toolchain's base directory.
+ # Consider ../lib and ../lib/clang/$VERSION/lib/linux.
+ P=$(ls "$HERE"/../lib/"$ASAN_RT" "$HERE"/../lib/clang/*/lib/linux/"$ASAN_RT" 2>/dev/null | sort | tail -1)
+ if [[ -n "$P" ]]; then
+ ASAN_RT_PATH="$(dirname "$P")"
+ fi
+fi
+
+if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT" ]]; then
+ echo "ASan runtime library not found"
+ exit 1
+fi
+
+TMPDIRBASE=$(mktemp -d)
+TMPDIROLD="$TMPDIRBASE/old"
+TMPDIR="$TMPDIRBASE/new"
+mkdir "$TMPDIROLD"
+
+echo '>> Remounting /system rw'
+$ADB root
+$ADB wait-for-device
+$ADB remount
+
+echo '>> Copying files from the device'
+$ADB pull /system/bin/app_process "$TMPDIROLD"
+$ADB pull /system/bin/app_process.real "$TMPDIROLD" || true
+$ADB pull /system/bin/asanwrapper "$TMPDIROLD" || true
+$ADB pull /system/lib/libclang_rt.asan-arm-android.so "$TMPDIROLD" || true
+cp -r "$TMPDIROLD" "$TMPDIR"
+
+if ! [[ -f "$TMPDIR/app_process" ]]; then
+ echo "app_process missing???"
+ exit 1
+fi
+
+if [[ -f "$TMPDIR/app_process.real" ]]; then
+ echo "app_process.real exists, updating the wrapper"
+else
+ echo "app_process.real missing, new installation"
+ mv "$TMPDIR/app_process" "$TMPDIR/app_process.real"
+fi
+
+echo '>> Generating wrappers'
+
+cp "$ASAN_RT_PATH/$ASAN_RT" "$TMPDIR/"
+
+# FIXME: alloc_dealloc_mismatch=0 prevents a failure in libdvm startup,
+# which may or may not be a real bug (probably not).
+ASAN_OPTIONS=start_deactivated=1,alloc_dealloc_mismatch=0
+if [[ x$extra_options != x ]] ; then
+ ASAN_OPTIONS="$ASAN_OPTIONS,$extra_options"
+fi
+
+# Zygote wrapper.
+cat <<EOF >"$TMPDIR/app_process"
+#!/system/bin/sh
+ASAN_OPTIONS=$ASAN_OPTIONS \\
+LD_PRELOAD=libclang_rt.asan-arm-android.so \\
+exec /system/bin/app_process.real \$@
+
+EOF
+
+# General command-line tool wrapper (use for anything that's not started as
+# zygote).
+cat <<EOF >"$TMPDIR/asanwrapper"
+#!/system/bin/sh
+LD_PRELOAD=libclang_rt.asan-arm-android.so \\
+exec \$@
+
+EOF
+
+if ! ( cd "$TMPDIRBASE" && diff -qr old/ new/ ) ; then
+ echo '>> Pushing files to the device'
+ $ADB push "$TMPDIR/$ASAN_RT" /system/lib/
+ $ADB push "$TMPDIR/app_process" /system/bin/app_process
+ $ADB push "$TMPDIR/app_process.real" /system/bin/app_process.real
+ $ADB push "$TMPDIR/asanwrapper" /system/bin/asanwrapper
+ $ADB shell chown root.shell \
+ /system/bin/app_process \
+ /system/bin/app_process.real \
+ /system/bin/asanwrapper
+ $ADB shell chmod 755 \
+ /system/bin/app_process \
+ /system/bin/app_process.real \
+ /system/bin/asanwrapper
+
+ echo '>> Restarting shell (asynchronous)'
+ $ADB shell stop
+ $ADB shell start
+
+ echo '>> Please wait until the device restarts'
+else
+ echo '>> Device is up to date'
+fi
+
+rm -r "$TMPDIRBASE"
diff --git a/tools/android/checkstyle/checkstyle.py b/tools/android/checkstyle/checkstyle.py
new file mode 100644
index 0000000..25f202f
--- /dev/null
+++ b/tools/android/checkstyle/checkstyle.py
@@ -0,0 +1,69 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Script that is used by PRESUBMIT.py to run style checks on Java files."""
+
+import os
+import subprocess
+
+
+CHROMIUM_SRC = os.path.normpath(
+ os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, os.pardir))
+CHECKSTYLE_ROOT = os.path.join(CHROMIUM_SRC, 'third_party', 'checkstyle',
+ 'checkstyle-5.7-all.jar')
+
+
+def RunCheckstyle(input_api, output_api, style_file):
+ if not os.path.exists(style_file):
+ file_error = (' Java checkstyle configuration file is missing: '
+ + style_file)
+ return [output_api.PresubmitError(file_error)]
+
+ # Filter out non-Java files and files that were deleted.
+ java_files = [x.LocalPath() for x in input_api.AffectedFiles(False, False)
+ if os.path.splitext(x.LocalPath())[1] == '.java']
+ if not java_files:
+ return []
+
+ # Run checkstyle
+ checkstyle_env = os.environ.copy()
+ checkstyle_env['JAVA_CMD'] = 'java'
+ try:
+ check = subprocess.Popen(['java', '-cp',
+ CHECKSTYLE_ROOT,
+ 'com.puppycrawl.tools.checkstyle.Main', '-c',
+ style_file] + java_files,
+ stdout=subprocess.PIPE, env=checkstyle_env)
+ stdout, _ = check.communicate()
+ if check.returncode == 0:
+ return []
+ except OSError as e:
+ import errno
+ if e.errno == errno.ENOENT:
+ install_error = (' checkstyle is not installed. Please run '
+ 'build/install-build-deps-android.sh')
+ return [output_api.PresubmitPromptWarning(install_error)]
+
+ # Remove non-error values from stdout
+ errors = stdout.splitlines()
+
+ if errors and errors[0] == 'Starting audit...':
+ del errors[0]
+ if errors and errors[-1] == 'Audit done.':
+ del errors[-1]
+
+ # Filter out warnings
+ errors = [x for x in errors if 'warning: ' not in x]
+ if not errors:
+ return []
+
+ local_path = input_api.PresubmitLocalPath()
+ output = []
+ for error in errors:
+ # Change the full file path to relative path in the output lines
+ full_path, end = error.split(':', 1)
+ rel_path = os.path.relpath(full_path, local_path)
+ output.append(' %s:%s' % (rel_path, end))
+ return [output_api.PresubmitPromptWarning('\n'.join(output))]
diff --git a/tools/android/checkstyle/chromium-style-5.0.xml b/tools/android/checkstyle/chromium-style-5.0.xml
new file mode 100644
index 0000000..bc40f87
--- /dev/null
+++ b/tools/android/checkstyle/chromium-style-5.0.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+ See installation instructions: https://sites.google.com/a/chromium.org/dev/checkstyle
+-->
+<module name="Checker">
+ <property name="severity" value="warning"/>
+ <property name="charset" value="UTF-8"/>
+ <module name="TreeWalker">
+ <module name="AvoidStarImport">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="IllegalCatch">
+ <property name="severity" value="warning"/>
+ </module>
+ <module name="RedundantImport">
+ <message key="import.redundant" value="Redundant import: {0}. Use :JavaImportOrganize (ECLIM) or Ctrl+Shift+O (Eclipse) to sort imports"/>
+ <property name="severity" value="error"/>
+ </module>
+ <module name="UnusedImports">
+ <property name="severity" value="error"/>
+ <property name="processJavadoc" value="true"/>
+ <message key="import.unused" value="Unused import: {0}. Use :JavaImportOrganize (ECLIM) or Ctrl+Shift+O (Eclipse) to sort imports"/>
+ </module>
+ <module name="JavadocType">
+ <property name="severity" value="error"/>
+ <property name="tokens" value="INTERFACE_DEF, CLASS_DEF"/>
+ <property name="scope" value="public"/>
+ <message key="javadoc.missing" value="Public classes and interfaces require JavaDoc comments."/>
+ </module>
+ <module name="JavadocMethod">
+ <property name="severity" value="warning"/>
+ <property name="scope" value="public"/>
+ <property name="allowMissingParamTags" value="true"/>
+ <property name="allowMissingPropertyJavadoc" value="true"/>
+ <property name="allowMissingReturnTag" value="true"/>
+ <property name="allowMissingThrowsTags" value="true"/>
+ </module>
+ <module name="PackageName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^[a-z]+(\.[a-z][a-z0-9_]{1,})*$"/>
+ </module>
+ <module name="SimplifyBooleanExpression">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="SimplifyBooleanReturn">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="TypeName">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="ConstantName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*)|(s[A-Z][a-zA-Z0-9]*)$"/>
+ <message key="name.invalidPattern" value="Static final field names must either be all caps (e.g. int HEIGHT_PX) for 'true' constants, or start with s (e.g. AtomicInteger sNextId or Runnable sSuspendTask) for fields with mutable state or that don't 'feel' like constants."/>
+ </module>
+ <!-- Non-public, non-static field names start with m. -->
+ <module name="MemberName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^m[A-Z][a-zA-Z0-9]*$"/>
+ <property name="applyToPublic" value="false"/>
+ <message key="name.invalidPattern" value="Non-public, non-static field names start with m."/>
+ </module>
+ <!-- Static field names start with s. -->
+ <module name="StaticVariableName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^s[A-Z][a-zA-Z0-9]*$"/>
+ <property name="applyToPublic" value="false"/>
+ <message key="name.invalidPattern" value="Static field names start with s."/>
+ </module>
+ <module name="MethodName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
+ <message key="name.invalidPattern" value="Method names should start with a lower case letter (e.g. getWidth())"/>
+ </module>
+ <module name="ParameterName">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="LocalFinalVariableName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^m|s|((([ms][a-z0-9])|([a-ln-rt-z]))[a-zA-Z0-9]*)$"/>
+ <message key="name.invalidPattern" value="Local variables should be camel-cased (e.g. int minWidth = 4)."/>
+ </module>
+ <module name="LocalVariableName">
+ <property name="severity" value="error"/>
+ <property name="format" value="^m|s|((([ms][a-z0-9])|([a-ln-rt-z]))[a-zA-Z0-9]*)$"/>
+ <message key="name.invalidPattern" value="Local variables should be camel-cased (e.g. int minWidth = 4)."/>
+ </module>
+ <module name="LineLength">
+ <property name="severity" value="error"/>
+ <property name="ignorePattern" value="^import.*$" />
+ <property name="max" value="100"/>
+ </module>
+ <module name="LeftCurly">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="RightCurly">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="NeedBraces">
+ <property name="severity" value="warning"/>
+ <property name="tokens" value="LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
+ </module>
+ <module name="EmptyBlock">
+ <property name="severity" value="error"/>
+ <property name="option" value="text"/>
+ <metadata name="altname" value="EmptyCatchBlock"/>
+ </module>
+ <module name="UpperEll">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="FallThrough">
+ <property name="severity" value="error"/>
+ <property name="reliefPattern" value=".*"/>
+ </module>
+ <module name="ModifierOrder">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="WhitespaceAround">
+ <property name="severity" value="error"/>
+ <property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN, TYPE_EXTENSION_AND" />
+ <property name="allowEmptyConstructors" value="true"/>
+ <property name="allowEmptyMethods" value="true"/>
+ </module>
+ <module name="WhitespaceAfter">
+ <property name="severity" value="error"/>
+ <property name="tokens" value="COMMA, SEMI, TYPECAST"/>
+ </module>
+ <module name="NoWhitespaceAfter">
+ <property name="severity" value="error"/>
+ <property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS"/>
+ </module>
+ <module name="NoWhitespaceBefore">
+ <property name="severity" value="error"/>
+ <property name="allowLineBreaks" value="true"/>
+ <property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
+ </module>
+ <module name="EmptyStatement">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="NoFinalizer">
+ <property name="severity" value="warning"/>
+ </module>
+ <module name="ParenPad">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="ImportOrder">
+ <property name="severity" value="error"/>
+ <message key="import.ordering" value="Wrong order for {0} import. Use :JavaImportOrganize (ECLIM) or Ctrl+Shift+O (Eclipse) to sort imports"/>
+ <property name="groups" value="android, com, dalvik, gov, junit, libcore, net, org, java, javax"/>
+ <property name="ordered" value="true"/>
+ <property name="option" value="top"/>
+ <property name="separated" value="true"/>
+ </module>
+ <!-- TODO(aurimas): make indentation an error once https://github.com/checkstyle/checkstyle/issues/255 is fixed. -->
+ <module name="Indentation">
+ <property name="severity" value="warning"/>
+ <property name="basicOffset" value="4"/>
+ <property name="throwsIndent" value="8"/>
+ </module>
+ </module>
+ <module name="FileTabCharacter">
+ <property name="severity" value="error"/>
+ </module>
+ <module name="RegexpSingleline">
+ <property name="format" value="((//.*)|(\*.*))FIXME"/>
+ <property name="message" value="TODO is preferred to FIXME. e.g. "TODO(johndoe):"/>
+ </module>
+ <module name="RegexpSingleline">
+ <property name="format" value="((//.*)|(\*.*))(?<!TODO\(.{0,100})(TODO[^(])|(TODO\([^)]*$)"/>
+ <property name="message" value="All TODOs should be named. e.g. "TODO(johndoe):"/>
+ </module>
+ <module name="RegexpSingleline">
+ <property name="severity" value="error"/>
+ <property name="format" value="[ \t]+$"/>
+ <property name="message" value="Trailing whitespace"/>
+ </module>
+ <module name="RegexpHeader">
+ <property name="severity" value="error"/>
+ <property name="header" value="^// Copyright 20\d\d The Chromium Authors. All rights reserved.$\n^// Use of this source code is governed by a BSD-style license that can be$\n^// found in the LICENSE file.$"/>
+ </module>
+</module>
diff --git a/tools/android/common/adb_connection.cc b/tools/android/common/adb_connection.cc
new file mode 100644
index 0000000..9985a3a
--- /dev/null
+++ b/tools/android/common/adb_connection.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/common/adb_connection.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "tools/android/common/net.h"
+
+namespace tools {
+namespace {
+
+void CloseSocket(int fd) {
+ if (fd >= 0) {
+ int old_errno = errno;
+ close(fd);
+ errno = old_errno;
+ }
+}
+
+} // namespace
+
+int ConnectAdbHostSocket(const char* forward_to) {
+ // ADB port forward request format: HHHHtcp:port:address.
+ // HHHH is the hexidecimal length of the "tcp:port:address" part.
+ const size_t kBufferMaxLength = 30;
+ const size_t kLengthOfLength = 4;
+ const size_t kAddressMaxLength = kBufferMaxLength - kLengthOfLength;
+
+ const char kAddressPrefix[] = { 't', 'c', 'p', ':' };
+ size_t address_length = arraysize(kAddressPrefix) + strlen(forward_to);
+ if (address_length > kBufferMaxLength - kLengthOfLength) {
+ LOG(ERROR) << "Forward to address is too long: " << forward_to;
+ return -1;
+ }
+
+ char request[kBufferMaxLength];
+ memcpy(request + kLengthOfLength, kAddressPrefix, arraysize(kAddressPrefix));
+ memcpy(request + kLengthOfLength + arraysize(kAddressPrefix),
+ forward_to, strlen(forward_to));
+
+ char length_buffer[kLengthOfLength + 1];
+ snprintf(length_buffer, arraysize(length_buffer), "%04X",
+ static_cast<int>(address_length));
+ memcpy(request, length_buffer, kLengthOfLength);
+
+ int host_socket = socket(AF_INET, SOCK_STREAM, 0);
+ if (host_socket < 0) {
+ LOG(ERROR) << "Failed to create adb socket: " << strerror(errno);
+ return -1;
+ }
+
+ DisableNagle(host_socket);
+
+ const int kAdbPort = 5037;
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = htons(kAdbPort);
+ if (HANDLE_EINTR(connect(host_socket, reinterpret_cast<sockaddr*>(&addr),
+ sizeof(addr))) < 0) {
+ LOG(ERROR) << "Failed to connect adb socket: " << strerror(errno);
+ CloseSocket(host_socket);
+ return -1;
+ }
+
+ size_t bytes_remaining = address_length + kLengthOfLength;
+ size_t bytes_sent = 0;
+ while (bytes_remaining > 0) {
+ int ret = HANDLE_EINTR(send(host_socket, request + bytes_sent,
+ bytes_remaining, 0));
+ if (ret < 0) {
+ LOG(ERROR) << "Failed to send request: " << strerror(errno);
+ CloseSocket(host_socket);
+ return -1;
+ }
+
+ bytes_sent += ret;
+ bytes_remaining -= ret;
+ }
+
+ const size_t kAdbStatusLength = 4;
+ char response[kBufferMaxLength];
+ int response_length = HANDLE_EINTR(recv(host_socket, response,
+ kBufferMaxLength, 0));
+ if (response_length < kAdbStatusLength ||
+ strncmp("OKAY", response, kAdbStatusLength) != 0) {
+ LOG(ERROR) << "Bad response from ADB: length: " << response_length
+ << " data: " << DumpBinary(response, response_length);
+ CloseSocket(host_socket);
+ return -1;
+ }
+
+ return host_socket;
+}
+
+} // namespace tools
diff --git a/tools/android/common/adb_connection.h b/tools/android/common/adb_connection.h
new file mode 100644
index 0000000..3fa0fb3
--- /dev/null
+++ b/tools/android/common/adb_connection.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_COMMON_ADB_CONNECTION_H_
+#define TOOLS_ANDROID_COMMON_ADB_CONNECTION_H_
+
+namespace tools {
+
+// Creates a socket that can forward to a host socket through ADB.
+// The format of forward_to is <port>:<ip_address>.
+// Returns the socket handle, or -1 on any error.
+int ConnectAdbHostSocket(const char* forward_to);
+
+} // namespace tools
+
+#endif // TOOLS_ANDROID_COMMON_ADB_CONNECTION_H_
+
diff --git a/tools/android/common/common.gyp b/tools/android/common/common.gyp
new file mode 100644
index 0000000..8622625
--- /dev/null
+++ b/tools/android/common/common.gyp
@@ -0,0 +1,26 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'android_tools_common',
+ 'type': 'static_library',
+ 'toolsets': ['host', 'target'],
+ 'include_dirs': [
+ '..',
+ '../../..',
+ ],
+ 'sources': [
+ 'adb_connection.cc',
+ 'adb_connection.h',
+ 'daemon.cc',
+ 'daemon.h',
+ 'net.cc',
+ 'net.h',
+ ],
+ },
+ ],
+}
+
diff --git a/tools/android/common/daemon.cc b/tools/android/common/daemon.cc
new file mode 100644
index 0000000..699c615
--- /dev/null
+++ b/tools/android/common/daemon.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/common/daemon.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+
+namespace {
+
+const char kNoSpawnDaemon[] = "D";
+
+int g_exit_status = 0;
+
+void Exit(int unused) {
+ _exit(g_exit_status);
+}
+
+void CloseFileDescriptor(int fd) {
+ int old_errno = errno;
+ close(fd);
+ errno = old_errno;
+}
+
+} // namespace
+
+namespace tools {
+
+bool HasHelpSwitch(const CommandLine& command_line) {
+ return command_line.HasSwitch("h") || command_line.HasSwitch("help");
+}
+
+bool HasNoSpawnDaemonSwitch(const CommandLine& command_line) {
+ return command_line.HasSwitch(kNoSpawnDaemon);
+}
+
+void ShowHelp(const char* program,
+ const char* extra_title,
+ const char* extra_descriptions) {
+ printf("Usage: %s [-%s] %s\n"
+ " -%s stops from spawning a daemon process\n%s",
+ program, kNoSpawnDaemon, extra_title, kNoSpawnDaemon,
+ extra_descriptions);
+}
+
+void SpawnDaemon(int exit_status) {
+ g_exit_status = exit_status;
+ signal(SIGUSR1, Exit);
+
+ if (fork()) {
+ // In parent process.
+ sleep(10); // Wait for the child process to finish setsid().
+ NOTREACHED();
+ }
+
+ // In child process.
+ setsid(); // Detach the child process from its parent.
+ kill(getppid(), SIGUSR1); // Inform the parent process to exit.
+
+ // Close the standard input and outputs, otherwise the process may block
+ // adbd when the shell exits.
+ // Comment out these lines if you want to see outputs for debugging.
+ CloseFileDescriptor(0);
+ CloseFileDescriptor(1);
+ CloseFileDescriptor(2);
+}
+
+} // namespace tools
diff --git a/tools/android/common/daemon.h b/tools/android/common/daemon.h
new file mode 100644
index 0000000..99faf72
--- /dev/null
+++ b/tools/android/common/daemon.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_COMMON_DAEMON_H_
+#define TOOLS_ANDROID_COMMON_DAEMON_H_
+
+namespace base {
+class CommandLine;
+}
+
+namespace tools {
+
+bool HasHelpSwitch(const base::CommandLine& command_line);
+
+bool HasNoSpawnDaemonSwitch(const base::CommandLine& command_line);
+
+void ShowHelp(const char* program,
+ const char* extra_title,
+ const char* extra_descriptions);
+
+// Spawns a daemon process and exits the current process with exit_status.
+// Any code executed after this function returns will be executed in the
+// spawned daemon process.
+void SpawnDaemon(int exit_status);
+
+} // namespace tools
+
+#endif // TOOLS_ANDROID_COMMON_DAEMON_H_
+
diff --git a/tools/android/common/net.cc b/tools/android/common/net.cc
new file mode 100644
index 0000000..3b9ef15
--- /dev/null
+++ b/tools/android/common/net.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/common/net.h"
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "base/strings/stringprintf.h"
+
+namespace tools {
+
+int DisableNagle(int socket) {
+ int on = 1;
+ return setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+}
+
+int DeferAccept(int socket) {
+ int on = 1;
+ return setsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, &on, sizeof(on));
+}
+
+std::string DumpBinary(const char* buffer, size_t length) {
+ std::string result = "[";
+ for (int i = 0; i < length; ++i) {
+ base::StringAppendF(&result, "%02x,",
+ static_cast<unsigned char>(buffer[i]));
+ }
+
+ if (length)
+ result.erase(result.length() - 1);
+
+ return result + "]";
+}
+
+} // namespace tools
+
diff --git a/tools/android/common/net.h b/tools/android/common/net.h
new file mode 100644
index 0000000..e361954
--- /dev/null
+++ b/tools/android/common/net.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_COMMON_NET_H_
+#define TOOLS_ANDROID_COMMON_NET_H_
+
+#include <string>
+
+namespace tools {
+
+// DisableNagle can improve TCP transmission performance. Both Chrome net stack
+// and adb tool use it.
+int DisableNagle(int socket);
+
+// Wake up listener only when data arrive.
+int DeferAccept(int socket);
+
+// Dumps a binary buffer into a string in a human-readable format.
+std::string DumpBinary(const char* buffer, size_t length);
+
+} // namespace tools
+
+#endif // TOOLS_ANDROID_COMMON_NET_H_
+
diff --git a/tools/android/file_poller/file_poller.cc b/tools/android/file_poller/file_poller.cc
new file mode 100644
index 0000000..c73db8b
--- /dev/null
+++ b/tools/android/file_poller/file_poller.cc
@@ -0,0 +1,207 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// When run with 2 or more arguments the file_poller tool will open a port on
+// the device, print it on its standard output and then start collect file
+// contents. The first argument is the polling rate in Hz, and the following
+// arguments are file to poll.
+// When run with the port of an already running file_poller, the tool will
+// contact the first instance, retrieve the sample and print those on its
+// standard output. This will also terminate the first instance.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+
+// Context containing the files to poll and the polling rate.
+struct Context {
+ size_t nb_files;
+ int* file_fds;
+ int poll_rate;
+};
+
+// Write from the buffer to the given file descriptor.
+void safe_write(int fd, const char* buffer, int size) {
+ const char* index = buffer;
+ size_t to_write = size;
+ while (to_write > 0) {
+ int written = write(fd, index, to_write);
+ if (written < 0)
+ PLOG(FATAL);
+ index += written;
+ to_write -= written;
+ }
+}
+
+// Transfer the content of a file descriptor to another.
+void transfer_to_fd(int fd_in, int fd_out) {
+ char buffer[1024];
+ int n;
+ while ((n = read(fd_in, buffer, sizeof(buffer))) > 0)
+ safe_write(fd_out, buffer, n);
+}
+
+// Transfer the content of a file descriptor to a buffer.
+int transfer_to_buffer(int fd_in, char* bufffer, size_t size) {
+ char* index = bufffer;
+ size_t to_read = size;
+ int n;
+ while (to_read > 0 && ((n = read(fd_in, index, to_read)) > 0)) {
+ index += n;
+ to_read -= n;
+ }
+ if (n < 0)
+ PLOG(FATAL);
+ return size - to_read;
+}
+
+// Try to open the file at the given path for reading. Exit in case of failure.
+int checked_open(const char* path) {
+ int fd = open(path, O_RDONLY);
+ if (fd < 0)
+ PLOG(FATAL);
+ return fd;
+}
+
+void transfer_measurement(int fd_in, int fd_out, bool last) {
+ char buffer[1024];
+ if (lseek(fd_in, 0, SEEK_SET) < 0)
+ PLOG(FATAL);
+ int n = transfer_to_buffer(fd_in, buffer, sizeof(buffer));
+ safe_write(fd_out, buffer, n - 1);
+ safe_write(fd_out, last ? "\n" : " ", 1);
+}
+
+// Acquire a sample and save it to the given file descriptor.
+void acquire_sample(int fd, const Context& context) {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ char buffer[1024];
+ int n = snprintf(buffer, sizeof(buffer), "%d.%06d ", tv.tv_sec, tv.tv_usec);
+ safe_write(fd, buffer, n);
+
+ for (int i = 0; i < context.nb_files; ++i)
+ transfer_measurement(context.file_fds[i], fd, i == (context.nb_files - 1));
+}
+
+void poll_content(const Context& context) {
+ // Create and bind the socket so that the port can be written to stdout.
+ int sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ struct sockaddr_in socket_info;
+ socket_info.sin_family = AF_INET;
+ socket_info.sin_addr.s_addr = htonl(INADDR_ANY);
+ socket_info.sin_port = htons(0);
+ if (bind(sockfd, (struct sockaddr*)&socket_info, sizeof(socket_info)) < 0)
+ PLOG(FATAL);
+ socklen_t size = sizeof(socket_info);
+ getsockname(sockfd, (struct sockaddr*)&socket_info, &size);
+ printf("%d\n", ntohs(socket_info.sin_port));
+ // Using a pipe to ensure child is diconnected from the terminal before
+ // quitting.
+ int pipes[2];
+ pipe(pipes);
+ pid_t pid = fork();
+ if (pid < 0)
+ PLOG(FATAL);
+ if (pid != 0) {
+ close(pipes[1]);
+ // Not expecting any data to be received.
+ read(pipes[0], NULL, 1);
+ signal(SIGCHLD, SIG_IGN);
+ return;
+ }
+
+ // Detach from terminal.
+ setsid();
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ close(pipes[0]);
+
+ // Start listening for incoming connection.
+ if (listen(sockfd, 1) < 0)
+ PLOG(FATAL);
+
+ // Signal the parent that it can now safely exit.
+ close(pipes[1]);
+
+ // Prepare file to store the samples.
+ int fd;
+ char filename[] = "/data/local/tmp/fileXXXXXX";
+ fd = mkstemp(filename);
+ unlink(filename);
+
+ // Collect samples until a client connect on the socket.
+ fd_set rfds;
+ struct timeval timeout;
+ do {
+ acquire_sample(fd, context);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000000 / context.poll_rate;
+ FD_ZERO(&rfds);
+ FD_SET(sockfd, &rfds);
+ } while (select(sockfd + 1, &rfds, NULL, NULL, &timeout) == 0);
+
+ // Collect a final sample.
+ acquire_sample(fd, context);
+
+ // Send the result back.
+ struct sockaddr_in remote_socket_info;
+ int rfd = accept(sockfd, (struct sockaddr*)&remote_socket_info, &size);
+ if (rfd < 0)
+ PLOG(FATAL);
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ PLOG(FATAL);
+ transfer_to_fd(fd, rfd);
+}
+
+void content_collection(int port) {
+ int sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ // Connect to localhost.
+ struct sockaddr_in socket_info;
+ socket_info.sin_family = AF_INET;
+ socket_info.sin_addr.s_addr = htonl(0x7f000001);
+ socket_info.sin_port = htons(port);
+ if (connect(sockfd, (struct sockaddr*)&socket_info, sizeof(socket_info)) <
+ 0) {
+ PLOG(FATAL);
+ }
+ transfer_to_fd(sockfd, STDOUT_FILENO);
+}
+
+int main(int argc, char** argv) {
+ if (argc == 1) {
+ fprintf(stderr,
+ "Usage: \n"
+ " %s port\n"
+ " %s rate FILE...\n",
+ argv[0],
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ if (argc == 2) {
+ // Argument is the port to connect to.
+ content_collection(atoi(argv[1]));
+ } else {
+ // First argument is the poll frequency, in Hz, following arguments are the
+ // file to poll.
+ Context context;
+ context.poll_rate = atoi(argv[1]);
+ context.nb_files = argc - 2;
+ context.file_fds = new int[context.nb_files];
+ for (int i = 2; i < argc; ++i)
+ context.file_fds[i - 2] = checked_open(argv[i]);
+ poll_content(context);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/tools/android/file_poller/file_poller.gyp b/tools/android/file_poller/file_poller.gyp
new file mode 100644
index 0000000..097344d
--- /dev/null
+++ b/tools/android/file_poller/file_poller.gyp
@@ -0,0 +1,18 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'file_poller',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ ],
+ 'sources': [
+ 'file_poller.cc',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/find_unused_resources.py b/tools/android/find_unused_resources.py
new file mode 100755
index 0000000..1e8fa48
--- /dev/null
+++ b/tools/android/find_unused_resources.py
@@ -0,0 +1,145 @@
+#!/usr/bin/python
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Lists unused Java strings and other resources."""
+
+import optparse
+import re
+import subprocess
+import sys
+
+
+def GetLibraryResources(r_txt_paths):
+ """Returns the resources packaged in a list of libraries.
+
+ Args:
+ r_txt_paths: paths to each library's generated R.txt file which lists the
+ resources it contains.
+
+ Returns:
+ The resources in the libraries as a list of tuples (type, name). Example:
+ [('drawable', 'arrow'), ('layout', 'month_picker'), ...]
+ """
+ resources = []
+ for r_txt_path in r_txt_paths:
+ with open(r_txt_path, 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ data_type, res_type, name, _ = line.split(None, 3)
+ assert data_type in ('int', 'int[]')
+ # Hide attrs, which are redundant with styleables and always appear
+ # unused, and hide ids, which are innocuous even if unused.
+ if res_type in ('attr', 'id'):
+ continue
+ resources.append((res_type, name))
+ return resources
+
+
+def GetUsedResources(source_paths, resource_types):
+ """Returns the types and names of resources used in Java or resource files.
+
+ Args:
+ source_paths: a list of files or folders collectively containing all the
+ Java files, resource files, and the AndroidManifest.xml.
+ resource_types: a list of resource types to look for. Example:
+ ['string', 'drawable']
+
+ Returns:
+ The resources referenced by the Java and resource files as a list of tuples
+ (type, name). Example:
+ [('drawable', 'app_icon'), ('layout', 'month_picker'), ...]
+ """
+ type_regex = '|'.join(map(re.escape, resource_types))
+ patterns = [r'@(())(%s)/(\w+)' % type_regex,
+ r'\b((\w+\.)*)R\.(%s)\.(\w+)' % type_regex]
+ resources = []
+ for pattern in patterns:
+ p = subprocess.Popen(
+ ['grep', '-REIhoe', pattern] + source_paths,
+ stdout=subprocess.PIPE)
+ grep_out, grep_err = p.communicate()
+ # Check stderr instead of return code, since return code is 1 when no
+ # matches are found.
+ assert not grep_err, 'grep failed'
+ matches = re.finditer(pattern, grep_out)
+ for match in matches:
+ package = match.group(1)
+ if package == 'android.':
+ continue
+ type_ = match.group(3)
+ name = match.group(4)
+ resources.append((type_, name))
+ return resources
+
+
+def FormatResources(resources):
+ """Formats a list of resources for printing.
+
+ Args:
+ resources: a list of resources, given as (type, name) tuples.
+ """
+ return '\n'.join(['%-12s %s' % (t, n) for t, n in sorted(resources)])
+
+
+def ParseArgs(args):
+ parser = optparse.OptionParser()
+ parser.add_option('-v', help='Show verbose output', action='store_true')
+ parser.add_option('-s', '--source-path', help='Specify a source folder path '
+ '(e.g. ui/android/java)', action='append', default=[])
+ parser.add_option('-r', '--r-txt-path', help='Specify a "first-party" R.txt '
+ 'file (e.g. out/Debug/content_shell_apk/R.txt)',
+ action='append', default=[])
+ parser.add_option('-t', '--third-party-r-txt-path', help='Specify an R.txt '
+ 'file for a third party library', action='append',
+ default=[])
+ options, args = parser.parse_args(args=args)
+ if args:
+ parser.error('positional arguments not allowed')
+ if not options.source_path:
+ parser.error('at least one source folder path must be specified with -s')
+ if not options.r_txt_path:
+ parser.error('at least one R.txt path must be specified with -r')
+ return (options.v, options.source_path, options.r_txt_path,
+ options.third_party_r_txt_path)
+
+
+def main(args=None):
+ verbose, source_paths, r_txt_paths, third_party_r_txt_paths = ParseArgs(args)
+ defined_resources = (set(GetLibraryResources(r_txt_paths)) -
+ set(GetLibraryResources(third_party_r_txt_paths)))
+ resource_types = list(set([r[0] for r in defined_resources]))
+ used_resources = set(GetUsedResources(source_paths, resource_types))
+ unused_resources = defined_resources - used_resources
+ undefined_resources = used_resources - defined_resources
+
+ # aapt dump fails silently. Notify the user if things look wrong.
+ if not defined_resources:
+ print >> sys.stderr, (
+ 'Warning: No resources found. Did you provide the correct R.txt paths?')
+ if not used_resources:
+ print >> sys.stderr, (
+ 'Warning: No resources referenced from Java or resource files. Did you '
+ 'provide the correct source paths?')
+ if undefined_resources:
+ print >> sys.stderr, (
+ 'Warning: found %d "undefined" resources that are referenced by Java '
+ 'files or by other resources, but are not defined anywhere. Run with '
+ '-v to see them.' % len(undefined_resources))
+
+ if verbose:
+ print '%d undefined resources:' % len(undefined_resources)
+ print FormatResources(undefined_resources), '\n'
+ print '%d resources defined:' % len(defined_resources)
+ print FormatResources(defined_resources), '\n'
+ print '%d used resources:' % len(used_resources)
+ print FormatResources(used_resources), '\n'
+ print '%d unused resources:' % len(unused_resources)
+ print FormatResources(unused_resources)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/android/findbugs_plugin/README b/tools/android/findbugs_plugin/README
new file mode 100644
index 0000000..3ba3f53
--- /dev/null
+++ b/tools/android/findbugs_plugin/README
@@ -0,0 +1,15 @@
+This is the FindBugs plugin for chrome on android.
+
+Currently it detects:
+- synchronized method
+- synchronized 'this'
+
+We don't want the synchronized method and synchronized 'this' to be
+used, the exception is the synchronized method defined in Android
+API.
+
+The plugin jar file was prebuilt and checked in, to rebuild the
+plugin, you need ant, and run below command, the new jar file will
+be in lib directory.
+
+ant install
diff --git a/tools/android/findbugs_plugin/build.xml b/tools/android/findbugs_plugin/build.xml
new file mode 100644
index 0000000..09ee13c
--- /dev/null
+++ b/tools/android/findbugs_plugin/build.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<project name="findbugs_plugin" basedir=".">
+
+ <description>
+ Build findbugs_plugin for Chromium Android
+ </description>
+ <property name="src.dir" location="src" />
+ <property name="lib.dir" location="../../../third_party/findbugs/lib" />
+ <property name="bin.dir" location="lib" />
+ <property name="intermediate.dir" location="intermediate" />
+ <property name="jar.name" value="chromiumPlugin.jar" />
+
+ <path id="classpath.id">
+ <fileset dir="${lib.dir}">
+ <include name="**/*.jar" />
+ </fileset>
+ </path>
+
+ <target name="makedir">
+ <mkdir dir="${intermediate.dir}" />
+ <mkdir dir="${bin.dir}" />
+ </target>
+
+ <target name="findbugs_plugin_classes" depends="makedir">
+ <javac srcdir="${src.dir}" destdir="${intermediate.dir}"
+ classpathref="classpath.id" includeantruntime="false" />
+ </target>
+
+ <target name="copy_xml_files" depends="makedir">
+ <copy file="messages.xml" todir="${intermediate.dir}" />
+ <copy file="findbugs.xml" todir="${intermediate.dir}" />
+ </target>
+
+ <target name="findbugs_plugin_jar" depends="findbugs_plugin_classes, copy_xml_files">
+ <jar destfile="${bin.dir}/${jar.name}" basedir="${intermediate.dir}">
+ </jar>
+ </target>
+
+ <target name="install" depends="findbugs_plugin_jar">
+ <delete dir="${intermediate.dir}" />
+ </target>
+</project>
diff --git a/tools/android/findbugs_plugin/findbugs.xml b/tools/android/findbugs_plugin/findbugs.xml
new file mode 100644
index 0000000..43b1f34
--- /dev/null
+++ b/tools/android/findbugs_plugin/findbugs.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<FindbugsPlugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="findbugsplugin.xsd"
+ pluginid="SynchronizedThisDetector"
+ provider="chromium"
+ website="http://code.google.com/p/chromium/wiki/UseFindBugsForAndroid">
+ <Detector class="org.chromium.tools.findbugs.plugin.SynchronizedThisDetector" reports="CHROMIUM_SYNCHRONIZED_THIS" />
+ <BugPattern type="CHROMIUM_SYNCHRONIZED_THIS" abbrev="CST" category="CORRECTNESS"/>
+
+ <Detector class="org.chromium.tools.findbugs.plugin.SynchronizedMethodDetector" reports="CHROMIUM_SYNCHRONIZED_METHOD" />
+ <BugPattern type="CHROMIUM_SYNCHRONIZED_METHOD" abbrev="CSM" category="CORRECTNESS"/>
+</FindbugsPlugin>
diff --git a/tools/android/findbugs_plugin/findbugs_plugin.gyp b/tools/android/findbugs_plugin/findbugs_plugin.gyp
new file mode 100644
index 0000000..16d06e6
--- /dev/null
+++ b/tools/android/findbugs_plugin/findbugs_plugin.gyp
@@ -0,0 +1,16 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'findbugs_plugin_test',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': 'test/java/',
+ },
+ 'includes': [ '../../../build/java.gypi' ],
+ }
+ ]
+}
diff --git a/tools/android/findbugs_plugin/lib/chromiumPlugin.jar b/tools/android/findbugs_plugin/lib/chromiumPlugin.jar
new file mode 100644
index 0000000..6ccf61b
--- /dev/null
+++ b/tools/android/findbugs_plugin/lib/chromiumPlugin.jar
Binary files differ
diff --git a/tools/android/findbugs_plugin/messages.xml b/tools/android/findbugs_plugin/messages.xml
new file mode 100644
index 0000000..aea983b
--- /dev/null
+++ b/tools/android/findbugs_plugin/messages.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<MessageCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="messagecollection.xsd">
+
+ <Plugin>
+ <ShortDescription>Chromium FindBugs Plugin </ShortDescription>
+ <Details>Adds style checks enforced in the chromium project.</Details>
+ </Plugin>
+
+ <Detector class="org.chromium.tools.findbugs.plugin.SynchronizedThisDetector">
+ <Details>
+ <![CDATA[
+ Shouldn't use synchronized(this).
+ ]]>
+ </Details>
+
+ </Detector>
+
+ <BugPattern type="CHROMIUM_SYNCHRONIZED_THIS">
+ <ShortDescription>Shouldn't use synchronized(this)</ShortDescription>
+ <LongDescription>Shouldn't use synchronized(this), please narrow down the synchronization scope.</LongDescription>
+ <Details>
+<![CDATA[
+<p>Shouldn't use synchronized(this), please narrow down the synchronization scope.</p>
+]]>
+ </Details>
+ </BugPattern>
+
+ <Detector class="org.chromium.tools.findbugs.plugin.SynchronizedMethodDetector">
+ <Details>
+ <![CDATA[
+ Shouldn't use synchronized method.
+ ]]>
+ </Details>
+
+ </Detector>
+
+ <BugPattern type="CHROMIUM_SYNCHRONIZED_METHOD">
+ <ShortDescription>Shouldn't use synchronized method</ShortDescription>
+ <LongDescription>Shouldn't use synchronized method, please narrow down the synchronization scope.</LongDescription>
+ <Details>
+<![CDATA[
+<p>Shouldn't use synchronized method, please narrow down the synchronization scope.</p>
+]]>
+ </Details>
+ </BugPattern>
+
+ <BugCode abbrev="CHROMIUM">CHROMIUM</BugCode>
+</MessageCollection>
diff --git a/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedMethodDetector.java b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedMethodDetector.java
new file mode 100644
index 0000000..d1d7614
--- /dev/null
+++ b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedMethodDetector.java
@@ -0,0 +1,37 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.tools.findbugs.plugin;
+
+import org.apache.bcel.classfile.Code;
+
+import edu.umd.cs.findbugs.BugInstance;
+import edu.umd.cs.findbugs.BugReporter;
+import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
+
+/**
+ * This class detects the synchronized method.
+ */
+public class SynchronizedMethodDetector extends OpcodeStackDetector {
+ private BugReporter mBugReporter;
+
+ public SynchronizedMethodDetector(BugReporter bugReporter) {
+ this.mBugReporter = bugReporter;
+ }
+
+ @Override
+ public void visit(Code code) {
+ if (getMethod().isSynchronized()) {
+ mBugReporter.reportBug(new BugInstance(this, "CHROMIUM_SYNCHRONIZED_METHOD",
+ NORMAL_PRIORITY)
+ .addClassAndMethod(this)
+ .addSourceLine(this));
+ }
+ super.visit(code);
+ }
+
+ @Override
+ public void sawOpcode(int arg0) {
+ }
+}
diff --git a/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedThisDetector.java b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedThisDetector.java
new file mode 100644
index 0000000..9a4e5e1
--- /dev/null
+++ b/tools/android/findbugs_plugin/src/org/chromium/tools/findbugs/plugin/SynchronizedThisDetector.java
@@ -0,0 +1,73 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.tools.findbugs.plugin;
+
+import org.apache.bcel.classfile.Code;
+
+import edu.umd.cs.findbugs.BugInstance;
+import edu.umd.cs.findbugs.BugReporter;
+import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
+
+/**
+ * This class detects the synchronized(this).
+ *
+ * The pattern of byte code of synchronized(this) is
+ * aload_0 # Load the 'this' pointer on top of stack
+ * dup # Duplicate the 'this' pointer
+ * astore_x # Store this for late use, it might be astore.
+ * monitorenter
+ */
+public class SynchronizedThisDetector extends OpcodeStackDetector {
+ private static final int PATTERN[] = {ALOAD_0, DUP, 0xff, 0xff, MONITORENTER};
+
+ private int mStep = 0;
+ private BugReporter mBugReporter;
+
+ public SynchronizedThisDetector(BugReporter bugReporter) {
+ mBugReporter = bugReporter;
+ }
+
+ @Override
+ public void visit(Code code) {
+ mStep = 0;
+ super.visit(code);
+ }
+
+ @Override
+ public void sawOpcode(int seen) {
+ if (PATTERN[mStep] == seen) {
+ mStep++;
+ if (mStep == PATTERN.length) {
+ mBugReporter.reportBug(new BugInstance(this, "CHROMIUM_SYNCHRONIZED_THIS",
+ NORMAL_PRIORITY)
+ .addClassAndMethod(this)
+ .addSourceLine(this));
+ mStep = 0;
+ return;
+ }
+ } else if (mStep == 2) {
+ // This could be astore_x
+ switch (seen) {
+ case ASTORE_0:
+ case ASTORE_1:
+ case ASTORE_2:
+ case ASTORE_3:
+ mStep += 2;
+ break;
+ case ASTORE:
+ mStep++;
+ break;
+ default:
+ mStep = 0;
+ break;
+ }
+ } else if (mStep == 3) {
+ // Could be any byte following the ASTORE.
+ mStep++;
+ } else {
+ mStep = 0;
+ }
+ }
+}
diff --git a/tools/android/findbugs_plugin/test/expected_result.txt b/tools/android/findbugs_plugin/test/expected_result.txt
new file mode 100644
index 0000000..076b007
--- /dev/null
+++ b/tools/android/findbugs_plugin/test/expected_result.txt
@@ -0,0 +1,3 @@
+M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At SimpleSynchronizedMethod.java
+M C CSM: Shouldn't use synchronized method, please narrow down the synchronization scope. At SimpleSynchronizedStaticMethod.java
+M C CST: Shouldn't use synchronized(this), please narrow down the synchronization scope. At SimpleSynchronizedThis.java
diff --git a/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedMethod.java b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedMethod.java
new file mode 100644
index 0000000..ded7848
--- /dev/null
+++ b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedMethod.java
@@ -0,0 +1,17 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.tools.findbugs.plugin;
+
+/**
+ * This class has synchronized method and is used to test
+ * SynchronizedMethodDetector.
+ */
+class SimpleSynchronizedMethod {
+ private int mCounter = 0;
+
+ synchronized void synchronizedMethod() {
+ mCounter++;
+ }
+}
diff --git a/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedStaticMethod.java b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedStaticMethod.java
new file mode 100644
index 0000000..d652dbe
--- /dev/null
+++ b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedStaticMethod.java
@@ -0,0 +1,16 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.tools.findbugs.plugin;
+
+/**
+ * This class is used to test SynchronizedMethodDetector
+ */
+class SimpleSynchronizedStaticMethod {
+ private static int sCounter = 0;
+
+ static synchronized void synchronizedStaticMethod() {
+ sCounter++;
+ }
+}
diff --git a/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedThis.java b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedThis.java
new file mode 100644
index 0000000..9125155
--- /dev/null
+++ b/tools/android/findbugs_plugin/test/java/src/org/chromium/tools/findbugs/plugin/SimpleSynchronizedThis.java
@@ -0,0 +1,19 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.tools.findbugs.plugin;
+
+/**
+ * This class has synchronized(this) statement and is used to test
+ * SynchronizedThisDetector.
+ */
+class SimpleSynchronizedThis {
+ private int mCounter = 0;
+
+ void synchronizedThis() {
+ synchronized (this) {
+ mCounter++;
+ }
+ }
+}
diff --git a/tools/android/findbugs_plugin/test/run_findbugs_plugin_tests.py b/tools/android/findbugs_plugin/test/run_findbugs_plugin_tests.py
new file mode 100755
index 0000000..c2e1531
--- /dev/null
+++ b/tools/android/findbugs_plugin/test/run_findbugs_plugin_tests.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This is used to test the findbugs plugin, it calls
+# build/android/pylib/utils/findbugs.py to analyze the classes in
+# org.chromium.tools.findbugs.plugin package, and expects to get the same
+# issue with those in expected_result.txt.
+#
+# Useful command line:
+# --rebaseline to generate the expected_result.txt, please make sure don't
+# remove the expected result of exsting tests.
+
+
+import optparse
+import os
+import sys
+
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
+ '..', '..', '..', '..',
+ 'build', 'android')))
+
+from pylib import constants
+from pylib.utils import findbugs
+
+
+def main(argv):
+ parser = findbugs.GetCommonParser()
+
+ options, _ = parser.parse_args()
+
+ if not options.known_bugs:
+ options.known_bugs = os.path.join(constants.DIR_SOURCE_ROOT, 'tools',
+ 'android', 'findbugs_plugin', 'test',
+ 'expected_result.txt')
+ if not options.only_analyze:
+ options.only_analyze = 'org.chromium.tools.findbugs.plugin.*'
+
+ return findbugs.Run(options)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/tools/android/forwarder/forwarder.cc b/tools/android/forwarder/forwarder.cc
new file mode 100644
index 0000000..fe49903
--- /dev/null
+++ b/tools/android/forwarder/forwarder.cc
@@ -0,0 +1,426 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "tools/android/common/adb_connection.h"
+#include "tools/android/common/daemon.h"
+#include "tools/android/common/net.h"
+
+namespace {
+
+const pthread_t kInvalidThread = static_cast<pthread_t>(-1);
+volatile bool g_killed = false;
+
+void CloseSocket(int fd) {
+ if (fd >= 0) {
+ int old_errno = errno;
+ close(fd);
+ errno = old_errno;
+ }
+}
+
+class Buffer {
+ public:
+ Buffer()
+ : bytes_read_(0),
+ write_offset_(0) {
+ }
+
+ bool CanRead() {
+ return bytes_read_ == 0;
+ }
+
+ bool CanWrite() {
+ return write_offset_ < bytes_read_;
+ }
+
+ int Read(int fd) {
+ int ret = -1;
+ if (CanRead()) {
+ ret = HANDLE_EINTR(read(fd, buffer_, kBufferSize));
+ if (ret > 0)
+ bytes_read_ = ret;
+ }
+ return ret;
+ }
+
+ int Write(int fd) {
+ int ret = -1;
+ if (CanWrite()) {
+ ret = HANDLE_EINTR(write(fd, buffer_ + write_offset_,
+ bytes_read_ - write_offset_));
+ if (ret > 0) {
+ write_offset_ += ret;
+ if (write_offset_ == bytes_read_) {
+ write_offset_ = 0;
+ bytes_read_ = 0;
+ }
+ }
+ }
+ return ret;
+ }
+
+ private:
+ // A big buffer to let our file-over-http bridge work more like real file.
+ static const int kBufferSize = 1024 * 128;
+ int bytes_read_;
+ int write_offset_;
+ char buffer_[kBufferSize];
+
+ DISALLOW_COPY_AND_ASSIGN(Buffer);
+};
+
+class Server;
+
+struct ForwarderThreadInfo {
+ ForwarderThreadInfo(Server* a_server, int a_forwarder_index)
+ : server(a_server),
+ forwarder_index(a_forwarder_index) {
+ }
+ Server* server;
+ int forwarder_index;
+};
+
+struct ForwarderInfo {
+ time_t start_time;
+ int socket1;
+ time_t socket1_last_byte_time;
+ size_t socket1_bytes;
+ int socket2;
+ time_t socket2_last_byte_time;
+ size_t socket2_bytes;
+};
+
+class Server {
+ public:
+ Server()
+ : thread_(kInvalidThread),
+ socket_(-1) {
+ memset(forward_to_, 0, sizeof(forward_to_));
+ memset(&forwarders_, 0, sizeof(forwarders_));
+ }
+
+ int GetFreeForwarderIndex() {
+ for (int i = 0; i < kMaxForwarders; i++) {
+ if (forwarders_[i].start_time == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ void DisposeForwarderInfo(int index) {
+ forwarders_[index].start_time = 0;
+ }
+
+ ForwarderInfo* GetForwarderInfo(int index) {
+ return &forwarders_[index];
+ }
+
+ void DumpInformation() {
+ LOG(INFO) << "Server information: " << forward_to_;
+ LOG(INFO) << "No.: age up(bytes,idle) down(bytes,idle)";
+ int count = 0;
+ time_t now = time(NULL);
+ for (int i = 0; i < kMaxForwarders; i++) {
+ const ForwarderInfo& info = forwarders_[i];
+ if (info.start_time) {
+ count++;
+ LOG(INFO) << count << ": " << now - info.start_time << " up("
+ << info.socket1_bytes << ","
+ << now - info.socket1_last_byte_time << " down("
+ << info.socket2_bytes << ","
+ << now - info.socket2_last_byte_time << ")";
+ }
+ }
+ }
+
+ void Shutdown() {
+ if (socket_ >= 0)
+ shutdown(socket_, SHUT_RDWR);
+ }
+
+ bool InitSocket(const char* arg);
+
+ void StartThread() {
+ pthread_create(&thread_, NULL, ServerThread, this);
+ }
+
+ void JoinThread() {
+ if (thread_ != kInvalidThread)
+ pthread_join(thread_, NULL);
+ }
+
+ private:
+ static void* ServerThread(void* arg);
+
+ // There are 3 kinds of threads that will access the array:
+ // 1. Server thread will get a free ForwarderInfo and initialize it;
+ // 2. Forwarder threads will dispose the ForwarderInfo when it finishes;
+ // 3. Main thread will iterate and print the forwarders.
+ // Using an array is not optimal, but can avoid locks or other complex
+ // inter-thread communication.
+ static const int kMaxForwarders = 512;
+ ForwarderInfo forwarders_[kMaxForwarders];
+
+ pthread_t thread_;
+ int socket_;
+ char forward_to_[40];
+
+ DISALLOW_COPY_AND_ASSIGN(Server);
+};
+
+// Forwards all outputs from one socket to another socket.
+void* ForwarderThread(void* arg) {
+ ForwarderThreadInfo* thread_info =
+ reinterpret_cast<ForwarderThreadInfo*>(arg);
+ Server* server = thread_info->server;
+ int index = thread_info->forwarder_index;
+ delete thread_info;
+ ForwarderInfo* info = server->GetForwarderInfo(index);
+ int socket1 = info->socket1;
+ int socket2 = info->socket2;
+ int nfds = socket1 > socket2 ? socket1 + 1 : socket2 + 1;
+ fd_set read_fds;
+ fd_set write_fds;
+ Buffer buffer1;
+ Buffer buffer2;
+
+ while (!g_killed) {
+ FD_ZERO(&read_fds);
+ if (buffer1.CanRead())
+ FD_SET(socket1, &read_fds);
+ if (buffer2.CanRead())
+ FD_SET(socket2, &read_fds);
+
+ FD_ZERO(&write_fds);
+ if (buffer1.CanWrite())
+ FD_SET(socket2, &write_fds);
+ if (buffer2.CanWrite())
+ FD_SET(socket1, &write_fds);
+
+ if (HANDLE_EINTR(select(nfds, &read_fds, &write_fds, NULL, NULL)) <= 0) {
+ LOG(ERROR) << "Select error: " << strerror(errno);
+ break;
+ }
+
+ int now = time(NULL);
+ if (FD_ISSET(socket1, &read_fds)) {
+ info->socket1_last_byte_time = now;
+ int bytes = buffer1.Read(socket1);
+ if (bytes <= 0)
+ break;
+ info->socket1_bytes += bytes;
+ }
+ if (FD_ISSET(socket2, &read_fds)) {
+ info->socket2_last_byte_time = now;
+ int bytes = buffer2.Read(socket2);
+ if (bytes <= 0)
+ break;
+ info->socket2_bytes += bytes;
+ }
+ if (FD_ISSET(socket1, &write_fds)) {
+ if (buffer2.Write(socket1) <= 0)
+ break;
+ }
+ if (FD_ISSET(socket2, &write_fds)) {
+ if (buffer1.Write(socket2) <= 0)
+ break;
+ }
+ }
+
+ CloseSocket(socket1);
+ CloseSocket(socket2);
+ server->DisposeForwarderInfo(index);
+ return NULL;
+}
+
+// Listens to a server socket. On incoming request, forward it to the host.
+// static
+void* Server::ServerThread(void* arg) {
+ Server* server = reinterpret_cast<Server*>(arg);
+ while (!g_killed) {
+ int forwarder_index = server->GetFreeForwarderIndex();
+ if (forwarder_index < 0) {
+ LOG(ERROR) << "Too many forwarders";
+ continue;
+ }
+
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ int socket = HANDLE_EINTR(accept(server->socket_,
+ reinterpret_cast<sockaddr*>(&addr),
+ &addr_len));
+ if (socket < 0) {
+ LOG(ERROR) << "Failed to accept: " << strerror(errno);
+ break;
+ }
+ tools::DisableNagle(socket);
+
+ int host_socket = tools::ConnectAdbHostSocket(server->forward_to_);
+ if (host_socket >= 0) {
+ // Set NONBLOCK flag because we use select().
+ fcntl(socket, F_SETFL, fcntl(socket, F_GETFL) | O_NONBLOCK);
+ fcntl(host_socket, F_SETFL, fcntl(host_socket, F_GETFL) | O_NONBLOCK);
+
+ ForwarderInfo* forwarder_info = server->GetForwarderInfo(forwarder_index);
+ time_t now = time(NULL);
+ forwarder_info->start_time = now;
+ forwarder_info->socket1 = socket;
+ forwarder_info->socket1_last_byte_time = now;
+ forwarder_info->socket1_bytes = 0;
+ forwarder_info->socket2 = host_socket;
+ forwarder_info->socket2_last_byte_time = now;
+ forwarder_info->socket2_bytes = 0;
+
+ pthread_t thread;
+ pthread_create(&thread, NULL, ForwarderThread,
+ new ForwarderThreadInfo(server, forwarder_index));
+ } else {
+ // Close the unused client socket which is failed to connect to host.
+ CloseSocket(socket);
+ }
+ }
+
+ CloseSocket(server->socket_);
+ server->socket_ = -1;
+ return NULL;
+}
+
+// Format of arg: <Device port>[:<Forward to port>:<Forward to address>]
+bool Server::InitSocket(const char* arg) {
+ char* endptr;
+ int local_port = static_cast<int>(strtol(arg, &endptr, 10));
+ if (local_port < 0)
+ return false;
+
+ if (*endptr != ':') {
+ snprintf(forward_to_, sizeof(forward_to_), "%d:127.0.0.1", local_port);
+ } else {
+ strncpy(forward_to_, endptr + 1, sizeof(forward_to_) - 1);
+ }
+
+ socket_ = socket(AF_INET, SOCK_STREAM, 0);
+ if (socket_ < 0) {
+ perror("server socket");
+ return false;
+ }
+ tools::DisableNagle(socket_);
+
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = htons(local_port);
+ int reuse_addr = 1;
+ setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+ &reuse_addr, sizeof(reuse_addr));
+ tools::DeferAccept(socket_);
+ if (HANDLE_EINTR(bind(socket_, reinterpret_cast<sockaddr*>(&addr),
+ sizeof(addr))) < 0 ||
+ HANDLE_EINTR(listen(socket_, 5)) < 0) {
+ perror("server bind");
+ CloseSocket(socket_);
+ socket_ = -1;
+ return false;
+ }
+
+ if (local_port == 0) {
+ socklen_t addrlen = sizeof(addr);
+ if (getsockname(socket_, reinterpret_cast<sockaddr*>(&addr), &addrlen)
+ != 0) {
+ perror("get listen address");
+ CloseSocket(socket_);
+ socket_ = -1;
+ return false;
+ }
+ local_port = ntohs(addr.sin_port);
+ }
+
+ printf("Forwarding device port %d to host %s\n", local_port, forward_to_);
+ return true;
+}
+
+int g_server_count = 0;
+Server* g_servers = NULL;
+
+void KillHandler(int unused) {
+ g_killed = true;
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].Shutdown();
+}
+
+void DumpInformation(int unused) {
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].DumpInformation();
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ printf("Android device to host TCP forwarder\n");
+ printf("Like 'adb forward' but in the reverse direction\n");
+
+ CommandLine command_line(argc, argv);
+ CommandLine::StringVector server_args = command_line.GetArgs();
+ if (tools::HasHelpSwitch(command_line) || server_args.empty()) {
+ tools::ShowHelp(
+ argv[0],
+ "<Device port>[:<Forward to port>:<Forward to address>] ...",
+ " <Forward to port> default is <Device port>\n"
+ " <Forward to address> default is 127.0.0.1\n"
+ "If <Device port> is 0, a port will by dynamically allocated.\n");
+ return 0;
+ }
+
+ g_servers = new Server[server_args.size()];
+ g_server_count = 0;
+ int failed_count = 0;
+ for (size_t i = 0; i < server_args.size(); i++) {
+ if (!g_servers[g_server_count].InitSocket(server_args[i].c_str())) {
+ printf("Couldn't start forwarder server for port spec: %s\n",
+ server_args[i].c_str());
+ ++failed_count;
+ } else {
+ ++g_server_count;
+ }
+ }
+
+ if (g_server_count == 0) {
+ printf("No forwarder servers could be started. Exiting.\n");
+ delete [] g_servers;
+ return failed_count;
+ }
+
+ if (!tools::HasNoSpawnDaemonSwitch(command_line))
+ tools::SpawnDaemon(failed_count);
+
+ signal(SIGTERM, KillHandler);
+ signal(SIGUSR2, DumpInformation);
+
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].StartThread();
+ for (int i = 0; i < g_server_count; i++)
+ g_servers[i].JoinThread();
+ g_server_count = 0;
+ delete [] g_servers;
+
+ return 0;
+}
+
diff --git a/tools/android/forwarder/forwarder.gyp b/tools/android/forwarder/forwarder.gyp
new file mode 100644
index 0000000..1df518b
--- /dev/null
+++ b/tools/android/forwarder/forwarder.gyp
@@ -0,0 +1,43 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'forwarder',
+ 'type': 'none',
+ 'dependencies': [
+ 'forwarder_symbols',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'strip_forwarder',
+ 'inputs': ['<(PRODUCT_DIR)/forwarder_symbols'],
+ 'outputs': ['<(PRODUCT_DIR)/forwarder'],
+ 'action': [
+ '<(android_strip)',
+ '--strip-unneeded',
+ '<@(_inputs)',
+ '-o',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ }, {
+ 'target_name': 'forwarder_symbols',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ '../common/common.gyp:android_tools_common',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ 'sources': [
+ 'forwarder.cc',
+ ],
+ },
+ ],
+}
+
diff --git a/tools/android/forwarder2/command.cc b/tools/android/forwarder2/command.cc
new file mode 100644
index 0000000..9b0aa24
--- /dev/null
+++ b/tools/android/forwarder2/command.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/command.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/logging.h"
+#include "base/safe_strerror_posix.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "tools/android/forwarder2/socket.h"
+
+using base::StringPiece;
+
+namespace {
+
+
+// Command format:
+// <port>:<type>
+//
+// Where:
+// <port> is a 5-chars zero-padded ASCII decimal integer
+// matching the target port for the command (e.g.
+// '08080' for port 8080)
+// <type> is a 3-char zero-padded ASCII decimal integer
+// matching a command::Type value (e.g. 002 for
+// ACK).
+// The column (:) is used as a separator for easier reading.
+const int kPortStringSize = 5;
+const int kCommandTypeStringSize = 2;
+// Command string size also includes the ':' separator char.
+const int kCommandStringSize = kPortStringSize + kCommandTypeStringSize + 1;
+
+} // namespace
+
+namespace forwarder2 {
+
+bool ReadCommand(Socket* socket,
+ int* port_out,
+ command::Type* command_type_out) {
+ char command_buffer[kCommandStringSize + 1];
+ // To make logging easier.
+ command_buffer[kCommandStringSize] = '\0';
+
+ int bytes_read = socket->ReadNumBytes(command_buffer, kCommandStringSize);
+ if (bytes_read != kCommandStringSize) {
+ if (bytes_read < 0)
+ LOG(ERROR) << "Read() error: " << safe_strerror(errno);
+ else if (!bytes_read)
+ LOG(ERROR) << "Read() error, endpoint was unexpectedly closed.";
+ else
+ LOG(ERROR) << "Read() error, not enough data received from the socket.";
+ return false;
+ }
+
+ StringPiece port_str(command_buffer, kPortStringSize);
+ if (!StringToInt(port_str, port_out)) {
+ LOG(ERROR) << "Could not parse the command port string: "
+ << port_str;
+ return false;
+ }
+
+ StringPiece command_type_str(
+ &command_buffer[kPortStringSize + 1], kCommandTypeStringSize);
+ int command_type;
+ if (!StringToInt(command_type_str, &command_type)) {
+ LOG(ERROR) << "Could not parse the command type string: "
+ << command_type_str;
+ return false;
+ }
+ *command_type_out = static_cast<command::Type>(command_type);
+ return true;
+}
+
+bool SendCommand(command::Type command, int port, Socket* socket) {
+ char buffer[kCommandStringSize + 1];
+ int len = snprintf(buffer, sizeof(buffer), "%05d:%02d", port, command);
+ CHECK_EQ(len, kCommandStringSize);
+ // Write the full command minus the leading \0 char.
+ return socket->WriteNumBytes(buffer, len) == len;
+}
+
+bool ReceivedCommand(command::Type command, Socket* socket) {
+ int port;
+ command::Type received_command;
+ if (!ReadCommand(socket, &port, &received_command))
+ return false;
+ return received_command == command;
+}
+
+} // namespace forwarder
diff --git a/tools/android/forwarder2/command.h b/tools/android/forwarder2/command.h
new file mode 100644
index 0000000..8e222ef
--- /dev/null
+++ b/tools/android/forwarder2/command.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_COMMAND_H_
+#define TOOLS_ANDROID_FORWARDER2_COMMAND_H_
+
+#include "base/basictypes.h"
+
+namespace forwarder2 {
+
+class Socket;
+
+namespace command {
+
+enum Type {
+ ACCEPT_ERROR = 0,
+ ACCEPT_SUCCESS,
+ ACK,
+ ADB_DATA_SOCKET_ERROR,
+ ADB_DATA_SOCKET_SUCCESS,
+ BIND_ERROR,
+ BIND_SUCCESS,
+ DATA_CONNECTION,
+ HOST_SERVER_ERROR,
+ HOST_SERVER_SUCCESS,
+ KILL_ALL_LISTENERS,
+ LISTEN,
+ UNLISTEN,
+ UNLISTEN_ERROR,
+ UNLISTEN_SUCCESS,
+};
+
+} // namespace command
+
+bool ReadCommand(Socket* socket,
+ int* port_out,
+ command::Type* command_type_out);
+
+// Helper function to read the command from the |socket| and return true if the
+// |command| is equal to the given command parameter.
+bool ReceivedCommand(command::Type command, Socket* socket);
+
+bool SendCommand(command::Type command, int port, Socket* socket);
+
+} // namespace forwarder
+
+#endif // TOOLS_ANDROID_FORWARDER2_COMMAND_H_
diff --git a/tools/android/forwarder2/common.cc b/tools/android/forwarder2/common.cc
new file mode 100644
index 0000000..3b7387d
--- /dev/null
+++ b/tools/android/forwarder2/common.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/common.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/safe_strerror_posix.h"
+
+namespace forwarder2 {
+
+void PError(const char* msg) {
+ LOG(ERROR) << msg << ": " << safe_strerror(errno);
+}
+
+void CloseFD(int fd) {
+ const int errno_copy = errno;
+ if (IGNORE_EINTR(close(fd)) < 0) {
+ PError("close");
+ errno = errno_copy;
+ }
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/common.h b/tools/android/forwarder2/common.h
new file mode 100644
index 0000000..43de57b
--- /dev/null
+++ b/tools/android/forwarder2/common.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Common helper functions/classes used both in the host and device forwarder.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_COMMON_H_
+#define TOOLS_ANDROID_FORWARDER2_COMMON_H_
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+
+// Preserving errno for Close() is important because the function is very often
+// used in cleanup code, after an error occurred, and it is very easy to pass an
+// invalid file descriptor to close() in this context, or more rarely, a
+// spurious signal might make close() return -1 + setting errno to EINTR,
+// masking the real reason for the original error. This leads to very unpleasant
+// debugging sessions.
+#define PRESERVE_ERRNO_HANDLE_EINTR(Func) \
+ do { \
+ int local_errno = errno; \
+ (void) HANDLE_EINTR(Func); \
+ errno = local_errno; \
+ } while (false);
+
+// Wrapper around RAW_LOG() which is signal-safe. The only purpose of this macro
+// is to avoid documenting uses of RawLog().
+#define SIGNAL_SAFE_LOG(Level, Msg) \
+ RAW_LOG(Level, Msg);
+
+namespace forwarder2 {
+
+// Note that the two following functions are not signal-safe.
+
+// Chromium logging-aware implementation of libc's perror().
+void PError(const char* msg);
+
+// Closes the provided file descriptor and logs an error if it failed.
+void CloseFD(int fd);
+
+// Helps build a formatted C-string allocated in a fixed-size array. This is
+// useful in signal handlers where base::StringPrintf() can't be used safely
+// (due to its use of LOG()).
+template <int BufferSize>
+class FixedSizeStringBuilder {
+ public:
+ FixedSizeStringBuilder() {
+ Reset();
+ }
+
+ const char* buffer() const { return buffer_; }
+
+ void Reset() {
+ buffer_[0] = 0;
+ write_ptr_ = buffer_;
+ }
+
+ // Returns the number of bytes appended to the underlying buffer or -1 if it
+ // failed.
+ int Append(const char* format, ...) PRINTF_FORMAT(/* + 1 for 'this' */ 2, 3) {
+ if (write_ptr_ >= buffer_ + BufferSize)
+ return -1;
+ va_list ap;
+ va_start(ap, format);
+ const int bytes_written = vsnprintf(
+ write_ptr_, BufferSize - (write_ptr_ - buffer_), format, ap);
+ va_end(ap);
+ if (bytes_written > 0)
+ write_ptr_ += bytes_written;
+ return bytes_written;
+ }
+
+ private:
+ char* write_ptr_;
+ char buffer_[BufferSize];
+
+ COMPILE_ASSERT(BufferSize >= 1, Size_of_buffer_must_be_at_least_one);
+ DISALLOW_COPY_AND_ASSIGN(FixedSizeStringBuilder);
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_COMMON_H_
diff --git a/tools/android/forwarder2/daemon.cc b/tools/android/forwarder2/daemon.cc
new file mode 100644
index 0000000..19a1054
--- /dev/null
+++ b/tools/android/forwarder2/daemon.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/daemon.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/safe_strerror_posix.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "tools/android/forwarder2/common.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace forwarder2 {
+namespace {
+
+const int kBufferSize = 256;
+
+// Timeout constant used for polling when connecting to the daemon's Unix Domain
+// Socket and also when waiting for its death when it is killed.
+const int kNumTries = 100;
+const int kIdleTimeMSec = 20;
+
+void InitLoggingForDaemon(const std::string& log_file) {
+ logging::LoggingSettings settings;
+ settings.logging_dest =
+ log_file.empty() ?
+ logging::LOG_TO_SYSTEM_DEBUG_LOG : logging::LOG_TO_FILE;
+ settings.log_file = log_file.c_str();
+ settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ CHECK(logging::InitLogging(settings));
+}
+
+bool RunServerAcceptLoop(const std::string& welcome_message,
+ Socket* server_socket,
+ Daemon::ServerDelegate* server_delegate) {
+ bool failed = false;
+ for (;;) {
+ scoped_ptr<Socket> client_socket(new Socket());
+ if (!server_socket->Accept(client_socket.get())) {
+ if (server_socket->DidReceiveEvent())
+ break;
+ PError("Accept()");
+ failed = true;
+ break;
+ }
+ if (!client_socket->Write(welcome_message.c_str(),
+ welcome_message.length() + 1)) {
+ PError("Write()");
+ failed = true;
+ continue;
+ }
+ server_delegate->OnClientConnected(client_socket.Pass());
+ }
+ return !failed;
+}
+
+void SigChildHandler(int signal_number) {
+ DCHECK_EQ(signal_number, SIGCHLD);
+ int status;
+ pid_t child_pid = waitpid(-1 /* any child */, &status, WNOHANG);
+ if (child_pid < 0) {
+ PError("waitpid");
+ return;
+ }
+ if (child_pid == 0)
+ return;
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ return;
+ // Avoid using StringAppendF() since it's unsafe in a signal handler due to
+ // its use of LOG().
+ FixedSizeStringBuilder<256> string_builder;
+ string_builder.Append("Daemon (pid=%d) died unexpectedly with ", child_pid);
+ if (WIFEXITED(status))
+ string_builder.Append("status %d.", WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ string_builder.Append("signal %d.", WTERMSIG(status));
+ else
+ string_builder.Append("unknown reason.");
+ SIGNAL_SAFE_LOG(ERROR, string_builder.buffer());
+}
+
+// Note that 0 is written to |lock_owner_pid| in case the file is not locked.
+bool GetFileLockOwnerPid(int fd, pid_t* lock_owner_pid) {
+ struct flock lock_info = {};
+ lock_info.l_type = F_WRLCK;
+ lock_info.l_whence = SEEK_CUR;
+ const int ret = HANDLE_EINTR(fcntl(fd, F_GETLK, &lock_info));
+ if (ret < 0) {
+ if (errno == EBADF) {
+ // Assume that the provided file descriptor corresponding to the PID file
+ // was valid until the daemon removed this file.
+ *lock_owner_pid = 0;
+ return true;
+ }
+ PError("fcntl");
+ return false;
+ }
+ if (lock_info.l_type == F_UNLCK) {
+ *lock_owner_pid = 0;
+ return true;
+ }
+ CHECK_EQ(F_WRLCK /* exclusive lock */, lock_info.l_type);
+ *lock_owner_pid = lock_info.l_pid;
+ return true;
+}
+
+scoped_ptr<Socket> ConnectToUnixDomainSocket(
+ const std::string& socket_name,
+ int tries_count,
+ int idle_time_msec,
+ const std::string& expected_welcome_message) {
+ for (int i = 0; i < tries_count; ++i) {
+ scoped_ptr<Socket> socket(new Socket());
+ if (!socket->ConnectUnix(socket_name)) {
+ if (idle_time_msec)
+ usleep(idle_time_msec * 1000);
+ continue;
+ }
+ char buf[kBufferSize];
+ DCHECK(expected_welcome_message.length() + 1 <= sizeof(buf));
+ memset(buf, 0, sizeof(buf));
+ if (socket->Read(buf, expected_welcome_message.length() + 1) < 0) {
+ perror("read");
+ continue;
+ }
+ if (expected_welcome_message != buf) {
+ LOG(ERROR) << "Unexpected message read from daemon: " << buf;
+ break;
+ }
+ return socket.Pass();
+ }
+ return scoped_ptr<Socket>();
+}
+
+} // namespace
+
+Daemon::Daemon(const std::string& log_file_path,
+ const std::string& identifier,
+ ClientDelegate* client_delegate,
+ ServerDelegate* server_delegate,
+ GetExitNotifierFDCallback get_exit_fd_callback)
+ : log_file_path_(log_file_path),
+ identifier_(identifier),
+ client_delegate_(client_delegate),
+ server_delegate_(server_delegate),
+ get_exit_fd_callback_(get_exit_fd_callback) {
+ DCHECK(client_delegate_);
+ DCHECK(server_delegate_);
+ DCHECK(get_exit_fd_callback_);
+}
+
+Daemon::~Daemon() {}
+
+bool Daemon::SpawnIfNeeded() {
+ const int kSingleTry = 1;
+ const int kNoIdleTime = 0;
+ scoped_ptr<Socket> client_socket = ConnectToUnixDomainSocket(
+ identifier_, kSingleTry, kNoIdleTime, identifier_);
+ if (!client_socket) {
+ switch (fork()) {
+ case -1:
+ PError("fork()");
+ return false;
+ // Child.
+ case 0: {
+ if (setsid() < 0) { // Detach the child process from its parent.
+ PError("setsid()");
+ exit(1);
+ }
+ InitLoggingForDaemon(log_file_path_);
+ CloseFD(STDIN_FILENO);
+ CloseFD(STDOUT_FILENO);
+ CloseFD(STDERR_FILENO);
+ const int null_fd = open("/dev/null", O_RDWR);
+ CHECK_EQ(null_fd, STDIN_FILENO);
+ CHECK_EQ(dup(null_fd), STDOUT_FILENO);
+ CHECK_EQ(dup(null_fd), STDERR_FILENO);
+ Socket command_socket;
+ if (!command_socket.BindUnix(identifier_)) {
+ scoped_ptr<Socket> client_socket = ConnectToUnixDomainSocket(
+ identifier_, kSingleTry, kNoIdleTime, identifier_);
+ if (client_socket.get()) {
+ // The daemon was spawned by a concurrent process.
+ exit(0);
+ }
+ PError("bind()");
+ exit(1);
+ }
+ server_delegate_->Init();
+ command_socket.AddEventFd(get_exit_fd_callback_());
+ return RunServerAcceptLoop(
+ identifier_, &command_socket, server_delegate_);
+ }
+ default:
+ break;
+ }
+ }
+ // Parent.
+ // Install the custom SIGCHLD handler.
+ sigset_t blocked_signals_set;
+ if (sigprocmask(0 /* first arg ignored */, NULL, &blocked_signals_set) < 0) {
+ PError("sigprocmask()");
+ return false;
+ }
+ struct sigaction old_action;
+ struct sigaction new_action;
+ memset(&new_action, 0, sizeof(new_action));
+ new_action.sa_handler = SigChildHandler;
+ new_action.sa_flags = SA_NOCLDSTOP;
+ sigemptyset(&new_action.sa_mask);
+ if (sigaction(SIGCHLD, &new_action, &old_action) < 0) {
+ PError("sigaction()");
+ return false;
+ }
+ // Connect to the daemon's Unix Domain Socket.
+ bool failed = false;
+ if (!client_socket) {
+ client_socket = ConnectToUnixDomainSocket(
+ identifier_, kNumTries, kIdleTimeMSec, identifier_);
+ if (!client_socket) {
+ LOG(ERROR) << "Could not connect to daemon's Unix Daemon socket";
+ failed = true;
+ }
+ }
+ if (!failed)
+ client_delegate_->OnDaemonReady(client_socket.get());
+ // Restore the previous signal action for SIGCHLD.
+ if (sigaction(SIGCHLD, &old_action, NULL) < 0) {
+ PError("sigaction");
+ failed = true;
+ }
+ return !failed;
+}
+
+bool Daemon::Kill() {
+ pid_t daemon_pid = Socket::GetUnixDomainSocketProcessOwner(identifier_);
+ if (daemon_pid < 0) {
+ LOG(ERROR) << "No forwarder daemon seems to be running";
+ return true;
+ }
+ if (kill(daemon_pid, SIGTERM) < 0) {
+ if (errno == ESRCH /* invalid PID */) {
+ // The daemon exited for some reason (e.g. kill by a process other than
+ // us) right before the call to kill() above.
+ LOG(ERROR) << "Could not kill daemon with PID " << daemon_pid;
+ return true;
+ }
+ PError("kill");
+ return false;
+ }
+ for (int i = 0; i < kNumTries; ++i) {
+ const pid_t previous_pid = daemon_pid;
+ daemon_pid = Socket::GetUnixDomainSocketProcessOwner(identifier_);
+ if (daemon_pid < 0)
+ return true;
+ // Since we are polling we might not see the 'daemon exited' event if
+ // another daemon was spawned during our idle period.
+ if (daemon_pid != previous_pid) {
+ LOG(WARNING) << "Daemon (pid=" << previous_pid
+ << ") was successfully killed but a new daemon (pid="
+ << daemon_pid << ") seems to be running now.";
+ return true;
+ }
+ usleep(kIdleTimeMSec * 1000);
+ }
+ LOG(ERROR) << "Timed out while killing daemon. "
+ "It might still be tearing down.";
+ return false;
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/daemon.h b/tools/android/forwarder2/daemon.h
new file mode 100644
index 0000000..4b05ea4
--- /dev/null
+++ b/tools/android/forwarder2/daemon.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_DAEMON_H_
+#define TOOLS_ANDROID_FORWARDER2_DAEMON_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace forwarder2 {
+
+class Socket;
+
+// Provides a way to spawn a daemon and communicate with it.
+class Daemon {
+ public:
+ // Callback used by the daemon to shutdown properly. See pipe_notifier.h for
+ // more details.
+ typedef int (*GetExitNotifierFDCallback)();
+
+ class ClientDelegate {
+ public:
+ virtual ~ClientDelegate() {}
+
+ // Called after the daemon is ready to receive commands.
+ virtual void OnDaemonReady(Socket* daemon_socket) = 0;
+ };
+
+ class ServerDelegate {
+ public:
+ virtual ~ServerDelegate() {}
+
+ // Called after the daemon bound its Unix Domain Socket. This can be used to
+ // setup signal handlers or perform global initialization.
+ virtual void Init() = 0;
+
+ virtual void OnClientConnected(scoped_ptr<Socket> client_socket) = 0;
+ };
+
+ // |identifier| should be a unique string identifier. It is used to
+ // bind/connect the underlying Unix Domain Socket.
+ // Note that this class does not take ownership of |client_delegate| and
+ // |server_delegate|.
+ Daemon(const std::string& log_file_path,
+ const std::string& identifier,
+ ClientDelegate* client_delegate,
+ ServerDelegate* server_delegate,
+ GetExitNotifierFDCallback get_exit_fd_callback);
+
+ ~Daemon();
+
+ // Returns whether the daemon was successfully spawned. Note that this does
+ // not necessarily mean that the current process was forked in case the daemon
+ // is already running.
+ bool SpawnIfNeeded();
+
+ // Kills the daemon and blocks until it exited. Returns whether it succeeded.
+ bool Kill();
+
+ private:
+ const std::string log_file_path_;
+ const std::string identifier_;
+ ClientDelegate* const client_delegate_;
+ ServerDelegate* const server_delegate_;
+ const GetExitNotifierFDCallback get_exit_fd_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_DAEMON_H_
diff --git a/tools/android/forwarder2/device_controller.cc b/tools/android/forwarder2/device_controller.cc
new file mode 100644
index 0000000..a4cb9c7
--- /dev/null
+++ b/tools/android/forwarder2/device_controller.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/device_controller.h"
+
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/single_thread_task_runner.h"
+#include "tools/android/forwarder2/command.h"
+#include "tools/android/forwarder2/device_listener.h"
+#include "tools/android/forwarder2/socket.h"
+#include "tools/android/forwarder2/util.h"
+
+namespace forwarder2 {
+
+// static
+scoped_ptr<DeviceController> DeviceController::Create(
+ const std::string& adb_unix_socket,
+ int exit_notifier_fd) {
+ scoped_ptr<DeviceController> device_controller;
+ scoped_ptr<Socket> host_socket(new Socket());
+ if (!host_socket->BindUnix(adb_unix_socket)) {
+ PLOG(ERROR) << "Could not BindAndListen DeviceController socket on port "
+ << adb_unix_socket << ": ";
+ return device_controller.Pass();
+ }
+ LOG(INFO) << "Listening on Unix Domain Socket " << adb_unix_socket;
+ device_controller.reset(
+ new DeviceController(host_socket.Pass(), exit_notifier_fd));
+ return device_controller.Pass();
+}
+
+DeviceController::~DeviceController() {
+ DCHECK(construction_task_runner_->RunsTasksOnCurrentThread());
+}
+
+void DeviceController::Start() {
+ AcceptHostCommandSoon();
+}
+
+DeviceController::DeviceController(scoped_ptr<Socket> host_socket,
+ int exit_notifier_fd)
+ : host_socket_(host_socket.Pass()),
+ exit_notifier_fd_(exit_notifier_fd),
+ construction_task_runner_(base::MessageLoopProxy::current()),
+ weak_ptr_factory_(this) {
+ host_socket_->AddEventFd(exit_notifier_fd);
+}
+
+void DeviceController::AcceptHostCommandSoon() {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&DeviceController::AcceptHostCommandInternal,
+ base::Unretained(this)));
+}
+
+void DeviceController::AcceptHostCommandInternal() {
+ scoped_ptr<Socket> socket(new Socket);
+ if (!host_socket_->Accept(socket.get())) {
+ if (!host_socket_->DidReceiveEvent())
+ PLOG(ERROR) << "Could not Accept DeviceController socket";
+ else
+ LOG(INFO) << "Received exit notification";
+ return;
+ }
+ base::ScopedClosureRunner accept_next_client(
+ base::Bind(&DeviceController::AcceptHostCommandSoon,
+ base::Unretained(this)));
+ // So that |socket| doesn't block on read if it has notifications.
+ socket->AddEventFd(exit_notifier_fd_);
+ int port;
+ command::Type command;
+ if (!ReadCommand(socket.get(), &port, &command)) {
+ LOG(ERROR) << "Invalid command received.";
+ return;
+ }
+ const ListenersMap::iterator listener_it = listeners_.find(port);
+ DeviceListener* const listener = listener_it == listeners_.end()
+ ? static_cast<DeviceListener*>(NULL) : listener_it->second.get();
+ switch (command) {
+ case command::LISTEN: {
+ if (listener != NULL) {
+ LOG(WARNING) << "Already forwarding port " << port
+ << ". Attempting to restart the listener.\n";
+ DeleteRefCountedValueInMapFromIterator(listener_it, &listeners_);
+ }
+ scoped_ptr<DeviceListener> new_listener(
+ DeviceListener::Create(
+ socket.Pass(), port,
+ base::Bind(&DeviceController::DeleteListenerOnError,
+ weak_ptr_factory_.GetWeakPtr())));
+ if (!new_listener)
+ return;
+ new_listener->Start();
+ // |port| can be zero, to allow dynamically allocated port, so instead, we
+ // call DeviceListener::listener_port() to retrieve the currently
+ // allocated port to this new listener.
+ const int listener_port = new_listener->listener_port();
+ listeners_.insert(
+ std::make_pair(listener_port,
+ linked_ptr<DeviceListener>(new_listener.release())));
+ LOG(INFO) << "Forwarding device port " << listener_port << " to host.";
+ break;
+ }
+ case command::DATA_CONNECTION:
+ if (listener == NULL) {
+ LOG(ERROR) << "Data Connection command received, but "
+ << "listener has not been set up yet for port " << port;
+ // After this point it is assumed that, once we close our Adb Data
+ // socket, the Adb forwarder command will propagate the closing of
+ // sockets all the way to the host side.
+ break;
+ }
+ listener->SetAdbDataSocket(socket.Pass());
+ break;
+ case command::UNLISTEN:
+ LOG(INFO) << "Unmapping port " << port;
+ if (!listener) {
+ LOG(ERROR) << "No listener found for port " << port;
+ SendCommand(command::UNLISTEN_ERROR, port, socket.get());
+ break;
+ }
+ DeleteRefCountedValueInMapFromIterator(listener_it, &listeners_);
+ SendCommand(command::UNLISTEN_SUCCESS, port, socket.get());
+ break;
+ default:
+ // TODO(felipeg): add a KillAllListeners command.
+ LOG(ERROR) << "Invalid command received. Port: " << port
+ << " Command: " << command;
+ }
+}
+
+// static
+void DeviceController::DeleteListenerOnError(
+ const base::WeakPtr<DeviceController>& device_controller_ptr,
+ scoped_ptr<DeviceListener> device_listener) {
+ DeviceListener* const listener = device_listener.release();
+ DeviceController* const controller = device_controller_ptr.get();
+ if (!controller) {
+ // |listener| was already deleted by the controller that did have its
+ // ownership.
+ return;
+ }
+ DCHECK(controller->construction_task_runner_->RunsTasksOnCurrentThread());
+ bool listener_did_exist = DeleteRefCountedValueInMap(
+ listener->listener_port(), &controller->listeners_);
+ DCHECK(listener_did_exist);
+ // Note that |listener| was deleted by DeleteRefCountedValueInMap().
+}
+
+} // namespace forwarder
diff --git a/tools/android/forwarder2/device_controller.h b/tools/android/forwarder2/device_controller.h
new file mode 100644
index 0000000..567a08d
--- /dev/null
+++ b/tools/android/forwarder2/device_controller.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_DEVICE_CONTROLLER_H_
+#define TOOLS_ANDROID_FORWARDER2_DEVICE_CONTROLLER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace forwarder2 {
+
+class DeviceListener;
+
+// There is a single DeviceController per device_forwarder process, and it is in
+// charge of managing all active redirections on the device side (one
+// DeviceListener each).
+class DeviceController {
+ public:
+ static scoped_ptr<DeviceController> Create(const std::string& adb_unix_socket,
+ int exit_notifier_fd);
+ ~DeviceController();
+
+ void Start();
+
+ private:
+ typedef base::hash_map<
+ int /* port */, linked_ptr<DeviceListener> > ListenersMap;
+
+ DeviceController(scoped_ptr<Socket> host_socket, int exit_notifier_fd);
+
+ void AcceptHostCommandSoon();
+ void AcceptHostCommandInternal();
+
+ // Note that this can end up being called after the DeviceController is
+ // destroyed which is why a weak pointer is used.
+ static void DeleteListenerOnError(
+ const base::WeakPtr<DeviceController>& device_controller_ptr,
+ scoped_ptr<DeviceListener> device_listener);
+
+ const scoped_ptr<Socket> host_socket_;
+ // Used to notify the controller to exit.
+ const int exit_notifier_fd_;
+ // Lets ensure DeviceListener instances are deleted on the thread they were
+ // created on.
+ const scoped_refptr<base::SingleThreadTaskRunner> construction_task_runner_;
+ ListenersMap listeners_;
+
+ //WeakPtrFactory's documentation says:
+ // Member variables should appear before the WeakPtrFactory, to ensure
+ // that any WeakPtrs to Controller are invalidated before its members
+ // variable's destructors are executed, rendering them invalid.
+ base::WeakPtrFactory<DeviceController> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceController);
+};
+
+} // namespace forwarder
+
+#endif // TOOLS_ANDROID_FORWARDER2_DEVICE_CONTROLLER_H_
diff --git a/tools/android/forwarder2/device_forwarder_main.cc b/tools/android/forwarder2/device_forwarder_main.cc
new file mode 100644
index 0000000..cad46f4
--- /dev/null
+++ b/tools/android/forwarder2/device_forwarder_main.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <signal.h>
+#include <stdlib.h>
+
+#include <iostream>
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread.h"
+#include "tools/android/forwarder2/common.h"
+#include "tools/android/forwarder2/daemon.h"
+#include "tools/android/forwarder2/device_controller.h"
+#include "tools/android/forwarder2/pipe_notifier.h"
+
+namespace forwarder2 {
+namespace {
+
+// Leaky global instance, accessed from the signal handler.
+forwarder2::PipeNotifier* g_notifier = NULL;
+
+const int kBufSize = 256;
+
+const char kUnixDomainSocketPath[] = "chrome_device_forwarder";
+const char kDaemonIdentifier[] = "chrome_device_forwarder_daemon";
+
+void KillHandler(int /* unused */) {
+ CHECK(g_notifier);
+ if (!g_notifier->Notify())
+ exit(1);
+}
+
+// Lets the daemon fetch the exit notifier file descriptor.
+int GetExitNotifierFD() {
+ DCHECK(g_notifier);
+ return g_notifier->receiver_fd();
+}
+
+class ServerDelegate : public Daemon::ServerDelegate {
+ public:
+ ServerDelegate() : initialized_(false) {}
+
+ virtual ~ServerDelegate() {
+ if (!controller_thread_.get())
+ return;
+ // The DeviceController instance, if any, is constructed on the controller
+ // thread. Make sure that it gets deleted on that same thread. Note that
+ // DeleteSoon() is not used here since it would imply reading |controller_|
+ // from the main thread while it's set on the internal thread.
+ controller_thread_->message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&ServerDelegate::DeleteControllerOnInternalThread,
+ base::Unretained(this)));
+ }
+
+ void DeleteControllerOnInternalThread() {
+ DCHECK(
+ controller_thread_->message_loop_proxy()->RunsTasksOnCurrentThread());
+ controller_.reset();
+ }
+
+ // Daemon::ServerDelegate:
+ virtual void Init() OVERRIDE {
+ DCHECK(!g_notifier);
+ g_notifier = new forwarder2::PipeNotifier();
+ signal(SIGTERM, KillHandler);
+ signal(SIGINT, KillHandler);
+ controller_thread_.reset(new base::Thread("controller_thread"));
+ controller_thread_->Start();
+ }
+
+ virtual void OnClientConnected(scoped_ptr<Socket> client_socket) OVERRIDE {
+ if (initialized_) {
+ client_socket->WriteString("OK");
+ return;
+ }
+ controller_thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&ServerDelegate::StartController, base::Unretained(this),
+ GetExitNotifierFD(), base::Passed(&client_socket)));
+ initialized_ = true;
+ }
+
+ private:
+ void StartController(int exit_notifier_fd, scoped_ptr<Socket> client_socket) {
+ DCHECK(!controller_.get());
+ scoped_ptr<DeviceController> controller(
+ DeviceController::Create(kUnixDomainSocketPath, exit_notifier_fd));
+ if (!controller.get()) {
+ client_socket->WriteString(
+ base::StringPrintf("ERROR: Could not initialize device controller "
+ "with ADB socket path: %s",
+ kUnixDomainSocketPath));
+ return;
+ }
+ controller_.swap(controller);
+ controller_->Start();
+ client_socket->WriteString("OK");
+ client_socket->Close();
+ }
+
+ scoped_ptr<DeviceController> controller_;
+ scoped_ptr<base::Thread> controller_thread_;
+ bool initialized_;
+};
+
+class ClientDelegate : public Daemon::ClientDelegate {
+ public:
+ ClientDelegate() : has_failed_(false) {}
+
+ bool has_failed() const { return has_failed_; }
+
+ // Daemon::ClientDelegate:
+ virtual void OnDaemonReady(Socket* daemon_socket) OVERRIDE {
+ char buf[kBufSize];
+ const int bytes_read = daemon_socket->Read(
+ buf, sizeof(buf) - 1 /* leave space for null terminator */);
+ CHECK_GT(bytes_read, 0);
+ DCHECK(bytes_read < sizeof(buf));
+ buf[bytes_read] = 0;
+ base::StringPiece msg(buf, bytes_read);
+ if (msg.starts_with("ERROR")) {
+ LOG(ERROR) << msg;
+ has_failed_ = true;
+ return;
+ }
+ }
+
+ private:
+ bool has_failed_;
+};
+
+int RunDeviceForwarder(int argc, char** argv) {
+ CommandLine::Init(argc, argv); // Needed by logging.
+ const bool kill_server = CommandLine::ForCurrentProcess()->HasSwitch(
+ "kill-server");
+ if ((kill_server && argc != 2) || (!kill_server && argc != 1)) {
+ std::cerr << "Usage: device_forwarder [--kill-server]" << std::endl;
+ return 1;
+ }
+ base::AtExitManager at_exit_manager; // Used by base::Thread.
+ ClientDelegate client_delegate;
+ ServerDelegate daemon_delegate;
+ const char kLogFilePath[] = ""; // Log to logcat.
+ Daemon daemon(kLogFilePath, kDaemonIdentifier, &client_delegate,
+ &daemon_delegate, &GetExitNotifierFD);
+
+ if (kill_server)
+ return !daemon.Kill();
+
+ if (!daemon.SpawnIfNeeded())
+ return 1;
+ return client_delegate.has_failed();
+}
+
+} // namespace
+} // namespace forwarder2
+
+int main(int argc, char** argv) {
+ return forwarder2::RunDeviceForwarder(argc, argv);
+}
diff --git a/tools/android/forwarder2/device_listener.cc b/tools/android/forwarder2/device_listener.cc
new file mode 100644
index 0000000..b48a746
--- /dev/null
+++ b/tools/android/forwarder2/device_listener.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/device_listener.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/single_thread_task_runner.h"
+#include "tools/android/forwarder2/command.h"
+#include "tools/android/forwarder2/forwarder.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace forwarder2 {
+
+// static
+scoped_ptr<DeviceListener> DeviceListener::Create(
+ scoped_ptr<Socket> host_socket,
+ int listener_port,
+ const ErrorCallback& error_callback) {
+ scoped_ptr<Socket> listener_socket(new Socket());
+ scoped_ptr<DeviceListener> device_listener;
+ if (!listener_socket->BindTcp("", listener_port)) {
+ LOG(ERROR) << "Device could not bind and listen to local port "
+ << listener_port;
+ SendCommand(command::BIND_ERROR, listener_port, host_socket.get());
+ return device_listener.Pass();
+ }
+ // In case the |listener_port_| was zero, GetPort() will return the
+ // currently (non-zero) allocated port for this socket.
+ listener_port = listener_socket->GetPort();
+ SendCommand(command::BIND_SUCCESS, listener_port, host_socket.get());
+ device_listener.reset(
+ new DeviceListener(listener_socket.Pass(), host_socket.Pass(),
+ listener_port, error_callback));
+ return device_listener.Pass();
+}
+
+DeviceListener::~DeviceListener() {
+ DCHECK(deletion_task_runner_->RunsTasksOnCurrentThread());
+ deletion_notifier_.Notify();
+}
+
+void DeviceListener::Start() {
+ thread_.Start();
+ AcceptNextClientSoon();
+}
+
+void DeviceListener::SetAdbDataSocket(scoped_ptr<Socket> adb_data_socket) {
+ thread_.message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&DeviceListener::OnAdbDataSocketReceivedOnInternalThread,
+ base::Unretained(this), base::Passed(&adb_data_socket)));
+}
+
+DeviceListener::DeviceListener(scoped_ptr<Socket> listener_socket,
+ scoped_ptr<Socket> host_socket,
+ int port,
+ const ErrorCallback& error_callback)
+ : self_deleter_helper_(this, error_callback),
+ listener_socket_(listener_socket.Pass()),
+ host_socket_(host_socket.Pass()),
+ listener_port_(port),
+ deletion_task_runner_(base::MessageLoopProxy::current()),
+ thread_("DeviceListener") {
+ CHECK(host_socket_.get());
+ DCHECK(deletion_task_runner_.get());
+ host_socket_->AddEventFd(deletion_notifier_.receiver_fd());
+ listener_socket_->AddEventFd(deletion_notifier_.receiver_fd());
+}
+
+void DeviceListener::AcceptNextClientSoon() {
+ thread_.message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&DeviceListener::AcceptClientOnInternalThread,
+ base::Unretained(this)));
+}
+
+void DeviceListener::AcceptClientOnInternalThread() {
+ device_data_socket_.reset(new Socket());
+ if (!listener_socket_->Accept(device_data_socket_.get())) {
+ if (listener_socket_->DidReceiveEvent()) {
+ LOG(INFO) << "Received exit notification, stopped accepting clients.";
+ OnInternalThreadError();
+ return;
+ }
+ LOG(WARNING) << "Could not Accept in ListenerSocket.";
+ SendCommand(command::ACCEPT_ERROR, listener_port_, host_socket_.get());
+ OnInternalThreadError();
+ return;
+ }
+ SendCommand(command::ACCEPT_SUCCESS, listener_port_, host_socket_.get());
+ if (!ReceivedCommand(command::HOST_SERVER_SUCCESS,
+ host_socket_.get())) {
+ SendCommand(command::ACK, listener_port_, host_socket_.get());
+ LOG(ERROR) << "Host could not connect to server.";
+ device_data_socket_->Close();
+ if (host_socket_->has_error()) {
+ LOG(ERROR) << "Adb Control connection lost. "
+ << "Listener port: " << listener_port_;
+ OnInternalThreadError();
+ return;
+ }
+ // It can continue if the host forwarder could not connect to the host
+ // server but the control connection is still alive (no errors). The device
+ // acknowledged that (above), and it can re-try later.
+ AcceptNextClientSoon();
+ return;
+ }
+}
+
+void DeviceListener::OnAdbDataSocketReceivedOnInternalThread(
+ scoped_ptr<Socket> adb_data_socket) {
+ DCHECK(adb_data_socket);
+ SendCommand(command::ADB_DATA_SOCKET_SUCCESS, listener_port_,
+ host_socket_.get());
+ forwarders_manager_.CreateAndStartNewForwarder(
+ device_data_socket_.Pass(), adb_data_socket.Pass());
+ AcceptNextClientSoon();
+}
+
+void DeviceListener::OnInternalThreadError() {
+ self_deleter_helper_.MaybeSelfDeleteSoon();
+}
+
+} // namespace forwarder
diff --git a/tools/android/forwarder2/device_listener.h b/tools/android/forwarder2/device_listener.h
new file mode 100644
index 0000000..c7724f4
--- /dev/null
+++ b/tools/android/forwarder2/device_listener.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_DEVICE_LISTENER_H_
+#define TOOLS_ANDROID_FORWARDER2_DEVICE_LISTENER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "tools/android/forwarder2/forwarders_manager.h"
+#include "tools/android/forwarder2/pipe_notifier.h"
+#include "tools/android/forwarder2/self_deleter_helper.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace forwarder2 {
+
+class Forwarder;
+
+// A DeviceListener instance is used in the device_forwarder program to bind to
+// a specific device-side |port| and wait for client connections. When a
+// connection happens, it informs the corresponding HostController instance
+// running on the host, through |host_socket|. Then the class expects a call to
+// its SetAdbDataSocket() method (performed by the device controller) once the
+// host opened a new connection to the device. When this happens, a new internal
+// Forwarder instance is started.
+// Note that instances of this class are owned by the device controller which
+// creates and destroys them on the same thread. In case an internal error
+// happens on the DeviceListener's internal thread, the DeviceListener
+// can also self-delete by executing the user-provided callback on the thread
+// the DeviceListener was created on.
+// Note that the DeviceListener's destructor joins its internal thread (i.e.
+// waits for its completion) which means that the internal thread is guaranteed
+// not to be running anymore once the object is deleted.
+class DeviceListener {
+ public:
+ // Callback that is used for self-deletion on error to let the device
+ // controller perform some additional cleanup work (e.g. removing the device
+ // listener instance from its internal map before deleting it).
+ typedef base::Callback<void (scoped_ptr<DeviceListener>)> ErrorCallback;
+
+ static scoped_ptr<DeviceListener> Create(scoped_ptr<Socket> host_socket,
+ int port,
+ const ErrorCallback& error_callback);
+
+ ~DeviceListener();
+
+ void Start();
+
+ void SetAdbDataSocket(scoped_ptr<Socket> adb_data_socket);
+
+ int listener_port() const { return listener_port_; }
+
+ private:
+ DeviceListener(scoped_ptr<Socket> listener_socket,
+ scoped_ptr<Socket> host_socket,
+ int port,
+ const ErrorCallback& error_callback);
+
+ // Pushes an AcceptClientOnInternalThread() task to the internal thread's
+ // message queue in order to wait for a new client soon.
+ void AcceptNextClientSoon();
+
+ void AcceptClientOnInternalThread();
+
+ void OnAdbDataSocketReceivedOnInternalThread(
+ scoped_ptr<Socket> adb_data_socket);
+
+ void OnInternalThreadError();
+
+ SelfDeleterHelper<DeviceListener> self_deleter_helper_;
+ // Used for the listener thread to be notified on destruction. We have one
+ // notifier per Listener thread since each Listener thread may be requested to
+ // exit for different reasons independently from each other and independent
+ // from the main program, ex. when the host requests to forward/listen the
+ // same port again. Both the |host_socket_| and |listener_socket_| must share
+ // the same receiver file descriptor from |deletion_notifier_| and it is set
+ // in the constructor.
+ PipeNotifier deletion_notifier_;
+ // The local device listener socket for accepting connections from the local
+ // port (listener_port_).
+ const scoped_ptr<Socket> listener_socket_;
+ // The listener socket for sending control commands.
+ const scoped_ptr<Socket> host_socket_;
+ scoped_ptr<Socket> device_data_socket_;
+ const int listener_port_;
+ // Task runner used for deletion set at construction time (i.e. the object is
+ // deleted on the same thread it is created on).
+ scoped_refptr<base::SingleThreadTaskRunner> deletion_task_runner_;
+ base::Thread thread_;
+ ForwardersManager forwarders_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceListener);
+};
+
+} // namespace forwarder
+
+#endif // TOOLS_ANDROID_FORWARDER2_DEVICE_LISTENER_H_
diff --git a/tools/android/forwarder2/forwarder.cc b/tools/android/forwarder2/forwarder.cc
new file mode 100644
index 0000000..1e0bcd0
--- /dev/null
+++ b/tools/android/forwarder2/forwarder.cc
@@ -0,0 +1,255 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/forwarder.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace forwarder2 {
+namespace {
+
+const int kBufferSize = 32 * 1024;
+
+} // namespace
+
+
+// Helper class to buffer reads and writes from one socket to another.
+// Each implements a small buffer connected two one input socket, and
+// one output socket.
+//
+// socket_from_ ---> [BufferedCopier] ---> socket_to_
+//
+// These objects are used in a pair to handle duplex traffic, as in:
+//
+// ------> [BufferedCopier_1] --->
+// / \
+// socket_1 * * socket_2
+// \ /
+// <------ [BufferedCopier_2] <----
+//
+// When a BufferedCopier is in the READING state (see below), it only listens
+// to events on its input socket, and won't detect when its output socket
+// disconnects. To work around this, its peer will call its Close() method
+// when that happens.
+
+class Forwarder::BufferedCopier {
+ public:
+ // Possible states:
+ // READING - Empty buffer and Waiting for input.
+ // WRITING - Data in buffer, and waiting for output.
+ // CLOSING - Like WRITING, but do not try to read after that.
+ // CLOSED - Completely closed.
+ //
+ // State transitions are:
+ //
+ // T01: READING ---[receive data]---> WRITING
+ // T02: READING ---[error on input socket]---> CLOSED
+ // T03: READING ---[Close() call]---> CLOSED
+ //
+ // T04: WRITING ---[write partial data]---> WRITING
+ // T05: WRITING ---[write all data]----> READING
+ // T06: WRITING ---[error on output socket]----> CLOSED
+ // T07: WRITING ---[Close() call]---> CLOSING
+ //
+ // T08: CLOSING ---[write partial data]---> CLOSING
+ // T09: CLOSING ---[write all data]----> CLOSED
+ // T10: CLOSING ---[Close() call]---> CLOSING
+ // T11: CLOSING ---[error on output socket] ---> CLOSED
+ //
+ enum State {
+ STATE_READING = 0,
+ STATE_WRITING = 1,
+ STATE_CLOSING = 2,
+ STATE_CLOSED = 3,
+ };
+
+ // Does NOT own the pointers.
+ BufferedCopier(Socket* socket_from, Socket* socket_to)
+ : socket_from_(socket_from),
+ socket_to_(socket_to),
+ bytes_read_(0),
+ write_offset_(0),
+ peer_(NULL),
+ state_(STATE_READING) {}
+
+ // Sets the 'peer_' field pointing to the other BufferedCopier in a pair.
+ void SetPeer(BufferedCopier* peer) {
+ DCHECK(!peer_);
+ peer_ = peer;
+ }
+
+ bool is_closed() const { return state_ == STATE_CLOSED; }
+
+ // Gently asks to close a buffer. Called either by the peer or the forwarder.
+ void Close() {
+ switch (state_) {
+ case STATE_READING:
+ state_ = STATE_CLOSED; // T03
+ break;
+ case STATE_WRITING:
+ state_ = STATE_CLOSING; // T07
+ break;
+ case STATE_CLOSING:
+ break; // T10
+ case STATE_CLOSED:
+ ;
+ }
+ }
+
+ // Call this before select(). This updates |read_fds|,
+ // |write_fds| and |max_fd| appropriately *if* the buffer isn't closed.
+ void PrepareSelect(fd_set* read_fds, fd_set* write_fds, int* max_fd) {
+ int fd;
+ switch (state_) {
+ case STATE_READING:
+ DCHECK(bytes_read_ == 0);
+ DCHECK(write_offset_ == 0);
+ fd = socket_from_->fd();
+ if (fd < 0) {
+ ForceClose(); // T02
+ return;
+ }
+ FD_SET(fd, read_fds);
+ break;
+
+ case STATE_WRITING:
+ case STATE_CLOSING:
+ DCHECK(bytes_read_ > 0);
+ DCHECK(write_offset_ < bytes_read_);
+ fd = socket_to_->fd();
+ if (fd < 0) {
+ ForceClose(); // T06
+ return;
+ }
+ FD_SET(fd, write_fds);
+ break;
+
+ case STATE_CLOSED:
+ return;
+ }
+ *max_fd = std::max(*max_fd, fd);
+ }
+
+ // Call this after a select() call to operate over the buffer.
+ void ProcessSelect(const fd_set& read_fds, const fd_set& write_fds) {
+ int fd, ret;
+ switch (state_) {
+ case STATE_READING:
+ fd = socket_from_->fd();
+ if (fd < 0) {
+ state_ = STATE_CLOSED; // T02
+ return;
+ }
+ if (!FD_ISSET(fd, &read_fds))
+ return;
+
+ ret = socket_from_->NonBlockingRead(buffer_, kBufferSize);
+ if (ret <= 0) {
+ ForceClose(); // T02
+ return;
+ }
+ bytes_read_ = ret;
+ write_offset_ = 0;
+ state_ = STATE_WRITING; // T01
+ break;
+
+ case STATE_WRITING:
+ case STATE_CLOSING:
+ fd = socket_to_->fd();
+ if (fd < 0) {
+ ForceClose(); // T06 + T11
+ return;
+ }
+ if (!FD_ISSET(fd, &write_fds))
+ return;
+
+ ret = socket_to_->NonBlockingWrite(buffer_ + write_offset_,
+ bytes_read_ - write_offset_);
+ if (ret <= 0) {
+ ForceClose(); // T06 + T11
+ return;
+ }
+
+ write_offset_ += ret;
+ if (write_offset_ < bytes_read_)
+ return; // T08 + T04
+
+ write_offset_ = 0;
+ bytes_read_ = 0;
+ if (state_ == STATE_CLOSING) {
+ ForceClose(); // T09
+ return;
+ }
+ state_ = STATE_READING; // T05
+ break;
+
+ case STATE_CLOSED:
+ ;
+ }
+ }
+
+ private:
+ // Internal method used to close the buffer and notify the peer, if any.
+ void ForceClose() {
+ if (peer_) {
+ peer_->Close();
+ peer_ = NULL;
+ }
+ state_ = STATE_CLOSED;
+ }
+
+ // Not owned.
+ Socket* socket_from_;
+ Socket* socket_to_;
+
+ int bytes_read_;
+ int write_offset_;
+ BufferedCopier* peer_;
+ State state_;
+ char buffer_[kBufferSize];
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedCopier);
+};
+
+Forwarder::Forwarder(scoped_ptr<Socket> socket1,
+ scoped_ptr<Socket> socket2)
+ : socket1_(socket1.Pass()),
+ socket2_(socket2.Pass()),
+ buffer1_(new BufferedCopier(socket1_.get(), socket2_.get())),
+ buffer2_(new BufferedCopier(socket2_.get(), socket1_.get())) {
+ buffer1_->SetPeer(buffer2_.get());
+ buffer2_->SetPeer(buffer1_.get());
+}
+
+Forwarder::~Forwarder() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Forwarder::RegisterFDs(fd_set* read_fds, fd_set* write_fds, int* max_fd) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ buffer1_->PrepareSelect(read_fds, write_fds, max_fd);
+ buffer2_->PrepareSelect(read_fds, write_fds, max_fd);
+}
+
+void Forwarder::ProcessEvents(const fd_set& read_fds, const fd_set& write_fds) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ buffer1_->ProcessSelect(read_fds, write_fds);
+ buffer2_->ProcessSelect(read_fds, write_fds);
+}
+
+bool Forwarder::IsClosed() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return buffer1_->is_closed() && buffer2_->is_closed();
+}
+
+void Forwarder::Shutdown() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ buffer1_->Close();
+ buffer2_->Close();
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/forwarder.gyp b/tools/android/forwarder2/forwarder.gyp
new file mode 100644
index 0000000..fbf5eba
--- /dev/null
+++ b/tools/android/forwarder2/forwarder.gyp
@@ -0,0 +1,70 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'forwarder2',
+ 'type': 'none',
+ 'dependencies': [
+ 'device_forwarder',
+ 'host_forwarder#host',
+ ],
+ # For the component build, ensure dependent shared libraries are stripped
+ # and put alongside forwarder to simplify pushing to the device.
+ 'variables': {
+ 'output_dir': '<(PRODUCT_DIR)/forwarder_dist/',
+ 'native_binary': '<(PRODUCT_DIR)/device_forwarder',
+ },
+ 'includes': ['../../../build/android/native_app_dependencies.gypi'],
+ },
+ {
+ 'target_name': 'device_forwarder',
+ 'type': 'executable',
+ 'toolsets': ['target'],
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ '../common/common.gyp:android_tools_common',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ 'sources': [
+ 'command.cc',
+ 'common.cc',
+ 'daemon.cc',
+ 'device_controller.cc',
+ 'device_forwarder_main.cc',
+ 'device_listener.cc',
+ 'forwarder.cc',
+ 'forwarders_manager.cc',
+ 'pipe_notifier.cc',
+ 'socket.cc',
+ ],
+ },
+ {
+ 'target_name': 'host_forwarder',
+ 'type': 'executable',
+ 'toolsets': ['host'],
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ '../common/common.gyp:android_tools_common',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ 'sources': [
+ 'command.cc',
+ 'common.cc',
+ 'daemon.cc',
+ 'forwarder.cc',
+ 'forwarders_manager.cc',
+ 'host_controller.cc',
+ 'host_forwarder_main.cc',
+ 'pipe_notifier.cc',
+ 'socket.cc',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/forwarder2/forwarder.h b/tools/android/forwarder2/forwarder.h
new file mode 100644
index 0000000..0be86fc
--- /dev/null
+++ b/tools/android/forwarder2/forwarder.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_FORWARDER_H_
+#define TOOLS_ANDROID_FORWARDER2_FORWARDER_H_
+
+#include <sys/select.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+
+namespace forwarder2 {
+
+class Socket;
+
+// Internal class that forwards traffic between |socket1| and |socket2|. Note
+// that this class is not thread-safe.
+class Forwarder {
+ public:
+ Forwarder(scoped_ptr<Socket> socket1, scoped_ptr<Socket> socket2);
+
+ ~Forwarder();
+
+ void RegisterFDs(fd_set* read_fds, fd_set* write_fds, int* max_fd);
+
+ void ProcessEvents(const fd_set& read_fds, const fd_set& write_fds);
+
+ bool IsClosed() const;
+
+ void Shutdown();
+
+ private:
+ class BufferedCopier;
+
+ base::ThreadChecker thread_checker_;
+ const scoped_ptr<Socket> socket1_;
+ const scoped_ptr<Socket> socket2_;
+ // Copies data from socket1 to socket2.
+ const scoped_ptr<BufferedCopier> buffer1_;
+ // Copies data from socket2 to socket1.
+ const scoped_ptr<BufferedCopier> buffer2_;
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_FORWARDER_H_
diff --git a/tools/android/forwarder2/forwarders_manager.cc b/tools/android/forwarder2/forwarders_manager.cc
new file mode 100644
index 0000000..1795cb5
--- /dev/null
+++ b/tools/android/forwarder2/forwarders_manager.cc
@@ -0,0 +1,132 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/forwarders_manager.h"
+
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/posix/eintr_wrapper.h"
+#include "tools/android/forwarder2/forwarder.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace forwarder2 {
+
+ForwardersManager::ForwardersManager() : thread_("ForwardersManagerThread") {
+ thread_.Start();
+ WaitForEventsOnInternalThreadSoon();
+}
+
+
+ForwardersManager::~ForwardersManager() {
+ deletion_notifier_.Notify();
+}
+
+void ForwardersManager::CreateAndStartNewForwarder(scoped_ptr<Socket> socket1,
+ scoped_ptr<Socket> socket2) {
+ // Note that the internal Forwarder vector is populated on the internal thread
+ // which is the only thread from which it's accessed.
+ thread_.message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&ForwardersManager::CreateNewForwarderOnInternalThread,
+ base::Unretained(this), base::Passed(&socket1),
+ base::Passed(&socket2)));
+
+ // Guarantees that the CreateNewForwarderOnInternalThread callback posted to
+ // the internal thread gets executed immediately.
+ wakeup_notifier_.Notify();
+}
+
+void ForwardersManager::CreateNewForwarderOnInternalThread(
+ scoped_ptr<Socket> socket1,
+ scoped_ptr<Socket> socket2) {
+ DCHECK(thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
+ forwarders_.push_back(new Forwarder(socket1.Pass(), socket2.Pass()));
+}
+
+void ForwardersManager::WaitForEventsOnInternalThreadSoon() {
+ thread_.message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&ForwardersManager::WaitForEventsOnInternalThread,
+ base::Unretained(this)));
+}
+
+void ForwardersManager::WaitForEventsOnInternalThread() {
+ DCHECK(thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
+ fd_set read_fds;
+ fd_set write_fds;
+
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+
+ // Populate the file descriptor sets.
+ int max_fd = -1;
+ for (ScopedVector<Forwarder>::iterator it = forwarders_.begin();
+ it != forwarders_.end(); ++it) {
+ Forwarder* const forwarder = *it;
+ forwarder->RegisterFDs(&read_fds, &write_fds, &max_fd);
+ }
+
+ const int notifier_fds[] = {
+ wakeup_notifier_.receiver_fd(),
+ deletion_notifier_.receiver_fd(),
+ };
+
+ for (int i = 0; i < arraysize(notifier_fds); ++i) {
+ const int notifier_fd = notifier_fds[i];
+ DCHECK_GT(notifier_fd, -1);
+ FD_SET(notifier_fd, &read_fds);
+ max_fd = std::max(max_fd, notifier_fd);
+ }
+
+ const int ret = HANDLE_EINTR(
+ select(max_fd + 1, &read_fds, &write_fds, NULL, NULL));
+ if (ret < 0) {
+ PLOG(ERROR) << "select";
+ return;
+ }
+
+ const bool must_shutdown = FD_ISSET(
+ deletion_notifier_.receiver_fd(), &read_fds);
+ if (must_shutdown && forwarders_.empty())
+ return;
+
+ base::ScopedClosureRunner wait_for_events_soon(
+ base::Bind(&ForwardersManager::WaitForEventsOnInternalThreadSoon,
+ base::Unretained(this)));
+
+ if (FD_ISSET(wakeup_notifier_.receiver_fd(), &read_fds)) {
+ // Note that the events on FDs other than the wakeup notifier one, if any,
+ // will be processed upon the next select().
+ wakeup_notifier_.Reset();
+ return;
+ }
+
+ // Notify the Forwarder instances and remove the ones that are closed.
+ for (size_t i = 0; i < forwarders_.size(); ) {
+ Forwarder* const forwarder = forwarders_[i];
+ forwarder->ProcessEvents(read_fds, write_fds);
+
+ if (must_shutdown)
+ forwarder->Shutdown();
+
+ if (!forwarder->IsClosed()) {
+ ++i;
+ continue;
+ }
+
+ std::swap(forwarders_[i], forwarders_.back());
+ forwarders_.pop_back();
+ }
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/forwarders_manager.h b/tools/android/forwarder2/forwarders_manager.h
new file mode 100644
index 0000000..4c6dea6
--- /dev/null
+++ b/tools/android/forwarder2/forwarders_manager.h
@@ -0,0 +1,45 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_FORWARDERS_MANAGER_H_
+#define TOOLS_ANDROID_FORWARDER2_FORWARDERS_MANAGER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/threading/thread.h"
+#include "tools/android/forwarder2/pipe_notifier.h"
+
+namespace forwarder2 {
+
+class Forwarder;
+class Socket;
+
+// Creates, owns and notifies Forwarder instances on its own internal thread.
+class ForwardersManager {
+ public:
+ ForwardersManager();
+
+ // Must be called on the thread the constructor was called on.
+ ~ForwardersManager();
+
+ // Can be called on any thread.
+ void CreateAndStartNewForwarder(scoped_ptr<Socket> socket1,
+ scoped_ptr<Socket> socket2);
+
+ private:
+ void CreateNewForwarderOnInternalThread(scoped_ptr<Socket> socket1,
+ scoped_ptr<Socket> socket2);
+
+ void WaitForEventsOnInternalThreadSoon();
+ void WaitForEventsOnInternalThread();
+
+ ScopedVector<Forwarder> forwarders_;
+ PipeNotifier deletion_notifier_;
+ PipeNotifier wakeup_notifier_;
+ base::Thread thread_;
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_FORWARDERS_MANAGER_H_
diff --git a/tools/android/forwarder2/host_controller.cc b/tools/android/forwarder2/host_controller.cc
new file mode 100644
index 0000000..94e63ec
--- /dev/null
+++ b/tools/android/forwarder2/host_controller.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/host_controller.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "tools/android/forwarder2/command.h"
+#include "tools/android/forwarder2/forwarder.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace forwarder2 {
+
+// static
+scoped_ptr<HostController> HostController::Create(
+ int device_port,
+ int host_port,
+ int adb_port,
+ int exit_notifier_fd,
+ const ErrorCallback& error_callback) {
+ scoped_ptr<HostController> host_controller;
+ scoped_ptr<PipeNotifier> delete_controller_notifier(new PipeNotifier());
+ scoped_ptr<Socket> adb_control_socket(new Socket());
+ adb_control_socket->AddEventFd(exit_notifier_fd);
+ adb_control_socket->AddEventFd(delete_controller_notifier->receiver_fd());
+ if (!adb_control_socket->ConnectTcp(std::string(), adb_port)) {
+ LOG(ERROR) << "Could not connect HostController socket on port: "
+ << adb_port;
+ return host_controller.Pass();
+ }
+ // Send the command to the device start listening to the "device_forward_port"
+ bool send_command_success = SendCommand(
+ command::LISTEN, device_port, adb_control_socket.get());
+ CHECK(send_command_success);
+ int device_port_allocated;
+ command::Type command;
+ if (!ReadCommand(
+ adb_control_socket.get(), &device_port_allocated, &command) ||
+ command != command::BIND_SUCCESS) {
+ LOG(ERROR) << "Device binding error using port " << device_port;
+ return host_controller.Pass();
+ }
+ host_controller.reset(
+ new HostController(
+ device_port_allocated, host_port, adb_port, exit_notifier_fd,
+ error_callback, adb_control_socket.Pass(),
+ delete_controller_notifier.Pass()));
+ return host_controller.Pass();
+}
+
+HostController::~HostController() {
+ DCHECK(deletion_task_runner_->RunsTasksOnCurrentThread());
+ delete_controller_notifier_->Notify();
+}
+
+void HostController::Start() {
+ thread_.Start();
+ ReadNextCommandSoon();
+}
+
+HostController::HostController(
+ int device_port,
+ int host_port,
+ int adb_port,
+ int exit_notifier_fd,
+ const ErrorCallback& error_callback,
+ scoped_ptr<Socket> adb_control_socket,
+ scoped_ptr<PipeNotifier> delete_controller_notifier)
+ : self_deleter_helper_(this, error_callback),
+ device_port_(device_port),
+ host_port_(host_port),
+ adb_port_(adb_port),
+ global_exit_notifier_fd_(exit_notifier_fd),
+ adb_control_socket_(adb_control_socket.Pass()),
+ delete_controller_notifier_(delete_controller_notifier.Pass()),
+ deletion_task_runner_(base::MessageLoopProxy::current()),
+ thread_("HostControllerThread") {
+}
+
+void HostController::ReadNextCommandSoon() {
+ thread_.message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&HostController::ReadCommandOnInternalThread,
+ base::Unretained(this)));
+}
+
+void HostController::ReadCommandOnInternalThread() {
+ if (!ReceivedCommand(command::ACCEPT_SUCCESS, adb_control_socket_.get())) {
+ LOG(ERROR) << "Did not receive ACCEPT_SUCCESS for port: "
+ << host_port_;
+ OnInternalThreadError();
+ return;
+ }
+ // Try to connect to host server.
+ scoped_ptr<Socket> host_server_data_socket(new Socket());
+ if (!host_server_data_socket->ConnectTcp(std::string(), host_port_)) {
+ LOG(ERROR) << "Could not Connect HostServerData socket on port: "
+ << host_port_;
+ SendCommand(
+ command::HOST_SERVER_ERROR, device_port_, adb_control_socket_.get());
+ if (ReceivedCommand(command::ACK, adb_control_socket_.get())) {
+ // It can continue if the host forwarder could not connect to the host
+ // server but the device acknowledged that, so that the device could
+ // re-try later.
+ ReadNextCommandSoon();
+ return;
+ }
+ OnInternalThreadError();
+ return;
+ }
+ LOG(INFO) << "Will send HOST_SERVER_SUCCESS: " << host_port_;
+ SendCommand(
+ command::HOST_SERVER_SUCCESS, device_port_, adb_control_socket_.get());
+ StartForwarder(host_server_data_socket.Pass());
+ ReadNextCommandSoon();
+}
+
+void HostController::StartForwarder(
+ scoped_ptr<Socket> host_server_data_socket) {
+ scoped_ptr<Socket> adb_data_socket(new Socket());
+ if (!adb_data_socket->ConnectTcp("", adb_port_)) {
+ LOG(ERROR) << "Could not connect AdbDataSocket on port: " << adb_port_;
+ OnInternalThreadError();
+ return;
+ }
+ // Open the Adb data connection, and send a command with the
+ // |device_forward_port| as a way for the device to identify the connection.
+ SendCommand(command::DATA_CONNECTION, device_port_, adb_data_socket.get());
+
+ // Check that the device received the new Adb Data Connection. Note that this
+ // check is done through the |adb_control_socket_| that is handled in the
+ // DeviceListener thread just after the call to WaitForAdbDataSocket().
+ if (!ReceivedCommand(command::ADB_DATA_SOCKET_SUCCESS,
+ adb_control_socket_.get())) {
+ LOG(ERROR) << "Device could not handle the new Adb Data Connection.";
+ OnInternalThreadError();
+ return;
+ }
+ forwarders_manager_.CreateAndStartNewForwarder(
+ host_server_data_socket.Pass(), adb_data_socket.Pass());
+}
+
+void HostController::OnInternalThreadError() {
+ UnmapPortOnDevice();
+ self_deleter_helper_.MaybeSelfDeleteSoon();
+}
+
+void HostController::UnmapPortOnDevice() {
+ Socket socket;
+ if (!socket.ConnectTcp("", adb_port_)) {
+ LOG(ERROR) << "Could not connect to device on port " << adb_port_;
+ return;
+ }
+ if (!SendCommand(command::UNLISTEN, device_port_, &socket)) {
+ LOG(ERROR) << "Could not send unmap command for port " << device_port_;
+ return;
+ }
+ if (!ReceivedCommand(command::UNLISTEN_SUCCESS, &socket)) {
+ LOG(ERROR) << "Unamp command failed for port " << device_port_;
+ return;
+ }
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/host_controller.h b/tools/android/forwarder2/host_controller.h
new file mode 100644
index 0000000..d228bcc
--- /dev/null
+++ b/tools/android/forwarder2/host_controller.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_HOST_CONTROLLER_H_
+#define TOOLS_ANDROID_FORWARDER2_HOST_CONTROLLER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "tools/android/forwarder2/forwarders_manager.h"
+#include "tools/android/forwarder2/pipe_notifier.h"
+#include "tools/android/forwarder2/self_deleter_helper.h"
+#include "tools/android/forwarder2/socket.h"
+
+namespace forwarder2 {
+
+// This class partners with DeviceController and has the same lifetime and
+// threading characteristics as DeviceListener. In a nutshell, this class
+// operates on its own thread and is destroyed on the thread it was constructed
+// on. The class' deletion can happen in two different ways:
+// - Its destructor was called by its owner (HostControllersManager).
+// - Its internal thread requested self-deletion after an error happened. In
+// this case the owner (HostControllersManager) is notified on the
+// construction thread through the provided ErrorCallback invoked with the
+// HostController instance. When this callback is invoked, it's up to the
+// owner to delete the instance.
+class HostController {
+ public:
+ // Callback used for self-deletion when an error happens so that the client
+ // can perform some cleanup work before deleting the HostController instance.
+ typedef base::Callback<void (scoped_ptr<HostController>)> ErrorCallback;
+
+ // If |device_port| is zero then a dynamic port is allocated (and retrievable
+ // through device_port() below).
+ static scoped_ptr<HostController> Create(int device_port,
+ int host_port,
+ int adb_port,
+ int exit_notifier_fd,
+ const ErrorCallback& error_callback);
+
+ ~HostController();
+
+ // Starts the internal controller thread.
+ void Start();
+
+ int adb_port() const { return adb_port_; }
+
+ int device_port() const { return device_port_; }
+
+ private:
+ HostController(int device_port,
+ int host_port,
+ int adb_port,
+ int exit_notifier_fd,
+ const ErrorCallback& error_callback,
+ scoped_ptr<Socket> adb_control_socket,
+ scoped_ptr<PipeNotifier> delete_controller_notifier);
+
+ void ReadNextCommandSoon();
+ void ReadCommandOnInternalThread();
+
+ void StartForwarder(scoped_ptr<Socket> host_server_data_socket);
+
+ // Note that this gets also called when ~HostController() is invoked.
+ void OnInternalThreadError();
+
+ void UnmapPortOnDevice();
+
+ SelfDeleterHelper<HostController> self_deleter_helper_;
+ const int device_port_;
+ const int host_port_;
+ const int adb_port_;
+ // Used to notify the controller when the process is killed.
+ const int global_exit_notifier_fd_;
+ scoped_ptr<Socket> adb_control_socket_;
+ // Used to cancel the pending blocking IO operations when the host controller
+ // instance is deleted.
+ scoped_ptr<PipeNotifier> delete_controller_notifier_;
+ // Task runner used for deletion set at deletion time (i.e. the object is
+ // deleted on the same thread it is created on).
+ const scoped_refptr<base::SingleThreadTaskRunner> deletion_task_runner_;
+ base::Thread thread_;
+ ForwardersManager forwarders_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostController);
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_HOST_CONTROLLER_H_
diff --git a/tools/android/forwarder2/host_forwarder_main.cc b/tools/android/forwarder2/host_forwarder_main.cc
new file mode 100644
index 0000000..59571b6
--- /dev/null
+++ b/tools/android/forwarder2/host_forwarder_main.cc
@@ -0,0 +1,460 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <iostream>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/pickle.h"
+#include "base/safe_strerror_posix.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/task_runner.h"
+#include "base/threading/thread.h"
+#include "tools/android/forwarder2/common.h"
+#include "tools/android/forwarder2/daemon.h"
+#include "tools/android/forwarder2/host_controller.h"
+#include "tools/android/forwarder2/pipe_notifier.h"
+#include "tools/android/forwarder2/socket.h"
+#include "tools/android/forwarder2/util.h"
+
+namespace forwarder2 {
+namespace {
+
+const char kLogFilePath[] = "/tmp/host_forwarder_log";
+const char kDaemonIdentifier[] = "chrome_host_forwarder_daemon";
+
+const char kKillServerCommand[] = "kill-server";
+const char kForwardCommand[] = "forward";
+
+const int kBufSize = 256;
+
+// Needs to be global to be able to be accessed from the signal handler.
+PipeNotifier* g_notifier = NULL;
+
+// Lets the daemon fetch the exit notifier file descriptor.
+int GetExitNotifierFD() {
+ DCHECK(g_notifier);
+ return g_notifier->receiver_fd();
+}
+
+void KillHandler(int signal_number) {
+ char buf[kBufSize];
+ if (signal_number != SIGTERM && signal_number != SIGINT) {
+ snprintf(buf, sizeof(buf), "Ignoring unexpected signal %d.", signal_number);
+ SIGNAL_SAFE_LOG(WARNING, buf);
+ return;
+ }
+ snprintf(buf, sizeof(buf), "Received signal %d.", signal_number);
+ SIGNAL_SAFE_LOG(WARNING, buf);
+ static int s_kill_handler_count = 0;
+ CHECK(g_notifier);
+ // If for some reason the forwarder get stuck in any socket waiting forever,
+ // we can send a SIGKILL or SIGINT three times to force it die
+ // (non-nicely). This is useful when debugging.
+ ++s_kill_handler_count;
+ if (!g_notifier->Notify() || s_kill_handler_count > 2)
+ exit(1);
+}
+
+// Manages HostController instances. There is one HostController instance for
+// each connection being forwarded. Note that forwarding can happen with many
+// devices (identified with a serial id).
+class HostControllersManager {
+ public:
+ HostControllersManager()
+ : weak_ptr_factory_(this),
+ controllers_(new HostControllerMap()),
+ has_failed_(false) {
+ }
+
+ ~HostControllersManager() {
+ if (!thread_.get())
+ return;
+ // Delete the controllers on the thread they were created on.
+ thread_->message_loop_proxy()->DeleteSoon(
+ FROM_HERE, controllers_.release());
+ }
+
+ void HandleRequest(const std::string& device_serial,
+ int device_port,
+ int host_port,
+ scoped_ptr<Socket> client_socket) {
+ // Lazy initialize so that the CLI process doesn't get this thread created.
+ InitOnce();
+ thread_->message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &HostControllersManager::HandleRequestOnInternalThread,
+ base::Unretained(this), device_serial, device_port, host_port,
+ base::Passed(&client_socket)));
+ }
+
+ bool has_failed() const { return has_failed_; }
+
+ private:
+ typedef base::hash_map<
+ std::string, linked_ptr<HostController> > HostControllerMap;
+
+ static std::string MakeHostControllerMapKey(int adb_port, int device_port) {
+ return base::StringPrintf("%d:%d", adb_port, device_port);
+ }
+
+ void InitOnce() {
+ if (thread_.get())
+ return;
+ at_exit_manager_.reset(new base::AtExitManager());
+ thread_.reset(new base::Thread("HostControllersManagerThread"));
+ thread_->Start();
+ }
+
+ // Invoked when a HostController instance reports an error (e.g. due to a
+ // device connectivity issue). Note that this could be called after the
+ // controller manager was destroyed which is why a weak pointer is used.
+ static void DeleteHostController(
+ const base::WeakPtr<HostControllersManager>& manager_ptr,
+ scoped_ptr<HostController> host_controller) {
+ HostController* const controller = host_controller.release();
+ HostControllersManager* const manager = manager_ptr.get();
+ if (!manager) {
+ // Note that |controller| is not leaked in this case since the host
+ // controllers manager owns the controllers. If the manager was deleted
+ // then all the controllers (including |controller|) were also deleted.
+ return;
+ }
+ DCHECK(manager->thread_->message_loop_proxy()->RunsTasksOnCurrentThread());
+ // Note that this will delete |controller| which is owned by the map.
+ DeleteRefCountedValueInMap(
+ MakeHostControllerMapKey(
+ controller->adb_port(), controller->device_port()),
+ manager->controllers_.get());
+ }
+
+ void HandleRequestOnInternalThread(const std::string& device_serial,
+ int device_port,
+ int host_port,
+ scoped_ptr<Socket> client_socket) {
+ const int adb_port = GetAdbPortForDevice(device_serial);
+ if (adb_port < 0) {
+ SendMessage(
+ "ERROR: could not get adb port for device. You might need to add "
+ "'adb' to your PATH or provide the device serial id.",
+ client_socket.get());
+ return;
+ }
+ if (device_port < 0) {
+ // Remove the previously created host controller.
+ const std::string controller_key = MakeHostControllerMapKey(
+ adb_port, -device_port);
+ const bool controller_did_exist = DeleteRefCountedValueInMap(
+ controller_key, controllers_.get());
+ SendMessage(
+ !controller_did_exist ? "ERROR: could not unmap port" : "OK",
+ client_socket.get());
+
+ RemoveAdbPortForDeviceIfNeeded(device_serial);
+ return;
+ }
+ if (host_port < 0) {
+ SendMessage("ERROR: missing host port", client_socket.get());
+ return;
+ }
+ const bool use_dynamic_port_allocation = device_port == 0;
+ if (!use_dynamic_port_allocation) {
+ const std::string controller_key = MakeHostControllerMapKey(
+ adb_port, device_port);
+ if (controllers_->find(controller_key) != controllers_->end()) {
+ LOG(INFO) << "Already forwarding device port " << device_port
+ << " to host port " << host_port;
+ SendMessage(base::StringPrintf("%d:%d", device_port, host_port),
+ client_socket.get());
+ return;
+ }
+ }
+ // Create a new host controller.
+ scoped_ptr<HostController> host_controller(
+ HostController::Create(
+ device_port, host_port, adb_port, GetExitNotifierFD(),
+ base::Bind(&HostControllersManager::DeleteHostController,
+ weak_ptr_factory_.GetWeakPtr())));
+ if (!host_controller.get()) {
+ has_failed_ = true;
+ SendMessage("ERROR: Connection to device failed.", client_socket.get());
+ return;
+ }
+ // Get the current allocated port.
+ device_port = host_controller->device_port();
+ LOG(INFO) << "Forwarding device port " << device_port << " to host port "
+ << host_port;
+ const std::string msg = base::StringPrintf("%d:%d", device_port, host_port);
+ if (!SendMessage(msg, client_socket.get()))
+ return;
+ host_controller->Start();
+ controllers_->insert(
+ std::make_pair(MakeHostControllerMapKey(adb_port, device_port),
+ linked_ptr<HostController>(host_controller.release())));
+ }
+
+ void RemoveAdbPortForDeviceIfNeeded(const std::string& device_serial) {
+ base::hash_map<std::string, int>::const_iterator it =
+ device_serial_to_adb_port_map_.find(device_serial);
+ if (it == device_serial_to_adb_port_map_.end())
+ return;
+
+ int port = it->second;
+ const std::string prefix = base::StringPrintf("%d:", port);
+ for (HostControllerMap::const_iterator others = controllers_->begin();
+ others != controllers_->end(); ++others) {
+ if (others->first.find(prefix) == 0U)
+ return;
+ }
+ // No other port is being forwarded to this device:
+ // - Remove it from our internal serial -> adb port map.
+ // - Remove from "adb forward" command.
+ LOG(INFO) << "Device " << device_serial << " has no more ports.";
+ device_serial_to_adb_port_map_.erase(device_serial);
+ const std::string serial_part = device_serial.empty() ?
+ std::string() : std::string("-s ") + device_serial;
+ const std::string command = base::StringPrintf(
+ "adb %s forward --remove tcp:%d",
+ serial_part.c_str(),
+ port);
+ const int ret = system(command.c_str());
+ LOG(INFO) << command << " ret: " << ret;
+ // Wait for the socket to be fully unmapped.
+ const std::string port_mapped_cmd = base::StringPrintf(
+ "lsof -nPi:%d",
+ port);
+ const int poll_interval_us = 500 * 1000;
+ int retries = 3;
+ while (retries) {
+ const int port_unmapped = system(port_mapped_cmd.c_str());
+ LOG(INFO) << "Device " << device_serial << " port " << port << " unmap "
+ << port_unmapped;
+ if (port_unmapped)
+ break;
+ --retries;
+ usleep(poll_interval_us);
+ }
+ }
+
+ int GetAdbPortForDevice(const std::string& device_serial) {
+ base::hash_map<std::string, int>::const_iterator it =
+ device_serial_to_adb_port_map_.find(device_serial);
+ if (it != device_serial_to_adb_port_map_.end())
+ return it->second;
+ Socket bind_socket;
+ CHECK(bind_socket.BindTcp("127.0.0.1", 0));
+ const int port = bind_socket.GetPort();
+ bind_socket.Close();
+ const std::string serial_part = device_serial.empty() ?
+ std::string() : std::string("-s ") + device_serial;
+ const std::string command = base::StringPrintf(
+ "adb %s forward tcp:%d localabstract:chrome_device_forwarder",
+ serial_part.c_str(),
+ port);
+ LOG(INFO) << command;
+ const int ret = system(command.c_str());
+ if (ret < 0 || !WIFEXITED(ret) || WEXITSTATUS(ret) != 0)
+ return -1;
+ device_serial_to_adb_port_map_[device_serial] = port;
+ return port;
+ }
+
+ bool SendMessage(const std::string& msg, Socket* client_socket) {
+ bool result = client_socket->WriteString(msg);
+ DCHECK(result);
+ if (!result)
+ has_failed_ = true;
+ return result;
+ }
+
+ base::WeakPtrFactory<HostControllersManager> weak_ptr_factory_;
+ base::hash_map<std::string, int> device_serial_to_adb_port_map_;
+ scoped_ptr<HostControllerMap> controllers_;
+ bool has_failed_;
+ scoped_ptr<base::AtExitManager> at_exit_manager_; // Needed by base::Thread.
+ scoped_ptr<base::Thread> thread_;
+};
+
+class ServerDelegate : public Daemon::ServerDelegate {
+ public:
+ ServerDelegate() : has_failed_(false) {}
+
+ bool has_failed() const {
+ return has_failed_ || controllers_manager_.has_failed();
+ }
+
+ // Daemon::ServerDelegate:
+ virtual void Init() OVERRIDE {
+ LOG(INFO) << "Starting host process daemon (pid=" << getpid() << ")";
+ DCHECK(!g_notifier);
+ g_notifier = new PipeNotifier();
+ signal(SIGTERM, KillHandler);
+ signal(SIGINT, KillHandler);
+ }
+
+ virtual void OnClientConnected(scoped_ptr<Socket> client_socket) OVERRIDE {
+ char buf[kBufSize];
+ const int bytes_read = client_socket->Read(buf, sizeof(buf));
+ if (bytes_read <= 0) {
+ if (client_socket->DidReceiveEvent())
+ return;
+ PError("Read()");
+ has_failed_ = true;
+ return;
+ }
+ const Pickle command_pickle(buf, bytes_read);
+ PickleIterator pickle_it(command_pickle);
+ std::string device_serial;
+ CHECK(pickle_it.ReadString(&device_serial));
+ int device_port;
+ if (!pickle_it.ReadInt(&device_port)) {
+ client_socket->WriteString("ERROR: missing device port");
+ return;
+ }
+ int host_port;
+ if (!pickle_it.ReadInt(&host_port))
+ host_port = -1;
+ controllers_manager_.HandleRequest(
+ device_serial, device_port, host_port, client_socket.Pass());
+ }
+
+ private:
+ bool has_failed_;
+ HostControllersManager controllers_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerDelegate);
+};
+
+class ClientDelegate : public Daemon::ClientDelegate {
+ public:
+ ClientDelegate(const Pickle& command_pickle)
+ : command_pickle_(command_pickle),
+ has_failed_(false) {
+ }
+
+ bool has_failed() const { return has_failed_; }
+
+ // Daemon::ClientDelegate:
+ virtual void OnDaemonReady(Socket* daemon_socket) OVERRIDE {
+ // Send the forward command to the daemon.
+ CHECK_EQ(command_pickle_.size(),
+ daemon_socket->WriteNumBytes(command_pickle_.data(),
+ command_pickle_.size()));
+ char buf[kBufSize];
+ const int bytes_read = daemon_socket->Read(
+ buf, sizeof(buf) - 1 /* leave space for null terminator */);
+ CHECK_GT(bytes_read, 0);
+ DCHECK(bytes_read < sizeof(buf));
+ buf[bytes_read] = 0;
+ base::StringPiece msg(buf, bytes_read);
+ if (msg.starts_with("ERROR")) {
+ LOG(ERROR) << msg;
+ has_failed_ = true;
+ return;
+ }
+ printf("%s\n", buf);
+ }
+
+ private:
+ const Pickle command_pickle_;
+ bool has_failed_;
+};
+
+void ExitWithUsage() {
+ std::cerr << "Usage: host_forwarder [options]\n\n"
+ "Options:\n"
+ " --serial-id=[0-9A-Z]{16}]\n"
+ " --map DEVICE_PORT HOST_PORT\n"
+ " --unmap DEVICE_PORT\n"
+ " --kill-server\n";
+ exit(1);
+}
+
+int PortToInt(const std::string& s) {
+ int value;
+ // Note that 0 is a valid port (used for dynamic port allocation).
+ if (!base::StringToInt(s, &value) || value < 0 ||
+ value > std::numeric_limits<uint16>::max()) {
+ LOG(ERROR) << "Could not convert string " << s << " to port";
+ ExitWithUsage();
+ }
+ return value;
+}
+
+int RunHostForwarder(int argc, char** argv) {
+ CommandLine::Init(argc, argv);
+ const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
+ bool kill_server = false;
+
+ Pickle pickle;
+ pickle.WriteString(
+ cmd_line.HasSwitch("serial-id") ?
+ cmd_line.GetSwitchValueASCII("serial-id") : std::string());
+
+ const std::vector<std::string> args = cmd_line.GetArgs();
+ if (cmd_line.HasSwitch("kill-server")) {
+ kill_server = true;
+ } else if (cmd_line.HasSwitch("unmap")) {
+ if (args.size() != 1)
+ ExitWithUsage();
+ // Note the minus sign below.
+ pickle.WriteInt(-PortToInt(args[0]));
+ } else if (cmd_line.HasSwitch("map")) {
+ if (args.size() != 2)
+ ExitWithUsage();
+ pickle.WriteInt(PortToInt(args[0]));
+ pickle.WriteInt(PortToInt(args[1]));
+ } else {
+ ExitWithUsage();
+ }
+
+ if (kill_server && args.size() > 0)
+ ExitWithUsage();
+
+ ClientDelegate client_delegate(pickle);
+ ServerDelegate daemon_delegate;
+ Daemon daemon(
+ kLogFilePath, kDaemonIdentifier, &client_delegate, &daemon_delegate,
+ &GetExitNotifierFD);
+
+ if (kill_server)
+ return !daemon.Kill();
+ if (!daemon.SpawnIfNeeded())
+ return 1;
+
+ return client_delegate.has_failed() || daemon_delegate.has_failed();
+}
+
+} // namespace
+} // namespace forwarder2
+
+int main(int argc, char** argv) {
+ return forwarder2::RunHostForwarder(argc, argv);
+}
diff --git a/tools/android/forwarder2/pipe_notifier.cc b/tools/android/forwarder2/pipe_notifier.cc
new file mode 100644
index 0000000..02842bd
--- /dev/null
+++ b/tools/android/forwarder2/pipe_notifier.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/pipe_notifier.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/safe_strerror_posix.h"
+
+namespace forwarder2 {
+
+PipeNotifier::PipeNotifier() {
+ int pipe_fd[2];
+ int ret = pipe(pipe_fd);
+ CHECK_EQ(0, ret);
+ receiver_fd_ = pipe_fd[0];
+ sender_fd_ = pipe_fd[1];
+ fcntl(sender_fd_, F_SETFL, O_NONBLOCK);
+}
+
+PipeNotifier::~PipeNotifier() {
+ close(receiver_fd_);
+ close(sender_fd_);
+}
+
+bool PipeNotifier::Notify() {
+ CHECK_NE(-1, sender_fd_);
+ errno = 0;
+ int ret = HANDLE_EINTR(write(sender_fd_, "1", 1));
+ if (ret < 0) {
+ PLOG(ERROR) << "write";
+ return false;
+ }
+ return true;
+}
+
+void PipeNotifier::Reset() {
+ char c;
+ int ret = HANDLE_EINTR(read(receiver_fd_, &c, 1));
+ if (ret < 0) {
+ PLOG(ERROR) << "read";
+ return;
+ }
+ DCHECK_EQ(1, ret);
+ DCHECK_EQ('1', c);
+}
+
+} // namespace forwarder
diff --git a/tools/android/forwarder2/pipe_notifier.h b/tools/android/forwarder2/pipe_notifier.h
new file mode 100644
index 0000000..aadb269
--- /dev/null
+++ b/tools/android/forwarder2/pipe_notifier.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_PIPE_NOTIFIER_H_
+#define TOOLS_ANDROID_FORWARDER2_PIPE_NOTIFIER_H_
+
+#include "base/basictypes.h"
+
+namespace forwarder2 {
+
+// Helper class used to create a unix pipe that sends notifications to the
+// |receiver_fd_| file descriptor when called |Notify()|. This should be used
+// by the main thread to notify other threads that it must exit.
+// The |receiver_fd_| can be put into a fd_set and used in a select together
+// with a socket waiting to accept or read.
+class PipeNotifier {
+ public:
+ PipeNotifier();
+ ~PipeNotifier();
+
+ bool Notify();
+
+ int receiver_fd() const { return receiver_fd_; }
+
+ void Reset();
+
+ private:
+ int sender_fd_;
+ int receiver_fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(PipeNotifier);
+};
+
+} // namespace forwarder
+
+#endif // TOOLS_ANDROID_FORWARDER2_PIPE_NOTIFIER_H_
diff --git a/tools/android/forwarder2/self_deleter_helper.h b/tools/android/forwarder2/self_deleter_helper.h
new file mode 100644
index 0000000..d96903d
--- /dev/null
+++ b/tools/android/forwarder2/self_deleter_helper.h
@@ -0,0 +1,141 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_SELF_DELETER_HELPER_H_
+#define TOOLS_ANDROID_FORWARDER2_SELF_DELETER_HELPER_H_
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+
+namespace base {
+
+class SingleThreadTaskRunner;
+
+} // namespace base
+
+namespace forwarder2 {
+
+// Helper template class to be used in the following case:
+// * T is the type of an object that implements some work through an internal
+// or worker thread.
+// * T wants the internal thread to invoke deletion of its own instance, on
+// the thread where the instance was created.
+//
+// To make this easier, do something like:
+// 1) Add a SelfDeleteHelper<T> member to your class T, and default-initialize
+// it in its constructor.
+// 2) In the internal thread, to trigger self-deletion, call the
+// MaybeDeleteSoon() method on this member.
+//
+// MaybeDeleteSoon() posts a task on the message loop where the T instance was
+// created to delete it. The task will be safely ignored if the instance is
+// otherwise deleted.
+//
+// Usage example:
+// class Object {
+// public:
+// typedef base::Callback<void (scoped_ptr<Object>)> ErrorCallback;
+//
+// Object(const ErrorCallback& error_callback)
+// : self_deleter_helper_(this, error_callback) {
+// }
+//
+// void StartWork() {
+// // Post a callback to DoSomethingOnWorkerThread() below to another
+// // thread.
+// }
+//
+// void DoSomethingOnWorkerThread() {
+// ...
+// if (error_happened)
+// self_deleter_helper_.MaybeDeleteSoon();
+// }
+//
+// private:
+// SelfDeleterHelper<MySelfDeletingClass> self_deleter_helper_;
+// };
+//
+// class ObjectOwner {
+// public:
+// ObjectOwner()
+// : object_(new Object(base::Bind(&ObjectOwner::DeleteObjectOnError,
+// base::Unretained(this))) {
+// // To keep this example simple base::Unretained(this) is used above but
+// // note that in a real world scenario the client would have to make sure
+// // that the ObjectOwner instance is still alive when
+// // DeleteObjectOnError() gets called below. This can be achieved by
+// // using a WeakPtr<ObjectOwner> for instance.
+// }
+//
+// void StartWork() {
+// object_->StartWork();
+// }
+//
+// private:
+// void DeleteObjectOnError(scoped_ptr<Object> object) {
+// DCHECK(thread_checker_.CalledOnValidThread());
+// DCHECK_EQ(object_, object);
+// // Do some extra work with |object| before it gets deleted...
+// object_.reset();
+// ignore_result(object.release());
+// }
+//
+// base::ThreadChecker thread_checker_;
+// scoped_ptr<Object> object_;
+// };
+//
+template <typename T>
+class SelfDeleterHelper {
+ public:
+ typedef base::Callback<void (scoped_ptr<T>)> DeletionCallback;
+
+ SelfDeleterHelper(T* self_deleting_object,
+ const DeletionCallback& deletion_callback)
+ : construction_runner_(base::MessageLoopProxy::current()),
+ self_deleting_object_(self_deleting_object),
+ deletion_callback_(deletion_callback),
+ weak_ptr_factory_(this) {
+ }
+
+ ~SelfDeleterHelper() {
+ DCHECK(construction_runner_->RunsTasksOnCurrentThread());
+ }
+
+ void MaybeSelfDeleteSoon() {
+ DCHECK(!construction_runner_->RunsTasksOnCurrentThread());
+ construction_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&SelfDeleterHelper::SelfDelete,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ private:
+ void SelfDelete() {
+ DCHECK(construction_runner_->RunsTasksOnCurrentThread());
+ deletion_callback_.Run(make_scoped_ptr(self_deleting_object_));
+ }
+
+ const scoped_refptr<base::SingleThreadTaskRunner> construction_runner_;
+ T* const self_deleting_object_;
+ const DeletionCallback deletion_callback_;
+
+ //WeakPtrFactory's documentation says:
+ // Member variables should appear before the WeakPtrFactory, to ensure
+ // that any WeakPtrs to Controller are invalidated before its members
+ // variable's destructors are executed, rendering them invalid.
+ base::WeakPtrFactory<SelfDeleterHelper<T> > weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelfDeleterHelper);
+};
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_SELF_DELETER_HELPER_H_
diff --git a/tools/android/forwarder2/socket.cc b/tools/android/forwarder2/socket.cc
new file mode 100644
index 0000000..9feac84
--- /dev/null
+++ b/tools/android/forwarder2/socket.cc
@@ -0,0 +1,448 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/android/forwarder2/socket.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/safe_strerror_posix.h"
+#include "tools/android/common/net.h"
+#include "tools/android/forwarder2/common.h"
+
+namespace {
+const int kNoTimeout = -1;
+const int kConnectTimeOut = 10; // Seconds.
+
+bool FamilyIsTCP(int family) {
+ return family == AF_INET || family == AF_INET6;
+}
+} // namespace
+
+namespace forwarder2 {
+
+bool Socket::BindUnix(const std::string& path) {
+ errno = 0;
+ if (!InitUnixSocket(path) || !BindAndListen()) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+bool Socket::BindTcp(const std::string& host, int port) {
+ errno = 0;
+ if (!InitTcpSocket(host, port) || !BindAndListen()) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+bool Socket::ConnectUnix(const std::string& path) {
+ errno = 0;
+ if (!InitUnixSocket(path) || !Connect()) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+bool Socket::ConnectTcp(const std::string& host, int port) {
+ errno = 0;
+ if (!InitTcpSocket(host, port) || !Connect()) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+Socket::Socket()
+ : socket_(-1),
+ port_(0),
+ socket_error_(false),
+ family_(AF_INET),
+ addr_ptr_(reinterpret_cast<sockaddr*>(&addr_.addr4)),
+ addr_len_(sizeof(sockaddr)) {
+ memset(&addr_, 0, sizeof(addr_));
+}
+
+Socket::~Socket() {
+ Close();
+}
+
+void Socket::Shutdown() {
+ if (!IsClosed()) {
+ PRESERVE_ERRNO_HANDLE_EINTR(shutdown(socket_, SHUT_RDWR));
+ }
+}
+
+void Socket::Close() {
+ if (!IsClosed()) {
+ CloseFD(socket_);
+ socket_ = -1;
+ }
+}
+
+bool Socket::InitSocketInternal() {
+ socket_ = socket(family_, SOCK_STREAM, 0);
+ if (socket_ < 0) {
+ PLOG(ERROR) << "socket";
+ return false;
+ }
+ tools::DisableNagle(socket_);
+ int reuse_addr = 1;
+ setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,
+ sizeof(reuse_addr));
+ if (!SetNonBlocking())
+ return false;
+ return true;
+}
+
+bool Socket::SetNonBlocking() {
+ const int flags = fcntl(socket_, F_GETFL);
+ if (flags < 0) {
+ PLOG(ERROR) << "fcntl";
+ return false;
+ }
+ if (flags & O_NONBLOCK)
+ return true;
+ if (fcntl(socket_, F_SETFL, flags | O_NONBLOCK) < 0) {
+ PLOG(ERROR) << "fcntl";
+ return false;
+ }
+ return true;
+}
+
+bool Socket::InitUnixSocket(const std::string& path) {
+ static const size_t kPathMax = sizeof(addr_.addr_un.sun_path);
+ // For abstract sockets we need one extra byte for the leading zero.
+ if (path.size() + 2 /* '\0' */ > kPathMax) {
+ LOG(ERROR) << "The provided path is too big to create a unix "
+ << "domain socket: " << path;
+ return false;
+ }
+ family_ = PF_UNIX;
+ addr_.addr_un.sun_family = family_;
+ // Copied from net/socket/unix_domain_socket_posix.cc
+ // Convert the path given into abstract socket name. It must start with
+ // the '\0' character, so we are adding it. |addr_len| must specify the
+ // length of the structure exactly, as potentially the socket name may
+ // have '\0' characters embedded (although we don't support this).
+ // Note that addr_.addr_un.sun_path is already zero initialized.
+ memcpy(addr_.addr_un.sun_path + 1, path.c_str(), path.size());
+ addr_len_ = path.size() + offsetof(struct sockaddr_un, sun_path) + 1;
+ addr_ptr_ = reinterpret_cast<sockaddr*>(&addr_.addr_un);
+ return InitSocketInternal();
+}
+
+bool Socket::InitTcpSocket(const std::string& host, int port) {
+ port_ = port;
+ if (host.empty()) {
+ // Use localhost: INADDR_LOOPBACK
+ family_ = AF_INET;
+ addr_.addr4.sin_family = family_;
+ addr_.addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ } else if (!Resolve(host)) {
+ return false;
+ }
+ CHECK(FamilyIsTCP(family_)) << "Invalid socket family.";
+ if (family_ == AF_INET) {
+ addr_.addr4.sin_port = htons(port_);
+ addr_ptr_ = reinterpret_cast<sockaddr*>(&addr_.addr4);
+ addr_len_ = sizeof(addr_.addr4);
+ } else if (family_ == AF_INET6) {
+ addr_.addr6.sin6_port = htons(port_);
+ addr_ptr_ = reinterpret_cast<sockaddr*>(&addr_.addr6);
+ addr_len_ = sizeof(addr_.addr6);
+ }
+ return InitSocketInternal();
+}
+
+bool Socket::BindAndListen() {
+ errno = 0;
+ if (HANDLE_EINTR(bind(socket_, addr_ptr_, addr_len_)) < 0 ||
+ HANDLE_EINTR(listen(socket_, SOMAXCONN)) < 0) {
+ PLOG(ERROR) << "bind/listen";
+ SetSocketError();
+ return false;
+ }
+ if (port_ == 0 && FamilyIsTCP(family_)) {
+ SockAddr addr;
+ memset(&addr, 0, sizeof(addr));
+ socklen_t addrlen = 0;
+ sockaddr* addr_ptr = NULL;
+ uint16* port_ptr = NULL;
+ if (family_ == AF_INET) {
+ addr_ptr = reinterpret_cast<sockaddr*>(&addr.addr4);
+ port_ptr = &addr.addr4.sin_port;
+ addrlen = sizeof(addr.addr4);
+ } else if (family_ == AF_INET6) {
+ addr_ptr = reinterpret_cast<sockaddr*>(&addr.addr6);
+ port_ptr = &addr.addr6.sin6_port;
+ addrlen = sizeof(addr.addr6);
+ }
+ errno = 0;
+ if (getsockname(socket_, addr_ptr, &addrlen) != 0) {
+ PLOG(ERROR) << "getsockname";
+ SetSocketError();
+ return false;
+ }
+ port_ = ntohs(*port_ptr);
+ }
+ return true;
+}
+
+bool Socket::Accept(Socket* new_socket) {
+ DCHECK(new_socket != NULL);
+ if (!WaitForEvent(READ, kNoTimeout)) {
+ SetSocketError();
+ return false;
+ }
+ errno = 0;
+ int new_socket_fd = HANDLE_EINTR(accept(socket_, NULL, NULL));
+ if (new_socket_fd < 0) {
+ SetSocketError();
+ return false;
+ }
+ tools::DisableNagle(new_socket_fd);
+ new_socket->socket_ = new_socket_fd;
+ if (!new_socket->SetNonBlocking())
+ return false;
+ return true;
+}
+
+bool Socket::Connect() {
+ DCHECK(fcntl(socket_, F_GETFL) & O_NONBLOCK);
+ errno = 0;
+ if (HANDLE_EINTR(connect(socket_, addr_ptr_, addr_len_)) < 0 &&
+ errno != EINPROGRESS) {
+ SetSocketError();
+ return false;
+ }
+ // Wait for connection to complete, or receive a notification.
+ if (!WaitForEvent(WRITE, kConnectTimeOut)) {
+ SetSocketError();
+ return false;
+ }
+ int socket_errno;
+ socklen_t opt_len = sizeof(socket_errno);
+ if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &socket_errno, &opt_len) < 0) {
+ PLOG(ERROR) << "getsockopt()";
+ SetSocketError();
+ return false;
+ }
+ if (socket_errno != 0) {
+ LOG(ERROR) << "Could not connect to host: " << safe_strerror(socket_errno);
+ SetSocketError();
+ return false;
+ }
+ return true;
+}
+
+bool Socket::Resolve(const std::string& host) {
+ struct addrinfo hints;
+ struct addrinfo* res;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= AI_CANONNAME;
+
+ int errcode = getaddrinfo(host.c_str(), NULL, &hints, &res);
+ if (errcode != 0) {
+ errno = 0;
+ SetSocketError();
+ freeaddrinfo(res);
+ return false;
+ }
+ family_ = res->ai_family;
+ switch (res->ai_family) {
+ case AF_INET:
+ memcpy(&addr_.addr4,
+ reinterpret_cast<sockaddr_in*>(res->ai_addr),
+ sizeof(sockaddr_in));
+ break;
+ case AF_INET6:
+ memcpy(&addr_.addr6,
+ reinterpret_cast<sockaddr_in6*>(res->ai_addr),
+ sizeof(sockaddr_in6));
+ break;
+ }
+ freeaddrinfo(res);
+ return true;
+}
+
+int Socket::GetPort() {
+ if (!FamilyIsTCP(family_)) {
+ LOG(ERROR) << "Can't call GetPort() on an unix domain socket.";
+ return 0;
+ }
+ return port_;
+}
+
+int Socket::ReadNumBytes(void* buffer, size_t num_bytes) {
+ int bytes_read = 0;
+ int ret = 1;
+ while (bytes_read < num_bytes && ret > 0) {
+ ret = Read(static_cast<char*>(buffer) + bytes_read, num_bytes - bytes_read);
+ if (ret >= 0)
+ bytes_read += ret;
+ }
+ return bytes_read;
+}
+
+void Socket::SetSocketError() {
+ socket_error_ = true;
+ DCHECK_NE(EAGAIN, errno);
+ DCHECK_NE(EWOULDBLOCK, errno);
+ Close();
+}
+
+int Socket::Read(void* buffer, size_t buffer_size) {
+ if (!WaitForEvent(READ, kNoTimeout)) {
+ SetSocketError();
+ return 0;
+ }
+ int ret = HANDLE_EINTR(read(socket_, buffer, buffer_size));
+ if (ret < 0) {
+ PLOG(ERROR) << "read";
+ SetSocketError();
+ }
+ return ret;
+}
+
+int Socket::NonBlockingRead(void* buffer, size_t buffer_size) {
+ DCHECK(fcntl(socket_, F_GETFL) & O_NONBLOCK);
+ int ret = HANDLE_EINTR(read(socket_, buffer, buffer_size));
+ if (ret < 0) {
+ PLOG(ERROR) << "read";
+ SetSocketError();
+ }
+ return ret;
+}
+
+int Socket::Write(const void* buffer, size_t count) {
+ if (!WaitForEvent(WRITE, kNoTimeout)) {
+ SetSocketError();
+ return 0;
+ }
+ int ret = HANDLE_EINTR(send(socket_, buffer, count, MSG_NOSIGNAL));
+ if (ret < 0) {
+ PLOG(ERROR) << "send";
+ SetSocketError();
+ }
+ return ret;
+}
+
+int Socket::NonBlockingWrite(const void* buffer, size_t count) {
+ DCHECK(fcntl(socket_, F_GETFL) & O_NONBLOCK);
+ int ret = HANDLE_EINTR(send(socket_, buffer, count, MSG_NOSIGNAL));
+ if (ret < 0) {
+ PLOG(ERROR) << "send";
+ SetSocketError();
+ }
+ return ret;
+}
+
+int Socket::WriteString(const std::string& buffer) {
+ return WriteNumBytes(buffer.c_str(), buffer.size());
+}
+
+void Socket::AddEventFd(int event_fd) {
+ Event event;
+ event.fd = event_fd;
+ event.was_fired = false;
+ events_.push_back(event);
+}
+
+bool Socket::DidReceiveEventOnFd(int fd) const {
+ for (size_t i = 0; i < events_.size(); ++i)
+ if (events_[i].fd == fd)
+ return events_[i].was_fired;
+ return false;
+}
+
+bool Socket::DidReceiveEvent() const {
+ for (size_t i = 0; i < events_.size(); ++i)
+ if (events_[i].was_fired)
+ return true;
+ return false;
+}
+
+int Socket::WriteNumBytes(const void* buffer, size_t num_bytes) {
+ int bytes_written = 0;
+ int ret = 1;
+ while (bytes_written < num_bytes && ret > 0) {
+ ret = Write(static_cast<const char*>(buffer) + bytes_written,
+ num_bytes - bytes_written);
+ if (ret >= 0)
+ bytes_written += ret;
+ }
+ return bytes_written;
+}
+
+bool Socket::WaitForEvent(EventType type, int timeout_secs) {
+ if (socket_ == -1)
+ return true;
+ DCHECK(fcntl(socket_, F_GETFL) & O_NONBLOCK);
+ fd_set read_fds;
+ fd_set write_fds;
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+ if (type == READ)
+ FD_SET(socket_, &read_fds);
+ else
+ FD_SET(socket_, &write_fds);
+ for (size_t i = 0; i < events_.size(); ++i)
+ FD_SET(events_[i].fd, &read_fds);
+ timeval tv = {};
+ timeval* tv_ptr = NULL;
+ if (timeout_secs > 0) {
+ tv.tv_sec = timeout_secs;
+ tv.tv_usec = 0;
+ tv_ptr = &tv;
+ }
+ int max_fd = socket_;
+ for (size_t i = 0; i < events_.size(); ++i)
+ if (events_[i].fd > max_fd)
+ max_fd = events_[i].fd;
+ if (HANDLE_EINTR(
+ select(max_fd + 1, &read_fds, &write_fds, NULL, tv_ptr)) <= 0) {
+ PLOG(ERROR) << "select";
+ return false;
+ }
+ bool event_was_fired = false;
+ for (size_t i = 0; i < events_.size(); ++i) {
+ if (FD_ISSET(events_[i].fd, &read_fds)) {
+ events_[i].was_fired = true;
+ event_was_fired = true;
+ }
+ }
+ return !event_was_fired;
+}
+
+// static
+pid_t Socket::GetUnixDomainSocketProcessOwner(const std::string& path) {
+ Socket socket;
+ if (!socket.ConnectUnix(path))
+ return -1;
+ ucred ucred;
+ socklen_t len = sizeof(ucred);
+ if (getsockopt(socket.socket_, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
+ CHECK_NE(ENOPROTOOPT, errno);
+ return -1;
+ }
+ return ucred.pid;
+}
+
+} // namespace forwarder2
diff --git a/tools/android/forwarder2/socket.h b/tools/android/forwarder2/socket.h
new file mode 100644
index 0000000..6047a1c
--- /dev/null
+++ b/tools/android/forwarder2/socket.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_SOCKET_H_
+#define TOOLS_ANDROID_FORWARDER2_SOCKET_H_
+
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace forwarder2 {
+
+// Wrapper class around unix socket api. Can be used to create, bind or
+// connect to both Unix domain sockets and TCP sockets.
+// TODO(pliard): Split this class into TCPSocket and UnixDomainSocket.
+class Socket {
+ public:
+ Socket();
+ ~Socket();
+
+ bool BindUnix(const std::string& path);
+ bool BindTcp(const std::string& host, int port);
+ bool ConnectUnix(const std::string& path);
+ bool ConnectTcp(const std::string& host, int port);
+
+ // Just a wrapper around unix socket shutdown(), see man 2 shutdown.
+ void Shutdown();
+
+ // Just a wrapper around unix socket close(), see man 2 close.
+ void Close();
+ bool IsClosed() const { return socket_ < 0; }
+
+ int fd() const { return socket_; }
+
+ bool Accept(Socket* new_socket);
+
+ // Returns the port allocated to this socket or zero on error.
+ int GetPort();
+
+ // Just a wrapper around unix read() function.
+ // Reads up to buffer_size, but may read less then buffer_size.
+ // Returns the number of bytes read.
+ int Read(void* buffer, size_t buffer_size);
+
+ // Non-blocking version of Read() above. This must be called after a
+ // successful call to select(). The socket must also be in non-blocking mode
+ // before calling this method.
+ int NonBlockingRead(void* buffer, size_t buffer_size);
+
+ // Wrapper around send().
+ int Write(const void* buffer, size_t count);
+
+ // Same as NonBlockingRead() but for writing.
+ int NonBlockingWrite(const void* buffer, size_t count);
+
+ // Calls Read() multiple times until num_bytes is written to the provided
+ // buffer. No bounds checking is performed.
+ // Returns number of bytes read, which can be different from num_bytes in case
+ // of errror.
+ int ReadNumBytes(void* buffer, size_t num_bytes);
+
+ // Calls Write() multiple times until num_bytes is written. No bounds checking
+ // is performed. Returns number of bytes written, which can be different from
+ // num_bytes in case of errror.
+ int WriteNumBytes(const void* buffer, size_t num_bytes);
+
+ // Calls WriteNumBytes for the given std::string. Note that the null
+ // terminator is not written to the socket.
+ int WriteString(const std::string& buffer);
+
+ bool has_error() const { return socket_error_; }
+
+ // |event_fd| must be a valid pipe file descriptor created from the
+ // PipeNotifier and must live (not be closed) at least as long as this socket
+ // is alive.
+ void AddEventFd(int event_fd);
+
+ // Returns whether Accept() or Connect() was interrupted because the socket
+ // received an external event fired through the provided fd.
+ bool DidReceiveEventOnFd(int fd) const;
+
+ bool DidReceiveEvent() const;
+
+ static pid_t GetUnixDomainSocketProcessOwner(const std::string& path);
+
+ private:
+ enum EventType {
+ READ,
+ WRITE
+ };
+
+ union SockAddr {
+ // IPv4 sockaddr
+ sockaddr_in addr4;
+ // IPv6 sockaddr
+ sockaddr_in6 addr6;
+ // Unix Domain sockaddr
+ sockaddr_un addr_un;
+ };
+
+ struct Event {
+ int fd;
+ bool was_fired;
+ };
+
+ bool SetNonBlocking();
+
+ // If |host| is empty, use localhost.
+ bool InitTcpSocket(const std::string& host, int port);
+ bool InitUnixSocket(const std::string& path);
+ bool BindAndListen();
+ bool Connect();
+
+ bool Resolve(const std::string& host);
+ bool InitSocketInternal();
+ void SetSocketError();
+
+ // Waits until either the Socket or the |exit_notifier_fd_| has received an
+ // event.
+ bool WaitForEvent(EventType type, int timeout_secs);
+
+ int socket_;
+ int port_;
+ bool socket_error_;
+
+ // Family of the socket (PF_INET, PF_INET6 or PF_UNIX).
+ int family_;
+
+ SockAddr addr_;
+
+ // Points to one of the members of the above union depending on the family.
+ sockaddr* addr_ptr_;
+ // Length of one of the members of the above union depending on the family.
+ socklen_t addr_len_;
+
+ // Used to listen for external events (e.g. process received a SIGTERM) while
+ // blocking on I/O operations.
+ std::vector<Event> events_;
+
+ DISALLOW_COPY_AND_ASSIGN(Socket);
+};
+
+} // namespace forwarder
+
+#endif // TOOLS_ANDROID_FORWARDER2_SOCKET_H_
diff --git a/tools/android/forwarder2/util.h b/tools/android/forwarder2/util.h
new file mode 100644
index 0000000..9947628
--- /dev/null
+++ b/tools/android/forwarder2/util.h
@@ -0,0 +1,36 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_FORWARDER2_UTIL_H_
+#define TOOLS_ANDROID_FORWARDER2_UTIL_H_
+
+#include "base/logging.h"
+
+namespace forwarder2 {
+
+// Safely deletes a ref-counted value in a provided map by unlinking the object
+// from the map before deleting it in case its destructor would access the map.
+// Deletion will only happen by definition if the object's refcount is set to 1
+// before this function gets called. Returns whether the element could be found
+// in the map.
+template <typename Map, typename K>
+bool DeleteRefCountedValueInMap(const K& key, Map* map) {
+ const typename Map::iterator it = map->find(key);
+ if (it == map->end())
+ return false;
+ DeleteRefCountedValueInMapFromIterator(it, map);
+ return true;
+}
+
+// See DeleteRefCountedValuetInMap() above.
+template <typename Map, typename Iterator>
+void DeleteRefCountedValueInMapFromIterator(Iterator it, Map* map) {
+ DCHECK(it != map->end());
+ const typename Map::value_type::second_type shared_ptr_copy = it->second;
+ map->erase(it);
+}
+
+} // namespace forwarder2
+
+#endif // TOOLS_ANDROID_FORWARDER2_UTIL_H_
diff --git a/tools/android/heap_profiler/DEPS b/tools/android/heap_profiler/DEPS
new file mode 100644
index 0000000..458e541
--- /dev/null
+++ b/tools/android/heap_profiler/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/bsdtrees",
+]
diff --git a/tools/android/heap_profiler/heap_dump.c b/tools/android/heap_profiler/heap_dump.c
new file mode 100644
index 0000000..5d468da
--- /dev/null
+++ b/tools/android/heap_profiler/heap_dump.c
@@ -0,0 +1,350 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The client dump tool for libheap_profiler. It attaches to a process (given
+// its pid) and dumps all the libheap_profiler tracking information in JSON.
+// The JSON output looks like this:
+// {
+// "total_allocated": 908748493, # Total bytes allocated and not freed.
+// "num_allocs": 37542, # Number of allocations.
+// "num_stacks": 3723, # Number of allocation call-sites.
+// "allocs": # Optional. Printed only with the -x arg.
+// {
+// "beef1234": {"l": 17, "f": 1, "s": "1a"},
+// ^ ^ ^ ^ Index of the corresponding entry in the
+// | | | next "stacks" section. Essentially a ref
+// | | | to the call site that created the alloc.
+// | | |
+// | | +-------> Flags (last arg of heap_profiler_alloc).
+// | +----------------> Length of the Alloc.
+// +-----------------------------> Start address of the Alloc (hex).
+// },
+// "stacks":
+// {
+// "1a": {"l": 17, "f": [1074792772, 1100849864, 1100850688, ...]},
+// ^ ^ ^
+// | | +-----> Stack frames (absolute virtual addresses).
+// | +--------------> Bytes allocated and not freed by the call site.
+// +---------------------> Index of the entry (as for "allocs" xref).
+// Indexes are hex and might not be monotonic.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+
+#include "tools/android/heap_profiler/heap_profiler.h"
+
+
+static void lseek_abs(int fd, size_t off);
+static void read_proc_cmdline(char* cmdline, int size);
+static ssize_t read_safe(int fd, void* buf, size_t count);
+
+static int pid;
+
+
+static int dump_process_heap(
+ int mem_fd,
+ FILE* fmaps,
+ bool dump_also_allocs,
+ bool pedantic, // Enable pedantic consistency checks on memory counters.
+ char* comment) {
+ HeapStats stats;
+ time_t tm;
+ char cmdline[512];
+
+ tm = time(NULL);
+ read_proc_cmdline(cmdline, sizeof(cmdline));
+
+ // Look for the mmap which contains the HeapStats in the target process vmem.
+ // On Linux/Android, the libheap_profiler mmaps explicitly /dev/zero. The
+ // region furthermore starts with a magic marker to disambiguate.
+ bool stats_mmap_found = false;
+ for (;;) {
+ char line[1024];
+ if (fgets(line, sizeof(line), fmaps) == NULL)
+ break;
+
+ uintptr_t start;
+ uintptr_t end;
+ char map_file[32];
+ int ret = sscanf(line, "%"SCNxPTR"-%"SCNxPTR" rw-p %*s %*s %*s %31s",
+ &start, &end, map_file);
+ const size_t size = end - start + 1;
+ if (ret != 3 || strcmp(map_file, "/dev/zero") != 0 || size < sizeof(stats))
+ continue;
+
+ // The mmap looks promising. Let's check for the magic marker.
+ lseek_abs(mem_fd, start);
+ ssize_t rsize = read_safe(mem_fd, &stats, sizeof(stats));
+
+ if (rsize == -1) {
+ perror("read");
+ return -1;
+ }
+
+ if (rsize < sizeof(stats))
+ continue;
+
+ if (stats.magic_start == HEAP_PROFILER_MAGIC_MARKER) {
+ stats_mmap_found = true;
+ break;
+ }
+ }
+
+ if (!stats_mmap_found) {
+ fprintf(stderr, "Could not find the HeapStats area. "
+ "It looks like libheap_profiler is not loaded.\n");
+ return -1;
+ }
+
+ // Print JSON-formatted output.
+ printf("{\n");
+ printf(" \"pid\": %d,\n", pid);
+ printf(" \"time\": %ld,\n", tm);
+ printf(" \"comment\": \"%s\",\n", comment);
+ printf(" \"cmdline\": \"%s\",\n", cmdline);
+ printf(" \"pagesize\": %d,\n", getpagesize());
+ printf(" \"total_allocated\": %zu,\n", stats.total_alloc_bytes);
+ printf(" \"num_allocs\": %"PRIu32",\n", stats.num_allocs);
+ printf(" \"num_stacks\": %"PRIu32",\n", stats.num_stack_traces);
+
+ uint32_t dbg_counted_allocs = 0;
+ size_t dbg_counted_total_alloc_bytes = 0;
+ bool prepend_trailing_comma = false; // JSON syntax, I hate you.
+ uint32_t i;
+
+ // Dump the optional allocation table.
+ if (dump_also_allocs) {
+ printf(" \"allocs\": {");
+ lseek_abs(mem_fd, (uintptr_t) stats.allocs);
+ for (i = 0; i < stats.max_allocs; ++i) {
+ Alloc alloc;
+ if (read_safe(mem_fd, &alloc, sizeof(alloc)) != sizeof(alloc)) {
+ fprintf(stderr, "ERROR: cannot read allocation table\n");
+ perror("read");
+ return -1;
+ }
+
+ // Skip empty (i.e. freed) entries.
+ if (alloc.start == 0 && alloc.end == 0)
+ continue;
+
+ if (alloc.end < alloc.start) {
+ fprintf(stderr, "ERROR: found inconsistent alloc.\n");
+ return -1;
+ }
+
+ size_t alloc_size = alloc.end - alloc.start + 1;
+ size_t stack_idx = (
+ (uintptr_t) alloc.st - (uintptr_t) stats.stack_traces) /
+ sizeof(StacktraceEntry);
+ dbg_counted_total_alloc_bytes += alloc_size;
+ ++dbg_counted_allocs;
+
+ if (prepend_trailing_comma)
+ printf(",");
+ prepend_trailing_comma = true;
+ printf("\"%"PRIxPTR"\": {\"l\": %zu, \"f\": %"PRIu32", \"s\": \"%zx\"}",
+ alloc.start, alloc_size, alloc.flags, stack_idx);
+ }
+ printf("},\n");
+
+ if (pedantic && dbg_counted_allocs != stats.num_allocs) {
+ fprintf(stderr,
+ "ERROR: inconsistent alloc count (%"PRIu32" vs %"PRIu32").\n",
+ dbg_counted_allocs, stats.num_allocs);
+ return -1;
+ }
+
+ if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
+ fprintf(stderr, "ERROR: inconsistent alloc totals (%zu vs %zu).\n",
+ dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
+ return -1;
+ }
+ }
+
+ // Dump the distinct stack traces.
+ printf(" \"stacks\": {");
+ prepend_trailing_comma = false;
+ dbg_counted_total_alloc_bytes = 0;
+ lseek_abs(mem_fd, (uintptr_t) stats.stack_traces);
+ for (i = 0; i < stats.max_stack_traces; ++i) {
+ StacktraceEntry st;
+ if (read_safe(mem_fd, &st, sizeof(st)) != sizeof(st)) {
+ fprintf(stderr, "ERROR: cannot read stack trace table\n");
+ perror("read");
+ return -1;
+ }
+
+ // Skip empty (i.e. freed) entries.
+ if (st.alloc_bytes == 0)
+ continue;
+
+ dbg_counted_total_alloc_bytes += st.alloc_bytes;
+
+ if (prepend_trailing_comma)
+ printf(",");
+ prepend_trailing_comma = true;
+
+ printf("\"%"PRIx32"\":{\"l\": %zu, \"f\": [", i, st.alloc_bytes);
+ size_t n = 0;
+ for (;;) {
+ printf("%" PRIuPTR, st.frames[n]);
+ ++n;
+ if (n == HEAP_PROFILER_MAX_DEPTH || st.frames[n] == 0)
+ break;
+ else
+ printf(",");
+ }
+ printf("]}");
+ }
+ printf("}\n}\n");
+
+ if (pedantic && dbg_counted_total_alloc_bytes != stats.total_alloc_bytes) {
+ fprintf(stderr, "ERROR: inconsistent stacks totals (%zu vs %zu).\n",
+ dbg_counted_total_alloc_bytes, stats.total_alloc_bytes);
+ return -1;
+ }
+
+ fflush(stdout);
+ return 0;
+}
+
+// Unfortunately lseek takes a *signed* offset, which is unsuitable for large
+// files like /proc/X/mem on 64-bit.
+static void lseek_abs(int fd, size_t off) {
+#define OFF_T_MAX ((off_t) ~(((uint64_t) 1) << (8 * sizeof(off_t) - 1)))
+ if (off <= OFF_T_MAX) {
+ lseek(fd, (off_t) off, SEEK_SET);
+ return;
+ }
+ lseek(fd, (off_t) OFF_T_MAX, SEEK_SET);
+ lseek(fd, (off_t) (off - OFF_T_MAX), SEEK_CUR);
+}
+
+static ssize_t read_safe(int fd, void* buf, size_t count) {
+ ssize_t res;
+ size_t bytes_read = 0;
+ if (count < 0)
+ return -1;
+ do {
+ do {
+ res = read(fd, buf + bytes_read, count - bytes_read);
+ } while (res == -1 && errno == EINTR);
+ if (res <= 0)
+ break;
+ bytes_read += res;
+ } while (bytes_read < count);
+ return bytes_read ? bytes_read : res;
+}
+
+static int open_proc_mem_fd() {
+ char path[64];
+ snprintf(path, sizeof(path), "/proc/%d/mem", pid);
+ int mem_fd = open(path, O_RDONLY);
+ if (mem_fd < 0) {
+ fprintf(stderr, "Could not attach to target process virtual memory.\n");
+ perror("open");
+ }
+ return mem_fd;
+}
+
+static FILE* open_proc_maps() {
+ char path[64];
+ snprintf(path, sizeof(path), "/proc/%d/maps", pid);
+ FILE* fmaps = fopen(path, "r");
+ if (fmaps == NULL) {
+ fprintf(stderr, "Could not open %s.\n", path);
+ perror("fopen");
+ }
+ return fmaps;
+}
+
+static void read_proc_cmdline(char* cmdline, int size) {
+ char path[64];
+ snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
+ int cmdline_fd = open(path, O_RDONLY);
+ if (cmdline_fd < 0) {
+ fprintf(stderr, "Could not open %s.\n", path);
+ perror("open");
+ cmdline[0] = '\0';
+ return;
+ }
+ int length = read_safe(cmdline_fd, cmdline, size);
+ if (length < 0) {
+ fprintf(stderr, "Could not read %s.\n", path);
+ perror("read");
+ length = 0;
+ }
+ close(cmdline_fd);
+ cmdline[length] = '\0';
+}
+
+int main(int argc, char** argv) {
+ char c;
+ int ret = 0;
+ bool dump_also_allocs = false;
+ bool pedantic = true;
+ char comment[1024] = { '\0' };
+
+ while (((c = getopt(argc, argv, "xnc:")) & 0x80) == 0) {
+ switch (c) {
+ case 'x':
+ dump_also_allocs = true;
+ break;
+ case 'n':
+ pedantic = false;
+ break;
+ case 'c':
+ strlcpy(comment, optarg, sizeof(comment));
+ break;
+ }
+ }
+
+ if (optind >= argc) {
+ printf("Usage: %s [-n] [-x] [-c comment] pid\n"
+ " -n: Skip pedantic checks on dump consistency.\n"
+ " -x: Extended dump, includes individual allocations.\n"
+ " -c: Appends the given comment to the JSON dump.\n",
+ argv[0]);
+ return -1;
+ }
+
+ pid = atoi(argv[optind]);
+
+ if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
+ perror("ptrace");
+ return -1;
+ }
+
+ // Wait for the process to actually freeze.
+ waitpid(pid, NULL, 0);
+
+ int mem_fd = open_proc_mem_fd();
+ if (mem_fd < 0)
+ ret = -1;
+
+ FILE* fmaps = open_proc_maps();
+ if (fmaps == NULL)
+ ret = -1;
+
+ if (ret == 0)
+ ret = dump_process_heap(mem_fd, fmaps, dump_also_allocs, pedantic, comment);
+
+ ptrace(PTRACE_DETACH, pid, NULL, NULL);
+
+ // Cleanup.
+ fflush(stdout);
+ close(mem_fd);
+ fclose(fmaps);
+ return ret;
+}
diff --git a/tools/android/heap_profiler/heap_profiler.c b/tools/android/heap_profiler/heap_profiler.c
new file mode 100644
index 0000000..aef63ba
--- /dev/null
+++ b/tools/android/heap_profiler/heap_profiler.c
@@ -0,0 +1,397 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a OS-independent* module which purpose is tracking allocations and
+// their call sites (stack traces). It is able to deal with hole punching
+// (read: munmap). Also, it has low overhead and its presence in the system its
+// barely noticeable, even if tracing *all* the processes.
+// This module does NOT know how to deal with stack unwinding. The caller must
+// do that and pass the addresses of the unwound stack.
+// * (Modulo three lines for mutexes.)
+//
+// Exposed API:
+// void heap_profiler_init(HeapStats*);
+// void heap_profiler_alloc(addr, size, stack_frames, depth, flags);
+// void heap_profiler_free(addr, size); (size == 0 means free entire region).
+//
+// The profiling information is tracked into two data structures:
+// 1) A RB-Tree of non-overlapping VM regions (allocs) sorted by their start
+// addr. Each entry tracks the start-end addresses and points to the stack
+// trace which created that allocation (see below).
+// 2) A (hash) table of stack traces. In general the #allocations >> #call sites
+// which create those allocations. In order to avoid duplicating the latter,
+// they are stored distinctly in this hash table and used by reference.
+//
+// / Process virtual address space \
+// +------+ +------+ +------+
+// |Alloc1| |Alloc2| |Alloc3| <- Allocs (a RB-Tree underneath)
+// +------+ +------+ +------+
+// Len: 12 Len: 4 Len: 4
+// | | | stack_traces
+// | | | +-----------+--------------+
+// | | | | Alloc tot | stack frames +
+// | | | +-----------+--------------+
+// +------------|-------------+------------> | 16 | 0x1234 .... |
+// | +-----------+--------------+
+// +--------------------------> | 4 | 0x5678 .... |
+// +-----------+--------------+
+// (A hash-table underneath)
+//
+// Final note: the memory for both 1) and 2) entries is carved out from two
+// static pools (i.e. stack_traces and allocs). The pools are treated as
+// a sbrk essentially, and are kept compact by reusing freed elements (hence
+// having a freelist for each of them).
+//
+// All the internal (static) functions here assume that the |lock| is held.
+
+#include <assert.h>
+#include <string.h>
+
+// Platform-dependent mutex boilerplate.
+#if defined(__linux__) || defined(__ANDROID__)
+#include <pthread.h>
+#define DEFINE_MUTEX(x) pthread_mutex_t x = PTHREAD_MUTEX_INITIALIZER
+#define LOCK_MUTEX(x) pthread_mutex_lock(&x)
+#define UNLOCK_MUTEX(x) pthread_mutex_unlock(&x)
+#else
+#error OS not supported.
+#endif
+
+#include "tools/android/heap_profiler/heap_profiler.h"
+
+
+static DEFINE_MUTEX(lock);
+
+// |stats| contains the global tracking metadata and is the entry point which
+// is read by the heap_dump tool.
+static HeapStats* stats;
+
+// +---------------------------------------------------------------------------+
+// + Stack traces hash-table +
+// +---------------------------------------------------------------------------+
+#define ST_ENTRIES_MAX (64 * 1024)
+#define ST_HASHTABLE_BUCKETS (64 * 1024) /* Must be a power of 2. */
+
+static StacktraceEntry stack_traces[ST_ENTRIES_MAX];
+static StacktraceEntry* stack_traces_freelist;
+static StacktraceEntry* stack_traces_ht[ST_HASHTABLE_BUCKETS];
+
+// Looks up a stack trace from the stack frames. Creates a new one if necessary.
+static StacktraceEntry* record_stacktrace(uintptr_t* frames, uint32_t depth) {
+ if (depth == 0)
+ return NULL;
+
+ if (depth > HEAP_PROFILER_MAX_DEPTH)
+ depth = HEAP_PROFILER_MAX_DEPTH;
+
+ uint32_t i;
+ uintptr_t hash = 0;
+ for (i = 0; i < depth; ++i)
+ hash = (hash << 1) ^ (frames[i]);
+ const uint32_t slot = hash & (ST_HASHTABLE_BUCKETS - 1);
+ StacktraceEntry* st = stack_traces_ht[slot];
+
+ // Look for an existing entry in the hash-table.
+ const size_t frames_length = depth * sizeof(uintptr_t);
+ while (st != NULL && st->hash != hash &&
+ memcmp(frames, st->frames, frames_length) != 0) {
+ st = st->next;
+ }
+
+ // If not found, create a new one from the stack_traces array and add it to
+ // the hash-table.
+ if (st == NULL) {
+ // Get a free element either from the freelist or from the pool.
+ if (stack_traces_freelist != NULL) {
+ st = stack_traces_freelist;
+ stack_traces_freelist = stack_traces_freelist->next;
+ } else if (stats->max_stack_traces < ST_ENTRIES_MAX) {
+ st = &stack_traces[stats->max_stack_traces];
+ ++stats->max_stack_traces;
+ } else {
+ return NULL;
+ }
+
+ memset(st, 0, sizeof(*st));
+ memcpy(st->frames, frames, frames_length);
+ st->hash = hash;
+ st->next = stack_traces_ht[slot];
+ stack_traces_ht[slot] = st;
+ ++stats->num_stack_traces;
+ }
+
+ return st;
+}
+
+// Frees up a stack trace and appends it to the corresponding freelist.
+static void free_stacktrace(StacktraceEntry* st) {
+ assert(st->alloc_bytes == 0);
+ const uint32_t slot = st->hash & (ST_HASHTABLE_BUCKETS - 1);
+
+ // The expected load factor of the hash-table is very low. Frees should be
+ // pretty rare. Hence don't bother with a doubly linked list, might cost more.
+ StacktraceEntry** prev = &stack_traces_ht[slot];
+ while (*prev != st)
+ prev = &((*prev)->next);
+
+ // Remove from the hash-table bucket.
+ assert(*prev == st);
+ *prev = st->next;
+
+ // Add to the freelist.
+ st->next = stack_traces_freelist;
+ stack_traces_freelist = st;
+ --stats->num_stack_traces;
+}
+
+// +---------------------------------------------------------------------------+
+// + Allocs RB-tree +
+// +---------------------------------------------------------------------------+
+#define ALLOCS_ENTRIES_MAX (256 * 1024)
+
+static Alloc allocs[ALLOCS_ENTRIES_MAX];
+static Alloc* allocs_freelist;
+static RB_HEAD(HeapEntriesTree, Alloc) allocs_tree =
+ RB_INITIALIZER(&allocs_tree);
+
+// Comparator used by the RB-Tree (mind the overflow, avoid arith on addresses).
+static int allocs_tree_cmp(Alloc *alloc_1, Alloc *alloc_2) {
+ if (alloc_1->start < alloc_2->start)
+ return -1;
+ if (alloc_1->start > alloc_2->start)
+ return 1;
+ return 0;
+}
+
+RB_PROTOTYPE(HeapEntriesTree, Alloc, rb_node, allocs_tree_cmp);
+RB_GENERATE(HeapEntriesTree, Alloc, rb_node, allocs_tree_cmp);
+
+// Allocates a new Alloc and inserts it in the tree.
+static Alloc* insert_alloc(
+ uintptr_t start, uintptr_t end, StacktraceEntry* st, uint32_t flags) {
+ Alloc* alloc = NULL;
+
+ // First of all, get a free element either from the freelist or from the pool.
+ if (allocs_freelist != NULL) {
+ alloc = allocs_freelist;
+ allocs_freelist = alloc->next_free;
+ } else if (stats->max_allocs < ALLOCS_ENTRIES_MAX) {
+ alloc = &allocs[stats->max_allocs];
+ ++stats->max_allocs;
+ } else {
+ return NULL; // OOM.
+ }
+
+ alloc->start = start;
+ alloc->end = end;
+ alloc->st = st;
+ alloc->flags = flags;
+ alloc->next_free = NULL;
+ RB_INSERT(HeapEntriesTree, &allocs_tree, alloc);
+ ++stats->num_allocs;
+ return alloc;
+}
+
+// Deletes all the allocs in the range [addr, addr+size[ dealing with partial
+// frees and hole punching. Note that in the general case this function might
+// need to deal with very unfortunate cases, as below:
+//
+// Alloc tree begin: [Alloc 1]----[Alloc 2]-------[Alloc 3][Alloc 4]---[Alloc 5]
+// Deletion range: [xxxxxxxxxxxxxxxxxxxx]
+// Alloc tree end: [Alloc 1]----[Al.2]----------------------[Al.4]---[Alloc 5]
+// Alloc3 has to be deleted and Alloc 2,4 shrunk.
+static uint32_t delete_allocs_in_range(void* addr, size_t size) {
+ uintptr_t del_start = (uintptr_t) addr;
+ uintptr_t del_end = del_start + size - 1;
+ uint32_t flags = 0;
+
+ Alloc* alloc = NULL;
+ Alloc* next_alloc = RB_ROOT(&allocs_tree);
+
+ // Lookup the first (by address) relevant Alloc to initiate the deletion walk.
+ // At the end of the loop next_alloc is either:
+ // - the closest alloc starting before (or exactly at) the start of the
+ // deletion range (i.e. addr == del_start).
+ // - the first alloc inside the deletion range.
+ // - the first alloc after the deletion range iff the range was already empty
+ // (in this case the next loop will just bail out doing nothing).
+ // - NULL: iff the entire tree is empty (as above).
+ while (next_alloc != NULL) {
+ alloc = next_alloc;
+ if (alloc->start > del_start) {
+ next_alloc = RB_LEFT(alloc, rb_node);
+ } else if (alloc->end < del_start) {
+ next_alloc = RB_RIGHT(alloc, rb_node);
+ } else { // alloc->start <= del_start && alloc->end >= del_start
+ break;
+ }
+ }
+
+ // Now scan the allocs linearly deleting chunks (or eventually whole allocs)
+ // until passing the end of the deleting region.
+ next_alloc = alloc;
+ while (next_alloc != NULL) {
+ alloc = next_alloc;
+ next_alloc = RB_NEXT(HeapEntriesTree, &allocs_tree, alloc);
+
+ if (size != 0) {
+ // In the general case we stop passed the end of the deletion range.
+ if (alloc->start > del_end)
+ break;
+
+ // This deals with the case of the first Alloc laying before the range.
+ if (alloc->end < del_start)
+ continue;
+ } else {
+ // size == 0 is a special case. It means deleting only the alloc which
+ // starts exactly at |del_start| if any (for dealing with free(ptr)).
+ if (alloc->start > del_start)
+ break;
+ if (alloc->start < del_start)
+ continue;
+ del_end = alloc->end;
+ }
+
+ // Reached this point the Alloc must overlap (partially or completely) with
+ // the deletion range.
+ assert(!(alloc->start > del_end || alloc->end < del_start));
+
+ StacktraceEntry* st = alloc->st;
+ flags |= alloc->flags;
+ uintptr_t freed_bytes = 0; // Bytes freed in this cycle.
+
+ if (del_start <= alloc->start) {
+ if (del_end >= alloc->end) {
+ // Complete overlap. Delete full Alloc. Note: the range might might
+ // still overlap with the next allocs.
+ // Begin: ------[alloc.start alloc.end]-[next alloc]
+ // Del range: [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]
+ // Result: ---------------------------------[next alloc]
+ // [next alloc] will be shrinked on the next iteration.
+ freed_bytes = alloc->end - alloc->start + 1;
+ RB_REMOVE(HeapEntriesTree, &allocs_tree, alloc);
+
+ // Clean-up, so heap_dump can tell this is a free entry and skip it.
+ alloc->start = alloc->end = 0;
+ alloc->st = NULL;
+
+ // Put in the freelist.
+ alloc->next_free = allocs_freelist;
+ allocs_freelist = alloc;
+ --stats->num_allocs;
+ } else {
+ // Partial overlap at beginning. Cut first part and shrink the alloc.
+ // Begin: ------[alloc.start alloc.end]-[next alloc]
+ // Del range: [xxxxxx]
+ // Result: ------------[start alloc.end]-[next alloc]
+ freed_bytes = del_end - alloc->start + 1;
+ alloc->start = del_end + 1;
+ // No need to update the tree even if we changed the key. The keys are
+ // still monotonic (because the ranges are guaranteed to not overlap).
+ }
+ } else {
+ if (del_end >= alloc->end) {
+ // Partial overlap at end. Cut last part and shrink the alloc left.
+ // Begin: ------[alloc.start alloc.end]-[next alloc]
+ // Del range: [xxxxxxxx]
+ // Result: ------[alloc.start alloc.end]-----[next alloc]
+ // [next alloc] will be shrinked on the next iteration.
+ freed_bytes = alloc->end - del_start + 1;
+ alloc->end = del_start - 1;
+ } else {
+ // Hole punching. Requires creating an extra alloc.
+ // Begin: ------[alloc.start alloc.end]-[next alloc]
+ // Del range: [xxx]
+ // Result: ------[ alloc 1 ]-----[ alloc 2 ]-[next alloc]
+ freed_bytes = del_end - del_start + 1;
+ const uintptr_t old_end = alloc->end;
+ alloc->end = del_start - 1;
+
+ // In case of OOM, don't count the 2nd alloc we failed to allocate.
+ if (insert_alloc(del_end + 1, old_end, st, alloc->flags) == NULL)
+ freed_bytes += (old_end - del_end);
+ }
+ }
+ // Now update the StackTraceEntry the Alloc was pointing to, eventually
+ // freeing it up.
+ assert(st->alloc_bytes >= freed_bytes);
+ st->alloc_bytes -= freed_bytes;
+ if (st->alloc_bytes == 0)
+ free_stacktrace(st);
+ stats->total_alloc_bytes -= freed_bytes;
+ }
+ return flags;
+}
+
+// +---------------------------------------------------------------------------+
+// + Library entry points (refer to heap_profiler.h for API doc). +
+// +---------------------------------------------------------------------------+
+void heap_profiler_free(void* addr, size_t size, uint32_t* old_flags) {
+ assert(size == 0 || ((uintptr_t) addr + (size - 1)) >= (uintptr_t) addr);
+
+ LOCK_MUTEX(lock);
+ uint32_t flags = delete_allocs_in_range(addr, size);
+ UNLOCK_MUTEX(lock);
+
+ if (old_flags != NULL)
+ *old_flags = flags;
+}
+
+void heap_profiler_alloc(void* addr, size_t size, uintptr_t* frames,
+ uint32_t depth, uint32_t flags) {
+ if (depth > HEAP_PROFILER_MAX_DEPTH)
+ depth = HEAP_PROFILER_MAX_DEPTH;
+
+ if (size == 0) // Apps calling malloc(0), sometimes it happens.
+ return;
+
+ const uintptr_t start = (uintptr_t) addr;
+ const uintptr_t end = start + (size - 1);
+ assert(start <= end);
+
+ LOCK_MUTEX(lock);
+
+ delete_allocs_in_range(addr, size);
+
+ StacktraceEntry* st = record_stacktrace(frames, depth);
+ if (st != NULL) {
+ Alloc* alloc = insert_alloc(start, end, st, flags);
+ if (alloc != NULL) {
+ st->alloc_bytes += size;
+ stats->total_alloc_bytes += size;
+ }
+ }
+
+ UNLOCK_MUTEX(lock);
+}
+
+void heap_profiler_init(HeapStats* heap_stats) {
+ LOCK_MUTEX(lock);
+
+ assert(stats == NULL);
+ stats = heap_stats;
+ memset(stats, 0, sizeof(HeapStats));
+ stats->magic_start = HEAP_PROFILER_MAGIC_MARKER;
+ stats->allocs = &allocs[0];
+ stats->stack_traces = &stack_traces[0];
+
+ UNLOCK_MUTEX(lock);
+}
+
+void heap_profiler_cleanup(void) {
+ LOCK_MUTEX(lock);
+
+ assert(stats != NULL);
+ memset(stack_traces, 0, sizeof(StacktraceEntry) * stats->max_stack_traces);
+ memset(stack_traces_ht, 0, sizeof(stack_traces_ht));
+ stack_traces_freelist = NULL;
+
+ memset(allocs, 0, sizeof(Alloc) * stats->max_allocs);
+ allocs_freelist = NULL;
+ RB_INIT(&allocs_tree);
+
+ stats = NULL;
+
+ UNLOCK_MUTEX(lock);
+}
diff --git a/tools/android/heap_profiler/heap_profiler.gyp b/tools/android/heap_profiler/heap_profiler.gyp
new file mode 100644
index 0000000..50e6797
--- /dev/null
+++ b/tools/android/heap_profiler/heap_profiler.gyp
@@ -0,0 +1,75 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ # libheap_profiler is the library that will be preloaded in the Android
+ # Zygote and contains the black magic to hook malloc/mmap calls.
+ 'target_name': 'heap_profiler',
+ 'type': 'shared_library',
+ 'include_dirs': [ '../../..' ],
+ 'sources': [ 'heap_profiler_hooks_android.c' ],
+ 'dependencies': [ 'heap_profiler_core' ],
+ },
+ {
+ # heap_profiler_core contains only the tracking metadata code without any
+ # hooks. It is required by both the hprof library itself and the unittest.
+ 'target_name': 'heap_profiler_core',
+ 'type': 'static_library',
+ 'sources': [
+ 'heap_profiler.c',
+ 'heap_profiler.h',
+ ],
+ 'include_dirs': [ '../../..' ],
+ },
+ {
+ 'target_name': 'heap_dump',
+ 'type': 'executable',
+ 'sources': [ 'heap_dump.c' ],
+ 'include_dirs': [ '../../..' ],
+ },
+ {
+ 'target_name': 'heap_profiler_unittests',
+ 'type': '<(gtest_target_type)',
+ 'sources': [ 'heap_profiler_unittest.cc' ],
+ 'dependencies': [
+ 'heap_profiler_core',
+ '../../../testing/android/native_test.gyp:native_test_native_code',
+ '../../../testing/gtest.gyp:gtest',
+ '../../../testing/gtest.gyp:gtest_main',
+ ],
+ 'include_dirs': [ '../../..' ],
+ },
+ {
+ 'target_name': 'heap_profiler_unittests_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'heap_profiler_unittests',
+ ],
+ 'variables': {
+ 'test_suite_name': 'heap_profiler_unittests',
+ },
+ 'includes': [ '../../../build/apk_test.gypi' ],
+ },
+ {
+ 'target_name': 'heap_profiler_integrationtest',
+ 'type': 'executable',
+ 'sources': [ 'heap_profiler_integrationtest.cc' ],
+ 'dependencies': [ '../../../testing/gtest.gyp:gtest' ],
+ 'include_dirs': [ '../../..' ],
+ },
+ {
+ 'target_name': 'heap_profiler_integrationtest_stripped',
+ 'type': 'none',
+ 'dependencies': [ 'heap_profiler_integrationtest' ],
+ 'actions': [{
+ 'action_name': 'strip heap_profiler_integrationtest',
+ 'inputs': [ '<(PRODUCT_DIR)/heap_profiler_integrationtest' ],
+ 'outputs': [ '<(PRODUCT_DIR)/heap_profiler_integrationtest_stripped' ],
+ 'action': [ '<(android_strip)', '<@(_inputs)', '-o', '<@(_outputs)' ],
+ }],
+ },
+ ],
+}
diff --git a/tools/android/heap_profiler/heap_profiler.h b/tools/android/heap_profiler/heap_profiler.h
new file mode 100644
index 0000000..491081d
--- /dev/null
+++ b/tools/android/heap_profiler/heap_profiler.h
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_ANDROID_HEAP_PROFILER_HEAP_PROFILER_H_
+#define TOOLS_ANDROID_HEAP_PROFILER_HEAP_PROFILER_H_
+
+#include <stdint.h>
+#include "third_party/bsdtrees/tree.h"
+
+#define HEAP_PROFILER_MAGIC_MARKER 0x42beef42L
+#define HEAP_PROFILER_MAX_DEPTH 12
+
+// The allocation is a result of a system malloc() invocation.
+#define HEAP_PROFILER_FLAGS_MALLOC 1
+
+// The allocation is a result of a mmap() invocation.
+#define HEAP_PROFILER_FLAGS_MMAP 2 // Allocation performed through mmap.
+
+// Only in the case of FLAGS_MMAP: The mmap is not anonymous (i.e. file backed).
+#define HEAP_PROFILER_FLAGS_MMAP_FILE 4
+
+// Android only: allocation made by the Zygote (before forking).
+#define HEAP_PROFILER_FLAGS_IN_ZYGOTE 8
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct StacktraceEntry {
+ uintptr_t frames[HEAP_PROFILER_MAX_DEPTH]; // Absolute addrs of stack frames.
+ uint32_t hash; // H(frames), used to keep these entries in a hashtable.
+
+ // Total number of bytes allocated through this code path. It is equal to the
+ // sum of Alloc instances' length which .bt == this.
+ size_t alloc_bytes;
+
+ // |next| has a dual purpose. When the entry is used (hence in the hashtable),
+ // this is a ptr to the next item in the same bucket. When the entry is free,
+ // this is a ptr to the next entry in the freelist.
+ struct StacktraceEntry* next;
+} StacktraceEntry;
+
+// Represents a contiguous range of virtual memory which has been allocated by
+// a give code path (identified by the corresponding StacktraceEntry).
+typedef struct Alloc {
+ RB_ENTRY(Alloc) rb_node; // Anchor for the RB-tree;
+ uintptr_t start;
+ uintptr_t end;
+ uint32_t flags; // See HEAP_PROFILER_FLAGS_*.
+ StacktraceEntry* st; // NULL == free entry.
+ struct Alloc* next_free;
+} Alloc;
+
+typedef struct {
+ uint32_t magic_start; // The magic marker used to locate the stats mmap.
+ uint32_t num_allocs; // The total number of allocation entries present.
+ uint32_t max_allocs; // The max number of items in |allocs|.
+ uint32_t num_stack_traces; // The total number of stack traces present.
+ uint32_t max_stack_traces; // The max number of items in |stack_traces|.
+ size_t total_alloc_bytes; // Total allocation bytes tracked.
+ Alloc* allocs; // Start of the the Alloc pool.
+ StacktraceEntry* stack_traces; // Start of the StacktraceEntry pool.
+} HeapStats;
+
+// Initialize the heap_profiler. The caller has to allocate the HeapStats
+// "superblock", since the way it is mapped is platform-specific.
+void heap_profiler_init(HeapStats* heap_stats);
+
+// Records and allocation. The caller must unwind the stack and pass the
+// frames array. Flags are optionals and don't affect the behavior of the
+// library (they're just kept along and dumped).
+void heap_profiler_alloc(void* addr,
+ size_t size,
+ uintptr_t* frames,
+ uint32_t depth,
+ uint32_t flags);
+
+// Frees any allocation (even partial) overlapping with the given range.
+// If old_flags != NULL, it will be filled with the flags of the deleted allocs.
+void heap_profiler_free(void* addr, size_t size, uint32_t* old_flags);
+
+// Cleans up the HeapStats and all the internal data structures.
+void heap_profiler_cleanup(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TOOLS_ANDROID_HEAP_PROFILER_HEAP_PROFILER_H_
diff --git a/tools/android/heap_profiler/heap_profiler_hooks_android.c b/tools/android/heap_profiler/heap_profiler_hooks_android.c
new file mode 100644
index 0000000..1480780
--- /dev/null
+++ b/tools/android/heap_profiler/heap_profiler_hooks_android.c
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <unwind.h>
+
+#include "tools/android/heap_profiler/heap_profiler.h"
+
+#define HEAP_PROFILER_EXPORT __attribute__((visibility("default")))
+
+
+static inline __attribute__((always_inline))
+uint32_t get_backtrace(uintptr_t* frames, uint32_t max_depth);
+
+// Function pointers typedefs for the hooked symbols.
+typedef void* (*mmap_t)(void*, size_t, int, int, int, off_t);
+typedef void* (*mmap2_t)(void*, size_t, int, int, int, off_t);
+typedef void* (*mmap64_t)(void*, size_t, int, int, int, off64_t);
+typedef void* (*mremap_t)(void*, size_t, size_t, unsigned long);
+typedef int (*munmap_t)(void*, size_t);
+typedef void* (*malloc_t)(size_t);
+typedef void* (*calloc_t)(size_t, size_t);
+typedef void* (*realloc_t)(void*, size_t);
+typedef void (*free_t)(void*);
+
+// And their actual definitions.
+static mmap_t real_mmap;
+static mmap2_t real_mmap2;
+static mmap64_t real_mmap64;
+static mremap_t real_mremap;
+static munmap_t real_munmap;
+static malloc_t real_malloc;
+static calloc_t real_calloc;
+static realloc_t real_realloc;
+static free_t real_free;
+static int* has_forked_off_zygote;
+
+HEAP_PROFILER_EXPORT const HeapStats* heap_profiler_stats_for_tests;
+
+// +---------------------------------------------------------------------------+
+// + Initialization of heap_profiler and lookup of hooks' addresses +
+// +---------------------------------------------------------------------------+
+__attribute__((constructor))
+static void initialize() {
+ real_mmap = (mmap_t) dlsym(RTLD_NEXT, "mmap");
+ real_mmap2 = (mmap_t) dlsym(RTLD_NEXT, "mmap2");
+ real_mmap64 = (mmap64_t) dlsym(RTLD_NEXT, "mmap64");
+ real_mremap = (mremap_t) dlsym(RTLD_NEXT, "mremap");
+ real_munmap = (munmap_t) dlsym(RTLD_NEXT, "munmap");
+ real_malloc = (malloc_t) dlsym(RTLD_NEXT, "malloc");
+ real_calloc = (calloc_t) dlsym(RTLD_NEXT, "calloc");
+ real_realloc = (realloc_t) dlsym(RTLD_NEXT, "realloc");
+ real_free = (free_t) dlsym(RTLD_NEXT, "free");
+
+ // gMallocLeakZygoteChild is an extra useful piece of information to have.
+ // When available, it tells whether we're in the zygote (=0) or forked (=1)
+ // a child off it. In the worst case it will be NULL and we'll just ignore it.
+ has_forked_off_zygote = (int*) dlsym(RTLD_NEXT, "gMallocLeakZygoteChild");
+
+ // Allocate room for the HeapStats area and initialize the heap profiler.
+ // Make an explicit map of /dev/zero (instead of MAP_ANONYMOUS), so that the
+ // heap_dump tool can easily spot the mapping in the target process.
+ int fd = open("/dev/zero", O_RDONLY);
+ if (fd < 0) {
+ abort(); // This world has gone wrong. Good night Vienna.
+ }
+
+ HeapStats* stats = (HeapStats*) real_mmap(
+ 0, sizeof(HeapStats), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ heap_profiler_stats_for_tests = stats;
+ heap_profiler_init(stats);
+}
+
+static inline __attribute__((always_inline)) void unwind_and_record_alloc(
+ void* start, size_t size, uint32_t flags) {
+ const int errno_save = errno;
+ uintptr_t frames[HEAP_PROFILER_MAX_DEPTH];
+ const uint32_t depth = get_backtrace(frames, HEAP_PROFILER_MAX_DEPTH);
+ if (has_forked_off_zygote != NULL && *has_forked_off_zygote == 0)
+ flags |= HEAP_PROFILER_FLAGS_IN_ZYGOTE;
+ heap_profiler_alloc(start, size, frames, depth, flags);
+ errno = errno_save;
+}
+
+static inline __attribute__((always_inline)) void discard_alloc(
+ void* start, size_t size, uint32_t* old_flags) {
+ const int errno_save = errno;
+ heap_profiler_free(start, size, old_flags);
+ errno = errno_save;
+}
+
+// Flags are non-functional extra decorators that are made available to the
+// final heap_dump tool, to get more details about the source of the allocation.
+static uint32_t get_flags_for_mmap(int fd) {
+ return HEAP_PROFILER_FLAGS_MMAP | (fd ? HEAP_PROFILER_FLAGS_MMAP_FILE : 0);
+}
+
+// +---------------------------------------------------------------------------+
+// + Actual mmap/malloc hooks +
+// +---------------------------------------------------------------------------+
+HEAP_PROFILER_EXPORT void* mmap(
+ void* addr, size_t size, int prot, int flags, int fd, off_t offset) {
+ void* ret = real_mmap(addr, size, prot, flags, fd, offset);
+ if (ret != MAP_FAILED)
+ unwind_and_record_alloc(ret, size, get_flags_for_mmap(fd));
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void* mmap2(
+ void* addr, size_t size, int prot, int flags, int fd, off_t pgoffset) {
+ void* ret = real_mmap2(addr, size, prot, flags, fd, pgoffset);
+ if (ret != MAP_FAILED)
+ unwind_and_record_alloc(ret, size, get_flags_for_mmap(fd));
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void* mmap64(
+ void* addr, size_t size, int prot, int flags, int fd, off64_t offset) {
+ void* ret = real_mmap64(addr, size, prot, flags, fd, offset);
+ if (ret != MAP_FAILED)
+ unwind_and_record_alloc(ret, size, get_flags_for_mmap(fd));
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void* mremap(
+ void* addr, size_t oldlen, size_t newlen, unsigned long flags) {
+ void* ret = real_mremap(addr, oldlen, newlen, flags);
+ if (ret != MAP_FAILED) {
+ uint32_t flags = 0;
+ if (addr)
+ discard_alloc(addr, oldlen, &flags);
+ if (newlen > 0)
+ unwind_and_record_alloc(ret, newlen, flags);
+ }
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT int munmap(void* ptr, size_t size) {
+ int ret = real_munmap(ptr, size);
+ discard_alloc(ptr, size, /*old_flags=*/NULL);
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void* malloc(size_t byte_count) {
+ void* ret = real_malloc(byte_count);
+ if (ret != NULL)
+ unwind_and_record_alloc(ret, byte_count, HEAP_PROFILER_FLAGS_MALLOC);
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void* calloc(size_t nmemb, size_t size) {
+ void* ret = real_calloc(nmemb, size);
+ if (ret != NULL)
+ unwind_and_record_alloc(ret, nmemb * size, HEAP_PROFILER_FLAGS_MALLOC);
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void* realloc(void* ptr, size_t size) {
+ void* ret = real_realloc(ptr, size);
+ uint32_t flags = 0;
+ if (ptr)
+ discard_alloc(ptr, 0, &flags);
+ if (ret != NULL)
+ unwind_and_record_alloc(ret, size, flags | HEAP_PROFILER_FLAGS_MALLOC);
+ return ret;
+}
+
+HEAP_PROFILER_EXPORT void free(void* ptr) {
+ real_free(ptr);
+ discard_alloc(ptr, 0, /*old_flags=*/NULL);
+}
+
+// +---------------------------------------------------------------------------+
+// + Stack unwinder +
+// +---------------------------------------------------------------------------+
+typedef struct {
+ uintptr_t* frames;
+ uint32_t frame_count;
+ uint32_t max_depth;
+ bool have_skipped_self;
+} stack_crawl_state_t;
+
+static _Unwind_Reason_Code unwind_fn(struct _Unwind_Context* ctx, void* arg) {
+ stack_crawl_state_t* state = (stack_crawl_state_t*) arg;
+ uintptr_t ip = _Unwind_GetIP(ctx);
+
+ if (ip != 0 && !state->have_skipped_self) {
+ state->have_skipped_self = true;
+ return _URC_NO_REASON;
+ }
+
+ state->frames[state->frame_count++] = ip;
+ return (state->frame_count >= state->max_depth) ?
+ _URC_END_OF_STACK : _URC_NO_REASON;
+}
+
+static uint32_t get_backtrace(uintptr_t* frames, uint32_t max_depth) {
+ stack_crawl_state_t state = {.frames = frames, .max_depth = max_depth};
+ _Unwind_Backtrace(unwind_fn, &state);
+ return state.frame_count;
+}
diff --git a/tools/android/heap_profiler/heap_profiler_integrationtest.cc b/tools/android/heap_profiler/heap_profiler_integrationtest.cc
new file mode 100644
index 0000000..ba7ebea
--- /dev/null
+++ b/tools/android/heap_profiler/heap_profiler_integrationtest.cc
@@ -0,0 +1,179 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/android/heap_profiler/heap_profiler.h"
+
+namespace {
+
+typedef void* (*AllocatorFn)(size_t);
+typedef int (*FreeFn)(void*, size_t);
+
+const size_t kSize1 = 499 * PAGE_SIZE;
+const size_t kSize2 = 503 * PAGE_SIZE;
+const size_t kSize3 = 509 * PAGE_SIZE;
+
+// The purpose of the four functions below is to create watermarked allocations,
+// so the test fixture can ascertain that the hooks work end-to-end.
+__attribute__((noinline)) void* MallocInner(size_t size) {
+ void* ptr = malloc(size);
+ // The memset below is to avoid tail-call elimination optimizations and ensure
+ // that this function will be part of the stack trace.
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+__attribute__((noinline)) void* MallocOuter(size_t size) {
+ void* ptr = MallocInner(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+__attribute__((noinline)) void* DoMmap(size_t size) {
+ return mmap(
+ 0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+}
+
+__attribute__((noinline)) void* MmapInner(size_t size) {
+ void* ptr = DoMmap(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+__attribute__((noinline)) void* MmapOuter(size_t size) {
+ void* ptr = MmapInner(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+const HeapStats* GetHeapStats() {
+ HeapStats* const* stats_ptr = reinterpret_cast<HeapStats* const*>(
+ dlsym(RTLD_DEFAULT, "heap_profiler_stats_for_tests"));
+ EXPECT_TRUE(stats_ptr != NULL);
+ const HeapStats* stats = *stats_ptr;
+ EXPECT_TRUE(stats != NULL);
+ EXPECT_EQ(HEAP_PROFILER_MAGIC_MARKER, stats->magic_start);
+ return stats;
+}
+
+bool StackTraceContains(const StacktraceEntry* s, AllocatorFn fn) {
+ // kExpectedFnLen is a gross estimation of the watermark functions' size.
+ // It tries to address the following problem: the addrs in the unwound stack
+ // stack frames will NOT point to the beginning of the functions, but to the
+ // PC after the call to malloc/mmap.
+ const size_t kExpectedFnLen = 16;
+ const uintptr_t fn_addr = reinterpret_cast<uintptr_t>(fn);
+ for (size_t i = 0; i < HEAP_PROFILER_MAX_DEPTH; ++i) {
+ if (s->frames[i] >= fn_addr && s->frames[i] <= fn_addr + kExpectedFnLen)
+ return true;
+ }
+ return false;
+}
+
+const StacktraceEntry* LookupStackTrace(size_t size, AllocatorFn fn) {
+ const HeapStats* stats = GetHeapStats();
+ for (size_t i = 0; i < stats->max_stack_traces; ++i) {
+ const StacktraceEntry* st = &stats->stack_traces[i];
+ if (st->alloc_bytes == size && StackTraceContains(st, fn))
+ return st;
+ }
+ return NULL;
+}
+
+int DoFree(void* addr, size_t /*size, ignored.*/) {
+ free(addr);
+ return 0;
+}
+
+void TestStackTracesWithParams(AllocatorFn outer_fn,
+ AllocatorFn inner_fn,
+ FreeFn free_fn) {
+ const HeapStats* stats = GetHeapStats();
+
+ void* m1 = outer_fn(kSize1);
+ void* m2 = inner_fn(kSize2);
+ void* m3 = inner_fn(kSize3);
+ free_fn(m3, kSize3);
+
+ const StacktraceEntry* st1 = LookupStackTrace(kSize1, inner_fn);
+ const StacktraceEntry* st2 = LookupStackTrace(kSize2, inner_fn);
+ const StacktraceEntry* st3 = LookupStackTrace(kSize3, inner_fn);
+
+ EXPECT_TRUE(st1 != NULL);
+ EXPECT_TRUE(StackTraceContains(st1, outer_fn));
+ EXPECT_TRUE(StackTraceContains(st1, inner_fn));
+
+ EXPECT_TRUE(st2 != NULL);
+ EXPECT_FALSE(StackTraceContains(st2, outer_fn));
+ EXPECT_TRUE(StackTraceContains(st2, inner_fn));
+
+ EXPECT_EQ(NULL, st3);
+
+ const size_t total_alloc_start = stats->total_alloc_bytes;
+ const size_t num_stack_traces_start = stats->num_stack_traces;
+
+ free_fn(m1, kSize1);
+ free_fn(m2, kSize2);
+
+ const size_t total_alloc_end = stats->total_alloc_bytes;
+ const size_t num_stack_traces_end = stats->num_stack_traces;
+
+ EXPECT_EQ(kSize1 + kSize2, total_alloc_start - total_alloc_end);
+ EXPECT_EQ(2, num_stack_traces_start - num_stack_traces_end);
+ EXPECT_EQ(NULL, LookupStackTrace(kSize1, inner_fn));
+ EXPECT_EQ(NULL, LookupStackTrace(kSize2, inner_fn));
+ EXPECT_EQ(NULL, LookupStackTrace(kSize3, inner_fn));
+}
+
+TEST(HeapProfilerIntegrationTest, TestMallocStackTraces) {
+ TestStackTracesWithParams(&MallocOuter, &MallocInner, &DoFree);
+}
+
+TEST(HeapProfilerIntegrationTest, TestMmapStackTraces) {
+ TestStackTracesWithParams(&MmapOuter, &MmapInner, &munmap);
+}
+
+// Returns the path of the directory containing the current executable.
+std::string GetExePath() {
+ char buf[1024];
+ ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
+ if (len == -1)
+ return std::string();
+ std::string path(buf, len);
+ size_t sep = path.find_last_of('/');
+ if (sep == std::string::npos)
+ return std::string();
+ path.erase(sep);
+ return path;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ // Re-launch the process itself forcing the preload of the libheap_profiler.
+ char* ld_preload = getenv("LD_PRELOAD");
+ if (ld_preload == NULL || strstr(ld_preload, "libheap_profiler.so") == NULL) {
+ char env_ld_lib_path[256];
+ strlcpy(env_ld_lib_path, "LD_LIBRARY_PATH=", sizeof(env_ld_lib_path));
+ strlcat(env_ld_lib_path, GetExePath().c_str(), sizeof(env_ld_lib_path));
+ char env_ld_preload[] = "LD_PRELOAD=libheap_profiler.so";
+ char* const env[] = {env_ld_preload, env_ld_lib_path, 0};
+ execve("/proc/self/exe", argv, env);
+ // execve() never returns, unless something goes wrong.
+ perror("execve");
+ assert(false);
+ }
+
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tools/android/heap_profiler/heap_profiler_unittest.cc b/tools/android/heap_profiler/heap_profiler_unittest.cc
new file mode 100644
index 0000000..65c2700
--- /dev/null
+++ b/tools/android/heap_profiler/heap_profiler_unittest.cc
@@ -0,0 +1,458 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <string.h>
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/android/heap_profiler/heap_profiler.h"
+
+namespace {
+
+class HeapProfilerTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE { heap_profiler_init(&stats_); }
+
+ virtual void TearDown() OVERRIDE {
+ CheckAllocVsStacktaceConsistency();
+ heap_profiler_cleanup();
+ }
+
+ protected:
+ struct StackTrace {
+ uintptr_t frames[HEAP_PROFILER_MAX_DEPTH];
+ size_t depth;
+ };
+
+ StackTrace GenStackTrace(size_t depth, uintptr_t base) {
+ assert(depth <= HEAP_PROFILER_MAX_DEPTH);
+ StackTrace st;
+ for (size_t i = 0; i < depth; ++i)
+ st.frames[i] = base + i * 0x10UL;
+ st.depth = depth;
+ return st;
+ }
+
+ void ExpectAlloc(uintptr_t start,
+ uintptr_t end,
+ const StackTrace& st,
+ uint32_t flags) {
+ for (uint32_t i = 0; i < stats_.max_allocs; ++i) {
+ const Alloc& alloc = stats_.allocs[i];
+ if (alloc.start != start || alloc.end != end)
+ continue;
+ // Check that the stack trace match.
+ for (uint32_t j = 0; j < st.depth; ++j) {
+ EXPECT_EQ(st.frames[j], alloc.st->frames[j])
+ << "Stacktrace not matching @ depth " << j;
+ }
+ EXPECT_EQ(flags, alloc.flags);
+ return;
+ }
+
+ FAIL() << "Alloc not found [" << std::hex << start << "," << end << "]";
+ }
+
+ void CheckAllocVsStacktaceConsistency() {
+ uint32_t allocs_seen = 0;
+ uint32_t stack_traces_seen = 0;
+ std::map<StacktraceEntry*, uintptr_t> stacktrace_bytes_by_alloc;
+
+ for (uint32_t i = 0; i < stats_.max_allocs; ++i) {
+ Alloc* alloc = &stats_.allocs[i];
+ if (alloc->start == 0 && alloc->end == 0)
+ continue;
+ ++allocs_seen;
+ stacktrace_bytes_by_alloc[alloc->st] += alloc->end - alloc->start + 1;
+ }
+
+ for (uint32_t i = 0; i < stats_.max_stack_traces; ++i) {
+ StacktraceEntry* st = &stats_.stack_traces[i];
+ if (st->alloc_bytes == 0)
+ continue;
+ ++stack_traces_seen;
+ EXPECT_EQ(1, stacktrace_bytes_by_alloc.count(st));
+ EXPECT_EQ(stacktrace_bytes_by_alloc[st], st->alloc_bytes);
+ }
+
+ EXPECT_EQ(allocs_seen, stats_.num_allocs);
+ EXPECT_EQ(stack_traces_seen, stats_.num_stack_traces);
+ }
+
+ HeapStats stats_;
+};
+
+TEST_F(HeapProfilerTest, SimpleAlloc) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 2048, st1.frames, st1.depth, 0);
+
+ EXPECT_EQ(2, stats_.num_allocs);
+ EXPECT_EQ(1, stats_.num_stack_traces);
+ EXPECT_EQ(1024 + 2048, stats_.total_alloc_bytes);
+ ExpectAlloc(0x1000, 0x13ff, st1, 0);
+ ExpectAlloc(0x2000, 0x27ff, st1, 0);
+}
+
+TEST_F(HeapProfilerTest, AllocMultipleStacks) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(4, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 2048, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x3000, 32, st1.frames, st1.depth, 0);
+
+ EXPECT_EQ(3, stats_.num_allocs);
+ EXPECT_EQ(2, stats_.num_stack_traces);
+ EXPECT_EQ(1024 + 2048 + 32, stats_.total_alloc_bytes);
+ ExpectAlloc(0x1000, 0x13ff, st1, 0);
+ ExpectAlloc(0x2000, 0x27ff, st2, 0);
+ ExpectAlloc(0x3000, 0x301f, st1, 0);
+}
+
+TEST_F(HeapProfilerTest, SimpleAllocAndFree) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_free((void*)0x1000, 1024, NULL);
+
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, Realloc) {
+ StackTrace st1 = GenStackTrace(8, 0);
+ heap_profiler_alloc((void*)0, 32, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0, 32, st1.frames, st1.depth, 0);
+}
+
+TEST_F(HeapProfilerTest, AllocAndFreeMultipleStacks) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 2048, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x3000, 32, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x4000, 64, st2.frames, st2.depth, 0);
+
+ heap_profiler_free((void*)0x1000, 1024, NULL);
+ heap_profiler_free((void*)0x3000, 32, NULL);
+
+ EXPECT_EQ(2, stats_.num_allocs);
+ EXPECT_EQ(2, stats_.num_stack_traces);
+ EXPECT_EQ(2048 + 64, stats_.total_alloc_bytes);
+ ExpectAlloc(0x2000, 0x27ff, st1, 0);
+ ExpectAlloc(0x4000, 0x403f, st2, 0);
+}
+
+TEST_F(HeapProfilerTest, AllocAndFreeAll) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 2048, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x3000, 32, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x4000, 64, st2.frames, st2.depth, 0);
+
+ heap_profiler_free((void*)0x1000, 1024, NULL);
+ heap_profiler_free((void*)0x2000, 2048, NULL);
+ heap_profiler_free((void*)0x3000, 32, NULL);
+ heap_profiler_free((void*)0x4000, 64, NULL);
+
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, AllocAndFreeWithZeroSize) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 2048, st2.frames, st2.depth, 0);
+
+ heap_profiler_free((void*)0x1000, 0, NULL);
+
+ EXPECT_EQ(1, stats_.num_allocs);
+ EXPECT_EQ(1, stats_.num_stack_traces);
+ EXPECT_EQ(2048, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, AllocAndFreeContiguous) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 4096, st2.frames, st2.depth, 0);
+
+ heap_profiler_free((void*)0x1000, 8192, NULL);
+
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, SparseAllocsOneLargeOuterFree) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+
+ heap_profiler_alloc((void*)0x1010, 1, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x1400, 2, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x1600, 5, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x9000, 4096, st2.frames, st2.depth, 0);
+
+ heap_profiler_free((void*)0x0800, 8192, NULL);
+ EXPECT_EQ(1, stats_.num_allocs);
+ EXPECT_EQ(1, stats_.num_stack_traces);
+ EXPECT_EQ(4096, stats_.total_alloc_bytes);
+ ExpectAlloc(0x9000, 0x9fff, st2, 0);
+}
+
+TEST_F(HeapProfilerTest, SparseAllocsOneLargePartiallyOverlappingFree) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ StackTrace st3 = GenStackTrace(4, 0x2000);
+
+ // This will be untouched.
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+
+ // These will be partially freed in one shot (% 64 a bytes "margin").
+ heap_profiler_alloc((void*)0x2000, 128, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x2400, 128, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x2f80, 128, st2.frames, st2.depth, 0);
+
+ // This will be untouched.
+ heap_profiler_alloc((void*)0x3000, 1024, st3.frames, st3.depth, 0);
+
+ heap_profiler_free((void*)0x2040, 4096 - 64 - 64, NULL);
+ EXPECT_EQ(4, stats_.num_allocs);
+ EXPECT_EQ(3, stats_.num_stack_traces);
+ EXPECT_EQ(1024 + 64 + 64 + 1024, stats_.total_alloc_bytes);
+
+ ExpectAlloc(0x1000, 0x13ff, st1, 0);
+ ExpectAlloc(0x2000, 0x203f, st2, 0);
+ ExpectAlloc(0x2fc0, 0x2fff, st2, 0);
+ ExpectAlloc(0x3000, 0x33ff, st3, 0);
+}
+
+TEST_F(HeapProfilerTest, AllocAndFreeScattered) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ heap_profiler_alloc((void*)0x1000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x3000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x4000, 4096, st1.frames, st1.depth, 0);
+
+ heap_profiler_free((void*)0x800, 4096, NULL);
+ EXPECT_EQ(4, stats_.num_allocs);
+ EXPECT_EQ(2048 + 4096 + 4096 + 4096, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0x1800, 4096, NULL);
+ EXPECT_EQ(3, stats_.num_allocs);
+ EXPECT_EQ(2048 + 4096 + 4096, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0x2800, 4096, NULL);
+ EXPECT_EQ(2, stats_.num_allocs);
+ EXPECT_EQ(2048 + 4096, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0x3800, 4096, NULL);
+ EXPECT_EQ(1, stats_.num_allocs);
+ EXPECT_EQ(2048, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0x4800, 4096, NULL);
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, AllocAndOverFreeContiguous) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 4096, st2.frames, st2.depth, 0);
+
+ heap_profiler_free((void*)0, 16834, NULL);
+
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, AllocContiguousAndPunchHole) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ heap_profiler_alloc((void*)0x1000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 4096, st2.frames, st2.depth, 0);
+
+ // Punch a 4k hole in the middle of the two contiguous 4k allocs.
+ heap_profiler_free((void*)0x1800, 4096, NULL);
+
+ EXPECT_EQ(2, stats_.num_allocs);
+ EXPECT_EQ(2, stats_.num_stack_traces);
+ EXPECT_EQ(4096, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, AllocAndPartialFree) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(6, 0x1000);
+ StackTrace st3 = GenStackTrace(7, 0x2000);
+ StackTrace st4 = GenStackTrace(9, 0x3000);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ heap_profiler_alloc((void*)0x2000, 1024, st2.frames, st2.depth, 0);
+ heap_profiler_alloc((void*)0x3000, 1024, st3.frames, st3.depth, 0);
+ heap_profiler_alloc((void*)0x4000, 1024, st4.frames, st4.depth, 0);
+
+ heap_profiler_free((void*)0x1000, 128, NULL); // Shrink left by 128B.
+ heap_profiler_free((void*)0x2380, 128, NULL); // Shrink right by 128B.
+ heap_profiler_free((void*)0x3100, 512, NULL); // 512B hole in the middle.
+ heap_profiler_free((void*)0x4000, 512, NULL); // Free up the 4th alloc...
+ heap_profiler_free((void*)0x4200, 512, NULL); // ...but do it in two halves.
+
+ EXPECT_EQ(4, stats_.num_allocs); // 1 + 2 + two sides around the hole 3.
+ EXPECT_EQ(3, stats_.num_stack_traces); // st4 should be gone.
+ EXPECT_EQ(896 + 896 + 512, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, RandomIndividualAllocAndFrees) {
+ static const size_t NUM_ST = 128;
+ static const size_t NUM_OPS = 1000;
+
+ StackTrace st[NUM_ST];
+ for (uint32_t i = 0; i < NUM_ST; ++i)
+ st[i] = GenStackTrace((i % 10) + 2, i * 128);
+
+ for (size_t i = 0; i < NUM_OPS; ++i) {
+ uintptr_t start = ((i + 7) << 8) & (0xffffff);
+ size_t size = (start >> 16) & 0x0fff;
+ if (i & 1) {
+ StackTrace* s = &st[start % NUM_ST];
+ heap_profiler_alloc((void*)start, size, s->frames, s->depth, 0);
+ } else {
+ heap_profiler_free((void*)start, size, NULL);
+ }
+ CheckAllocVsStacktaceConsistency();
+ }
+}
+
+TEST_F(HeapProfilerTest, RandomAllocAndFreesBatches) {
+ static const size_t NUM_ST = 128;
+ static const size_t NUM_ALLOCS = 100;
+
+ StackTrace st[NUM_ST];
+ for (size_t i = 0; i < NUM_ST; ++i)
+ st[i] = GenStackTrace((i % 10) + 2, i * NUM_ST);
+
+ for (int repeat = 0; repeat < 5; ++repeat) {
+ for (size_t i = 0; i < NUM_ALLOCS; ++i) {
+ StackTrace* s = &st[i % NUM_ST];
+ heap_profiler_alloc(
+ (void*)(i * 4096), ((i + 1) * 32) % 4097, s->frames, s->depth, 0);
+ CheckAllocVsStacktaceConsistency();
+ }
+
+ for (size_t i = 0; i < NUM_ALLOCS; ++i) {
+ heap_profiler_free((void*)(i * 1024), ((i + 1) * 64) % 16000, NULL);
+ CheckAllocVsStacktaceConsistency();
+ }
+ }
+}
+
+TEST_F(HeapProfilerTest, UnwindStackTooLargeShouldSaturate) {
+ StackTrace st1 = GenStackTrace(HEAP_PROFILER_MAX_DEPTH, 0x0);
+ uintptr_t many_frames[100] = {};
+ memcpy(many_frames, st1.frames, sizeof(uintptr_t) * st1.depth);
+ heap_profiler_alloc((void*)0x1000, 1024, many_frames, 100, 0);
+ ExpectAlloc(0x1000, 0x13ff, st1, 0);
+}
+
+TEST_F(HeapProfilerTest, NoUnwindShouldNotCrashButNoop) {
+ heap_profiler_alloc((void*)0x1000, 1024, NULL, 0, 0);
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, FreeNonExisting) {
+ StackTrace st1 = GenStackTrace(5, 0x0);
+ heap_profiler_free((void*)0x1000, 1024, NULL);
+ heap_profiler_free((void*)0x1400, 1024, NULL);
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+ heap_profiler_alloc((void*)0x1000, 1024, st1.frames, st1.depth, 0);
+ EXPECT_EQ(1, stats_.num_allocs);
+ EXPECT_EQ(1024, stats_.total_alloc_bytes);
+}
+
+TEST_F(HeapProfilerTest, FlagsConsistency) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ uint32_t flags = 0;
+ heap_profiler_alloc((void*)0x1000, 4096, st1.frames, st1.depth, 42);
+ heap_profiler_alloc((void*)0x2000, 4096, st1.frames, st1.depth, 142);
+
+ ExpectAlloc(0x1000, 0x1fff, st1, 42);
+ ExpectAlloc(0x2000, 0x2fff, st1, 142);
+
+ // Punch a 4k hole in the middle of the two contiguous 4k allocs.
+ heap_profiler_free((void*)0x1800, 4096, NULL);
+
+ ExpectAlloc(0x1000, 0x17ff, st1, 42);
+ heap_profiler_free((void*)0x1000, 2048, &flags);
+ EXPECT_EQ(42, flags);
+
+ ExpectAlloc(0x2800, 0x2fff, st1, 142);
+ heap_profiler_free((void*)0x2800, 2048, &flags);
+ EXPECT_EQ(142, flags);
+}
+
+TEST_F(HeapProfilerTest, BeConsistentOnOOM) {
+ static const size_t NUM_ALLOCS = 512 * 1024;
+ uintptr_t frames[1];
+
+ for (uintptr_t i = 0; i < NUM_ALLOCS; ++i) {
+ frames[0] = i;
+ heap_profiler_alloc((void*)(i * 32), 32, frames, 1, 0);
+ }
+
+ CheckAllocVsStacktaceConsistency();
+ // Check that we're saturating, otherwise this entire test is pointless.
+ EXPECT_LT(stats_.num_allocs, NUM_ALLOCS);
+ EXPECT_LT(stats_.num_stack_traces, NUM_ALLOCS);
+
+ for (uintptr_t i = 0; i < NUM_ALLOCS; ++i)
+ heap_profiler_free((void*)(i * 32), 32, NULL);
+
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+}
+
+#ifdef __LP64__
+TEST_F(HeapProfilerTest, Test64Bit) {
+ StackTrace st1 = GenStackTrace(8, 0x0);
+ StackTrace st2 = GenStackTrace(10, 0x7fffffff70000000L);
+ StackTrace st3 = GenStackTrace(10, 0xffffffff70000000L);
+ heap_profiler_alloc((void*)0x1000, 4096, st1.frames, st1.depth, 0);
+ heap_profiler_alloc(
+ (void*)0x7ffffffffffff000L, 4096, st2.frames, st2.depth, 0);
+ heap_profiler_alloc(
+ (void*)0xfffffffffffff000L, 4096, st3.frames, st3.depth, 0);
+ EXPECT_EQ(3, stats_.num_allocs);
+ EXPECT_EQ(3, stats_.num_stack_traces);
+ EXPECT_EQ(4096 + 4096 + 4096, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0x1000, 4096, NULL);
+ EXPECT_EQ(2, stats_.num_allocs);
+ EXPECT_EQ(2, stats_.num_stack_traces);
+ EXPECT_EQ(4096 + 4096, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0x7ffffffffffff000L, 4096, NULL);
+ EXPECT_EQ(1, stats_.num_allocs);
+ EXPECT_EQ(1, stats_.num_stack_traces);
+ EXPECT_EQ(4096, stats_.total_alloc_bytes);
+
+ heap_profiler_free((void*)0xfffffffffffff000L, 4096, NULL);
+ EXPECT_EQ(0, stats_.num_allocs);
+ EXPECT_EQ(0, stats_.num_stack_traces);
+ EXPECT_EQ(0, stats_.total_alloc_bytes);
+}
+#endif
+
+} // namespace
diff --git a/tools/android/md5sum/BUILD.gn b/tools/android/md5sum/BUILD.gn
new file mode 100644
index 0000000..5f91a88
--- /dev/null
+++ b/tools/android/md5sum/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# GYP: //tools/android/md5sum/md5sum.gyp:md5sum
+group("md5sum") {
+ datadeps = [
+ ":md5sum_bin($host_toolchain)",
+ ":md5sum_bin($default_toolchain)",
+ ":md5sum_prepare_dist($default_toolchain)",
+ ":md5sum_copy_host($host_toolchain)",
+ ]
+ # TODO(cjhopman): Remove once group datadeps are fixed.
+ deps = datadeps
+}
+
+# GYP: //tools/android/md5sum/md5sum.gyp:md5sum_bin_device (and md5sum_bin_host)
+executable("md5sum_bin") {
+ sources = [
+ "md5sum.cc"
+ ]
+ deps = [
+ "//base"
+ ]
+
+ # TODO(GYP)
+ #'conditions': [
+ #[ 'order_profiling!=0 and OS=="android"', {
+ #'dependencies': [ '../../../tools/cygprofile/cygprofile.gyp:cygprofile', ],
+ #}],
+ #],
+}
+
+if (current_toolchain == default_toolchain) {
+ import("//build/config/android/rules.gni")
+
+ # GYP: //tools/android/md5sum/md5sum.gyp:md5sum_stripped_device_bin
+ create_native_executable_dist("md5sum_prepare_dist") {
+ dist_dir = "$root_build_dir/md5sum_dist"
+ binary = "$root_build_dir/exe.stripped/md5sum_bin"
+ }
+} else {
+ # GYP: //tools/android/md5sum/md5sum.gyp:md5sum_bin_host
+ copy("md5sum_copy_host") {
+ sources = [
+ "$root_out_dir/md5sum_bin"
+ ]
+ outputs = [
+ "$root_build_dir/md5sum_bin_host"
+ ]
+ }
+}
+
diff --git a/tools/android/md5sum/md5sum.cc b/tools/android/md5sum/md5sum.cc
new file mode 100644
index 0000000..07ce2c2
--- /dev/null
+++ b/tools/android/md5sum/md5sum.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Md5sum implementation for Android. This version handles files as well as
+// directories. Its output is sorted by file path.
+
+#include <fstream>
+#include <iostream>
+#include <set>
+#include <string>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/md5.h"
+
+namespace {
+
+const int kBufferSize = 1024;
+
+// Returns whether |path|'s MD5 was successfully written to |digest_string|.
+bool MD5Sum(const char* path, std::string* digest_string) {
+ std::ifstream stream(path);
+ if (!stream.good()) {
+ LOG(ERROR) << "Could not open file " << path;
+ return false;
+ }
+ base::MD5Context ctx;
+ base::MD5Init(&ctx);
+ char buf[kBufferSize];
+ while (stream.good()) {
+ std::streamsize bytes_read = stream.readsome(buf, sizeof(buf));
+ if (bytes_read == 0)
+ break;
+ base::MD5Update(&ctx, base::StringPiece(buf, bytes_read));
+ }
+ if (stream.fail()) {
+ LOG(ERROR) << "Error reading file " << path;
+ return false;
+ }
+ base::MD5Digest digest;
+ base::MD5Final(&digest, &ctx);
+ *digest_string = base::MD5DigestToBase16(digest);
+ return true;
+}
+
+// Returns the set of all files contained in |files|. This handles directories
+// by walking them recursively. Excludes, .svn directories and file under them.
+std::set<std::string> MakeFileSet(const char** files) {
+ const std::string svn_dir_component = FILE_PATH_LITERAL("/.svn/");
+ std::set<std::string> file_set;
+ for (const char** file = files; *file; ++file) {
+ base::FilePath file_path(*file);
+ if (base::DirectoryExists(file_path)) {
+ base::FileEnumerator file_enumerator(
+ file_path, true /* recurse */, base::FileEnumerator::FILES);
+ for (base::FilePath child, empty;
+ (child = file_enumerator.Next()) != empty; ) {
+ // If the path contains /.svn/, ignore it.
+ if (child.value().find(svn_dir_component) == std::string::npos) {
+ child = base::MakeAbsoluteFilePath(child);
+ file_set.insert(child.value());
+ }
+ }
+ } else {
+ file_set.insert(*file);
+ }
+ }
+ return file_set;
+}
+
+} // namespace
+
+int main(int argc, const char* argv[]) {
+ if (argc < 2) {
+ LOG(ERROR) << "Usage: md5sum <path/to/file_or_dir>...";
+ return 1;
+ }
+ const std::set<std::string> files = MakeFileSet(argv + 1);
+ bool failed = false;
+ std::string digest;
+ for (std::set<std::string>::const_iterator it = files.begin();
+ it != files.end(); ++it) {
+ if (!MD5Sum(it->c_str(), &digest))
+ failed = true;
+ base::FilePath file_path(*it);
+ std::cout << digest << " "
+ << base::MakeAbsoluteFilePath(file_path).value() << std::endl;
+ }
+ return failed;
+}
diff --git a/tools/android/md5sum/md5sum.gyp b/tools/android/md5sum/md5sum.gyp
new file mode 100644
index 0000000..75d664e
--- /dev/null
+++ b/tools/android/md5sum/md5sum.gyp
@@ -0,0 +1,81 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ # GN: //tools/android/md5sum:md5sum
+ 'target_name': 'md5sum',
+ 'type': 'none',
+ 'dependencies': [
+ 'md5sum_stripped_device_bin',
+ 'md5sum_bin_host#host',
+ ],
+ # For the component build, ensure dependent shared libraries are stripped
+ # and put alongside md5sum to simplify pushing to the device.
+ 'variables': {
+ 'output_dir': '<(PRODUCT_DIR)/md5sum_dist/',
+ 'native_binary': '<(PRODUCT_DIR)/md5sum_bin',
+ },
+ 'includes': ['../../../build/android/native_app_dependencies.gypi'],
+ },
+ {
+ # GN: //tools/android/md5sum:md5sum_bin($default_toolchain)
+ 'target_name': 'md5sum_device_bin',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ 'sources': [
+ 'md5sum.cc',
+ ],
+ 'conditions': [
+ [ 'order_profiling!=0 and OS=="android"', {
+ 'dependencies': [ '../../../tools/cygprofile/cygprofile.gyp:cygprofile', ],
+ }],
+ ],
+ },
+ {
+ # GN: //tools/android/md5sum:md5sum_prepare_dist
+ 'target_name': 'md5sum_stripped_device_bin',
+ 'type': 'none',
+ 'dependencies': [
+ 'md5sum_device_bin',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'strip_md5sum_device_bin',
+ 'inputs': ['<(PRODUCT_DIR)/md5sum_device_bin'],
+ 'outputs': ['<(PRODUCT_DIR)/md5sum_bin'],
+ 'action': [
+ '<(android_strip)',
+ '--strip-unneeded',
+ '<@(_inputs)',
+ '-o',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ },
+ # Same binary but for the host rather than the device.
+ {
+ # GN: //tools/android/md5sum:md5sum_copy_host($default_toolchain)
+ 'target_name': 'md5sum_bin_host',
+ 'toolsets': ['host'],
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ 'sources': [
+ 'md5sum.cc',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/memconsumer/java/AndroidManifest.xml b/tools/android/memconsumer/java/AndroidManifest.xml
new file mode 100644
index 0000000..c7f12e4
--- /dev/null
+++ b/tools/android/memconsumer/java/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2013 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.memconsumer" android:versionCode="1"
+ android:versionName="1.0">
+
+ <application
+ android:label="MemConsumer">
+ <activity android:name=".MemConsumer" android:icon="@drawable/icon" android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <service android:name="ResidentService" android:enabled="true" />
+ </application>
+
+</manifest>
diff --git a/tools/android/memconsumer/java/res/drawable/icon.png b/tools/android/memconsumer/java/res/drawable/icon.png
new file mode 100644
index 0000000..cb10c9b
--- /dev/null
+++ b/tools/android/memconsumer/java/res/drawable/icon.png
Binary files differ
diff --git a/tools/android/memconsumer/java/res/drawable/notification_icon.png b/tools/android/memconsumer/java/res/drawable/notification_icon.png
new file mode 100644
index 0000000..7fc92c3
--- /dev/null
+++ b/tools/android/memconsumer/java/res/drawable/notification_icon.png
Binary files differ
diff --git a/tools/android/memconsumer/java/src/org/chromium/memconsumer/MemConsumer.java b/tools/android/memconsumer/java/src/org/chromium/memconsumer/MemConsumer.java
new file mode 100644
index 0000000..17566f8
--- /dev/null
+++ b/tools/android/memconsumer/java/src/org/chromium/memconsumer/MemConsumer.java
@@ -0,0 +1,107 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.memconsumer;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.NumberPicker;
+import android.widget.TextView;
+
+public class MemConsumer extends Activity {
+ public static final String NOTIFICATION_ACTION =
+ MemConsumer.class.toString() + ".NOTIFICATION";
+
+ private ResidentService mResidentService;
+ private int mMemory = 0;
+ private NumberPicker mMemoryPicker;
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ mResidentService = ((ResidentService.ServiceBinder) binder).getService();
+ mResidentService.useMemory(mMemory);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mResidentService = null;
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mMemoryPicker = new NumberPicker(this);
+ mMemoryPicker.setGravity(Gravity.CENTER);
+ mMemoryPicker.setMaxValue(Integer.MAX_VALUE);
+ mMemoryPicker.setMinValue(0);
+ mMemoryPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ @Override
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ updateMemoryConsumption(picker.getValue());
+ }
+ });
+ for (int i = 0; i < mMemoryPicker.getChildCount(); i++) {
+ View child = mMemoryPicker.getChildAt(i);
+ if (child instanceof EditText) {
+ EditText editText = (EditText) child;
+ editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction (TextView v, int actionId, KeyEvent event) {
+ if (v.getText().length() > 0) {
+ updateMemoryConsumption(Integer.parseInt(v.getText().toString()));
+ }
+ return false;
+ }
+ });
+ }
+ }
+ setContentView(mMemoryPicker);
+ onNewIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (intent.getAction() == NOTIFICATION_ACTION) {
+ updateMemoryConsumption(0);
+ return;
+ }
+ if (!intent.hasExtra("memory")) return;
+ updateMemoryConsumption(intent.getIntExtra("memory", 0));
+ }
+
+ void updateMemoryConsumption(int memory) {
+ if (memory == mMemory || memory < 0) return;
+ mMemory = memory;
+ mMemoryPicker.setValue(mMemory);
+ if (mResidentService == null) {
+ if (mMemory > 0) {
+ Intent resident = new Intent();
+ resident.setClass(this, ResidentService.class);
+ startService(resident);
+ bindService(new Intent(this, ResidentService.class),
+ mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+ } else {
+ mResidentService.useMemory(mMemory);
+ if (mMemory == 0) {
+ unbindService(mServiceConnection);
+ stopService(new Intent(this, ResidentService.class));
+ mResidentService = null;
+ }
+ }
+ }
+}
diff --git a/tools/android/memconsumer/java/src/org/chromium/memconsumer/ResidentService.java b/tools/android/memconsumer/java/src/org/chromium/memconsumer/ResidentService.java
new file mode 100644
index 0000000..e40fbfe
--- /dev/null
+++ b/tools/android/memconsumer/java/src/org/chromium/memconsumer/ResidentService.java
@@ -0,0 +1,62 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.memconsumer;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class ResidentService extends Service {
+ static {
+ // Loading the native library.
+ System.loadLibrary("memconsumer");
+ }
+
+ public class ServiceBinder extends Binder {
+ ResidentService getService() {
+ return ResidentService.this;
+ }
+ }
+
+ private static final int RESIDENT_NOTIFICATION_ID = 1;
+
+ private final IBinder mBinder = new ServiceBinder();
+ private boolean mIsInForeground = false;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ public void useMemory(long memory) {
+ if (memory > 0) {
+ Intent notificationIntent = new Intent(this, MemConsumer.class);
+ notificationIntent.setAction(MemConsumer.NOTIFICATION_ACTION);
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(this, 0, notificationIntent, 0);
+ Notification notification =
+ new Notification.Builder(getApplicationContext()).
+ setContentTitle("MC running (" + memory + "Mb)").
+ setSmallIcon(R.drawable.notification_icon).
+ setDeleteIntent(pendingIntent).
+ setContentIntent(pendingIntent).
+ build();
+ startForeground(RESIDENT_NOTIFICATION_ID, notification);
+ mIsInForeground = true;
+ }
+ if (mIsInForeground && memory == 0) {
+ stopForeground(true);
+ mIsInForeground = false;
+ }
+ nativeUseMemory(memory * 1024 * 1024);
+ }
+
+ // Allocate the amount of memory in native code. Otherwise the memory
+ // allocation is limited by the framework.
+ private native void nativeUseMemory(long memory);
+}
diff --git a/tools/android/memconsumer/memconsumer.gyp b/tools/android/memconsumer/memconsumer.gyp
new file mode 100644
index 0000000..f721fc1
--- /dev/null
+++ b/tools/android/memconsumer/memconsumer.gyp
@@ -0,0 +1,39 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'memconsumer',
+ 'type': 'none',
+ 'dependencies': [
+ 'memconsumer_apk',
+ ],
+ },
+ {
+ 'target_name': 'memconsumer_apk',
+ 'type': 'none',
+ 'variables': {
+ 'apk_name': 'MemConsumer',
+ 'java_in_dir': 'java',
+ 'resource_dir': 'java/res',
+ 'native_lib_target': 'libmemconsumer',
+ },
+ 'dependencies': [
+ 'libmemconsumer',
+ ],
+ 'includes': [ '../../../build/java_apk.gypi' ],
+ },
+ {
+ 'target_name': 'libmemconsumer',
+ 'type': 'shared_library',
+ 'sources': [
+ 'memconsumer_hook.cc',
+ ],
+ 'libraries': [
+ '-llog',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/memconsumer/memconsumer_hook.cc b/tools/android/memconsumer/memconsumer_hook.cc
new file mode 100644
index 0000000..9ae0bc1
--- /dev/null
+++ b/tools/android/memconsumer/memconsumer_hook.cc
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <android/log.h>
+#include <jni.h>
+#include <stdio.h>
+#include <string.h>
+
+extern "C" {
+JNIEXPORT void JNICALL
+ Java_org_chromium_memconsumer_ResidentService_nativeUseMemory(JNIEnv* env,
+ jobject clazz,
+ jlong memory);
+}
+
+namespace {
+
+uint32_t get_random() {
+ static uint32_t m_w = 1;
+ static uint32_t m_z = 1;
+ m_z = 36969 * (m_z & 65535) + (m_z >> 16);
+ m_w = 18000 * (m_w & 65535) + (m_w >> 16);
+ return (m_z << 16) + m_w;
+}
+
+} // namespace
+
+JNIEXPORT void JNICALL
+ Java_org_chromium_memconsumer_ResidentService_nativeUseMemory(
+ JNIEnv* env,
+ jobject clazz,
+ jlong memory) {
+ static uint32_t* g_memory = NULL;
+ if (g_memory)
+ free(g_memory);
+ if (memory == 0) {
+ g_memory = NULL;
+ return;
+ }
+ g_memory = static_cast<uint32_t*>(malloc(memory));
+ if (!g_memory) {
+ __android_log_print(ANDROID_LOG_WARN,
+ "MemConsumer",
+ "Unable to allocate %ld bytes",
+ memory);
+ }
+ // If memory allocation failed, try to allocate as much as possible.
+ while (!g_memory) {
+ memory /= 2;
+ g_memory = static_cast<uint32_t*>(malloc(memory));
+ }
+ for (int i = 0; i < memory / sizeof(uint32_t); ++i)
+ *(g_memory + i) = get_random();
+}
diff --git a/tools/android/memdump/memdump.cc b/tools/android/memdump/memdump.cc
new file mode 100644
index 0000000..cb4b05a
--- /dev/null
+++ b/tools/android/memdump/memdump.cc
@@ -0,0 +1,534 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+const unsigned int kPageSize = getpagesize();
+
+namespace {
+
+class BitSet {
+ public:
+ void resize(size_t nbits) {
+ data_.resize((nbits + 7) / 8);
+ }
+
+ void set(uint32 bit) {
+ const uint32 byte_idx = bit / 8;
+ CHECK(byte_idx < data_.size());
+ data_[byte_idx] |= (1 << (bit & 7));
+ }
+
+ std::string AsB64String() const {
+ // Simple optimization: strip trailing zero bytes from the bitmap.
+ // For instance, if a region has 32 pages but only the first 9 are resident,
+ // The full bitmap would be 0xff 0x01 0x00 0x00, the stripped one 0xff 0x01.
+ // It can save up to some seconds when printing large mmaps, in particular
+ // in presence of large virtual address space reservations (where none of
+ // the pages are resident).
+ size_t end = data_.size();
+ while (end > 0 && data_[end - 1] == '\0')
+ --end;
+ std::string bits(&data_[0], end);
+ std::string b64_string;
+ base::Base64Encode(bits, &b64_string);
+ return b64_string;
+ }
+
+ private:
+ std::vector<char> data_;
+};
+
+// An entry in /proc/<pid>/pagemap.
+struct PageMapEntry {
+ uint64 page_frame_number : 55;
+ uint unused : 8;
+ uint present : 1;
+};
+
+// Describes a memory page.
+struct PageInfo {
+ int64 page_frame_number; // Physical page id, also known as PFN.
+ int64 flags;
+ int32 times_mapped;
+};
+
+struct PageCount {
+ PageCount() : total_count(0), unevictable_count(0) {}
+
+ int total_count;
+ int unevictable_count;
+};
+
+struct MemoryMap {
+ std::string name;
+ std::string flags;
+ uint64 start_address;
+ uint64 end_address;
+ uint64 offset;
+ PageCount private_pages;
+ // app_shared_pages[i] contains the number of pages mapped in i+2 processes
+ // (only among the processes that are being analyzed).
+ std::vector<PageCount> app_shared_pages;
+ PageCount other_shared_pages;
+ std::vector<PageInfo> committed_pages;
+ // committed_pages_bits is a bitset reflecting the present bit for all the
+ // virtual pages of the mapping.
+ BitSet committed_pages_bits;
+};
+
+struct ProcessMemory {
+ pid_t pid;
+ std::vector<MemoryMap> memory_maps;
+};
+
+bool PageIsUnevictable(const PageInfo& page_info) {
+ // These constants are taken from kernel-page-flags.h.
+ const int KPF_DIRTY = 4; // Note that only file-mapped pages can be DIRTY.
+ const int KPF_ANON = 12; // Anonymous pages are dirty per definition.
+ const int KPF_UNEVICTABLE = 18;
+ const int KPF_MLOCKED = 33;
+
+ return (page_info.flags & ((1ll << KPF_DIRTY) |
+ (1ll << KPF_ANON) |
+ (1ll << KPF_UNEVICTABLE) |
+ (1ll << KPF_MLOCKED))) ?
+ true : false;
+}
+
+// Number of times a physical page is mapped in a process.
+typedef base::hash_map<uint64, int> PFNMap;
+
+// Parses lines from /proc/<PID>/maps, e.g.:
+// 401e7000-401f5000 r-xp 00000000 103:02 158 /system/bin/linker
+bool ParseMemoryMapLine(const std::string& line,
+ std::vector<std::string>* tokens,
+ MemoryMap* memory_map) {
+ tokens->clear();
+ base::SplitString(line, ' ', tokens);
+ if (tokens->size() < 2)
+ return false;
+ const std::string& addr_range = tokens->at(0);
+ std::vector<std::string> range_tokens;
+ base::SplitString(addr_range, '-', &range_tokens);
+ const std::string& start_address_token = range_tokens.at(0);
+ if (!base::HexStringToUInt64(start_address_token,
+ &memory_map->start_address)) {
+ return false;
+ }
+ const std::string& end_address_token = range_tokens.at(1);
+ if (!base::HexStringToUInt64(end_address_token, &memory_map->end_address)) {
+ return false;
+ }
+ if (tokens->at(1).size() != strlen("rwxp"))
+ return false;
+ memory_map->flags.swap(tokens->at(1));
+ if (!base::HexStringToUInt64(tokens->at(2), &memory_map->offset))
+ return false;
+ memory_map->committed_pages_bits.resize(
+ (memory_map->end_address - memory_map->start_address) / kPageSize);
+ const int map_name_index = 5;
+ if (tokens->size() >= map_name_index + 1) {
+ for (std::vector<std::string>::const_iterator it =
+ tokens->begin() + map_name_index; it != tokens->end(); ++it) {
+ if (!it->empty()) {
+ if (!memory_map->name.empty())
+ memory_map->name.append(" ");
+ memory_map->name.append(*it);
+ }
+ }
+ }
+ return true;
+}
+
+// Reads sizeof(T) bytes from file |fd| at |offset|.
+template <typename T>
+bool ReadFromFileAtOffset(int fd, off_t offset, T* value) {
+ if (lseek64(fd, offset * sizeof(*value), SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek";
+ return false;
+ }
+ ssize_t bytes = read(fd, value, sizeof(*value));
+ if (bytes != sizeof(*value) && bytes != 0) {
+ PLOG(ERROR) << "read";
+ return false;
+ }
+ return true;
+}
+
+// Fills |process_maps| in with the process memory maps identified by |pid|.
+bool GetProcessMaps(pid_t pid, std::vector<MemoryMap>* process_maps) {
+ std::ifstream maps_file(base::StringPrintf("/proc/%d/maps", pid).c_str());
+ if (!maps_file.good()) {
+ PLOG(ERROR) << "open";
+ return false;
+ }
+ std::string line;
+ std::vector<std::string> tokens;
+ while (std::getline(maps_file, line) && !line.empty()) {
+ MemoryMap memory_map = {};
+ if (!ParseMemoryMapLine(line, &tokens, &memory_map)) {
+ LOG(ERROR) << "Could not parse line: " << line;
+ return false;
+ }
+ process_maps->push_back(memory_map);
+ }
+ return true;
+}
+
+// Fills |committed_pages| in with the set of committed pages contained in the
+// provided memory map.
+bool GetPagesForMemoryMap(int pagemap_fd,
+ const MemoryMap& memory_map,
+ std::vector<PageInfo>* committed_pages,
+ BitSet* committed_pages_bits) {
+ const off64_t offset = memory_map.start_address / kPageSize;
+ if (lseek64(pagemap_fd, offset * sizeof(PageMapEntry), SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek";
+ return false;
+ }
+ for (uint64 addr = memory_map.start_address, page_index = 0;
+ addr < memory_map.end_address;
+ addr += kPageSize, ++page_index) {
+ DCHECK_EQ(0, addr % kPageSize);
+ PageMapEntry page_map_entry = {};
+ COMPILE_ASSERT(sizeof(PageMapEntry) == sizeof(uint64), unexpected_size);
+ ssize_t bytes = read(pagemap_fd, &page_map_entry, sizeof(page_map_entry));
+ if (bytes != sizeof(PageMapEntry) && bytes != 0) {
+ PLOG(ERROR) << "read";
+ return false;
+ }
+ if (page_map_entry.present) { // Ignore non-committed pages.
+ if (page_map_entry.page_frame_number == 0)
+ continue;
+ PageInfo page_info = {};
+ page_info.page_frame_number = page_map_entry.page_frame_number;
+ committed_pages->push_back(page_info);
+ committed_pages_bits->set(page_index);
+ }
+ }
+ return true;
+}
+
+// Fills |committed_pages| with mapping count and flags information gathered
+// looking-up /proc/kpagecount and /proc/kpageflags.
+bool SetPagesInfo(int pagecount_fd,
+ int pageflags_fd,
+ std::vector<PageInfo>* pages) {
+ for (std::vector<PageInfo>::iterator it = pages->begin();
+ it != pages->end(); ++it) {
+ PageInfo* const page_info = &*it;
+ int64 times_mapped;
+ if (!ReadFromFileAtOffset(
+ pagecount_fd, page_info->page_frame_number, ×_mapped)) {
+ return false;
+ }
+ DCHECK(times_mapped <= std::numeric_limits<int32_t>::max());
+ page_info->times_mapped = static_cast<int32>(times_mapped);
+
+ int64 page_flags;
+ if (!ReadFromFileAtOffset(
+ pageflags_fd, page_info->page_frame_number, &page_flags)) {
+ return false;
+ }
+ page_info->flags = page_flags;
+ }
+ return true;
+}
+
+// Fills in the provided vector of Page Frame Number maps. This lets
+// ClassifyPages() know how many times each page is mapped in the processes.
+void FillPFNMaps(const std::vector<ProcessMemory>& processes_memory,
+ std::vector<PFNMap>* pfn_maps) {
+ int current_process_index = 0;
+ for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
+ it != processes_memory.end(); ++it, ++current_process_index) {
+ const std::vector<MemoryMap>& memory_maps = it->memory_maps;
+ for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
+ it != memory_maps.end(); ++it) {
+ const std::vector<PageInfo>& pages = it->committed_pages;
+ for (std::vector<PageInfo>::const_iterator it = pages.begin();
+ it != pages.end(); ++it) {
+ const PageInfo& page_info = *it;
+ PFNMap* const pfn_map = &(*pfn_maps)[current_process_index];
+ const std::pair<PFNMap::iterator, bool> result = pfn_map->insert(
+ std::make_pair(page_info.page_frame_number, 0));
+ ++result.first->second;
+ }
+ }
+ }
+}
+
+// Sets the private_count/app_shared_pages/other_shared_count fields of the
+// provided memory maps for each process.
+void ClassifyPages(std::vector<ProcessMemory>* processes_memory) {
+ std::vector<PFNMap> pfn_maps(processes_memory->size());
+ FillPFNMaps(*processes_memory, &pfn_maps);
+ // Hash set keeping track of the physical pages mapped in a single process so
+ // that they can be counted only once.
+ base::hash_set<uint64> physical_pages_mapped_in_process;
+
+ for (std::vector<ProcessMemory>::iterator it = processes_memory->begin();
+ it != processes_memory->end(); ++it) {
+ std::vector<MemoryMap>* const memory_maps = &it->memory_maps;
+ physical_pages_mapped_in_process.clear();
+ for (std::vector<MemoryMap>::iterator it = memory_maps->begin();
+ it != memory_maps->end(); ++it) {
+ MemoryMap* const memory_map = &*it;
+ const size_t processes_count = processes_memory->size();
+ memory_map->app_shared_pages.resize(processes_count - 1);
+ const std::vector<PageInfo>& pages = memory_map->committed_pages;
+ for (std::vector<PageInfo>::const_iterator it = pages.begin();
+ it != pages.end(); ++it) {
+ const PageInfo& page_info = *it;
+ if (page_info.times_mapped == 1) {
+ ++memory_map->private_pages.total_count;
+ if (PageIsUnevictable(page_info))
+ ++memory_map->private_pages.unevictable_count;
+ continue;
+ }
+ const uint64 page_frame_number = page_info.page_frame_number;
+ const std::pair<base::hash_set<uint64>::iterator, bool> result =
+ physical_pages_mapped_in_process.insert(page_frame_number);
+ const bool did_insert = result.second;
+ if (!did_insert) {
+ // This physical page (mapped multiple times in the same process) was
+ // already counted.
+ continue;
+ }
+ // See if the current physical page is also mapped in the other
+ // processes that are being analyzed.
+ int times_mapped = 0;
+ int mapped_in_processes_count = 0;
+ for (std::vector<PFNMap>::const_iterator it = pfn_maps.begin();
+ it != pfn_maps.end(); ++it) {
+ const PFNMap& pfn_map = *it;
+ const PFNMap::const_iterator found_it = pfn_map.find(
+ page_frame_number);
+ if (found_it == pfn_map.end())
+ continue;
+ ++mapped_in_processes_count;
+ times_mapped += found_it->second;
+ }
+ PageCount* page_count_to_update = NULL;
+ if (times_mapped == page_info.times_mapped) {
+ // The physical page is only mapped in the processes that are being
+ // analyzed.
+ if (mapped_in_processes_count > 1) {
+ // The physical page is mapped in multiple processes.
+ page_count_to_update =
+ &memory_map->app_shared_pages[mapped_in_processes_count - 2];
+ } else {
+ // The physical page is mapped multiple times in the same process.
+ page_count_to_update = &memory_map->private_pages;
+ }
+ } else {
+ page_count_to_update = &memory_map->other_shared_pages;
+ }
+ ++page_count_to_update->total_count;
+ if (PageIsUnevictable(page_info))
+ ++page_count_to_update->unevictable_count;
+ }
+ }
+ }
+}
+
+void AppendAppSharedField(const std::vector<PageCount>& app_shared_pages,
+ std::string* out) {
+ out->append("[");
+ for (std::vector<PageCount>::const_iterator it = app_shared_pages.begin();
+ it != app_shared_pages.end(); ++it) {
+ out->append(base::IntToString(it->total_count * kPageSize));
+ out->append(":");
+ out->append(base::IntToString(it->unevictable_count * kPageSize));
+ if (it + 1 != app_shared_pages.end())
+ out->append(",");
+ }
+ out->append("]");
+}
+
+void DumpProcessesMemoryMapsInShortFormat(
+ const std::vector<ProcessMemory>& processes_memory) {
+ const int KB_PER_PAGE = kPageSize >> 10;
+ std::vector<int> totals_app_shared(processes_memory.size());
+ std::string buf;
+ std::cout << "pid\tprivate\t\tshared_app\tshared_other (KB)\n";
+ for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
+ it != processes_memory.end(); ++it) {
+ const ProcessMemory& process_memory = *it;
+ std::fill(totals_app_shared.begin(), totals_app_shared.end(), 0);
+ int total_private = 0, total_other_shared = 0;
+ const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps;
+ for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
+ it != memory_maps.end(); ++it) {
+ const MemoryMap& memory_map = *it;
+ total_private += memory_map.private_pages.total_count;
+ for (size_t i = 0; i < memory_map.app_shared_pages.size(); ++i)
+ totals_app_shared[i] += memory_map.app_shared_pages[i].total_count;
+ total_other_shared += memory_map.other_shared_pages.total_count;
+ }
+ double total_app_shared = 0;
+ for (size_t i = 0; i < totals_app_shared.size(); ++i)
+ total_app_shared += static_cast<double>(totals_app_shared[i]) / (i + 2);
+ base::SStringPrintf(
+ &buf, "%d\t%d\t\t%d\t\t%d\n",
+ process_memory.pid,
+ total_private * KB_PER_PAGE,
+ static_cast<int>(total_app_shared) * KB_PER_PAGE,
+ total_other_shared * KB_PER_PAGE);
+ std::cout << buf;
+ }
+}
+
+void DumpProcessesMemoryMapsInExtendedFormat(
+ const std::vector<ProcessMemory>& processes_memory) {
+ std::string buf;
+ std::string app_shared_buf;
+ for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
+ it != processes_memory.end(); ++it) {
+ const ProcessMemory& process_memory = *it;
+ std::cout << "[ PID=" << process_memory.pid << "]" << '\n';
+ const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps;
+ for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
+ it != memory_maps.end(); ++it) {
+ const MemoryMap& memory_map = *it;
+ app_shared_buf.clear();
+ AppendAppSharedField(memory_map.app_shared_pages, &app_shared_buf);
+ base::SStringPrintf(
+ &buf,
+ "%"PRIx64"-%"PRIx64" %s %"PRIx64" private_unevictable=%d private=%d "
+ "shared_app=%s shared_other_unevictable=%d shared_other=%d "
+ "\"%s\" [%s]\n",
+ memory_map.start_address,
+ memory_map.end_address,
+ memory_map.flags.c_str(),
+ memory_map.offset,
+ memory_map.private_pages.unevictable_count * kPageSize,
+ memory_map.private_pages.total_count * kPageSize,
+ app_shared_buf.c_str(),
+ memory_map.other_shared_pages.unevictable_count * kPageSize,
+ memory_map.other_shared_pages.total_count * kPageSize,
+ memory_map.name.c_str(),
+ memory_map.committed_pages_bits.AsB64String().c_str());
+ std::cout << buf;
+ }
+ }
+}
+
+bool CollectProcessMemoryInformation(int page_count_fd,
+ int page_flags_fd,
+ ProcessMemory* process_memory) {
+ const pid_t pid = process_memory->pid;
+ base::ScopedFD pagemap_fd(HANDLE_EINTR(open(
+ base::StringPrintf("/proc/%d/pagemap", pid).c_str(), O_RDONLY)));
+ if (!pagemap_fd.is_valid()) {
+ PLOG(ERROR) << "open";
+ return false;
+ }
+ std::vector<MemoryMap>* const process_maps = &process_memory->memory_maps;
+ if (!GetProcessMaps(pid, process_maps))
+ return false;
+ for (std::vector<MemoryMap>::iterator it = process_maps->begin();
+ it != process_maps->end(); ++it) {
+ std::vector<PageInfo>* const committed_pages = &it->committed_pages;
+ BitSet* const pages_bits = &it->committed_pages_bits;
+ GetPagesForMemoryMap(pagemap_fd.get(), *it, committed_pages, pages_bits);
+ SetPagesInfo(page_count_fd, page_flags_fd, committed_pages);
+ }
+ return true;
+}
+
+void KillAll(const std::vector<pid_t>& pids, int signal_number) {
+ for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end();
+ ++it) {
+ kill(*it, signal_number);
+ }
+}
+
+void ExitWithUsage() {
+ LOG(ERROR) << "Usage: memdump [-a] <PID1>... <PIDN>";
+ exit(EXIT_FAILURE);
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ if (argc == 1)
+ ExitWithUsage();
+ const bool short_output = !strncmp(argv[1], "-a", 2);
+ if (short_output) {
+ if (argc == 2)
+ ExitWithUsage();
+ ++argv;
+ }
+ std::vector<pid_t> pids;
+ for (const char* const* ptr = argv + 1; *ptr; ++ptr) {
+ pid_t pid;
+ if (!base::StringToInt(*ptr, &pid))
+ return EXIT_FAILURE;
+ pids.push_back(pid);
+ }
+
+ std::vector<ProcessMemory> processes_memory(pids.size());
+ {
+ base::ScopedFD page_count_fd(
+ HANDLE_EINTR(open("/proc/kpagecount", O_RDONLY)));
+ if (!page_count_fd.is_valid()) {
+ PLOG(ERROR) << "open /proc/kpagecount";
+ return EXIT_FAILURE;
+ }
+
+ base::ScopedFD page_flags_fd(open("/proc/kpageflags", O_RDONLY));
+ if (!page_flags_fd.is_valid()) {
+ PLOG(ERROR) << "open /proc/kpageflags";
+ return EXIT_FAILURE;
+ }
+
+ base::ScopedClosureRunner auto_resume_processes(
+ base::Bind(&KillAll, pids, SIGCONT));
+ KillAll(pids, SIGSTOP);
+ for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end();
+ ++it) {
+ ProcessMemory* const process_memory =
+ &processes_memory[it - pids.begin()];
+ process_memory->pid = *it;
+ if (!CollectProcessMemoryInformation(
+ page_count_fd.get(), page_flags_fd.get(), process_memory)) {
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ ClassifyPages(&processes_memory);
+ if (short_output)
+ DumpProcessesMemoryMapsInShortFormat(processes_memory);
+ else
+ DumpProcessesMemoryMapsInExtendedFormat(processes_memory);
+ return EXIT_SUCCESS;
+}
diff --git a/tools/android/memdump/memdump.gyp b/tools/android/memdump/memdump.gyp
new file mode 100644
index 0000000..f47cedf
--- /dev/null
+++ b/tools/android/memdump/memdump.gyp
@@ -0,0 +1,39 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'memdump-unstripped',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ ],
+ 'sources': [
+ 'memdump.cc',
+ ],
+ },
+ {
+ 'target_name': 'memdump',
+ 'type': 'none',
+ 'dependencies': [
+ 'memdump-unstripped',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'strip_memdump',
+ 'inputs': ['<(PRODUCT_DIR)/memdump-unstripped'],
+ 'outputs': ['<(PRODUCT_DIR)/memdump'],
+ 'action': [
+ '<(android_strip)',
+ '--strip-unneeded',
+ '<@(_inputs)',
+ '-o',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ },
+ ],
+}
diff --git a/tools/android/memdump/memsymbols.py b/tools/android/memdump/memsymbols.py
new file mode 100755
index 0000000..3721963
--- /dev/null
+++ b/tools/android/memdump/memsymbols.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+#
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import base64
+import os
+import sys
+import re
+
+from optparse import OptionParser
+
+"""Extracts the list of resident symbols of a library loaded in a process.
+
+This scripts combines the extended output of memdump for a given process
+(obtained through memdump -x PID) and the symbol table of a .so loaded in that
+process (obtained through nm -C lib-with-symbols.so), filtering out only those
+symbols that, at the time of the snapshot, were resident in memory (that are,
+the symbols which start address belongs to a mapped page of the .so which was
+resident at the time of the snapshot).
+The aim is to perform a "code coverage"-like profiling of a binary, intersecting
+run-time information (list of resident pages) and debug symbols.
+"""
+
+_PAGE_SIZE = 4096
+
+
+def _TestBit(word, bit):
+ assert(bit >= 0 and bit < 8)
+ return not not ((word >> bit) & 1)
+
+
+def _HexAddr(addr):
+ return hex(addr)[2:].zfill(8)
+
+
+def _GetResidentPagesSet(memdump_contents, lib_name, verbose):
+ """Parses the memdump output and extracts the resident page set for lib_name.
+ Args:
+ memdump_contents: Array of strings (lines) of a memdump output.
+ lib_name: A string containing the name of the library.so to be matched.
+ verbose: Print a verbose header for each mapping matched.
+
+ Returns:
+ A set of resident pages (the key is the page index) for all the
+ mappings matching .*lib_name.
+ """
+ resident_pages = set()
+ MAP_RX = re.compile(
+ r'^([0-9a-f]+)-([0-9a-f]+) ([\w-]+) ([0-9a-f]+) .* "(.*)" \[(.*)\]$')
+ for line in memdump_contents:
+ line = line.rstrip('\r\n')
+ if line.startswith('[ PID'):
+ continue
+
+ r = MAP_RX.match(line)
+ if not r:
+ sys.stderr.write('Skipping %s from %s\n' % (line, memdump_file))
+ continue
+
+ map_start = int(r.group(1), 16)
+ map_end = int(r.group(2), 16)
+ prot = r.group(3)
+ offset = int(r.group(4), 16)
+ assert(offset % _PAGE_SIZE == 0)
+ lib = r.group(5)
+ enc_bitmap = r.group(6)
+
+ if not lib.endswith(lib_name):
+ continue
+
+ bitmap = base64.b64decode(enc_bitmap)
+ map_pages_count = (map_end - map_start + 1) / _PAGE_SIZE
+ bitmap_pages_count = len(bitmap) * 8
+
+ if verbose:
+ print 'Found %s: mapped %d pages in mode %s @ offset %s.' % (
+ lib, map_pages_count, prot, _HexAddr(offset))
+ print ' Map range in the process VA: [%s - %s]. Len: %s' % (
+ _HexAddr(map_start),
+ _HexAddr(map_end),
+ _HexAddr(map_pages_count * _PAGE_SIZE))
+ print ' Corresponding addresses in the binary: [%s - %s]. Len: %s' % (
+ _HexAddr(offset),
+ _HexAddr(offset + map_end - map_start),
+ _HexAddr(map_pages_count * _PAGE_SIZE))
+ print ' Bitmap: %d pages' % bitmap_pages_count
+ print ''
+
+ assert(bitmap_pages_count >= map_pages_count)
+ for i in xrange(map_pages_count):
+ bitmap_idx = i / 8
+ bitmap_off = i % 8
+ if (bitmap_idx < len(bitmap) and
+ _TestBit(ord(bitmap[bitmap_idx]), bitmap_off)):
+ resident_pages.add(offset / _PAGE_SIZE + i)
+ return resident_pages
+
+
+def main(argv):
+ NM_RX = re.compile(r'^([0-9a-f]+)\s+.*$')
+
+ parser = OptionParser()
+ parser.add_option("-r", "--reverse",
+ action="store_true", dest="reverse", default=False,
+ help="Print out non present symbols")
+ parser.add_option("-v", "--verbose",
+ action="store_true", dest="verbose", default=False,
+ help="Print out verbose debug information.")
+
+ (options, args) = parser.parse_args()
+
+ if len(args) != 3:
+ print 'Usage: %s [-v] memdump.file nm.file library.so' % (
+ os.path.basename(argv[0]))
+ return 1
+
+ memdump_file = args[0]
+ nm_file = args[1]
+ lib_name = args[2]
+
+ if memdump_file == '-':
+ memdump_contents = sys.stdin.readlines()
+ else:
+ memdump_contents = open(memdump_file, 'r').readlines()
+ resident_pages = _GetResidentPagesSet(memdump_contents,
+ lib_name,
+ options.verbose)
+
+ # Process the nm symbol table, filtering out the resident symbols.
+ nm_fh = open(nm_file, 'r')
+ for line in nm_fh:
+ line = line.rstrip('\r\n')
+ # Skip undefined symbols (lines with no address).
+ if line.startswith(' '):
+ continue
+
+ r = NM_RX.match(line)
+ if not r:
+ sys.stderr.write('Skipping %s from %s\n' % (line, nm_file))
+ continue
+
+ sym_addr = int(r.group(1), 16)
+ sym_page = sym_addr / _PAGE_SIZE
+ last_sym_matched = (sym_page in resident_pages)
+ if (sym_page in resident_pages) != options.reverse:
+ print line
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/tools/android/mempressure.py b/tools/android/mempressure.py
new file mode 100755
index 0000000..fa3daba
--- /dev/null
+++ b/tools/android/mempressure.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import collections
+import logging
+import optparse
+import os
+import sys
+
+BUILD_ANDROID_DIR = os.path.join(os.path.dirname(__file__),
+ os.pardir,
+ os.pardir,
+ 'build',
+ 'android')
+sys.path.append(BUILD_ANDROID_DIR)
+from pylib import android_commands
+from pylib import constants
+from pylib import flag_changer
+from pylib.device import device_errors
+from pylib.device import device_utils
+
+# Browser Constants
+DEFAULT_BROWSER = 'chrome'
+
+# Action Constants
+ACTION_PACKAGE = 'org.chromium.base'
+ACTION_TRIM = {
+ 'moderate' : ACTION_PACKAGE + '.ACTION_TRIM_MEMORY_MODERATE',
+ 'critical' : ACTION_PACKAGE + '.ACTION_TRIM_MEMORY_RUNNING_CRITICAL',
+ 'complete' : ACTION_PACKAGE + '.ACTION_TRIM_MEMORY'
+}
+ACTION_LOW = ACTION_PACKAGE + '.ACTION_LOW_MEMORY'
+
+# Command Line Constants
+ENABLE_TEST_INTENTS_FLAG = '--enable-test-intents'
+
+def main(argv):
+ option_parser = optparse.OptionParser()
+ option_parser.add_option('-l',
+ '--low',
+ help='Simulate Activity#onLowMemory()',
+ action='store_true')
+ option_parser.add_option('-t',
+ '--trim',
+ help=('Simulate Activity#onTrimMemory(...) with ' +
+ ', '.join(ACTION_TRIM.keys())),
+ type='string')
+ option_parser.add_option('-b',
+ '--browser',
+ default=DEFAULT_BROWSER,
+ help=('Which browser to use. One of ' +
+ ', '.join(constants.PACKAGE_INFO.keys()) +
+ ' [default: %default]'),
+ type='string')
+
+ (options, args) = option_parser.parse_args(argv)
+
+ if len(args) > 1:
+ print 'Unknown argument: ', args[1:]
+ option_parser.print_help()
+ sys.exit(1)
+
+ if options.low and options.trim:
+ option_parser.error('options --low and --trim are mutually exclusive')
+
+ if not options.low and not options.trim:
+ option_parser.print_help()
+ sys.exit(1)
+
+ action = None
+ if options.low:
+ action = ACTION_LOW
+ elif options.trim in ACTION_TRIM.keys():
+ action = ACTION_TRIM[options.trim]
+
+ if action is None:
+ option_parser.print_help()
+ sys.exit(1)
+
+ if not options.browser in constants.PACKAGE_INFO.keys():
+ option_parser.error('Unknown browser option ' + options.browser)
+
+ package_info = constants.PACKAGE_INFO[options.browser]
+
+ package = package_info.package
+ activity = package_info.activity
+
+ devices = android_commands.GetAttachedDevices()
+ if not devices:
+ raise device_errors.NoDevicesError()
+ elif len(devices) > 1:
+ logging.warning('Multiple devices attached. Using %s.' % devices[0])
+ device = device_utils.DeviceUtils(devices[0])
+
+ try:
+ device.EnableRoot()
+ except device_errors.CommandFailedError as e:
+ # Try to change the flags and start the activity anyway.
+ # TODO(jbudorick) Handle this exception appropriately after interface
+ # conversions are finished.
+ logging.error(str(e))
+ flags = flag_changer.FlagChanger(device, package_info.cmdline_file)
+ if ENABLE_TEST_INTENTS_FLAG not in flags.Get():
+ flags.AddFlags([ENABLE_TEST_INTENTS_FLAG])
+
+ device.StartActivity(intent.Intent(package=package, activity=activity,
+ action=action))
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/tools/android/ps_ext/ps_ext.c b/tools/android/ps_ext/ps_ext.c
new file mode 100644
index 0000000..06cf7bc
--- /dev/null
+++ b/tools/android/ps_ext/ps_ext.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+ * This tool is essentially an extended version of ps with JSON output.
+ * Its output is meant consumed by scripts / tools for gathering OS/ps stats.
+ * Output units:
+ * All times are expressed in ticks.
+ * All memory counters are expressed in Kb.
+ */
+
+static void dump_time(void) {
+ float uptime_secs = 0.0F;
+ const long rate = sysconf(_SC_CLK_TCK);
+ FILE *f = fopen("/proc/uptime", "r");
+ if (!f)
+ return;
+ fscanf(f, "%f", &uptime_secs);
+ fclose(f);
+ const long ticks = (long) (rate * uptime_secs);
+ printf(" \"time\": { \"ticks\": %ld, \"rate\": %ld}", ticks, rate);
+}
+
+static void dump_cpu_stats(void) {
+ FILE *f = fopen("/proc/stat", "r");
+ if (!f)
+ return;
+ printf(" \"cpu\":\n [\n");
+
+ bool terminate_prev_line = false;
+ while (!feof(f)) {
+ char line[256];
+ char cpu[8];
+ long unsigned t_usr = 0;
+ long unsigned t_nice = 0;
+ long unsigned t_sys = 0;
+ long unsigned t_idle = 0;
+ fgets(line, sizeof(line), f);
+
+ /* Skip the total 'cpu ' line and the other irrelevant ones. */
+ if (strncmp(line, "cpu", 3) != 0 || line[3] == ' ')
+ continue;
+ if (sscanf(line, "%s %lu %lu %lu %lu",
+ cpu, &t_usr, &t_nice, &t_sys, &t_idle) != 5) {
+ continue;
+ }
+
+ if (terminate_prev_line)
+ printf(",\n");
+ terminate_prev_line = true;
+ printf(" {\"usr\": %lu, \"sys\": %lu, \"idle\": %lu}",
+ t_usr + t_nice, t_sys, t_idle);
+ }
+ fclose(f);
+ printf("\n ]");
+}
+
+static void dump_mem_stats(void) {
+ FILE *f = fopen("/proc/meminfo", "r");
+ if (!f)
+ return;
+ printf(" \"mem\":\n {\n");
+
+ bool terminate_prev_line = false;
+ while (!feof(f)) {
+ char line[256];
+ char key[32];
+ long value = 0;
+
+ fgets(line, sizeof(line), f);
+ if (sscanf(line, "%s %lu %*s", key, &value) < 2)
+ continue;
+
+ if (terminate_prev_line)
+ printf(",\n");
+ terminate_prev_line = true;
+ printf(" \"%s\": %lu", key, value);
+ }
+ fclose(f);
+ printf("\n }");
+}
+
+static void dump_proc_stats(void) {
+ struct dirent *de;
+ DIR *d = opendir("/proc");
+ if (!d)
+ return;
+
+ const long kb_per_page = sysconf(_SC_PAGESIZE) / 1024;
+ bool terminate_prev_line = false;
+ printf(" \"processes\":\n {\n");
+ while ((de = readdir(d))) {
+ if (!isdigit(de->d_name[0]))
+ continue;
+ const int pid = atoi(de->d_name);
+
+ /* Don't print out ourselves (how civilized). */
+ if (pid == getpid())
+ continue;
+
+ char cmdline[64];
+ char fpath[32];
+ FILE *f;
+
+ /* Read full process path / package from cmdline. */
+ sprintf(fpath, "/proc/%d/cmdline", pid);
+ f = fopen(fpath, "r");
+ if (!f)
+ continue;
+ cmdline[0] = '\0';
+ fgets(cmdline, sizeof(cmdline), f);
+ fclose(f);
+
+ /* Read cpu/io/mem stats. */
+ char proc_name[256];
+ long num_threads = 0;
+ long unsigned min_faults = 0;
+ long unsigned maj_faults = 0;
+ long unsigned utime = 0;
+ long unsigned ktime = 0;
+ long unsigned vm_rss = 0;
+ long long unsigned start_time = 0;
+
+ sprintf(fpath, "/proc/%d/stat", pid);
+ f = fopen(fpath, "r");
+ if (!f)
+ continue;
+ fscanf(f, "%*d %s %*c %*d %*d %*d %*d %*d %*u %lu %*u %lu %*u %lu %lu "
+ "%*d %*d %*d %*d %ld %*d %llu %*u %ld", proc_name, &min_faults,
+ &maj_faults, &utime, &ktime, &num_threads, &start_time, &vm_rss);
+ fclose(f);
+
+ /* Prefer the cmdline when available, since it contains the package name. */
+ char const * const cmd = (strlen(cmdline) > 0) ? cmdline : proc_name;
+
+ if (terminate_prev_line)
+ printf(",\n");
+ terminate_prev_line = true;
+ printf(" \"%d\": {"
+ "\"name\": \"%s\", "
+ "\"n_threads\": %ld, "
+ "\"start_time\": %llu, "
+ "\"user_time\": %lu, "
+ "\"sys_time\": %lu, "
+ "\"min_faults\": %lu, "
+ "\"maj_faults\": %lu, "
+ "\"vm_rss\": %lu"
+ "}",
+ pid,
+ cmd,
+ num_threads,
+ start_time,
+ utime,
+ ktime,
+ min_faults,
+ maj_faults,
+ vm_rss * kb_per_page);
+ }
+ closedir(d);
+ printf("\n }");
+}
+
+int main()
+{
+ printf("{\n");
+
+ dump_time();
+ printf(",\n");
+
+ dump_mem_stats();
+ printf(",\n");
+
+ dump_cpu_stats();
+ printf(",\n");
+
+ dump_proc_stats();
+ printf("\n}\n");
+
+ return 0;
+}
diff --git a/tools/android/ps_ext/ps_ext.gyp b/tools/android/ps_ext/ps_ext.gyp
new file mode 100644
index 0000000..f467d93
--- /dev/null
+++ b/tools/android/ps_ext/ps_ext.gyp
@@ -0,0 +1,36 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'ps_ext-unstripped',
+ 'type': 'executable',
+ 'sources': [
+ 'ps_ext.c',
+ ],
+ },
+ {
+ 'target_name': 'ps_ext',
+ 'type': 'none',
+ 'dependencies': [
+ 'ps_ext-unstripped',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'strip_ps_ext',
+ 'inputs': ['<(PRODUCT_DIR)/ps_ext-unstripped'],
+ 'outputs': ['<(PRODUCT_DIR)/ps_ext'],
+ 'action': [
+ '<(android_strip)',
+ '--strip-unneeded',
+ '<@(_inputs)',
+ '-o',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ },
+ ],
+}
diff --git a/tools/android/purge_ashmem/purge_ashmem.c b/tools/android/purge_ashmem/purge_ashmem.c
new file mode 100644
index 0000000..0de582d
--- /dev/null
+++ b/tools/android/purge_ashmem/purge_ashmem.c
@@ -0,0 +1,20 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "third_party/ashmem/ashmem.h"
+
+int main(void) {
+ const int pages_purged = ashmem_purge_all();
+ if (pages_purged < 0) {
+ perror("ashmem_purge_all");
+ return EXIT_FAILURE;
+ }
+ printf("Purged %d pages (%d KBytes)\n",
+ pages_purged, pages_purged * getpagesize() / 1024);
+ return EXIT_SUCCESS;
+}
diff --git a/tools/android/purge_ashmem/purge_ashmem.gyp b/tools/android/purge_ashmem/purge_ashmem.gyp
new file mode 100644
index 0000000..d563b1f
--- /dev/null
+++ b/tools/android/purge_ashmem/purge_ashmem.gyp
@@ -0,0 +1,21 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'purge_ashmem',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../third_party/ashmem/ashmem.gyp:ashmem',
+ ],
+ 'include_dirs': [
+ '../../../',
+ ],
+ 'sources': [
+ 'purge_ashmem.c',
+ ],
+ },
+ ],
+}
diff --git a/tools/android/remove_strings.py b/tools/android/remove_strings.py
new file mode 100755
index 0000000..b8c4807
--- /dev/null
+++ b/tools/android/remove_strings.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Remove strings by name from a GRD file."""
+
+import optparse
+import re
+import sys
+
+
+def RemoveStrings(grd_path, string_names):
+ """Removes strings with the given names from a GRD file. Overwrites the file.
+
+ Args:
+ grd_path: path to the GRD file.
+ string_names: a list of string names to be removed.
+ """
+ with open(grd_path, 'r') as f:
+ grd = f.read()
+ names_pattern = '|'.join(map(re.escape, string_names))
+ pattern = r'<message [^>]*name="(%s)".*?</message>\s*' % names_pattern
+ grd = re.sub(pattern, '', grd, flags=re.DOTALL)
+ with open(grd_path, 'w') as f:
+ f.write(grd)
+
+
+def ParseArgs(args):
+ usage = 'usage: %prog GRD_PATH...'
+ parser = optparse.OptionParser(
+ usage=usage, description='Remove strings from GRD files. Reads string '
+ 'names from stdin, and removes strings with those names from the listed '
+ 'GRD files.')
+ options, args = parser.parse_args(args=args)
+ if not args:
+ parser.error('must provide GRD_PATH argument(s)')
+ return args
+
+
+def main(args=None):
+ grd_paths = ParseArgs(args)
+ strings_to_remove = filter(None, map(str.strip, sys.stdin.readlines()))
+ for grd_path in grd_paths:
+ RemoveStrings(grd_path, strings_to_remove)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/android/run_pie/run_pie.c b/tools/android/run_pie/run_pie.c
new file mode 100644
index 0000000..ee1a622
--- /dev/null
+++ b/tools/android/run_pie/run_pie.c
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+// This is a wrapper to run position independent executables on Android ICS,
+// where the linker doesn't support PIE. This requires the PIE binaries to be
+// built with CFLAGS +=-fvisibility=default -fPIE, and LDFLAGS += -rdynamic -pie
+// such that the main() symbol remains exported and can be dlsym-ed.
+
+#define ERR_PREFIX "[PIE Loader] "
+
+typedef int (*main_t)(int, char**);
+
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ printf("Usage: %s path_to_pie_executable [args]\n", argv[0]);
+ return -1;
+ }
+
+ // Shift left the argv[]. argv is what /proc/PID/cmdline prints out. In turn
+ // cmdline is what Android "ps" prints out. In turn "ps" is what many scripts
+ // look for to decide which processes to kill / killall.
+ int i;
+ char* next_argv_start = argv[0];
+ for (i = 1; i < argc; ++i) {
+ const size_t argv_len = strlen(argv[i]) + 1;
+ memcpy(argv[i - 1], argv[i], argv_len);
+ next_argv_start += argv_len;
+ argv[i] = next_argv_start;
+ }
+ argv[argc - 1] = NULL; // The last argv must be a NULL ptr.
+
+ // Set also the proc name accordingly (/proc/PID/comm).
+ prctl(PR_SET_NAME, (long) argv[0]);
+
+ // dlopen should not fail, unless:
+ // - The target binary does not exists:
+ // - The dependent .so libs cannot be loaded.
+ // In both cases, just bail out with an explicit error message.
+ void* handle = dlopen(argv[0], RTLD_NOW);
+ if (handle == NULL) {
+ printf(ERR_PREFIX "dlopen() failed: %s.\n", dlerror());
+ return -1;
+ }
+
+ main_t pie_main = (main_t) dlsym(handle, "main");
+ if (pie_main) {
+ return pie_main(argc - 1, argv);
+ }
+
+ // If we reached this point dlsym failed, very likely because the target
+ // binary has not been compiled with the proper CFLAGS / LDFLAGS.
+ // At this point the most sensible thing to do is running that normally
+ // via exec and hope that the target binary wasn't a PIE.
+ execv(argv[0], argv);
+
+ // exevc is supposed to never return, unless it fails.
+ printf(ERR_PREFIX "Both dlsym() and the execv() fallback failed.\n");
+ perror("execv");
+ return -1;
+}
diff --git a/tools/android/run_pie/run_pie.gyp b/tools/android/run_pie/run_pie.gyp
new file mode 100644
index 0000000..b713dc4
--- /dev/null
+++ b/tools/android/run_pie/run_pie.gyp
@@ -0,0 +1,49 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'run_pie-unstripped',
+ 'type': 'executable',
+ 'sources': [
+ 'run_pie.c',
+ ],
+ # See crbug.com/373219. This is the only Android executable which must be
+ # non PIE.
+ 'cflags!': [
+ '-fPIE',
+ ],
+ 'ldflags!': [
+ '-pie',
+ ],
+ # Don't inherit unneeded dependencies on stlport.so, so the binary remains
+ # self-contained also in component=shared_library builds.
+ 'libraries!': [
+ '-l<(android_stlport_library)',
+ ],
+ },
+ {
+ 'target_name': 'run_pie',
+ 'type': 'none',
+ 'dependencies': [
+ 'run_pie-unstripped',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'strip_run_pie',
+ 'inputs': ['<(PRODUCT_DIR)/run_pie-unstripped'],
+ 'outputs': ['<(PRODUCT_DIR)/run_pie'],
+ 'action': [
+ '<(android_strip)',
+ '--strip-unneeded',
+ '<@(_inputs)',
+ '-o',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ },
+ ],
+}