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)', + ], + }, + ], + }, + ], +}