ADB命令执行流程

Post on May 05, 2022 by Wei Lin

Native具体过程

Base on: Android T

以命令”adb shell am start -a android.intent.action.VIEW -t text/plain”为例,隐式打开可以读取text/plain类型的Activity。

am命令

以am命令为例,其位于ROM的路径为/system/bin/am,源码路径为frameworks/base/cmds/am/am。am是一个shell文件,其内容为:

#!/system/bin/sh

if [ "$1" != "instrument" ] ; then  # 如果第一个参数非 instrument
    cmd activity "$@"    # $@ 表示am命令后的所有参数
else
    base=/system
    export CLASSPATH=$base/framework/am.jar
	# app_process启动虚拟机执行 Am.java 类
    exec app_process $base/bin com.android.commands.am.Am "$@"
fi

instrument为am命令的一个子命令,用于启动一个Instrumentation测试。除了am命令,还有其它常用的adb命令,如下。

wm命令:
#!/system/bin/sh
cmd window "$@"

pm命令:
#!/system/bin/sh
cmd package "$@"


主要看一下am命令执行流程,非instrument测试环境下执行cmd activity “$@”。”$@”表示命令后的所有参数,如果执行的命令为”adb shell am start -a android.intent.action.VIEW -t text/plain”,则”$@”内容为”start -a android.intent.action.VIEW -t text/plain”

shell脚本中首先调用了cmd命令,cmd插入的参数为activity “$@”,接下来看一下cmd的执行流程。

cmd命令

ROM中的cmd命令位于/system/bin/cmd,是一个二进制可执行文件。

源码中的cmd代码位于frameworks/native/cmds/cmd目录下。

从程序入口main.cpp开始执行:

int main(int argc, char* const argv[]) {
    signal(SIGPIPE, SIG_IGN);

    std::vector<std::string_view> arguments;
    arguments.reserve(argc - 1);
    // 0th argument is a program name, skipping.
    for (int i = 1; i < argc; ++i) {
        arguments.emplace_back(argv[i]);
    }

    return cmdMain(arguments, android::aout, android::aerr, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, RunMode::kStandalone);
}


插入的参数保存在argv[]中:

argv=[cmd, activity, start, -a, android.intent.action.VIEW, -t, text/plain]

然后将参数赋值到arguments中,最后arguments的内容为:

arguments=[activity, start, -a, android.intent.action.VIEW, -t, text/plain]


执行到cmd.cpp中的cmdMain()中,其主要方法如下:

int cmdMain(const std::vector<std::string_view> &argv, TextOutput &outputLog, TextOutput &errorLog,
            int in, int out, int err, RunMode runMode) {
	//...
	// 获取到需要的服务名
	// waitForService=0
	bool waitForService = ((argc > 1) && (argv[0] == "-w"));
	int serviceIdx = (waitForService) ? 1 : 0;
	// cmd="activity"
	const auto cmd = argv[serviceIdx];

	Vector<String16> args;
	String16 serviceName = String16(cmd.data(), cmd.size());

	// 通过Binder获取服务对象
	sp<IBinder> service;
	if (waitForService) {
		service = sm->waitForService(serviceName);
	} else {
		service = sm->checkService(serviceName);
	}

	// 在shellCommand()中与service通信
	status_t error = IBinder::shellCommand(service, in, out, err, args, cb, result);
	//...
}

然后调用到IBinder::shellCommand(),需要调用的服务引用service和参数列表args。

Native->Framework

IBinder::shellCommand()方法位于frameworks/native/libs/binder/Binder.cpp,在这里实现了与服务端的Binder通信。最终调用到Android Framework层,之后执行Java代码。

