目的:調查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);}
#endifif (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 offstatic 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/12441485fchmod(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后文件的屬性