From ac61dba1bea84e4913a971b1a17fc89157a1eafb Mon Sep 17 00:00:00 2001 From: Robin Liu Date: Wed, 8 Nov 2023 09:42:19 -0500 Subject: [PATCH] Enable capturing from Windows --- capture_service/CMakeLists.txt | 13 ++- capture_service/command_utils.cc | 13 --- capture_service/command_utils.h | 5 +- capture_service/command_utils_win32.cc | 156 +++++++++++++++++++++++++ capture_service/device_mgr.cc | 19 ++- capture_service/device_mgr.h | 25 +++- scripts/build_android.bat | 4 +- ui/main_window.cpp | 1 - ui/trace_window.cpp | 23 ++-- ui/trace_window.h | 3 +- 10 files changed, 225 insertions(+), 37 deletions(-) create mode 100644 capture_service/command_utils_win32.cc diff --git a/capture_service/CMakeLists.txt b/capture_service/CMakeLists.txt index 927a54dc6..726643b06 100644 --- a/capture_service/CMakeLists.txt +++ b/capture_service/CMakeLists.txt @@ -58,17 +58,24 @@ target_link_libraries(dive_grpc_proto PRIVATE ${_PROTOBUF_LIBPROTOBUF}) set_property(TARGET dive_grpc_proto PROPERTY POSITION_INDEPENDENT_CODE 1) +if(WIN32) + set(COMMAND_UTILS_SRC "command_utils_win32.cc") + add_definitions(-DUNICODE -D_UNICODE) +else() + set(COMMAND_UTILS_SRC "command_utils.cc") +endif() + if(ANDROID) add_library(service service.cc trace_mgr.cc android_trace_mgr.cc - command_utils.cc) + ${COMMAND_UTILS_SRC}) else() add_library(service service.cc trace_mgr.cc - command_utils.cc) + ${COMMAND_UTILS_SRC}) endif() target_link_libraries(service PRIVATE dive_grpc_proto @@ -102,7 +109,7 @@ else() absl::algorithm ) - add_library(device_mgr device_mgr.cc command_utils.cc android_application.cc) + add_library(device_mgr device_mgr.cc ${COMMAND_UTILS_SRC} android_application.cc) target_link_libraries(device_mgr absl::flags absl::flags_parse diff --git a/capture_service/command_utils.cc b/capture_service/command_utils.cc index 7ddd505d3..a43277583 100644 --- a/capture_service/command_utils.cc +++ b/capture_service/command_utils.cc @@ -28,14 +28,6 @@ limitations under the License. namespace Dive { -#if defined(WIN32) -// TODO(renfeng): figure out how to run exe on Windows -CommandResult RunCommand(const std::string &command, bool quiet) -{ - CommandResult result; - return result; -} -#else CommandResult RunCommand(const std::string &command, bool quiet) { CommandResult result; @@ -78,10 +70,5 @@ CommandResult RunCommand(const std::string &command, bool quiet) return result; } -#endif -CommandResult AdbSession::Run(const std::string &command, bool quiet) const -{ - return RunCommand("adb -s " + m_serial + " " + command, quiet); -} } // namespace Dive \ No newline at end of file diff --git a/capture_service/command_utils.h b/capture_service/command_utils.h index f81f031d4..9c71563e0 100644 --- a/capture_service/command_utils.h +++ b/capture_service/command_utils.h @@ -42,7 +42,10 @@ class AdbSession { } - CommandResult Run(const std::string &command, bool quiet = false) const; + inline CommandResult Run(const std::string &command, bool quiet = true) const + { + return RunCommand("adb -s " + m_serial + " " + command, quiet); + } private: std::string m_serial; diff --git a/capture_service/command_utils_win32.cc b/capture_service/command_utils_win32.cc new file mode 100644 index 000000000..9ce636086 --- /dev/null +++ b/capture_service/command_utils_win32.cc @@ -0,0 +1,156 @@ +/* +Copyright 2023 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "command_utils.h" + +#include +#include "absl/strings/ascii.h" +#include "log.h" +#ifndef WIN32 +# error "Build this for Win32 platform only" +#endif +#include + +namespace Dive +{ + +CommandResult RunCommand(const std::string &command, bool quiet) +{ + CommandResult result; + HANDLE hChildStdOutRd = NULL; + HANDLE hChildStdOutWr = NULL; + HANDLE hChildStdErrRd = NULL; + HANDLE hChildStdErrWr = NULL; + + SECURITY_ATTRIBUTES sa; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&sa, sizeof(sa)); + ZeroMemory(&si, sizeof(si)); + ZeroMemory(&pi, sizeof(pi)); + + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&hChildStdOutRd, &hChildStdOutWr, &sa, 0)) + { + LOGE("Create pipe to read stdout failed."); + return result; + } + if (!SetHandleInformation(hChildStdOutRd, HANDLE_FLAG_INHERIT, 0)) + { + LOGE("SetHandleInformation for stdout failed."); + return result; + } + if (!CreatePipe(&hChildStdErrRd, &hChildStdErrWr, &sa, 0)) + { + LOGE("CreatePipe for stderr failed"); + return result; + } + if (!SetHandleInformation(hChildStdErrRd, HANDLE_FLAG_INHERIT, 0)) + { + LOGE("SetHandleInformation for stdout failed"); + return result; + } + + si.cb = sizeof(si); + si.dwFlags |= STARTF_USESTDHANDLES; + si.hStdOutput = hChildStdOutWr; + si.hStdError = hChildStdErrWr; + + int len = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, NULL, 0); + std::vector cmd(len); + + int res = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, cmd.data(), len); + if (res == 0) + { + LOGE("Failed to convert std::string to utf-8 string"); + return result; + } + + bool bSuccess = CreateProcessW(NULL, + cmd.data(), // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + CREATE_UNICODE_ENVIRONMENT, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &si, // STARTUPINFO pointer + &pi); // receives PROCESS_INFORMATION + + if (!bSuccess) + { + LOGE("Error create process %d", GetLastError()); + return result; + } + else + { + + CloseHandle(hChildStdOutWr); + CloseHandle(hChildStdErrWr); + } + + BOOL success = FALSE; + char buf[4096]; + DWORD dwOutputRead, dwErrorRead; + + for (;;) + { + success = ReadFile(hChildStdOutRd, buf, sizeof(buf), &dwOutputRead, NULL); + result.m_output += std::string(buf, dwOutputRead); + if (!success && !dwOutputRead) + break; + } + result.m_output = absl::StripAsciiWhitespace(result.m_output); + + for (;;) + { + success = ReadFile(hChildStdErrRd, buf, sizeof(buf), &dwErrorRead, NULL); + result.m_err += std::string(buf, dwErrorRead); + + if (!success && !dwErrorRead) + break; + } + result.m_err = absl::StripAsciiWhitespace(result.m_err); + + CloseHandle(hChildStdOutRd); + CloseHandle(hChildStdErrRd); + + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, (LPDWORD)&result.m_ret); + LOGD("result->m_ret is %d\n", result.m_ret); + if (!quiet) + { + LOGI("Command: %s\n Output: %s\n", command.c_str(), result.m_output.c_str()); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + if (!result.Ok() && !quiet) + { + + LOGE("Command `%s` failed with return code %d, stderr: %s \n", + command.c_str(), + result.m_ret, + result.m_output.c_str()); + } + return result; +} + +} // namespace Dive diff --git a/capture_service/device_mgr.cc b/capture_service/device_mgr.cc index ac0618703..dbbb8ea71 100644 --- a/capture_service/device_mgr.cc +++ b/capture_service/device_mgr.cc @@ -30,6 +30,12 @@ limitations under the License. namespace Dive { +DeviceManager &GetDeviceManager() +{ + static DeviceManager mgr; + return mgr; +} + AndroidDevice::AndroidDevice(const std::string &serial) : m_serial(serial), m_adb(serial) @@ -42,6 +48,7 @@ AndroidDevice::AndroidDevice(const std::string &serial) : LOGD("enforce: %s\n", m_original_state.m_enforce.c_str()); LOGD("select: %s\n", GetDeviceDisplayName().c_str()); + LOGD("AndroidDevice created.\n"); } AndroidDevice::~AndroidDevice() @@ -50,6 +57,7 @@ AndroidDevice::~AndroidDevice() { CleanupDevice(); } + LOGD("AndroidDevice destroyed.\n"); } std::string AndroidDevice::GetDeviceDisplayName() const @@ -72,25 +80,28 @@ std::vector AndroidDevice::ListPackage(PackageListOptions option) c std::vector fields = absl::StrSplit(line, ':'); if (fields.size() == 2 && fields[0] == "package") { + std::string package(absl::StripAsciiWhitespace(fields[1])); if (option.debuggable_only) { - std::string output = Adb().Run("shell dumpsys package " + fields[1]).Out(); + std::string output = Adb().Run("shell dumpsys package " + package).Out(); // TODO: find out more reliable way to find if app is debuggable. if (!absl::StrContains(output, "DEBUGGABLE")) { continue; } } - package_list.push_back(fields[1]); + package_list.push_back(package); } } std::sort(package_list.begin(), package_list.end()); return package_list; } -std::filesystem::path ResolveAndroidLibPath(std::string name) +std::filesystem::path ResolveAndroidLibPath(const std::string &name) { - std::vector search_paths{ std::filesystem::path{ + LOGD("cwd: %s\n", std::filesystem::current_path().c_str()); + std::vector search_paths{ std::filesystem::path{ "./install" }, + std::filesystem::path{ "../../build_android/Release/bin" }, std::filesystem::path{ "../../install" }, std::filesystem::path{ "./" } }; diff --git a/capture_service/device_mgr.h b/capture_service/device_mgr.h index c38a1251d..1e5056678 100644 --- a/capture_service/device_mgr.h +++ b/capture_service/device_mgr.h @@ -45,6 +45,9 @@ class AndroidDevice explicit AndroidDevice(const std::string &serial); ~AndroidDevice(); + AndroidDevice &operator=(const AndroidDevice &) = delete; + AndroidDevice(const AndroidDevice &) = delete; + struct PackageListOptions { bool with_system_package; @@ -75,23 +78,37 @@ class AndroidDevice class DeviceManager { public: + DeviceManager() = default; + DeviceManager &operator=(const DeviceManager &) = delete; + DeviceManager(const DeviceManager &) = delete; + std::vector ListDevice() const; AndroidDevice *SelectDevice(const std::string &serial) { assert(!serial.empty()); - m_device = std::make_unique(serial); + if (!serial.empty()) + { + m_device = std::make_unique(serial); + } return m_device.get(); } - void RemoveDevice() { m_device = nullptr; } + void RemoveDevice() + { + if (m_device != nullptr) + { + m_device = nullptr; + } + } AndroidDevice *GetDevice() const { return m_device.get(); } void Cleanup(const std::string &serial, const std::string &package); private: - std::unique_ptr m_device; + std::unique_ptr m_device{ nullptr }; }; -std::filesystem::path ResolveAndroidLibPath(std::string name); +std::filesystem::path ResolveAndroidLibPath(const std::string &name); +DeviceManager &GetDeviceManager(); } // namespace Dive \ No newline at end of file diff --git a/scripts/build_android.bat b/scripts/build_android.bat index 2a2fb3484..bb98afbdf 100644 --- a/scripts/build_android.bat +++ b/scripts/build_android.bat @@ -29,6 +29,7 @@ set startTime=%time% pushd !BUILD_DIR! cmake -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK_HOME%/build/cmake/android.toolchain.cmake ^ -G "Ninja"^ + -DCMAKE_MAKE_PROGRAM="ninja" ^ -DCMAKE_BUILD_TYPE=!build! ^ -DCMAKE_SYSTEM_NAME=Android ^ -DANDROID_ABI=arm64-v8a ^ @@ -41,7 +42,8 @@ set startTime=%time% -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER ^ %SRC_DIR% - cmake --build . --config=!build! -j %NUMBER_OF_PROCESSORS% + cmake --build . --config=!build! -j + if "%%b" == "Release" cmake --install . popd )) diff --git a/ui/main_window.cpp b/ui/main_window.cpp index 0a27a7f06..f4ef1848d 100644 --- a/ui/main_window.cpp +++ b/ui/main_window.cpp @@ -223,7 +223,6 @@ MainWindow::MainWindow() horizontal_splitter->setStretchFactor(2, 1); m_trace_dig = new TraceDialog(this); - m_trace_dig->setAttribute(Qt::WA_DeleteOnClose); // Main Window requires a central widget. // Make the horizontal splitter that central widget so it takes up the whole area. diff --git a/ui/trace_window.cpp b/ui/trace_window.cpp index 3e5fa84d8..cf00da021 100644 --- a/ui/trace_window.cpp +++ b/ui/trace_window.cpp @@ -46,6 +46,7 @@ const std::vector kAppTypes{ "Vulkan", "OpenXR" }; // ================================================================================================= TraceDialog::TraceDialog(QWidget *parent) { + qDebug() << "TraceDialog created."; m_capture_layout = new QHBoxLayout(); m_dev_label = new QLabel(tr("Devices:")); m_pkg_label = new QLabel(tr("Packages:")); @@ -65,7 +66,7 @@ TraceDialog::TraceDialog(QWidget *parent) m_main_layout = new QVBoxLayout(); - m_devices = m_dev_mgr.ListDevice(); + m_devices = Dive::GetDeviceManager().ListDevice(); for (size_t i = 0; i < m_devices.size(); i++) { QStandardItem *item = new QStandardItem(m_devices[i].c_str()); @@ -74,12 +75,12 @@ TraceDialog::TraceDialog(QWidget *parent) if (!m_devices.empty()) { m_cur_dev = m_devices[0]; + qDebug() << "Device selected: " << m_cur_dev.c_str(); } - qDebug() << "Device selected: " << m_cur_dev.c_str(); if (!m_cur_dev.empty()) { - auto device = m_dev_mgr.SelectDevice(m_cur_dev); + auto device = Dive::GetDeviceManager().SelectDevice(m_cur_dev); device->SetupDevice(); m_pkg_list = device->ListPackage(); } @@ -126,17 +127,23 @@ TraceDialog::TraceDialog(QWidget *parent) TraceDialog::~TraceDialog() { - m_dev_mgr.RemoveDevice(); + qDebug() << "TraceDialog destroyed."; + Dive::GetDeviceManager().RemoveDevice(); } void TraceDialog::OnDeviceSelected(const QString &s) { + if (s.isEmpty()) + { + qDebug() << "No devices selected"; + return; + } int dev_index = m_dev_box->currentIndex(); qDebug() << "Device selected: " << m_cur_dev.c_str(); assert(static_cast(dev_index) < m_devices.size()); m_cur_dev = m_devices[dev_index]; assert(m_cur_dev == s.toStdString()); - auto device = m_dev_mgr.SelectDevice(m_cur_dev); + auto device = Dive::GetDeviceManager().SelectDevice(m_cur_dev); m_pkg_list = device->ListPackage(); m_pkg_model->clear(); for (size_t i = 0; i < m_pkg_list.size(); i++) @@ -154,8 +161,7 @@ void TraceDialog::OnPackageSelected(const QString &s) void TraceDialog::OnStartClicked() { - auto device = m_dev_mgr.GetDevice(); - assert(device != nullptr); + auto device = Dive::GetDeviceManager().GetDevice(); if (!device) { // TODO: add a warning message. @@ -226,7 +232,8 @@ void TraceDialog::OnCaptureClicked() std::filesystem::path p(*trace_file_path); std::filesystem::path target(capture_path); target /= p.filename(); - m_dev_mgr.GetDevice()->RetrieveTraceFile(*trace_file_path, target.generic_string()); + Dive::GetDeviceManager().GetDevice()->RetrieveTraceFile(*trace_file_path, + target.generic_string()); qDebug() << "Capture saved at " << target.generic_string().c_str(); QString capture_saved_path(target.generic_string().c_str()); diff --git a/ui/trace_window.h b/ui/trace_window.h index 6e9747c99..53165d726 100644 --- a/ui/trace_window.h +++ b/ui/trace_window.h @@ -36,7 +36,7 @@ class TraceDialog : public QDialog public: TraceDialog(QWidget *parent = 0); ~TraceDialog(); - void Cleanup() { m_dev_mgr.RemoveDevice(); } + void Cleanup() { Dive::GetDeviceManager().RemoveDevice(); } private slots: void OnDeviceSelected(const QString &); @@ -70,7 +70,6 @@ private slots: QHBoxLayout *m_button_layout; QVBoxLayout *m_main_layout; - Dive::DeviceManager m_dev_mgr; std::vector m_devices; std::string m_cur_dev; std::vector m_pkg_list;