status_t IBinder::shellCommand(const sp<IBinder>& target, int in, int out, int err,
    Vector<String16>& args, const sp<IShellCallback>& callback,
    const sp<IResultReceiver>& resultReceiver)
{
    Parcel send;
    Parcel reply;
    send.writeFileDescriptor(in);
    send.writeFileDescriptor(out);
    send.writeFileDescriptor(err);
    const size_t numArgs = args.size();
    send.writeInt32(numArgs);
    for (size_t i = 0; i < numArgs; i++) {
        send.writeString16(args[i]);
    }
    send.writeStrongBinder(callback != nullptr ? IInterface::asBinder(callback) : nullptr);
    send.writeStrongBinder(resultReceiver != nullptr ? IInterface::asBinder(resultReceiver) : nullptr);
    return target->transact(SHELL_COMMAND_TRANSACTION, send, &reply);
}

target即是要调用的服务代理,Binder通信具体过程略。

对am来说,其服务端代码位于ActivityManagerService;对wm来说,位于WindowManagerService,调用到了它们的onShellCommand()中。

接下来详细说一下Framework层的具体过程,以便可以在这里自定义shell命令。

Framework具体过程

Base on: Android 13

以命令”adb shell am start -a android.intent.action.VIEW -t text/plain”为例,隐式打开可以读取text/plain类型的Activity。

am命令在framework中首先执行AMS.onShellCommand()。

AMS.onShellCommand()

am命令在framework中首先执行AMS.onShellCommand()。

每执行一次shell命令,就new一个ActivityManagerShellCommand对象,通过该对象控制整个命令的流程。

@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
        FileDescriptor err, String[] args, ShellCallback callback,
        ResultReceiver resultReceiver) {
    (new ActivityManagerShellCommand(this, false)).exec(
            this, in, out, err, args, callback, resultReceiver);
}


其中比较重要的参数是String[] args,它表示输入的命令参数数组。

String[] args = ["start", "-a", "android.intent.action.VIEW", "-t", "text/plain"]

这里的ActivityManagerShellCommand.exec()是有返回值的,但实际上没做什么处理。

AMSC.exec()

AMSC=ActivityManagerShellCommand

BasicShellCommandHandler源码位置:frameworks/libs/modules-utils/java/com/android/modules/utils/BasicShellCommandHandler.java

ActivityManagerShellCommand的继承关系为:

final class ActivityManagerShellCommand extends ShellCommand {...}
public abstract class ShellCommand extends BasicShellCommandHandler {...}

所以实际调用如下:

ShellCommand.exec() ->

BasicShellCommandHandler.exec()

BasicShellCommandHandler中初始化参数:

public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
        String[] args) {
    String cmd;
    int start;
    if (args != null && args.length > 0) {
        cmd = args[0];
        start = 1;
    } else {
        cmd = null;
        start = 0;
    }
    init(target, in, out, err, args, start);
    mCmd = cmd;

    int res = -1;
    try {
        res = onCommand(mCmd);
        if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
    } catch (Throwable e) {
        //...
    }
    //...
}

// 这里主要进行对象的初始化
public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
        String[] args, int firstArgPos) {
    mTarget = target;
    mIn = in;
    mOut = out;
    mErr = err;
    mArgs = args;
    mCmd = null;
    mArgPos = firstArgPos;
    mCurArgData = null;
    mFileIn = null;
    mFileOut = null;
    mFileErr = null;
    mOutPrintWriter = null;
    mErrPrintWriter = null;
    mInputStream = null;
}


大致流程:

  • 调用init()初始化成员对象,将参数列表args赋给属性mArgs。

String[] mArgs = [“start”, “-a”, “android.intent.action.VIEW”, “-t”, “text/plain”]

  • 获取命令mCmd,mCmd = “start”

  • 调用onCommand(mCmd)

AMSC.onCommand()

AMSC=ActivityManagerShellCommand

这里的onCommand(String cmd)就是上例中的cmd = “start”,所以接下来执行runStartActivity(pw)。

