目的:调查adb remount 与adb shell进去后执行remount的差异
调试方法:添加log编译adbd,替换system\apex\com.android.adbd\bin\adbd
一、调查adb remount实现
关键代码:system\core\adb\daemon\services.cpp
unique_fd daemon_service_to_fd(std::string_view name, atransport* transport) {
ADB_LOG(Service) << "transport " << transport->serial_name() << " opening service " << name;
#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
if (name.starts_with("abb:") || name.starts_with("abb_exec:")) {
return execute_abb_command(name);
}
#endif
#if defined(__ANDROID__)
if (name.starts_with("framebuffer:")) {
return create_service_thread("fb", framebuffer_service);
} else if (android::base::ConsumePrefix(&name, "remount:")) {
std::string cmd = "/system/bin/remount ";
cmd += name;
return StartSubprocess(cmd, nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone);
} else if (android::base::ConsumePrefix(&name, "reboot:")) {
return reboot_device(std::string(name));
} else if (name.starts_with("root:")) {
return create_service_thread("root", restart_root_service);
} else if (name.starts_with("unroot:")) {
return create_service_thread("unroot", restart_unroot_service);
} else if (android::base::ConsumePrefix(&name, "backup:")) {
std::string cmd = "/system/bin/bu backup ";
cmd += name;
return StartSubprocess(cmd, nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone);
} else if (name.starts_with("restore:")) {
return StartSubprocess("/system/bin/bu restore", nullptr, SubprocessType::kRaw,
SubprocessProtocol::kNone);
} else if (name.starts_with("disable-verity:")) {
return StartSubprocess("/system/bin/disable-verity", nullptr, SubprocessType::kRaw,
SubprocessProtocol::kNone);
} else if (name.starts_with("enable-verity:")) {
return StartSubprocess("/system/bin/enable-verity", nullptr, SubprocessType::kRaw,
SubprocessProtocol::kNone);
} else if (android::base::ConsumePrefix(&name, "tcpip:")) {
std::string str(name);
int port;
if (sscanf(str.c_str(), "%d", &port) != 1) {
return unique_fd{};
}
return create_service_thread("tcp",
std::bind(restart_tcp_service, std::placeholders::_1, port));
} else if (name.starts_with("usb:")) {
return create_service_thread("usb", restart_usb_service);
}
#endif
if (android::base::ConsumePrefix(&name, "dev:")) {
return unique_fd{unix_open(name, O_RDWR | O_CLOEXEC)};
} else if (android::base::ConsumePrefix(&name, "jdwp:")) {
pid_t pid;
if (!ParseUint(&pid, name)) {
return unique_fd{};
}
return create_jdwp_connection_fd(pid);
} else if (android::base::ConsumePrefix(&name, "shell")) {
return ShellService(name, transport);
} else if (android::base::ConsumePrefix(&name, "exec:")) {
return StartSubprocess(std::string(name), nullptr, SubprocessType::kRaw,
SubprocessProtocol::kNone);
} else if (name.starts_with("sync:")) {
return create_service_thread("sync", file_sync_service);
} else if (android::base::ConsumePrefix(&name, "reverse:")) {
return reverse_service(name, transport);
} else if (name == "reconnect") {
return create_service_thread(
"reconnect", std::bind(reconnect_service, std::placeholders::_1, transport));
} else if (name == "spin") {
return create_service_thread("spin", spin_service);
}
return unique_fd{};
}
根据之前调试adb方法可以得知adb remount会打印如下log
I adbd : transport UsbFfs opening service shell,v2,TERM=xterm-256color,raw:remount
参考文档:Android adb自身调试log开关-CSDN博客
进而会执行上面daemon_service_to_fd函数中的如下else if
else if (android::base::ConsumePrefix(&name, "shell")) {
return ShellService(name, transport);
在ShellService中会解析参数和cmd,再调用StartSubprocess
// Shell service string can look like:
// shell[,arg1,arg2,...]:[command]
unique_fd ShellService(std::string_view args, const atransport* transport) {
size_t delimiter_index = args.find(':');
if (delimiter_index == std::string::npos) {
LOG(ERROR) << "No ':' found in shell service arguments: " << args;
return unique_fd{};
}
// TODO: android::base::Split(const std::string_view&, ...)
std::string service_args(args.substr(0, delimiter_index));
std::string command(args.substr(delimiter_index + 1));
// Defaults:
// PTY for interactive, raw for non-interactive.
// No protocol.
// $TERM set to "dumb".
SubprocessType type(command.empty() ? SubprocessType::kPty : SubprocessType::kRaw);
SubprocessProtocol protocol = SubprocessProtocol::kNone;
std::string terminal_type = "dumb";
for (const std::string& arg : android::base::Split(service_args, ",")) {
if (arg == kShellServiceArgRaw) {
type = SubprocessType::kRaw;
} else if (arg == kShellServiceArgPty) {
type = SubprocessType::kPty;
} else if (arg == kShellServiceArgShellProtocol) {
protocol = SubprocessProtocol::kShell;
} else if (arg.starts_with("TERM=")) {
terminal_type = arg.substr(strlen("TERM="));
} else if (!arg.empty()) {
// This is not an error to allow for future expansion.
LOG(WARNING) << "Ignoring unknown shell service argument: " << arg;
}
}
return StartSubprocess(command, terminal_type.c_str(), type, protocol);
}
继续跟踪也没发现adb remount有额外的参数或指令执行,看来只有一个remount,与在串口执行并无差异。
那为何adb remount后可以直接覆盖原文件,而串口执行remount必须先删除原文件才能cp成功呢?
adb remount
#cp system/apex/com.android.adbd/bin/adbd22 system/apex/com.android.adbd/bin/adbd
# sync
串口remount
cp system/apex/com.android.adbd/bin/adbd22 system/apex/com.android.adbd/bin/adbd
cp: system/apex/com.android.adbd/bin/adbd: Text file busy
尝试使用adb shell remount,也可以直接覆盖,adb log和adb remount一样
尝试使用adb shell /system/bin/remount,无法覆盖,和串口remount现象一样
adb log如下
03-17 01:19:13.704 8511 8511 I adbd : transport UsbFfs opening service shell,v2,TERM=xterm-256color,raw:/system/bin/remount
03-17 01:19:13.704 8511 8511 I adbd : shell opening service ,v2,TERM=xterm-256color,raw:/system/bin/remount
03-17 01:19:13.704 8511 8511 I adbd : ShellService xterm-256color opening service /system/bin/remount
03-17 01:19:13.704 8511 8511 E adbd : starting raw subprocess (protocol=shell, TERM=xterm-256color): '/system/bin/remount'
说明adb remount并不是调用的/system/bin/remount,而是一个系统集成的函数实现的,相当于调用了一个remount系统函数 ,这也就明白为何adb remount没有走daemon_service_to_fd中的单独remount else if(调用的/system/bin/remount)
走哪个remount是system\core\adb\client\commandline.cpp中如下代码实现的
else if (!strcmp(argv[0], "remount")) {
FeatureSet features;
std::string error;
if (!adb_get_feature_set(&features, &error)) {
fprintf(stderr, "error: %s\n", error.c_str());
return 1;
}
if (CanUseFeature(features, kFeatureRemountShell)) {
std::vector<const char*> args = {"shell"};
args.insert(args.cend(), argv, argv + argc);
return adb_shell_noinput(args.size(), args.data());
} else if (argc > 1) {
auto command = android::base::StringPrintf("%s:%s", argv[0], argv[1]);
return adb_connect_command(command);
} else {
return adb_connect_command("remount:");
}
}
反转又来了。。。
排查了system/core下remount实现只有一处system\core\fs_mgr\fs_mgr_remount.cpp
查看Android.bp,此文件是编译remount指令的源码,但实现方式的确也和init中的raw命令类似叫do_remount
system\core\init\builtins.cpp中没有remount的实现
// Builtin-function-map start
const BuiltinFunctionMap& GetBuiltinFunctionMap() {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const BuiltinFunctionMap builtin_functions = {
{"bootchart", {1, 1, {false, do_bootchart}}},
{"chmod", {2, 2, {true, do_chmod}}},
{"chown", {2, 3, {true, do_chown}}},
{"class_reset", {1, 1, {false, do_class_reset}}},
{"class_reset_post_data", {1, 1, {false, do_class_reset_post_data}}},
{"class_restart", {1, 1, {false, do_class_restart}}},
{"class_start", {1, 1, {false, do_class_start}}},
{"class_start_post_data", {1, 1, {false, do_class_start_post_data}}},
{"class_stop", {1, 1, {false, do_class_stop}}},
{"copy", {2, 2, {true, do_copy}}},
{"domainname", {1, 1, {true, do_domainname}}},
{"enable", {1, 1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
{"exec_start", {1, 1, {false, do_exec_start}}},
{"export", {2, 2, {false, do_export}}},
{"hostname", {1, 1, {true, do_hostname}}},
{"ifup", {1, 1, {true, do_ifup}}},
{"init_user0", {0, 0, {false, do_init_user0}}},
{"insmod", {1, kMax, {true, do_insmod}}},
{"installkey", {1, 1, {false, do_installkey}}},
{"interface_restart", {1, 1, {false, do_interface_restart}}},
{"interface_start", {1, 1, {false, do_interface_start}}},
{"interface_stop", {1, 1, {false, do_interface_stop}}},
{"load_persist_props", {0, 0, {false, do_load_persist_props}}},
{"load_system_props", {0, 0, {false, do_load_system_props}}},
{"loglevel", {1, 1, {false, do_loglevel}}},
{"mark_post_data", {0, 0, {false, do_mark_post_data}}},
{"mkdir", {1, 6, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {0, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
{"perform_apex_config", {0, 0, {false, do_perform_apex_config}}},
{"umount", {1, 1, {false, do_umount}}},
{"umount_all", {0, 1, {false, do_umount_all}}},
{"update_linker_config", {0, 0, {false, do_update_linker_config}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"remount_userdata", {0, 0, {false, do_remount_userdata}}},
{"restart", {1, 1, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
在fs_mgr_remount.cpp添加log,编译remount二进制,发现执行串口remount和adb remount都会打印这个log,说明两条指令又走到一起了。那为何表现会不同?
又有新的发现,若先串口执行remount,再adb remount,CP覆盖也会报错。。。晕了
反复尝试,发现adb remount后也会覆盖报错,奇怪了,难道是我替换后remount后导致的?
先排查到这里,至少基本理清了adb remount的调用流程,也算是有收获。
真相来了。。。
前面adb remount后可以覆盖,串口remount不能覆盖是因为,adb remount后有push 过system/apex/com.android.adbd/bin/adbd这个文件,然后再手动cp就可以覆盖,而无法覆盖都是因为没有先adb push那个文件。所以导致能不能覆盖不是remount导致的,而是adb push 具备特异功能
二、新版本adb push 无需再手动改权限、selinux标签的原因
在老版本Android系统,若push文件到system/bin等路径下,需要手动改权限,若开启了selinux还需要手动改selinux标签,否则会导致系统启动失败或相应服务启动异常。而在新版本Android系统则无需担心,这些步骤由adb自动完成了。
文件:system\core\adb\daemon\file_sync_service.cpp
先看调试log:
03-17 02:27:30.294 7466 8560 E adbd : sync id_name stat_v2 name:system/apex/com.android.adbd/bin/adbd
03-17 02:27:30.301 7466 8560 E adbd : sync id_name send_v2 name:system/apex/com.android.adbd/bin/adbd
adb push会先查询目标文件的属性信息并记录,然后才发送文件。在发送文件过程还会记录原文件夹、文件的属性,并会修改 push后的文件的属性
大概的实现代码如下:
static bool handle_sync_command(int fd, std::vector<char>& buffer) {
D("sync: waiting for request");
SyncRequest request;
if (!ReadFdExactly(fd, &request, sizeof(request))) {
SendSyncFail(fd, "command read failure");
return false;
}
size_t path_length = request.path_length;
if (path_length > 1024) {
SendSyncFail(fd, "path too long");
return false;
}
char name[1025];
if (!ReadFdExactly(fd, name, path_length)) {
SendSyncFail(fd, "filename read failure");
return false;
}
name[path_length] = 0;
std::string id_name = sync_id_to_name(request.id);
LOG(ERROR) << "sync id_name:" << id_name.c_str() << " name:" << name;
switch (request.id) {
case ID_LSTAT_V1:
if (!do_lstat_v1(fd, name)) return false;
break;
case ID_LSTAT_V2:
case ID_STAT_V2:
if (!do_stat_v2(fd, request.id, name)) return false;
break;
case ID_LIST_V1:
if (!do_list_v1(fd, name)) return false;
break;
case ID_LIST_V2:
if (!do_list_v2(fd, name)) return false;
break;
case ID_SEND_V1:
if (!do_send_v1(fd, name, buffer)) return false;
break;
case ID_SEND_V2:
if (!do_send_v2(fd, name, buffer)) return false;
break;
case ID_RECV_V1:
if (!do_recv_v1(fd, name, buffer)) return false;
break;
case ID_RECV_V2:
if (!do_recv_v2(fd, name, buffer)) return false;
break;
case ID_QUIT:
return false;
default:
SendSyncFail(fd, StringPrintf("unknown command %08x", request.id));
return false;
}
return true;
}
static bool do_stat_v2(int s, uint32_t id, const char* path) {
syncmsg msg = {};
msg.stat_v2.id = id;
decltype(&stat) stat_fn;
if (id == ID_STAT_V2) {
stat_fn = stat;
} else {
stat_fn = lstat;
}
struct stat st = {};
int rc = stat_fn(path, &st);
if (rc == -1) {
msg.stat_v2.error = errno_to_wire(errno);
} else {
msg.stat_v2.dev = st.st_dev;
msg.stat_v2.ino = st.st_ino;
msg.stat_v2.mode = st.st_mode;
msg.stat_v2.nlink = st.st_nlink;
msg.stat_v2.uid = st.st_uid;
msg.stat_v2.gid = st.st_gid;
msg.stat_v2.size = st.st_size;
msg.stat_v2.atime = st.st_atime;
msg.stat_v2.mtime = st.st_mtime;
msg.stat_v2.ctime = st.st_ctime;
}
return WriteFdExactly(s, &msg.stat_v2, sizeof(msg.stat_v2));
}
static bool handle_send_file(borrowed_fd s, const char* path, uint32_t* timestamp, uid_t uid,
gid_t gid, uint64_t capabilities, mode_t mode, bool compressed,
std::vector<char>& buffer, bool do_unlink) {
int rc;
syncmsg msg;
__android_log_security_bswrite(SEC_TAG_ADB_SEND_FILE, path);
unique_fd fd(adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
if (fd < 0 && errno == ENOENT) {
if (!secure_mkdirs(Dirname(path))) {
SendSyncFailErrno(s, "secure_mkdirs failed");
goto fail;
}
fd.reset(adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
}
if (fd < 0 && errno == EEXIST) {
fd.reset(adb_open_mode(path, O_WRONLY | O_CLOEXEC, mode));
}
if (fd < 0) {
SendSyncFailErrno(s, "couldn't create file");
goto fail;
} else {
if (fchown(fd.get(), uid, gid) == -1) {
SendSyncFailErrno(s, "fchown failed");
goto fail;
}
#if defined(__ANDROID__)
// Not all filesystems support setting SELinux labels. http://b/23530370.
selinux_android_restorecon(path, 0);
#endif
// fchown clears the setuid bit - restore it if present.
// Ignore the result of calling fchmod. It's not supported
// by all filesystems, so we don't check for success. b/12441485
fchmod(fd.get(), mode);
}
{
rc = posix_fadvise(fd.get(), 0, 0,
POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED);
if (rc != 0) {
D("[ Failed to fadvise: %s ]", strerror(rc));
}
bool result;
if (compressed) {
result = handle_send_file_compressed(s, std::move(fd), timestamp);
} else {
result = handle_send_file_uncompressed(s, std::move(fd), timestamp, buffer);
}
if (!result) {
goto fail;
}
if (!update_capabilities(path, capabilities)) {
SendSyncFailErrno(s, "update_capabilities failed");
goto fail;
}
msg.status.id = ID_OKAY;
msg.status.msglen = 0;
return WriteFdExactly(s, &msg.status, sizeof(msg.status));
}
fail:
....
}
调用fchmod改push后文件的属性