@Override
public int onCommand(String cmd) {
    if (cmd == null) {
        return handleDefaultCommands(cmd);
    }
    final PrintWriter pw = getOutPrintWriter();
    try {
        switch (cmd) {
            case "start":
            case "start-activity":
                return runStartActivity(pw);
            case "startservice":
            case "start-service":
                return runStartService(pw, false);
            case "startforegroundservice":
            case "startfgservice":
            case "start-foreground-service":
            case "start-fg-service":
                return runStartService(pw, true);
            case "stopservice":
            case "stop-service":
                return runStopService(pw);
            case "broadcast":
                return runSendBroadcast(pw);
            //...
            default:
                return handleDefaultCommands(cmd);
        }
    } catch (RemoteException e) {
        pw.println("Remote exception: " + e);
    }
    return -1;
}

注:pw是一个PrintWriter对象,通过getOutPrintWriter()获取输出流对象,该对象实际上表示输出的终端对象。

AMSC.runStartActivity()

这里主要进行以下几步:

  • 在这里通过参数列表构造intent
Intent intent;
try {
    intent = makeIntent(UserHandle.USER_CURRENT);
} catch (URISyntaxException e) {
    throw new RuntimeException(e.getMessage(), e);
}
  • 进一步处理intent
  • 通过intent打开Activity并返回结果
if (mWaitOption) {
    result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
            mimeType, null, null, 0, mStartFlags, profilerInfo,
            options != null ? options.toBundle() : null, mUserId);
    res = result.result;
} else {
    res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null,
            intent, mimeType, null, null, 0, mStartFlags, profilerInfo,
            options != null ? options.toBundle() : null, mUserId);
}
  • 通过res作一些提示,输出到终端

AMSC.makeIntent()

private Intent makeIntent(int defUser) throws URISyntaxException {
    mStartFlags = 0;
    mWaitOption = false;
    mStopOption = false;
    mRepeat = 0;
    mProfileFile = null;
    mSamplingInterval = 0;
    mAutoStop = false;
    mStreaming = false;
    mUserId = defUser;
    mDisplayId = INVALID_DISPLAY;
    mWindowingMode = WINDOWING_MODE_UNDEFINED;
    mActivityType = ACTIVITY_TYPE_UNDEFINED;
    mTaskId = INVALID_TASK_ID;
    mIsTaskOverlay = false;
    mIsLockTask = false;
    mAsync = false;
    mBroadcastOptions = null;

    return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {...}

这里主要调用Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler),该方法中除了Intent类自己需要处理的参数外,还可以自定义CommandOptionHandler,解释自己的参数列表。

Intent.parseCommandArgs()

public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
        throws URISyntaxException {
    Intent intent = new Intent();
    Intent baseIntent = intent;
    boolean hasIntentInfo = false;

    Uri data = null;
    String type = null;

    String opt;
    while ((opt=cmd.getNextOption()) != null) {
        switch (opt) {
            case "-a":
                intent.setAction(cmd.getNextArgRequired());
                if (intent == baseIntent) {
                    hasIntentInfo = true;
                }
                break;
            case "-d":
                data = Uri.parse(cmd.getNextArgRequired());
                if (intent == baseIntent) {
                    hasIntentInfo = true;
                }
                break;
            case "-t":
                type = cmd.getNextArgRequired();
                if (intent == baseIntent) {
                    hasIntentInfo = true;
                }
                break;
            //...
            default:
                if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
                    // Okay, caller handled this option.
                } else {
                    throw new IllegalArgumentException("Unknown option: " + opt);
                }
                break;
        }
    }
    intent.setDataAndType(data, type);
    //...
    return intent;
}

自定义shell命令

需求说明

Resolver端在请求Provider时,执行过程为:

(1) Resolver端通过authority请求Provider引用;

(2) system_server处理请求并返回Provider引用;

(3) Resolver端获取引用后调用具体方法如query()等,访问Provider。


需要在Resolver端监控指定的Provider(根据authority属性区分)并打印相关信息,主要在ActivityThread.acquireProvider()中执行关键过程,

需要在system_server监控指定的Provider(根据authority属性区分)并打印相关信息,主要在ContentProviderHelper.getContentProviderImpl()中执行关键过程。

定义变量

在ActivityThread中需要实现的功能如下,其中通过shell控制的变量有两个,即AUTH和isPrintStack。

public static String AUTH = null;
public static boolean isPrintStack = false;

// 监听auth为ActivityThread.AUTH的Provider的情况
// 如果ActivityThread.AUTH=all,则监控所有
public void printAuth(String curAuth) {
    if (ActivityThread.AUTH == null) {
        return;
    }
    if ("all".equals(ActivityThread.AUTH) ||
            curAuth.equals(ActivityThread.AUTH)) {
        String TestTAG = "AMS Provider Test";
        Slog.w(TestTAG, "CallerPackage" + "{" +
                "Uid=" + Process.myUid() + "," +
                "Pid=" + Process.myPid() + "} " +
                "start execute acquireProvider() for " + ActivityThread.AUTH);
        if (isPrintStack) {
            new Exception(TestTAG).printStackTrace();
        }
    }
}

命令格式

我的shell命令格式如下:

adb shell am monitor-auth --auth com.demoapp.mydemo.provider --uid all --stack true --end

monitor-auth:即命令标识
--auth:后面接具体的authority,all表示所有authority都打印
--uid:Resolver端的uid,all表示所有客户端的请求都会被打印
--stack:标识是否需要print堆栈,yes或者no
--end:结束标识,调用函数处理


# 打印系统中所有进程的所有authority日志
adb shell am monitor-auth --auth all --uid all --stack no --end

# 打印uid=10340的Resolver端请求com.demoapp.provider时的日志,并打印请求端堆栈
adb shell am monitor-auth --auth com.demoapp.provider --uid 10340 --stack yes --end

# 关闭日志打印
adb shell am monitor-auth --auth null --uid null --stack no --end

自定义命令代码

在AMSC.onCommand(String cmd)中通过不同命令执行不同流程,所以在switch中添加:

// AMS Test Log
case "monitor-auth":
    runMonitorAuth();
    return 0;

然后在AMSC中自定义处理流程,即runMonitorAuth()函数,如下:

// AMS Test Log
private void runMonitorAuth() throws RemoteException {
    String opt;
    String authority = null;
    int uid = 0;
    boolean isPrintStack = false;

    // getNextOption()获取类似-a的参数
    while ((opt = getNextOption()) != null) {
        switch (opt) {
            case "--auth": //获取auth
                String auth = getNextArg(); // 获取--auth后面的参数值
                if (auth == null || auth.equals("null")) {
                    authority = null;
                } else {
                    authority = auth;
                }
                break;
            case "--uid":
                String resUid = getNextArg();
                if ("all".equals(resUid)) {
                    uid = -1;
                } else {
                    uid = Integer.parseInt(resUid);
                }
                break;
            case "--stack": //是否打印堆栈
                String resStack = getNextArg();
                isPrintStack = resStack.equals("yes");
                break;
            case "--end":
                mInterface.setMonitorAuth(authority, uid, isPrintStack);
                break;
            default:
                break;
        }
    }
}

通过mInterface.setMonitorAuth()调用到AMS,需要在IActivityManager.aidl中添加该函数。


在AMS中添加代码并调用到APP端:

public void setMonitorAuth(String authority, int uid, boolean isPrintStack) {
    // 设置系统进程的AUTH
    ActivityThread.AUTH = authority;
    // 设置指定Uid进程的AUTH
    final int numOfNames = getProcessNamesLOSP().getMap().size();
    for (int ip = 0; ip < numOfNames; ip++) {
        //获取某个进程名下的所有进程
        SparseArray<ProcessRecord> procs = getProcessNamesLOSP().getMap().valueAt(ip);
        if (procs == null) {
            return;
        }
        for (int ia = 0, size = procs.size(); ia < size; ia++) {
            ProcessRecord app = procs.valueAt(ia);
            IApplicationThread thread;
            // 比较uid,如果uid=-1则对所有正在运行的进程执行操作
            if ((uid == -1 || app.uid == uid) && (thread = app.getThread()) != null) {
                try {
                    thread.setMonitorAuth(authority, isPrintStack);
                } catch (RemoteException e) {
                    // If the other end already died, then our work here is done.
                }
            }
        }
    }
}

通过IApplicationThread调用到APP端,所以还需要在IApplicationThread.aidl找那个添加接口。


最后在ActivityThread.ApplicationThread类中给APP端赋值:

public void setMonitorAuth(String authority, boolean isPrint) {
    AUTH = authority;
    isPrintStack = isPrint;
}


最后源码中重新编译framework和services模块即可。

make services -j8		# 编译services
make framework-minus-apex -j8		# Android 11(R) 版本开始用此命令

然后push到指定目录即可验证效果。

其它

ADB命令格式

执行到framework后,adb命令可看作由cmd、option、args三部分组成,一条指令只有一个cmd,但option和args可以有多组。

如:adb shell am start -a android.intent.action.VIEW -t text/plain


其中adb shell表示进入手机环境,这里不讨论这个。am标识处理这条命令的相应service,”am”表示ActivityManagerService。进入到实际处理类后,实际执行的是”start -a android.intent.action.VIEW -t text/plain”,

在各个ShellCommand类中如ActivityManagerShellCommand,这条指令被存储为一个数组String[] mArgs = [“start”, “-a”, “android.intent.action.VIEW”, “-t”, “text/plain”]。

(1) am:即是上述说的cmd

(2) -a和-t:就是option,意为选项

(3) option后面跟的就是实际参数


其中option可以用”-“和”–“表示,具体如下:

格式 说明
”-“+单字符 常见格式。如-a value,后面跟具体参数值
”–“+多字符 常见格式。如–key value,后面也跟具体参数值
”–“+单字符 同”–“+多字符
”-“+多字符 较少用。如-avalue,-a后面的表示参数值,没有空格
   

处理命令的函数

在ActivityManagerShellCommand中,通过String getNextOption()每次读取一个option。

/**
 * Return the next option on the command line -- that is an argument that
 * starts with '-'.  If the next argument is not an option, null is returned.
 */
public String getNextOption() {
    if (mCurArgData != null) {
        String prev = mArgs[mArgPos - 1];
        throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
    }
    if (mArgPos >= mArgs.length) {
        return null;
    }
    String arg = mArgs[mArgPos];
    if (!arg.startsWith("-")) {
        return null;
    }
    mArgPos++;
    if (arg.equals("--")) {
        return null;
    }
    if (arg.length() > 1 && arg.charAt(1) != '-') {
        if (arg.length() > 2) { //这里处理-avalue类型的参数
            mCurArgData = arg.substring(2); //返回值value
            return arg.substring(0, 2);  //返回option即-a
        } else {
            mCurArgData = null;
            return arg;
        }
    }
    mCurArgData = null;
    return arg; //返回形如-a或--key的option
}

使用getNextArgRequired()或getNextArg()获取参数值。getNextArgRequired()多了个判空流程并输出异常。实际上只要调用就会从mArgs读取一个值,无论是option还是args。

/**
 * Return the next argument on the command line, whatever it is; if there are
 * no arguments left, throws an IllegalArgumentException to report this to the user.
 */
public String getNextArgRequired() {
    String arg = getNextArg();
    if (arg == null) {
        String prev = mArgs[mArgPos - 1];
        throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
    }
    return arg;
}

/**
 * Return the next argument on the command line, whatever it is; if there are
 * no arguments left, return null.
 */
public String getNextArg() {
    if (mCurArgData != null) {
        String arg = mCurArgData;
        mCurArgData = null;
        return arg;
    } else if (mArgPos < mArgs.length) {
        return mArgs[mArgPos++];
    } else {
        return null;
    }
}