Refactor native code
This commit is contained in:
parent
be6e449fb4
commit
c362449e33
|
|
@ -0,0 +1,9 @@
|
|||
package com.elderdrivers.riru.edxp.art;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
public class ClassLinker {
|
||||
|
||||
public static native void setEntryPointsToInterpreter(Member method);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.elderdrivers.riru.edxp.art;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
public class Heap {
|
||||
|
||||
public static native int waitForGcToComplete(long thread);
|
||||
|
||||
}
|
||||
|
|
@ -18,17 +18,6 @@ public class ConfigManager {
|
|||
private static final String DYNAMIC_MODULES = INSTALLER_DATA_BASE_DIR + "conf/dynamicmodules";
|
||||
private static final Set<String> WHITE_LIST = Collections.singleton(INSTALLER_PACKAGE_NAME);
|
||||
private static final HashMap<String, Boolean> compatModeCache = new HashMap<>();
|
||||
private static volatile boolean IS_DYNAMIC_MODULES = false;
|
||||
|
||||
public static boolean isDynamicModulesMode() {
|
||||
return IS_DYNAMIC_MODULES;
|
||||
}
|
||||
|
||||
public static synchronized void setDynamicModulesMode(boolean isDynamicModulesMode) {
|
||||
if (isDynamicModulesMode != IS_DYNAMIC_MODULES) {
|
||||
IS_DYNAMIC_MODULES = isDynamicModulesMode;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean shouldUseWhitelist() {
|
||||
return isFileExists(USE_WHITE_LIST);
|
||||
|
|
@ -59,4 +48,16 @@ public class ConfigManager {
|
|||
private static boolean isFileExists(String path) {
|
||||
return SELinuxHelper.getAppDataFileService().checkFileExists(path);
|
||||
}
|
||||
|
||||
public static native boolean isBlackWhiteListEnabled();
|
||||
|
||||
public static native boolean isDynamicModulesEnabled();
|
||||
|
||||
public static native boolean isResourcesHookEnabled();
|
||||
|
||||
public static native boolean isDeoptBootImageEnabled();
|
||||
|
||||
public static native String getInstallerPackageName();
|
||||
|
||||
public static native boolean isAppNeedHook(String appDataDir);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.elderdrivers.riru.edxp.core;
|
||||
|
||||
public class ResourcesHook {
|
||||
|
||||
public static native boolean initXResourcesNative();
|
||||
|
||||
public static native boolean removeFinalFlagNative(Class clazz);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.elderdrivers.riru.edxp.core;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Yahfa {
|
||||
|
||||
public static native boolean backupAndHookNative(Object target, Method hook, Method backup);
|
||||
|
||||
public static native void ensureMethodCached(Method hook, Method backup);
|
||||
|
||||
// JNI.ToReflectedMethod() could return either Method or Constructor
|
||||
public static native Object findMethodNative(Class targetClass, String methodName, String methodSig);
|
||||
|
||||
public static native void init(int SDK_version);
|
||||
|
||||
public static native void setMethodNonCompilable(Member member);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.elderdrivers.riru.edxp.framework;
|
||||
|
||||
public class Zygote {
|
||||
|
||||
// prevent from fatal error caused by holding not whitelisted file descriptors when forking zygote
|
||||
// https://github.com/rovo89/Xposed/commit/b3ba245ad04cd485699fb1d2ebde7117e58214ff
|
||||
public static native void closeFilesBeforeFork();
|
||||
|
||||
public static native void reopenFilesAfterFork();
|
||||
|
||||
}
|
||||
|
|
@ -3,10 +3,10 @@ import org.gradle.internal.os.OperatingSystem
|
|||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
version "v0.4.2.3_alpha"
|
||||
version "v0.4.3.0_alpha"
|
||||
|
||||
ext {
|
||||
versionCode = "4230"
|
||||
versionCode = "4300"
|
||||
module_name = "EdXposed"
|
||||
jar_dest_dir = "${projectDir}/template_override/system/framework/"
|
||||
is_windows = OperatingSystem.current().isWindows()
|
||||
|
|
@ -31,7 +31,7 @@ android {
|
|||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
|
||||
cppFlags "-std=c++11 -ffixed-x18 -Qunused-arguments -frtti"
|
||||
cppFlags "-std=c++17 -ffixed-x18 -Qunused-arguments -frtti"
|
||||
cFlags "-std=gnu99 -ffixed-x18 -Qunused-arguments -frtti"
|
||||
}
|
||||
}
|
||||
|
|
@ -49,8 +49,8 @@ android {
|
|||
release {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags "-fvisibility=hidden -fvisibility-inlines-hidden -O2 -s"
|
||||
cFlags "-fvisibility=hidden -fvisibility-inlines-hidden -O2 -s"
|
||||
cppFlags "-fvisibility=hidden -fvisibility-inlines-hidden -O2 -s -Wno-unused-value"
|
||||
cFlags "-fvisibility=hidden -fvisibility-inlines-hidden -O2 -s -Wno-unused-value"
|
||||
}
|
||||
}
|
||||
minifyEnabled true
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ cmake_minimum_required(VERSION 3.4.1)
|
|||
add_subdirectory(xhook)
|
||||
add_subdirectory(riru)
|
||||
add_subdirectory(yahfa)
|
||||
add_subdirectory(substrate)
|
||||
add_subdirectory(substrate)
|
||||
add_subdirectory(android)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(SRC_LIST
|
||||
android-base/logging.cpp
|
||||
android-base/file.cpp
|
||||
android-base/threads.cpp
|
||||
android-base/strings.cpp
|
||||
)
|
||||
add_library(android STATIC ${SRC_LIST})
|
||||
|
||||
target_include_directories(android PUBLIC .)
|
||||
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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 "android-base/file.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <ftw.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#if defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#define O_NOFOLLOW 0
|
||||
#define OS_PATH_SEPARATOR '\\'
|
||||
#else
|
||||
#define OS_PATH_SEPARATOR '/'
|
||||
#endif
|
||||
#include "android-base/logging.h" // and must be after windows.h for ERROR
|
||||
#include "android-base/macros.h" // For TEMP_FAILURE_RETRY on Darwin.
|
||||
#include "android-base/unique_fd.h"
|
||||
#include "android-base/utf8.h"
|
||||
#ifdef _WIN32
|
||||
int mkstemp(char* template_name) {
|
||||
if (_mktemp(template_name) == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
// Use open() to match the close() that TemporaryFile's destructor does.
|
||||
// Use O_BINARY to match base file APIs.
|
||||
return open(template_name, O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR);
|
||||
}
|
||||
char* mkdtemp(char* template_name) {
|
||||
if (_mktemp(template_name) == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (_mkdir(template_name) == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
return template_name;
|
||||
}
|
||||
#endif
|
||||
namespace {
|
||||
std::string GetSystemTempDir() {
|
||||
#if defined(__ANDROID__)
|
||||
const auto* tmpdir = getenv("TMPDIR");
|
||||
if (tmpdir == nullptr) tmpdir = "/data/local/tmp";
|
||||
if (access(tmpdir, R_OK | W_OK | X_OK) == 0) {
|
||||
return tmpdir;
|
||||
}
|
||||
// Tests running in app context can't access /data/local/tmp,
|
||||
// so try current directory if /data/local/tmp is not accessible.
|
||||
return ".";
|
||||
#elif defined(_WIN32)
|
||||
char tmp_dir[MAX_PATH];
|
||||
DWORD result = GetTempPathA(sizeof(tmp_dir), tmp_dir); // checks TMP env
|
||||
CHECK_NE(result, 0ul) << "GetTempPathA failed, error: " << GetLastError();
|
||||
CHECK_LT(result, sizeof(tmp_dir)) << "path truncated to: " << result;
|
||||
// GetTempPath() returns a path with a trailing slash, but init()
|
||||
// does not expect that, so remove it.
|
||||
CHECK_EQ(tmp_dir[result - 1], '\\');
|
||||
tmp_dir[result - 1] = '\0';
|
||||
return tmp_dir;
|
||||
#else
|
||||
const auto* tmpdir = getenv("TMPDIR");
|
||||
if (tmpdir == nullptr) tmpdir = "/tmp";
|
||||
return tmpdir;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
TemporaryFile::TemporaryFile() {
|
||||
init(GetSystemTempDir());
|
||||
}
|
||||
TemporaryFile::TemporaryFile(const std::string& tmp_dir) {
|
||||
init(tmp_dir);
|
||||
}
|
||||
TemporaryFile::~TemporaryFile() {
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
if (remove_file_) {
|
||||
unlink(path);
|
||||
}
|
||||
}
|
||||
int TemporaryFile::release() {
|
||||
int result = fd;
|
||||
fd = -1;
|
||||
return result;
|
||||
}
|
||||
void TemporaryFile::init(const std::string& tmp_dir) {
|
||||
snprintf(path, sizeof(path), "%s%cTemporaryFile-XXXXXX", tmp_dir.c_str(), OS_PATH_SEPARATOR);
|
||||
fd = mkstemp(path);
|
||||
}
|
||||
TemporaryDir::TemporaryDir() {
|
||||
init(GetSystemTempDir());
|
||||
}
|
||||
TemporaryDir::~TemporaryDir() {
|
||||
if (!remove_dir_and_contents_) return;
|
||||
auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
|
||||
switch (file_type) {
|
||||
case FTW_D:
|
||||
case FTW_DP:
|
||||
case FTW_DNR:
|
||||
if (rmdir(child) == -1) {
|
||||
PLOG(ERROR) << "rmdir " << child;
|
||||
}
|
||||
break;
|
||||
case FTW_NS:
|
||||
default:
|
||||
if (rmdir(child) != -1) break;
|
||||
// FALLTHRU (for gcc, lint, pcc, etc; and following for clang)
|
||||
FALLTHROUGH_INTENDED;
|
||||
case FTW_F:
|
||||
case FTW_SL:
|
||||
case FTW_SLN:
|
||||
if (unlink(child) == -1) {
|
||||
PLOG(ERROR) << "unlink " << child;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
nftw(path, callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
|
||||
}
|
||||
bool TemporaryDir::init(const std::string& tmp_dir) {
|
||||
snprintf(path, sizeof(path), "%s%cTemporaryDir-XXXXXX", tmp_dir.c_str(), OS_PATH_SEPARATOR);
|
||||
return (mkdtemp(path) != nullptr);
|
||||
}
|
||||
namespace android {
|
||||
namespace base {
|
||||
// Versions of standard library APIs that support UTF-8 strings.
|
||||
using namespace android::base::utf8;
|
||||
bool ReadFdToString(borrowed_fd fd, std::string* content) {
|
||||
content->clear();
|
||||
// Although original we had small files in mind, this code gets used for
|
||||
// very large files too, where the std::string growth heuristics might not
|
||||
// be suitable. https://code.google.com/p/android/issues/detail?id=258500.
|
||||
struct stat sb;
|
||||
if (fstat(fd.get(), &sb) != -1 && sb.st_size > 0) {
|
||||
content->reserve(sb.st_size);
|
||||
}
|
||||
char buf[BUFSIZ];
|
||||
ssize_t n;
|
||||
while ((n = TEMP_FAILURE_RETRY(read(fd.get(), &buf[0], sizeof(buf)))) > 0) {
|
||||
content->append(buf, n);
|
||||
}
|
||||
return (n == 0) ? true : false;
|
||||
}
|
||||
bool ReadFileToString(const std::string& path, std::string* content, bool follow_symlinks) {
|
||||
content->clear();
|
||||
int flags = O_RDONLY | O_CLOEXEC | O_BINARY | (follow_symlinks ? 0 : O_NOFOLLOW);
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags)));
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
return ReadFdToString(fd, content);
|
||||
}
|
||||
bool WriteStringToFd(const std::string& content, borrowed_fd fd) {
|
||||
const char* p = content.data();
|
||||
size_t left = content.size();
|
||||
while (left > 0) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, left));
|
||||
if (n == -1) {
|
||||
return false;
|
||||
}
|
||||
p += n;
|
||||
left -= n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool CleanUpAfterFailedWrite(const std::string& path) {
|
||||
// Something went wrong. Let's not leave a corrupt file lying around.
|
||||
int saved_errno = errno;
|
||||
unlink(path.c_str());
|
||||
errno = saved_errno;
|
||||
return false;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
bool WriteStringToFile(const std::string& content, const std::string& path,
|
||||
mode_t mode, uid_t owner, gid_t group,
|
||||
bool follow_symlinks) {
|
||||
int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY |
|
||||
(follow_symlinks ? 0 : O_NOFOLLOW);
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)));
|
||||
if (fd == -1) {
|
||||
PLOG(ERROR) << "android::WriteStringToFile open failed";
|
||||
return false;
|
||||
}
|
||||
// We do an explicit fchmod here because we assume that the caller really
|
||||
// meant what they said and doesn't want the umask-influenced mode.
|
||||
if (fchmod(fd, mode) == -1) {
|
||||
PLOG(ERROR) << "android::WriteStringToFile fchmod failed";
|
||||
return CleanUpAfterFailedWrite(path);
|
||||
}
|
||||
if (fchown(fd, owner, group) == -1) {
|
||||
PLOG(ERROR) << "android::WriteStringToFile fchown failed";
|
||||
return CleanUpAfterFailedWrite(path);
|
||||
}
|
||||
if (!WriteStringToFd(content, fd)) {
|
||||
PLOG(ERROR) << "android::WriteStringToFile write failed";
|
||||
return CleanUpAfterFailedWrite(path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
bool WriteStringToFile(const std::string& content, const std::string& path,
|
||||
bool follow_symlinks) {
|
||||
int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY |
|
||||
(follow_symlinks ? 0 : O_NOFOLLOW);
|
||||
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags, 0666)));
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
return WriteStringToFd(content, fd) || CleanUpAfterFailedWrite(path);
|
||||
}
|
||||
bool ReadFully(borrowed_fd fd, void* data, size_t byte_count) {
|
||||
uint8_t* p = reinterpret_cast<uint8_t*>(data);
|
||||
size_t remaining = byte_count;
|
||||
while (remaining > 0) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), p, remaining));
|
||||
if (n <= 0) return false;
|
||||
p += n;
|
||||
remaining -= n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
// Windows implementation of pread. Note that this DOES move the file descriptors read position,
|
||||
// but it does so atomically.
|
||||
static ssize_t pread(borrowed_fd fd, void* data, size_t byte_count, off64_t offset) {
|
||||
DWORD bytes_read;
|
||||
OVERLAPPED overlapped;
|
||||
memset(&overlapped, 0, sizeof(OVERLAPPED));
|
||||
overlapped.Offset = static_cast<DWORD>(offset);
|
||||
overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
|
||||
if (!ReadFile(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), data,
|
||||
static_cast<DWORD>(byte_count), &bytes_read, &overlapped)) {
|
||||
// In case someone tries to read errno (since this is masquerading as a POSIX call)
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return static_cast<ssize_t>(bytes_read);
|
||||
}
|
||||
#endif
|
||||
bool ReadFullyAtOffset(borrowed_fd fd, void* data, size_t byte_count, off64_t offset) {
|
||||
uint8_t* p = reinterpret_cast<uint8_t*>(data);
|
||||
while (byte_count > 0) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(pread(fd.get(), p, byte_count, offset));
|
||||
if (n <= 0) return false;
|
||||
p += n;
|
||||
byte_count -= n;
|
||||
offset += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count) {
|
||||
const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
|
||||
size_t remaining = byte_count;
|
||||
while (remaining > 0) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, remaining));
|
||||
if (n == -1) return false;
|
||||
p += n;
|
||||
remaining -= n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool RemoveFileIfExists(const std::string& path, std::string* err) {
|
||||
struct stat st;
|
||||
#if defined(_WIN32)
|
||||
// TODO: Windows version can't handle symbolic links correctly.
|
||||
int result = stat(path.c_str(), &st);
|
||||
bool file_type_removable = (result == 0 && S_ISREG(st.st_mode));
|
||||
#else
|
||||
int result = lstat(path.c_str(), &st);
|
||||
bool file_type_removable = (result == 0 && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)));
|
||||
#endif
|
||||
if (result == -1) {
|
||||
if (errno == ENOENT || errno == ENOTDIR) return true;
|
||||
if (err != nullptr) *err = strerror(errno);
|
||||
return false;
|
||||
}
|
||||
if (result == 0) {
|
||||
if (!file_type_removable) {
|
||||
if (err != nullptr) {
|
||||
*err = "is not a regular file or symbolic link";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (unlink(path.c_str()) == -1) {
|
||||
if (err != nullptr) {
|
||||
*err = strerror(errno);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
bool Readlink(const std::string& path, std::string* result) {
|
||||
result->clear();
|
||||
// Most Linux file systems (ext2 and ext4, say) limit symbolic links to
|
||||
// 4095 bytes. Since we'll copy out into the string anyway, it doesn't
|
||||
// waste memory to just start there. We add 1 so that we can recognize
|
||||
// whether it actually fit (rather than being truncated to 4095).
|
||||
std::vector<char> buf(4095 + 1);
|
||||
while (true) {
|
||||
ssize_t size = readlink(path.c_str(), &buf[0], buf.size());
|
||||
// Unrecoverable error?
|
||||
if (size == -1) return false;
|
||||
// It fit! (If size == buf.size(), it may have been truncated.)
|
||||
if (static_cast<size_t>(size) < buf.size()) {
|
||||
result->assign(&buf[0], size);
|
||||
return true;
|
||||
}
|
||||
// Double our buffer and try again.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if !defined(_WIN32)
|
||||
bool Realpath(const std::string& path, std::string* result) {
|
||||
result->clear();
|
||||
// realpath may exit with EINTR. Retry if so.
|
||||
char* realpath_buf = nullptr;
|
||||
do {
|
||||
realpath_buf = realpath(path.c_str(), nullptr);
|
||||
} while (realpath_buf == nullptr && errno == EINTR);
|
||||
if (realpath_buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
result->assign(realpath_buf);
|
||||
free(realpath_buf);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
std::string GetExecutablePath() {
|
||||
#if defined(__linux__)
|
||||
std::string path;
|
||||
android::base::Readlink("/proc/self/exe", &path);
|
||||
return path;
|
||||
#elif defined(__APPLE__)
|
||||
char path[PATH_MAX + 1];
|
||||
uint32_t path_len = sizeof(path);
|
||||
int rc = _NSGetExecutablePath(path, &path_len);
|
||||
if (rc < 0) {
|
||||
std::unique_ptr<char> path_buf(new char[path_len]);
|
||||
_NSGetExecutablePath(path_buf.get(), &path_len);
|
||||
return path_buf.get();
|
||||
}
|
||||
return path;
|
||||
#elif defined(_WIN32)
|
||||
char path[PATH_MAX + 1];
|
||||
DWORD result = GetModuleFileName(NULL, path, sizeof(path) - 1);
|
||||
if (result == 0 || result == sizeof(path) - 1) return "";
|
||||
path[PATH_MAX - 1] = 0;
|
||||
return path;
|
||||
#else
|
||||
#error unknown OS
|
||||
#endif
|
||||
}
|
||||
std::string GetExecutableDirectory() {
|
||||
return Dirname(GetExecutablePath());
|
||||
}
|
||||
std::string Basename(const std::string& path) {
|
||||
// Copy path because basename may modify the string passed in.
|
||||
std::string result(path);
|
||||
#if !defined(__BIONIC__)
|
||||
// Use lock because basename() may write to a process global and return a
|
||||
// pointer to that. Note that this locking strategy only works if all other
|
||||
// callers to basename in the process also grab this same lock, but its
|
||||
// better than nothing. Bionic's basename returns a thread-local buffer.
|
||||
static std::mutex& basename_lock = *new std::mutex();
|
||||
std::lock_guard<std::mutex> lock(basename_lock);
|
||||
#endif
|
||||
// Note that if std::string uses copy-on-write strings, &str[0] will cause
|
||||
// the copy to be made, so there is no chance of us accidentally writing to
|
||||
// the storage for 'path'.
|
||||
char* name = basename(&result[0]);
|
||||
// In case basename returned a pointer to a process global, copy that string
|
||||
// before leaving the lock.
|
||||
result.assign(name);
|
||||
return result;
|
||||
}
|
||||
std::string Dirname(const std::string& path) {
|
||||
// Copy path because dirname may modify the string passed in.
|
||||
std::string result(path);
|
||||
#if !defined(__BIONIC__)
|
||||
// Use lock because dirname() may write to a process global and return a
|
||||
// pointer to that. Note that this locking strategy only works if all other
|
||||
// callers to dirname in the process also grab this same lock, but its
|
||||
// better than nothing. Bionic's dirname returns a thread-local buffer.
|
||||
static std::mutex& dirname_lock = *new std::mutex();
|
||||
std::lock_guard<std::mutex> lock(dirname_lock);
|
||||
#endif
|
||||
// Note that if std::string uses copy-on-write strings, &str[0] will cause
|
||||
// the copy to be made, so there is no chance of us accidentally writing to
|
||||
// the storage for 'path'.
|
||||
char* parent = dirname(&result[0]);
|
||||
// In case dirname returned a pointer to a process global, copy that string
|
||||
// before leaving the lock.
|
||||
result.assign(parent);
|
||||
return result;
|
||||
}
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#include "android-base/macros.h"
|
||||
#include "android-base/off64_t.h"
|
||||
#include "android-base/unique_fd.h"
|
||||
#if !defined(_WIN32) && !defined(O_BINARY)
|
||||
/** Windows needs O_BINARY, but Unix never mangles line endings. */
|
||||
#define O_BINARY 0
|
||||
#endif
|
||||
#if defined(_WIN32) && !defined(O_CLOEXEC)
|
||||
/** Windows has O_CLOEXEC but calls it O_NOINHERIT for some reason. */
|
||||
#define O_CLOEXEC O_NOINHERIT
|
||||
#endif
|
||||
class TemporaryFile {
|
||||
public:
|
||||
TemporaryFile();
|
||||
explicit TemporaryFile(const std::string& tmp_dir);
|
||||
~TemporaryFile();
|
||||
// Release the ownership of fd, caller is reponsible for closing the
|
||||
// fd or stream properly.
|
||||
int release();
|
||||
// Don't remove the temporary file in the destructor.
|
||||
void DoNotRemove() { remove_file_ = false; }
|
||||
int fd;
|
||||
char path[1024];
|
||||
private:
|
||||
void init(const std::string& tmp_dir);
|
||||
bool remove_file_ = true;
|
||||
DISALLOW_COPY_AND_ASSIGN(TemporaryFile);
|
||||
};
|
||||
class TemporaryDir {
|
||||
public:
|
||||
TemporaryDir();
|
||||
~TemporaryDir();
|
||||
// Don't remove the temporary dir in the destructor.
|
||||
void DoNotRemove() { remove_dir_and_contents_ = false; }
|
||||
char path[1024];
|
||||
private:
|
||||
bool init(const std::string& tmp_dir);
|
||||
bool remove_dir_and_contents_ = true;
|
||||
DISALLOW_COPY_AND_ASSIGN(TemporaryDir);
|
||||
};
|
||||
namespace android {
|
||||
namespace base {
|
||||
bool ReadFdToString(borrowed_fd fd, std::string* content);
|
||||
bool ReadFileToString(const std::string& path, std::string* content,
|
||||
bool follow_symlinks = false);
|
||||
bool WriteStringToFile(const std::string& content, const std::string& path,
|
||||
bool follow_symlinks = false);
|
||||
bool WriteStringToFd(const std::string& content, borrowed_fd fd);
|
||||
#if !defined(_WIN32)
|
||||
bool WriteStringToFile(const std::string& content, const std::string& path,
|
||||
mode_t mode, uid_t owner, gid_t group,
|
||||
bool follow_symlinks = false);
|
||||
#endif
|
||||
bool ReadFully(borrowed_fd fd, void* data, size_t byte_count);
|
||||
// Reads `byte_count` bytes from the file descriptor at the specified offset.
|
||||
// Returns false if there was an IO error or EOF was reached before reading `byte_count` bytes.
|
||||
//
|
||||
// NOTE: On Linux/Mac, this function wraps pread, which provides atomic read support without
|
||||
// modifying the read pointer of the file descriptor. On Windows, however, the read pointer does
|
||||
// get modified. This means that ReadFullyAtOffset can be used concurrently with other calls to the
|
||||
// same function, but concurrently seeking or reading incrementally can lead to unexpected
|
||||
// behavior.
|
||||
bool ReadFullyAtOffset(borrowed_fd fd, void* data, size_t byte_count, off64_t offset);
|
||||
bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count);
|
||||
bool RemoveFileIfExists(const std::string& path, std::string* err = nullptr);
|
||||
#if !defined(_WIN32)
|
||||
bool Realpath(const std::string& path, std::string* result);
|
||||
bool Readlink(const std::string& path, std::string* result);
|
||||
#endif
|
||||
std::string GetExecutablePath();
|
||||
std::string GetExecutableDirectory();
|
||||
// Like the regular basename and dirname, but thread-safe on all
|
||||
// platforms and capable of correctly handling exotic Windows paths.
|
||||
std::string Basename(const std::string& path);
|
||||
std::string Dirname(const std::string& path);
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include "android-base/logging.h"
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <time.h>
|
||||
// For getprogname(3) or program_invocation_short_name.
|
||||
#if defined(__ANDROID__) || defined(__APPLE__)
|
||||
#include <stdlib.h>
|
||||
#elif defined(__GLIBC__)
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#if defined(__linux__)
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// Headers for LogMessage::LogLine.
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#include <android/set_abort_message.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/threads.h>
|
||||
namespace android {
|
||||
namespace base {
|
||||
// BSD-based systems like Android/macOS have getprogname(). Others need us to provide one.
|
||||
#if defined(__GLIBC__) || defined(_WIN32)
|
||||
static const char* getprogname() {
|
||||
#if defined(__GLIBC__)
|
||||
return program_invocation_short_name;
|
||||
#elif defined(_WIN32)
|
||||
static bool first = true;
|
||||
static char progname[MAX_PATH] = {};
|
||||
if (first) {
|
||||
snprintf(progname, sizeof(progname), "%s",
|
||||
android::base::Basename(android::base::GetExecutablePath()).c_str());
|
||||
first = false;
|
||||
}
|
||||
return progname;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
static const char* GetFileBasename(const char* file) {
|
||||
// We can't use basename(3) even on Unix because the Mac doesn't
|
||||
// have a non-modifying basename.
|
||||
const char* last_slash = strrchr(file, '/');
|
||||
if (last_slash != nullptr) {
|
||||
return last_slash + 1;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
const char* last_backslash = strrchr(file, '\\');
|
||||
if (last_backslash != nullptr) {
|
||||
return last_backslash + 1;
|
||||
}
|
||||
#endif
|
||||
return file;
|
||||
}
|
||||
#if defined(__linux__)
|
||||
static int OpenKmsg() {
|
||||
#if defined(__ANDROID__)
|
||||
// pick up 'file w /dev/kmsg' environment from daemon's init rc file
|
||||
const auto val = getenv("ANDROID_FILE__dev_kmsg");
|
||||
if (val != nullptr) {
|
||||
int fd;
|
||||
if (android::base::ParseInt(val, &fd, 0)) {
|
||||
auto flags = fcntl(fd, F_GETFL);
|
||||
if ((flags != -1) && ((flags & O_ACCMODE) == O_WRONLY)) return fd;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
|
||||
}
|
||||
#endif
|
||||
static std::mutex& LoggingLock() {
|
||||
static auto& logging_lock = *new std::mutex();
|
||||
return logging_lock;
|
||||
}
|
||||
static LogFunction& Logger() {
|
||||
#ifdef __ANDROID__
|
||||
static auto& logger = *new LogFunction(LogdLogger());
|
||||
#else
|
||||
static auto& logger = *new LogFunction(StderrLogger);
|
||||
#endif
|
||||
return logger;
|
||||
}
|
||||
static AbortFunction& Aborter() {
|
||||
static auto& aborter = *new AbortFunction(DefaultAborter);
|
||||
return aborter;
|
||||
}
|
||||
static std::recursive_mutex& TagLock() {
|
||||
static auto& tag_lock = *new std::recursive_mutex();
|
||||
return tag_lock;
|
||||
}
|
||||
static std::string* gDefaultTag;
|
||||
std::string GetDefaultTag() {
|
||||
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
||||
if (gDefaultTag == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return *gDefaultTag;
|
||||
}
|
||||
void SetDefaultTag(const std::string& tag) {
|
||||
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
||||
if (gDefaultTag != nullptr) {
|
||||
delete gDefaultTag;
|
||||
gDefaultTag = nullptr;
|
||||
}
|
||||
if (!tag.empty()) {
|
||||
gDefaultTag = new std::string(tag);
|
||||
}
|
||||
}
|
||||
static bool gInitialized = false;
|
||||
static LogSeverity gMinimumLogSeverity = INFO;
|
||||
#if defined(__linux__)
|
||||
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
|
||||
const char* tag, const char*, unsigned int, const char* msg) {
|
||||
// clang-format off
|
||||
static constexpr int kLogSeverityToKernelLogLevel[] = {
|
||||
[android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log
|
||||
// level)
|
||||
[android::base::DEBUG] = 7, // KERN_DEBUG
|
||||
[android::base::INFO] = 6, // KERN_INFO
|
||||
[android::base::WARNING] = 4, // KERN_WARNING
|
||||
[android::base::ERROR] = 3, // KERN_ERROR
|
||||
[android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT
|
||||
[android::base::FATAL] = 2, // KERN_CRIT
|
||||
};
|
||||
// clang-format on
|
||||
static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
|
||||
"Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
|
||||
static int klog_fd = OpenKmsg();
|
||||
if (klog_fd == -1) return;
|
||||
int level = kLogSeverityToKernelLogLevel[severity];
|
||||
// The kernel's printk buffer is only 1024 bytes.
|
||||
// TODO: should we automatically break up long lines into multiple lines?
|
||||
// Or we could log but with something like "..." at the end?
|
||||
char buf[1024];
|
||||
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);
|
||||
if (size > sizeof(buf)) {
|
||||
size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
|
||||
level, tag, size);
|
||||
}
|
||||
iovec iov[1];
|
||||
iov[0].iov_base = buf;
|
||||
iov[0].iov_len = size;
|
||||
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
|
||||
}
|
||||
#endif
|
||||
void StderrLogger(LogId, LogSeverity severity, const char* tag, const char* file, unsigned int line,
|
||||
const char* message) {
|
||||
struct tm now;
|
||||
time_t t = time(nullptr);
|
||||
#if defined(_WIN32)
|
||||
localtime_s(&now, &t);
|
||||
#else
|
||||
localtime_r(&t, &now);
|
||||
#endif
|
||||
char timestamp[32];
|
||||
strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now);
|
||||
static const char log_characters[] = "VDIWEFF";
|
||||
static_assert(arraysize(log_characters) - 1 == FATAL + 1,
|
||||
"Mismatch in size of log_characters and values in LogSeverity");
|
||||
char severity_char = log_characters[severity];
|
||||
fprintf(stderr, "%s %c %s %5d %5" PRIu64 " %s:%u] %s\n", tag ? tag : "nullptr", severity_char,
|
||||
timestamp, getpid(), GetThreadId(), file, line, message);
|
||||
}
|
||||
void StdioLogger(LogId, LogSeverity severity, const char* /*tag*/, const char* /*file*/,
|
||||
unsigned int /*line*/, const char* message) {
|
||||
if (severity >= WARNING) {
|
||||
fflush(stdout);
|
||||
fprintf(stderr, "%s: %s\n", GetFileBasename(getprogname()), message);
|
||||
} else {
|
||||
fprintf(stdout, "%s\n", message);
|
||||
}
|
||||
}
|
||||
void DefaultAborter(const char* abort_message) {
|
||||
#ifdef __ANDROID__
|
||||
android_set_abort_message(abort_message);
|
||||
#else
|
||||
UNUSED(abort_message);
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
LogdLogger::LogdLogger(LogId default_log_id) : default_log_id_(default_log_id) {
|
||||
}
|
||||
void LogdLogger::operator()(LogId id, LogSeverity severity, const char* tag,
|
||||
const char* file, unsigned int line,
|
||||
const char* message) {
|
||||
static constexpr android_LogPriority kLogSeverityToAndroidLogPriority[] = {
|
||||
ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO,
|
||||
ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL,
|
||||
ANDROID_LOG_FATAL,
|
||||
};
|
||||
static_assert(arraysize(kLogSeverityToAndroidLogPriority) == FATAL + 1,
|
||||
"Mismatch in size of kLogSeverityToAndroidLogPriority and values in LogSeverity");
|
||||
int priority = kLogSeverityToAndroidLogPriority[severity];
|
||||
if (id == DEFAULT) {
|
||||
id = default_log_id_;
|
||||
}
|
||||
static constexpr log_id kLogIdToAndroidLogId[] = {
|
||||
LOG_ID_MAX, LOG_ID_MAIN, LOG_ID_SYSTEM,
|
||||
};
|
||||
static_assert(arraysize(kLogIdToAndroidLogId) == SYSTEM + 1,
|
||||
"Mismatch in size of kLogIdToAndroidLogId and values in LogId");
|
||||
log_id lg_id = kLogIdToAndroidLogId[id];
|
||||
if (priority == ANDROID_LOG_FATAL) {
|
||||
__android_log_buf_print(lg_id, priority, tag, "%s:%u] %s", file, line,
|
||||
message);
|
||||
} else {
|
||||
__android_log_buf_print(lg_id, priority, tag, "%s", message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
|
||||
SetLogger(std::forward<LogFunction>(logger));
|
||||
SetAborter(std::forward<AbortFunction>(aborter));
|
||||
if (gInitialized) {
|
||||
return;
|
||||
}
|
||||
gInitialized = true;
|
||||
// Stash the command line for later use. We can use /proc/self/cmdline on
|
||||
// Linux to recover this, but we don't have that luxury on the Mac/Windows,
|
||||
// and there are a couple of argv[0] variants that are commonly used.
|
||||
if (argv != nullptr) {
|
||||
SetDefaultTag(basename(argv[0]));
|
||||
}
|
||||
const char* tags = getenv("ANDROID_LOG_TAGS");
|
||||
if (tags == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::vector<std::string> specs = Split(tags, " ");
|
||||
for (size_t i = 0; i < specs.size(); ++i) {
|
||||
// "tag-pattern:[vdiwefs]"
|
||||
std::string spec(specs[i]);
|
||||
if (spec.size() == 3 && StartsWith(spec, "*:")) {
|
||||
switch (spec[2]) {
|
||||
case 'v':
|
||||
gMinimumLogSeverity = VERBOSE;
|
||||
continue;
|
||||
case 'd':
|
||||
gMinimumLogSeverity = DEBUG;
|
||||
continue;
|
||||
case 'i':
|
||||
gMinimumLogSeverity = INFO;
|
||||
continue;
|
||||
case 'w':
|
||||
gMinimumLogSeverity = WARNING;
|
||||
continue;
|
||||
case 'e':
|
||||
gMinimumLogSeverity = ERROR;
|
||||
continue;
|
||||
case 'f':
|
||||
gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
|
||||
continue;
|
||||
// liblog will even suppress FATAL if you say 's' for silent, but that's
|
||||
// crazy!
|
||||
case 's':
|
||||
gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
|
||||
<< ")";
|
||||
}
|
||||
}
|
||||
void SetLogger(LogFunction&& logger) {
|
||||
std::lock_guard<std::mutex> lock(LoggingLock());
|
||||
Logger() = std::move(logger);
|
||||
}
|
||||
void SetAborter(AbortFunction&& aborter) {
|
||||
std::lock_guard<std::mutex> lock(LoggingLock());
|
||||
Aborter() = std::move(aborter);
|
||||
}
|
||||
// This indirection greatly reduces the stack impact of having lots of
|
||||
// checks/logging in a function.
|
||||
class LogMessageData {
|
||||
public:
|
||||
LogMessageData(const char* file, unsigned int line, LogId id, LogSeverity severity,
|
||||
const char* tag, int error)
|
||||
: file_(GetFileBasename(file)),
|
||||
line_number_(line),
|
||||
id_(id),
|
||||
severity_(severity),
|
||||
tag_(tag),
|
||||
error_(error) {}
|
||||
const char* GetFile() const {
|
||||
return file_;
|
||||
}
|
||||
unsigned int GetLineNumber() const {
|
||||
return line_number_;
|
||||
}
|
||||
LogSeverity GetSeverity() const {
|
||||
return severity_;
|
||||
}
|
||||
const char* GetTag() const { return tag_; }
|
||||
LogId GetId() const {
|
||||
return id_;
|
||||
}
|
||||
int GetError() const {
|
||||
return error_;
|
||||
}
|
||||
std::ostream& GetBuffer() {
|
||||
return buffer_;
|
||||
}
|
||||
std::string ToString() const {
|
||||
return buffer_.str();
|
||||
}
|
||||
private:
|
||||
std::ostringstream buffer_;
|
||||
const char* const file_;
|
||||
const unsigned int line_number_;
|
||||
const LogId id_;
|
||||
const LogSeverity severity_;
|
||||
const char* const tag_;
|
||||
const int error_;
|
||||
DISALLOW_COPY_AND_ASSIGN(LogMessageData);
|
||||
};
|
||||
LogMessage::LogMessage(const char* file, unsigned int line, LogId id, LogSeverity severity,
|
||||
const char* tag, int error)
|
||||
: data_(new LogMessageData(file, line, id, severity, tag, error)) {}
|
||||
LogMessage::~LogMessage() {
|
||||
// Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
|
||||
if (!WOULD_LOG(data_->GetSeverity())) {
|
||||
return;
|
||||
}
|
||||
// Finish constructing the message.
|
||||
if (data_->GetError() != -1) {
|
||||
data_->GetBuffer() << ": " << strerror(data_->GetError());
|
||||
}
|
||||
std::string msg(data_->ToString());
|
||||
if (data_->GetSeverity() == FATAL) {
|
||||
#ifdef __ANDROID__
|
||||
// Set the bionic abort message early to avoid liblog doing it
|
||||
// with the individual lines, so that we get the whole message.
|
||||
android_set_abort_message(msg.c_str());
|
||||
#endif
|
||||
}
|
||||
{
|
||||
// Do the actual logging with the lock held.
|
||||
std::lock_guard<std::mutex> lock(LoggingLock());
|
||||
if (msg.find('\n') == std::string::npos) {
|
||||
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetId(), data_->GetSeverity(),
|
||||
data_->GetTag(), msg.c_str());
|
||||
} else {
|
||||
msg += '\n';
|
||||
size_t i = 0;
|
||||
while (i < msg.size()) {
|
||||
size_t nl = msg.find('\n', i);
|
||||
msg[nl] = '\0';
|
||||
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetId(), data_->GetSeverity(),
|
||||
data_->GetTag(), &msg[i]);
|
||||
// Undo the zero-termination so we can give the complete message to the aborter.
|
||||
msg[nl] = '\n';
|
||||
i = nl + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Abort if necessary.
|
||||
if (data_->GetSeverity() == FATAL) {
|
||||
Aborter()(msg.c_str());
|
||||
}
|
||||
}
|
||||
std::ostream& LogMessage::stream() {
|
||||
return data_->GetBuffer();
|
||||
}
|
||||
void LogMessage::LogLine(const char* file, unsigned int line, LogId id, LogSeverity severity,
|
||||
const char* tag, const char* message) {
|
||||
if (tag == nullptr) {
|
||||
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
||||
if (gDefaultTag == nullptr) {
|
||||
gDefaultTag = new std::string(getprogname());
|
||||
}
|
||||
Logger()(id, severity, gDefaultTag->c_str(), file, line, message);
|
||||
} else {
|
||||
Logger()(id, severity, tag, file, line, message);
|
||||
}
|
||||
}
|
||||
LogSeverity GetMinimumLogSeverity() {
|
||||
return gMinimumLogSeverity;
|
||||
}
|
||||
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity) {
|
||||
LogSeverity old_severity = gMinimumLogSeverity;
|
||||
gMinimumLogSeverity = new_severity;
|
||||
return old_severity;
|
||||
}
|
||||
ScopedLogSeverity::ScopedLogSeverity(LogSeverity new_severity) {
|
||||
old_ = SetMinimumLogSeverity(new_severity);
|
||||
}
|
||||
ScopedLogSeverity::~ScopedLogSeverity() {
|
||||
SetMinimumLogSeverity(old_);
|
||||
}
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,429 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
//
|
||||
// Google-style C++ logging.
|
||||
//
|
||||
// This header provides a C++ stream interface to logging.
|
||||
//
|
||||
// To log:
|
||||
//
|
||||
// LOG(INFO) << "Some text; " << some_value;
|
||||
//
|
||||
// Replace `INFO` with any severity from `enum LogSeverity`.
|
||||
//
|
||||
// To log the result of a failed function and include the string
|
||||
// representation of `errno` at the end:
|
||||
//
|
||||
// PLOG(ERROR) << "Write failed";
|
||||
//
|
||||
// The output will be something like `Write failed: I/O error`.
|
||||
// Remember this as 'P' as in perror(3).
|
||||
//
|
||||
// To output your own types, simply implement operator<< as normal.
|
||||
//
|
||||
// By default, output goes to logcat on Android and stderr on the host.
|
||||
// A process can use `SetLogger` to decide where all logging goes.
|
||||
// Implementations are provided for logcat, stderr, and dmesg.
|
||||
//
|
||||
// By default, the process' name is used as the log tag.
|
||||
// Code can choose a specific log tag by defining LOG_TAG
|
||||
// before including this header.
|
||||
// This header also provides assertions:
|
||||
//
|
||||
// CHECK(must_be_true);
|
||||
// CHECK_EQ(a, b) << z_is_interesting_too;
|
||||
// NOTE: For Windows, you must include logging.h after windows.h to allow the
|
||||
// following code to suppress the evil ERROR macro:
|
||||
#ifdef _WIN32
|
||||
// windows.h includes wingdi.h which defines an evil macro ERROR.
|
||||
#ifdef ERROR
|
||||
#undef ERROR
|
||||
#endif
|
||||
#endif
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include "macros.h"
|
||||
// Note: DO NOT USE DIRECTLY. Use LOG_TAG instead.
|
||||
#ifdef _LOG_TAG_INTERNAL
|
||||
#error "_LOG_TAG_INTERNAL must not be defined"
|
||||
#endif
|
||||
#ifdef LOG_TAG
|
||||
#define _LOG_TAG_INTERNAL LOG_TAG
|
||||
#else
|
||||
#define _LOG_TAG_INTERNAL "EdXposed"
|
||||
#endif
|
||||
namespace android {
|
||||
namespace base {
|
||||
enum LogSeverity {
|
||||
VERBOSE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
FATAL_WITHOUT_ABORT,
|
||||
FATAL,
|
||||
};
|
||||
enum LogId {
|
||||
DEFAULT,
|
||||
MAIN,
|
||||
SYSTEM,
|
||||
};
|
||||
using LogFunction = std::function<void(LogId, LogSeverity, const char*, const char*,
|
||||
unsigned int, const char*)>;
|
||||
using AbortFunction = std::function<void(const char*)>;
|
||||
// Loggers for use with InitLogging/SetLogger.
|
||||
// Log to the kernel log (dmesg).
|
||||
void KernelLogger(LogId, LogSeverity, const char*, const char*, unsigned int, const char*);
|
||||
// Log to stderr in the full logcat format (with pid/tid/time/tag details).
|
||||
void StderrLogger(LogId, LogSeverity, const char*, const char*, unsigned int, const char*);
|
||||
// Log just the message to stdout/stderr (without pid/tid/time/tag details).
|
||||
// The choice of stdout versus stderr is based on the severity.
|
||||
// Errors are also prefixed by the program name (as with err(3)/error(3)).
|
||||
// Useful for replacing printf(3)/perror(3)/err(3)/error(3) in command-line tools.
|
||||
void StdioLogger(LogId, LogSeverity, const char*, const char*, unsigned int, const char*);
|
||||
void DefaultAborter(const char* abort_message);
|
||||
std::string GetDefaultTag();
|
||||
void SetDefaultTag(const std::string& tag);
|
||||
#ifdef __ANDROID__
|
||||
// We expose this even though it is the default because a user that wants to
|
||||
// override the default log buffer will have to construct this themselves.
|
||||
class LogdLogger {
|
||||
public:
|
||||
explicit LogdLogger(LogId default_log_id = android::base::MAIN);
|
||||
void operator()(LogId, LogSeverity, const char* tag, const char* file,
|
||||
unsigned int line, const char* message);
|
||||
private:
|
||||
LogId default_log_id_;
|
||||
};
|
||||
#endif
|
||||
// Configure logging based on ANDROID_LOG_TAGS environment variable.
|
||||
// We need to parse a string that looks like
|
||||
//
|
||||
// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
|
||||
//
|
||||
// The tag (or '*' for the global level) comes first, followed by a colon and a
|
||||
// letter indicating the minimum priority level we're expected to log. This can
|
||||
// be used to reveal or conceal logs with specific tags.
|
||||
#ifdef __ANDROID__
|
||||
#define INIT_LOGGING_DEFAULT_LOGGER LogdLogger()
|
||||
#else
|
||||
#define INIT_LOGGING_DEFAULT_LOGGER StderrLogger
|
||||
#endif
|
||||
void InitLogging(char* argv[],
|
||||
LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER,
|
||||
AbortFunction&& aborter = DefaultAborter);
|
||||
#undef INIT_LOGGING_DEFAULT_LOGGER
|
||||
// Replace the current logger.
|
||||
void SetLogger(LogFunction&& logger);
|
||||
// Replace the current aborter.
|
||||
void SetAborter(AbortFunction&& aborter);
|
||||
class ErrnoRestorer {
|
||||
public:
|
||||
ErrnoRestorer()
|
||||
: saved_errno_(errno) {
|
||||
}
|
||||
~ErrnoRestorer() {
|
||||
errno = saved_errno_;
|
||||
}
|
||||
// Allow this object to be used as part of && operation.
|
||||
operator bool() const {
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
const int saved_errno_;
|
||||
DISALLOW_COPY_AND_ASSIGN(ErrnoRestorer);
|
||||
};
|
||||
// A helper macro that produces an expression that accepts both a qualified name and an
|
||||
// unqualified name for a LogSeverity, and returns a LogSeverity value.
|
||||
// Note: DO NOT USE DIRECTLY. This is an implementation detail.
|
||||
#define SEVERITY_LAMBDA(severity) ([&]() { \
|
||||
using ::android::base::VERBOSE; \
|
||||
using ::android::base::DEBUG; \
|
||||
using ::android::base::INFO; \
|
||||
using ::android::base::WARNING; \
|
||||
using ::android::base::ERROR; \
|
||||
using ::android::base::FATAL_WITHOUT_ABORT; \
|
||||
using ::android::base::FATAL; \
|
||||
return (severity); }())
|
||||
#ifdef __clang_analyzer__
|
||||
// Clang's static analyzer does not see the conditional statement inside
|
||||
// LogMessage's destructor that will abort on FATAL severity.
|
||||
#define ABORT_AFTER_LOG_FATAL for (;; abort())
|
||||
struct LogAbortAfterFullExpr {
|
||||
~LogAbortAfterFullExpr() __attribute__((noreturn)) { abort(); }
|
||||
explicit operator bool() const { return false; }
|
||||
};
|
||||
// Provides an expression that evaluates to the truthiness of `x`, automatically
|
||||
// aborting if `c` is true.
|
||||
#define ABORT_AFTER_LOG_EXPR_IF(c, x) (((c) && ::android::base::LogAbortAfterFullExpr()) || (x))
|
||||
// Note to the static analyzer that we always execute FATAL logs in practice.
|
||||
#define MUST_LOG_MESSAGE(severity) (SEVERITY_LAMBDA(severity) == ::android::base::FATAL)
|
||||
#else
|
||||
#define ABORT_AFTER_LOG_FATAL
|
||||
#define ABORT_AFTER_LOG_EXPR_IF(c, x) (x)
|
||||
#define MUST_LOG_MESSAGE(severity) false
|
||||
#endif
|
||||
#define ABORT_AFTER_LOG_FATAL_EXPR(x) ABORT_AFTER_LOG_EXPR_IF(true, x)
|
||||
// Defines whether the given severity will be logged or silently swallowed.
|
||||
#define WOULD_LOG(severity) \
|
||||
(UNLIKELY((SEVERITY_LAMBDA(severity)) >= ::android::base::GetMinimumLogSeverity()) || \
|
||||
MUST_LOG_MESSAGE(severity))
|
||||
// Get an ostream that can be used for logging at the given severity and to the default
|
||||
// destination.
|
||||
//
|
||||
// Notes:
|
||||
// 1) This will not check whether the severity is high enough. One should use WOULD_LOG to filter
|
||||
// usage manually.
|
||||
// 2) This does not save and restore errno.
|
||||
#define LOG_STREAM(severity) LOG_STREAM_TO(DEFAULT, severity)
|
||||
// Get an ostream that can be used for logging at the given severity and to the
|
||||
// given destination. The same notes as for LOG_STREAM apply.
|
||||
#define LOG_STREAM_TO(dest, severity) \
|
||||
::android::base::LogMessage(__FILE__, __LINE__, ::android::base::dest, \
|
||||
SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1) \
|
||||
.stream()
|
||||
// Logs a message to logcat on Android otherwise to stderr. If the severity is
|
||||
// FATAL it also causes an abort. For example:
|
||||
//
|
||||
// LOG(FATAL) << "We didn't expect to reach here";
|
||||
#define LOG(severity) LOG_TO(DEFAULT, severity)
|
||||
// Checks if we want to log something, and sets up appropriate RAII objects if
|
||||
// so.
|
||||
// Note: DO NOT USE DIRECTLY. This is an implementation detail.
|
||||
#define LOGGING_PREAMBLE(severity) \
|
||||
(WOULD_LOG(severity) && \
|
||||
ABORT_AFTER_LOG_EXPR_IF((SEVERITY_LAMBDA(severity)) == ::android::base::FATAL, true) && \
|
||||
::android::base::ErrnoRestorer())
|
||||
// Logs a message to logcat with the specified log ID on Android otherwise to
|
||||
// stderr. If the severity is FATAL it also causes an abort.
|
||||
// Use an expression here so we can support the << operator following the macro,
|
||||
// like "LOG(DEBUG) << xxx;".
|
||||
#define LOG_TO(dest, severity) LOGGING_PREAMBLE(severity) && LOG_STREAM_TO(dest, severity)
|
||||
// A variant of LOG that also logs the current errno value. To be used when
|
||||
// library calls fail.
|
||||
#define PLOG(severity) PLOG_TO(DEFAULT, severity)
|
||||
// Behaves like PLOG, but logs to the specified log ID.
|
||||
#define PLOG_TO(dest, severity) \
|
||||
LOGGING_PREAMBLE(severity) && \
|
||||
::android::base::LogMessage(__FILE__, __LINE__, ::android::base::dest, \
|
||||
SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, errno) \
|
||||
.stream()
|
||||
// Marker that code is yet to be implemented.
|
||||
#define UNIMPLEMENTED(level) \
|
||||
LOG(level) << __PRETTY_FUNCTION__ << " unimplemented "
|
||||
// Check whether condition x holds and LOG(FATAL) if not. The value of the
|
||||
// expression x is only evaluated once. Extra logging can be appended using <<
|
||||
// after. For example:
|
||||
//
|
||||
// CHECK(false == true) results in a log message of
|
||||
// "Check failed: false == true".
|
||||
#define CHECK(x) \
|
||||
LIKELY((x)) || ABORT_AFTER_LOG_FATAL_EXPR(false) || \
|
||||
::android::base::LogMessage(__FILE__, __LINE__, ::android::base::DEFAULT, \
|
||||
::android::base::FATAL, _LOG_TAG_INTERNAL, -1) \
|
||||
.stream() \
|
||||
<< "Check failed: " #x << " "
|
||||
// clang-format off
|
||||
// Helper for CHECK_xx(x,y) macros.
|
||||
#define CHECK_OP(LHS, RHS, OP) \
|
||||
for (auto _values = ::android::base::MakeEagerEvaluator(LHS, RHS); \
|
||||
UNLIKELY(!(_values.lhs OP _values.rhs)); \
|
||||
/* empty */) \
|
||||
ABORT_AFTER_LOG_FATAL \
|
||||
::android::base::LogMessage(__FILE__, __LINE__, ::android::base::DEFAULT, \
|
||||
::android::base::FATAL, _LOG_TAG_INTERNAL, -1) \
|
||||
.stream() \
|
||||
<< "Check failed: " << #LHS << " " << #OP << " " << #RHS << " (" #LHS "=" << _values.lhs \
|
||||
<< ", " #RHS "=" << _values.rhs << ") "
|
||||
// clang-format on
|
||||
// Check whether a condition holds between x and y, LOG(FATAL) if not. The value
|
||||
// of the expressions x and y is evaluated once. Extra logging can be appended
|
||||
// using << after. For example:
|
||||
//
|
||||
// CHECK_NE(0 == 1, false) results in
|
||||
// "Check failed: false != false (0==1=false, false=false) ".
|
||||
#define CHECK_EQ(x, y) CHECK_OP(x, y, == )
|
||||
#define CHECK_NE(x, y) CHECK_OP(x, y, != )
|
||||
#define CHECK_LE(x, y) CHECK_OP(x, y, <= )
|
||||
#define CHECK_LT(x, y) CHECK_OP(x, y, < )
|
||||
#define CHECK_GE(x, y) CHECK_OP(x, y, >= )
|
||||
#define CHECK_GT(x, y) CHECK_OP(x, y, > )
|
||||
// clang-format off
|
||||
// Helper for CHECK_STRxx(s1,s2) macros.
|
||||
#define CHECK_STROP(s1, s2, sense) \
|
||||
while (UNLIKELY((strcmp(s1, s2) == 0) != (sense))) \
|
||||
ABORT_AFTER_LOG_FATAL \
|
||||
::android::base::LogMessage(__FILE__, __LINE__, ::android::base::DEFAULT, \
|
||||
::android::base::FATAL, _LOG_TAG_INTERNAL, -1) \
|
||||
.stream() \
|
||||
<< "Check failed: " << "\"" << (s1) << "\"" \
|
||||
<< ((sense) ? " == " : " != ") << "\"" << (s2) << "\""
|
||||
// clang-format on
|
||||
// Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not.
|
||||
#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true)
|
||||
#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false)
|
||||
// Perform the pthread function call(args), LOG(FATAL) on error.
|
||||
#define CHECK_PTHREAD_CALL(call, args, what) \
|
||||
do { \
|
||||
int rc = call args; \
|
||||
if (rc != 0) { \
|
||||
errno = rc; \
|
||||
ABORT_AFTER_LOG_FATAL \
|
||||
PLOG(FATAL) << #call << " failed for " << (what); \
|
||||
} \
|
||||
} while (false)
|
||||
// CHECK that can be used in a constexpr function. For example:
|
||||
//
|
||||
// constexpr int half(int n) {
|
||||
// return
|
||||
// DCHECK_CONSTEXPR(n >= 0, , 0)
|
||||
// CHECK_CONSTEXPR((n & 1) == 0),
|
||||
// << "Extra debugging output: n = " << n, 0)
|
||||
// n / 2;
|
||||
// }
|
||||
#define CHECK_CONSTEXPR(x, out, dummy) \
|
||||
(UNLIKELY(!(x))) \
|
||||
? (LOG(FATAL) << "Check failed: " << #x out, dummy) \
|
||||
:
|
||||
// DCHECKs are debug variants of CHECKs only enabled in debug builds. Generally
|
||||
// CHECK should be used unless profiling identifies a CHECK as being in
|
||||
// performance critical code.
|
||||
#if defined(NDEBUG) && !defined(__clang_analyzer__)
|
||||
static constexpr bool kEnableDChecks = false;
|
||||
#else
|
||||
static constexpr bool kEnableDChecks = true;
|
||||
#endif
|
||||
#define DCHECK(x) \
|
||||
if (::android::base::kEnableDChecks) CHECK(x)
|
||||
#define DCHECK_EQ(x, y) \
|
||||
if (::android::base::kEnableDChecks) CHECK_EQ(x, y)
|
||||
#define DCHECK_NE(x, y) \
|
||||
if (::android::base::kEnableDChecks) CHECK_NE(x, y)
|
||||
#define DCHECK_LE(x, y) \
|
||||
if (::android::base::kEnableDChecks) CHECK_LE(x, y)
|
||||
#define DCHECK_LT(x, y) \
|
||||
if (::android::base::kEnableDChecks) CHECK_LT(x, y)
|
||||
#define DCHECK_GE(x, y) \
|
||||
if (::android::base::kEnableDChecks) CHECK_GE(x, y)
|
||||
#define DCHECK_GT(x, y) \
|
||||
if (::android::base::kEnableDChecks) CHECK_GT(x, y)
|
||||
#define DCHECK_STREQ(s1, s2) \
|
||||
if (::android::base::kEnableDChecks) CHECK_STREQ(s1, s2)
|
||||
#define DCHECK_STRNE(s1, s2) \
|
||||
if (::android::base::kEnableDChecks) CHECK_STRNE(s1, s2)
|
||||
#if defined(NDEBUG) && !defined(__clang_analyzer__)
|
||||
#define DCHECK_CONSTEXPR(x, out, dummy)
|
||||
#else
|
||||
#define DCHECK_CONSTEXPR(x, out, dummy) CHECK_CONSTEXPR(x, out, dummy)
|
||||
#endif
|
||||
// Temporary class created to evaluate the LHS and RHS, used with
|
||||
// MakeEagerEvaluator to infer the types of LHS and RHS.
|
||||
template <typename LHS, typename RHS>
|
||||
struct EagerEvaluator {
|
||||
constexpr EagerEvaluator(LHS l, RHS r) : lhs(l), rhs(r) {
|
||||
}
|
||||
LHS lhs;
|
||||
RHS rhs;
|
||||
};
|
||||
// Helper function for CHECK_xx.
|
||||
template <typename LHS, typename RHS>
|
||||
constexpr EagerEvaluator<LHS, RHS> MakeEagerEvaluator(LHS lhs, RHS rhs) {
|
||||
return EagerEvaluator<LHS, RHS>(lhs, rhs);
|
||||
}
|
||||
// Explicitly instantiate EagerEvalue for pointers so that char*s aren't treated
|
||||
// as strings. To compare strings use CHECK_STREQ and CHECK_STRNE. We rely on
|
||||
// signed/unsigned warnings to protect you against combinations not explicitly
|
||||
// listed below.
|
||||
#define EAGER_PTR_EVALUATOR(T1, T2) \
|
||||
template <> \
|
||||
struct EagerEvaluator<T1, T2> { \
|
||||
EagerEvaluator(T1 l, T2 r) \
|
||||
: lhs(reinterpret_cast<const void*>(l)), \
|
||||
rhs(reinterpret_cast<const void*>(r)) { \
|
||||
} \
|
||||
const void* lhs; \
|
||||
const void* rhs; \
|
||||
}
|
||||
EAGER_PTR_EVALUATOR(const char*, const char*);
|
||||
EAGER_PTR_EVALUATOR(const char*, char*);
|
||||
EAGER_PTR_EVALUATOR(char*, const char*);
|
||||
EAGER_PTR_EVALUATOR(char*, char*);
|
||||
EAGER_PTR_EVALUATOR(const unsigned char*, const unsigned char*);
|
||||
EAGER_PTR_EVALUATOR(const unsigned char*, unsigned char*);
|
||||
EAGER_PTR_EVALUATOR(unsigned char*, const unsigned char*);
|
||||
EAGER_PTR_EVALUATOR(unsigned char*, unsigned char*);
|
||||
EAGER_PTR_EVALUATOR(const signed char*, const signed char*);
|
||||
EAGER_PTR_EVALUATOR(const signed char*, signed char*);
|
||||
EAGER_PTR_EVALUATOR(signed char*, const signed char*);
|
||||
EAGER_PTR_EVALUATOR(signed char*, signed char*);
|
||||
// Data for the log message, not stored in LogMessage to avoid increasing the
|
||||
// stack size.
|
||||
class LogMessageData;
|
||||
// A LogMessage is a temporarily scoped object used by LOG and the unlikely part
|
||||
// of a CHECK. The destructor will abort if the severity is FATAL.
|
||||
class LogMessage {
|
||||
public:
|
||||
LogMessage(const char* file, unsigned int line, LogId id, LogSeverity severity, const char* tag,
|
||||
int error);
|
||||
~LogMessage();
|
||||
// Returns the stream associated with the message, the LogMessage performs
|
||||
// output when it goes out of scope.
|
||||
std::ostream& stream();
|
||||
// The routine that performs the actual logging.
|
||||
static void LogLine(const char* file, unsigned int line, LogId id, LogSeverity severity,
|
||||
const char* tag, const char* msg);
|
||||
private:
|
||||
const std::unique_ptr<LogMessageData> data_;
|
||||
DISALLOW_COPY_AND_ASSIGN(LogMessage);
|
||||
};
|
||||
// Get the minimum severity level for logging.
|
||||
LogSeverity GetMinimumLogSeverity();
|
||||
// Set the minimum severity level for logging, returning the old severity.
|
||||
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity);
|
||||
// Allows to temporarily change the minimum severity level for logging.
|
||||
class ScopedLogSeverity {
|
||||
public:
|
||||
explicit ScopedLogSeverity(LogSeverity level);
|
||||
~ScopedLogSeverity();
|
||||
private:
|
||||
LogSeverity old_;
|
||||
};
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
namespace std {
|
||||
// Emit a warning of ostream<< with std::string*. The intention was most likely to print *string.
|
||||
//
|
||||
// Note: for this to work, we need to have this in a namespace.
|
||||
// Note: lots of ifdef magic to make this work with Clang (platform) vs GCC (windows tools)
|
||||
// Note: using diagnose_if(true) under Clang and nothing under GCC/mingw as there is no common
|
||||
// attribute support.
|
||||
// Note: using a pragma because "-Wgcc-compat" (included in "-Weverything") complains about
|
||||
// diagnose_if.
|
||||
// Note: to print the pointer, use "<< static_cast<const void*>(string_pointer)" instead.
|
||||
// Note: a not-recommended alternative is to let Clang ignore the warning by adding
|
||||
// -Wno-user-defined-warnings to CPPFLAGS.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgcc-compat"
|
||||
#define OSTREAM_STRING_POINTER_USAGE_WARNING \
|
||||
__attribute__((diagnose_if(true, "Unexpected logging of string pointer", "warning")))
|
||||
inline std::ostream& operator<<(std::ostream& stream, const std::string* string_pointer)
|
||||
OSTREAM_STRING_POINTER_USAGE_WARNING {
|
||||
return stream << static_cast<const void*>(string_pointer);
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
} // namespace std
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stddef.h> // for size_t
|
||||
#include <unistd.h> // for TEMP_FAILURE_RETRY
|
||||
#include <utility>
|
||||
// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't.
|
||||
#ifndef TEMP_FAILURE_RETRY
|
||||
#define TEMP_FAILURE_RETRY(exp) \
|
||||
({ \
|
||||
decltype(exp) _rc; \
|
||||
do { \
|
||||
_rc = (exp); \
|
||||
} while (_rc == -1 && errno == EINTR); \
|
||||
_rc; \
|
||||
})
|
||||
#endif
|
||||
// A macro to disallow the copy constructor and operator= functions
|
||||
// This must be placed in the private: declarations for a class.
|
||||
//
|
||||
// For disallowing only assign or copy, delete the relevant operator or
|
||||
// constructor, for example:
|
||||
// void operator=(const TypeName&) = delete;
|
||||
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
|
||||
// semantically, one should either use disallow both or neither. Try to
|
||||
// avoid these in new code.
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&) = delete; \
|
||||
void operator=(const TypeName&) = delete
|
||||
// A macro to disallow all the implicit constructors, namely the
|
||||
// default constructor, copy constructor and operator= functions.
|
||||
//
|
||||
// This should be used in the private: declarations for a class
|
||||
// that wants to prevent anyone from instantiating it. This is
|
||||
// especially useful for classes containing only static methods.
|
||||
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
|
||||
TypeName() = delete; \
|
||||
DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||
// The expression is a compile-time constant, and therefore can be
|
||||
// used in defining new arrays, for example. If you use arraysize on
|
||||
// a pointer by mistake, you will get a compile-time error.
|
||||
//
|
||||
// One caveat is that arraysize() doesn't accept any array of an
|
||||
// anonymous type or a type defined inside a function. In these rare
|
||||
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
|
||||
// due to a limitation in C++'s template system. The limitation might
|
||||
// eventually be removed, but it hasn't happened yet.
|
||||
// This template function declaration is used in defining arraysize.
|
||||
// Note that the function doesn't need an implementation, as we only
|
||||
// use its type.
|
||||
template <typename T, size_t N>
|
||||
char(&ArraySizeHelper(T(&array)[N]))[N]; // NOLINT(readability/casting)
|
||||
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
|
||||
#define SIZEOF_MEMBER(t, f) sizeof(std::declval<t>().f)
|
||||
// Changing this definition will cause you a lot of pain. A majority of
|
||||
// vendor code defines LIKELY and UNLIKELY this way, and includes
|
||||
// this header through an indirect path.
|
||||
#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true ))
|
||||
#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false ))
|
||||
#define WARN_UNUSED __attribute__((warn_unused_result))
|
||||
// A deprecated function to call to create a false use of the parameter, for
|
||||
// example:
|
||||
// int foo(int x) { UNUSED(x); return 10; }
|
||||
// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED.
|
||||
template <typename... T>
|
||||
void UNUSED(const T&...) {
|
||||
}
|
||||
// An attribute to place on a parameter to a function, for example:
|
||||
// int foo(int x ATTRIBUTE_UNUSED) { return 10; }
|
||||
// to avoid compiler warnings.
|
||||
#define ATTRIBUTE_UNUSED __attribute__((__unused__))
|
||||
// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through
|
||||
// between switch labels:
|
||||
// switch (x) {
|
||||
// case 40:
|
||||
// case 41:
|
||||
// if (truth_is_out_there) {
|
||||
// ++x;
|
||||
// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in
|
||||
// // comments.
|
||||
// } else {
|
||||
// return x;
|
||||
// }
|
||||
// case 42:
|
||||
// ...
|
||||
//
|
||||
// As shown in the example above, the FALLTHROUGH_INTENDED macro should be
|
||||
// followed by a semicolon. It is designed to mimic control-flow statements
|
||||
// like 'break;', so it can be placed in most places where 'break;' can, but
|
||||
// only if there are no statements on the execution path between it and the
|
||||
// next switch label.
|
||||
//
|
||||
// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to
|
||||
// [[clang::fallthrough]] attribute, which is analysed when performing switch
|
||||
// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang
|
||||
// documentation on language extensions for details:
|
||||
// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough
|
||||
//
|
||||
// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no
|
||||
// effect on diagnostics.
|
||||
//
|
||||
// In either case this macro has no effect on runtime behavior and performance
|
||||
// of code.
|
||||
#ifndef FALLTHROUGH_INTENDED
|
||||
#define FALLTHROUGH_INTENDED [[clang::fallthrough]] // NOLINT
|
||||
#endif
|
||||
// Current ABI string
|
||||
#if defined(__arm__)
|
||||
#define ABI_STRING "arm"
|
||||
#elif defined(__aarch64__)
|
||||
#define ABI_STRING "arm64"
|
||||
#elif defined(__i386__)
|
||||
#define ABI_STRING "x86"
|
||||
#elif defined(__x86_64__)
|
||||
#define ABI_STRING "x86_64"
|
||||
#elif defined(__mips__) && !defined(__LP64__)
|
||||
#define ABI_STRING "mips"
|
||||
#elif defined(__mips__) && defined(__LP64__)
|
||||
#define ABI_STRING "mips64"
|
||||
#endif
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#if defined(__APPLE__)
|
||||
/** Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */
|
||||
typedef off_t off64_t;
|
||||
#endif
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
namespace android {
|
||||
namespace base {
|
||||
// Parses the unsigned decimal or hexadecimal integer in the string 's' and sets
|
||||
// 'out' to that value if it is specified. Optionally allows the caller to define
|
||||
// a 'max' beyond which otherwise valid values will be rejected. Returns boolean
|
||||
// success; 'out' is untouched if parsing fails.
|
||||
template <typename T>
|
||||
bool ParseUint(const char* s, T* out, T max = std::numeric_limits<T>::max(),
|
||||
bool allow_suffixes = false) {
|
||||
static_assert(std::is_unsigned<T>::value, "ParseUint can only be used with unsigned types");
|
||||
while (isspace(*s)) {
|
||||
s++;
|
||||
}
|
||||
if (s[0] == '-') {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
|
||||
errno = 0;
|
||||
char* end;
|
||||
unsigned long long int result = strtoull(s, &end, base);
|
||||
if (errno != 0) return false;
|
||||
if (end == s) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
if (*end != '\0') {
|
||||
const char* suffixes = "bkmgtpe";
|
||||
const char* suffix;
|
||||
if ((!allow_suffixes || (suffix = strchr(suffixes, tolower(*end))) == nullptr) ||
|
||||
__builtin_mul_overflow(result, 1ULL << (10 * (suffix - suffixes)), &result)) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (max < result) {
|
||||
errno = ERANGE;
|
||||
return false;
|
||||
}
|
||||
if (out != nullptr) {
|
||||
*out = static_cast<T>(result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// TODO: string_view
|
||||
template <typename T>
|
||||
bool ParseUint(const std::string& s, T* out, T max = std::numeric_limits<T>::max(),
|
||||
bool allow_suffixes = false) {
|
||||
return ParseUint(s.c_str(), out, max, allow_suffixes);
|
||||
}
|
||||
template <typename T>
|
||||
bool ParseByteCount(const char* s, T* out, T max = std::numeric_limits<T>::max()) {
|
||||
return ParseUint(s, out, max, true);
|
||||
}
|
||||
// TODO: string_view
|
||||
template <typename T>
|
||||
bool ParseByteCount(const std::string& s, T* out, T max = std::numeric_limits<T>::max()) {
|
||||
return ParseByteCount(s.c_str(), out, max);
|
||||
}
|
||||
// Parses the signed decimal or hexadecimal integer in the string 's' and sets
|
||||
// 'out' to that value if it is specified. Optionally allows the caller to define
|
||||
// a 'min' and 'max' beyond which otherwise valid values will be rejected. Returns
|
||||
// boolean success; 'out' is untouched if parsing fails.
|
||||
template <typename T>
|
||||
bool ParseInt(const char* s, T* out,
|
||||
T min = std::numeric_limits<T>::min(),
|
||||
T max = std::numeric_limits<T>::max()) {
|
||||
static_assert(std::is_signed<T>::value, "ParseInt can only be used with signed types");
|
||||
while (isspace(*s)) {
|
||||
s++;
|
||||
}
|
||||
int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
|
||||
errno = 0;
|
||||
char* end;
|
||||
long long int result = strtoll(s, &end, base);
|
||||
if (errno != 0) {
|
||||
return false;
|
||||
}
|
||||
if (s == end || *end != '\0') {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
if (result < min || max < result) {
|
||||
errno = ERANGE;
|
||||
return false;
|
||||
}
|
||||
if (out != nullptr) {
|
||||
*out = static_cast<T>(result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// TODO: string_view
|
||||
template <typename T>
|
||||
bool ParseInt(const std::string& s, T* out,
|
||||
T min = std::numeric_limits<T>::min(),
|
||||
T max = std::numeric_limits<T>::max()) {
|
||||
return ParseInt(s.c_str(), out, min, max);
|
||||
}
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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 "android-base/strings.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace android {
|
||||
namespace base {
|
||||
#define CHECK_NE(a, b) \
|
||||
if ((a) == (b)) abort();
|
||||
std::vector<std::string> Split(const std::string& s,
|
||||
const std::string& delimiters) {
|
||||
CHECK_NE(delimiters.size(), 0U);
|
||||
std::vector<std::string> result;
|
||||
size_t base = 0;
|
||||
size_t found;
|
||||
while (true) {
|
||||
found = s.find_first_of(delimiters, base);
|
||||
result.push_back(s.substr(base, found - base));
|
||||
if (found == s.npos) break;
|
||||
base = found + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::string Trim(const std::string& s) {
|
||||
std::string result;
|
||||
if (s.size() == 0) {
|
||||
return result;
|
||||
}
|
||||
size_t start_index = 0;
|
||||
size_t end_index = s.size() - 1;
|
||||
// Skip initial whitespace.
|
||||
while (start_index < s.size()) {
|
||||
if (!isspace(s[start_index])) {
|
||||
break;
|
||||
}
|
||||
start_index++;
|
||||
}
|
||||
// Skip terminating whitespace.
|
||||
while (end_index >= start_index) {
|
||||
if (!isspace(s[end_index])) {
|
||||
break;
|
||||
}
|
||||
end_index--;
|
||||
}
|
||||
// All spaces, no beef.
|
||||
if (end_index < start_index) {
|
||||
return "";
|
||||
}
|
||||
// Start_index is the first non-space, end_index is the last one.
|
||||
return s.substr(start_index, end_index - start_index + 1);
|
||||
}
|
||||
// These cases are probably the norm, so we mark them extern in the header to
|
||||
// aid compile time and binary size.
|
||||
template std::string Join(const std::vector<std::string>&, char);
|
||||
template std::string Join(const std::vector<const char*>&, char);
|
||||
template std::string Join(const std::vector<std::string>&, const std::string&);
|
||||
template std::string Join(const std::vector<const char*>&, const std::string&);
|
||||
bool StartsWith(std::string_view s, std::string_view prefix) {
|
||||
return s.substr(0, prefix.size()) == prefix;
|
||||
}
|
||||
bool StartsWith(std::string_view s, char prefix) {
|
||||
return !s.empty() && s.front() == prefix;
|
||||
}
|
||||
bool StartsWithIgnoreCase(std::string_view s, std::string_view prefix) {
|
||||
return s.size() >= prefix.size() && strncasecmp(s.data(), prefix.data(), prefix.size()) == 0;
|
||||
}
|
||||
bool EndsWith(std::string_view s, std::string_view suffix) {
|
||||
return s.size() >= suffix.size() && s.substr(s.size() - suffix.size(), suffix.size()) == suffix;
|
||||
}
|
||||
bool EndsWith(std::string_view s, char suffix) {
|
||||
return !s.empty() && s.back() == suffix;
|
||||
}
|
||||
bool EndsWithIgnoreCase(std::string_view s, std::string_view suffix) {
|
||||
return s.size() >= suffix.size() &&
|
||||
strncasecmp(s.data() + (s.size() - suffix.size()), suffix.data(), suffix.size()) == 0;
|
||||
}
|
||||
bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs) {
|
||||
return lhs.size() == rhs.size() && strncasecmp(lhs.data(), rhs.data(), lhs.size()) == 0;
|
||||
}
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
namespace android {
|
||||
namespace base {
|
||||
// Splits a string into a vector of strings.
|
||||
//
|
||||
// The string is split at each occurrence of a character in delimiters.
|
||||
//
|
||||
// The empty string is not a valid delimiter list.
|
||||
std::vector<std::string> Split(const std::string& s,
|
||||
const std::string& delimiters);
|
||||
// Trims whitespace off both ends of the given string.
|
||||
std::string Trim(const std::string& s);
|
||||
// Joins a container of things into a single string, using the given separator.
|
||||
template <typename ContainerT, typename SeparatorT>
|
||||
std::string Join(const ContainerT& things, SeparatorT separator) {
|
||||
if (things.empty()) {
|
||||
return "";
|
||||
}
|
||||
std::ostringstream result;
|
||||
result << *things.begin();
|
||||
for (auto it = std::next(things.begin()); it != things.end(); ++it) {
|
||||
result << separator << *it;
|
||||
}
|
||||
return result.str();
|
||||
}
|
||||
// We instantiate the common cases in strings.cpp.
|
||||
extern template std::string Join(const std::vector<std::string>&, char);
|
||||
extern template std::string Join(const std::vector<const char*>&, char);
|
||||
extern template std::string Join(const std::vector<std::string>&, const std::string&);
|
||||
extern template std::string Join(const std::vector<const char*>&, const std::string&);
|
||||
// Tests whether 's' starts with 'prefix'.
|
||||
bool StartsWith(std::string_view s, std::string_view prefix);
|
||||
bool StartsWith(std::string_view s, char prefix);
|
||||
bool StartsWithIgnoreCase(std::string_view s, std::string_view prefix);
|
||||
// Tests whether 's' ends with 'suffix'.
|
||||
bool EndsWith(std::string_view s, std::string_view suffix);
|
||||
bool EndsWith(std::string_view s, char suffix);
|
||||
bool EndsWithIgnoreCase(std::string_view s, std::string_view suffix);
|
||||
// Tests whether 'lhs' equals 'rhs', ignoring case.
|
||||
bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs);
|
||||
// Removes `prefix` from the start of the given string and returns true (if
|
||||
// it was present), false otherwise.
|
||||
inline bool ConsumePrefix(std::string_view* s, std::string_view prefix) {
|
||||
if (!StartsWith(*s, prefix)) return false;
|
||||
s->remove_prefix(prefix.size());
|
||||
return true;
|
||||
}
|
||||
// Removes `suffix` from the end of the given string and returns true (if
|
||||
// it was present), false otherwise.
|
||||
inline bool ConsumeSuffix(std::string_view* s, std::string_view suffix) {
|
||||
if (!EndsWith(*s, suffix)) return false;
|
||||
s->remove_suffix(suffix.size());
|
||||
return true;
|
||||
}
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
|
||||
#define CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
|
||||
#define SCOPED_CAPABILITY \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
|
||||
#define SHARED_CAPABILITY(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(shared_capability(__VA_ARGS__))
|
||||
#define GUARDED_BY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
|
||||
#define PT_GUARDED_BY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
|
||||
#define EXCLUSIVE_LOCKS_REQUIRED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
|
||||
#define SHARED_LOCKS_REQUIRED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
|
||||
#define ACQUIRED_BEFORE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
|
||||
#define ACQUIRED_AFTER(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
|
||||
#define REQUIRES(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
|
||||
#define REQUIRES_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
|
||||
#define ACQUIRE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
|
||||
#define ACQUIRE_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
|
||||
#define RELEASE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
|
||||
#define RELEASE_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
|
||||
#define TRY_ACQUIRE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
|
||||
#define TRY_ACQUIRE_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
|
||||
#define EXCLUDES(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
|
||||
#define ASSERT_CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
|
||||
#define ASSERT_SHARED_CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
|
||||
#define RETURN_CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
|
||||
#define EXCLUSIVE_LOCK_FUNCTION(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
|
||||
#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))
|
||||
#define SHARED_LOCK_FUNCTION(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
|
||||
#define SHARED_TRYLOCK_FUNCTION(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))
|
||||
#define UNLOCK_FUNCTION(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
|
||||
#define SCOPED_LOCKABLE \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
|
||||
#define LOCK_RETURNED(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
|
||||
#define NO_THREAD_SAFETY_ANALYSIS \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
|
||||
namespace android {
|
||||
namespace base {
|
||||
// A class to help thread safety analysis deal with std::unique_lock and condition_variable.
|
||||
//
|
||||
// Clang's thread safety analysis currently doesn't perform alias analysis, so movable types
|
||||
// like std::unique_lock can't be marked with thread safety annotations. This helper allows
|
||||
// for manual assertion of lock state in a scope.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// std::mutex mutex;
|
||||
// std::condition_variable cv;
|
||||
// std::vector<int> vec GUARDED_BY(mutex);
|
||||
//
|
||||
// int pop() {
|
||||
// std::unique_lock lock(mutex);
|
||||
// ScopedLockAssertion lock_assertion(mutex);
|
||||
// cv.wait(lock, []() {
|
||||
// ScopedLockAssertion lock_assertion(mutex);
|
||||
// return !vec.empty();
|
||||
// });
|
||||
//
|
||||
// int result = vec.back();
|
||||
// vec.pop_back();
|
||||
// return result;
|
||||
// }
|
||||
class SCOPED_CAPABILITY ScopedLockAssertion {
|
||||
public:
|
||||
ScopedLockAssertion(std::mutex& mutex) ACQUIRE(mutex) {}
|
||||
~ScopedLockAssertion() RELEASE() {}
|
||||
};
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* 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 <android-base/threads.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
#include <syscall.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
namespace android {
|
||||
namespace base {
|
||||
uint64_t GetThreadId() {
|
||||
#if defined(__BIONIC__)
|
||||
return gettid();
|
||||
#elif defined(__APPLE__)
|
||||
uint64_t tid;
|
||||
pthread_threadid_np(NULL, &tid);
|
||||
return tid;
|
||||
#elif defined(__linux__)
|
||||
return syscall(__NR_gettid);
|
||||
#elif defined(_WIN32)
|
||||
return GetCurrentThreadId();
|
||||
#endif
|
||||
}
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
#if defined(__GLIBC__)
|
||||
int tgkill(int tgid, int tid, int sig) {
|
||||
return syscall(__NR_tgkill, tgid, tid, sig);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
namespace android {
|
||||
namespace base {
|
||||
uint64_t GetThreadId();
|
||||
}
|
||||
} // namespace android
|
||||
#if defined(__GLIBC__)
|
||||
// bionic has this Linux-specifix call, but glibc doesn't.
|
||||
extern "C" int tgkill(int tgid, int tid, int sig);
|
||||
#endif
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
// DO NOT INCLUDE OTHER LIBBASE HEADERS!
|
||||
// This file gets used in libbinder, and libbinder is used everywhere.
|
||||
// Including other headers from libbase frequently results in inclusion of
|
||||
// android-base/macros.h, which causes macro collisions.
|
||||
// Container for a file descriptor that automatically closes the descriptor as
|
||||
// it goes out of scope.
|
||||
//
|
||||
// unique_fd ufd(open("/some/path", "r"));
|
||||
// if (ufd.get() == -1) return error;
|
||||
//
|
||||
// // Do something useful, possibly including 'return'.
|
||||
//
|
||||
// return 0; // Descriptor is closed for you.
|
||||
//
|
||||
// unique_fd is also known as ScopedFd/ScopedFD/scoped_fd; mentioned here to help
|
||||
// you find this class if you're searching for one of those names.
|
||||
#if defined(__BIONIC__)
|
||||
#include <android/fdsan.h>
|
||||
#endif
|
||||
namespace android {
|
||||
namespace base {
|
||||
struct DefaultCloser {
|
||||
#if defined(__BIONIC__)
|
||||
static void Tag(int fd, void* old_addr, void* new_addr) {
|
||||
if (android_fdsan_exchange_owner_tag) {
|
||||
uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
|
||||
reinterpret_cast<uint64_t>(old_addr));
|
||||
uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
|
||||
reinterpret_cast<uint64_t>(new_addr));
|
||||
android_fdsan_exchange_owner_tag(fd, old_tag, new_tag);
|
||||
}
|
||||
}
|
||||
static void Close(int fd, void* addr) {
|
||||
if (android_fdsan_close_with_tag) {
|
||||
uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
|
||||
reinterpret_cast<uint64_t>(addr));
|
||||
android_fdsan_close_with_tag(fd, tag);
|
||||
} else {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void Close(int fd) {
|
||||
// Even if close(2) fails with EINTR, the fd will have been closed.
|
||||
// Using TEMP_FAILURE_RETRY will either lead to EBADF or closing someone
|
||||
// else's fd.
|
||||
// http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
template <typename Closer>
|
||||
class unique_fd_impl final {
|
||||
public:
|
||||
unique_fd_impl() {}
|
||||
explicit unique_fd_impl(int fd) { reset(fd); }
|
||||
~unique_fd_impl() { reset(); }
|
||||
unique_fd_impl(unique_fd_impl&& other) noexcept { reset(other.release()); }
|
||||
unique_fd_impl& operator=(unique_fd_impl&& s) noexcept {
|
||||
int fd = s.fd_;
|
||||
s.fd_ = -1;
|
||||
reset(fd, &s);
|
||||
return *this;
|
||||
}
|
||||
void reset(int new_value = -1) { reset(new_value, nullptr); }
|
||||
int get() const { return fd_; }
|
||||
#if !defined(ANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION)
|
||||
// unique_fd's operator int is dangerous, but we have way too much code that
|
||||
// depends on it, so make this opt-in at first.
|
||||
operator int() const { return get(); } // NOLINT
|
||||
#endif
|
||||
bool operator>=(int rhs) const { return get() >= rhs; }
|
||||
bool operator<(int rhs) const { return get() < rhs; }
|
||||
bool operator==(int rhs) const { return get() == rhs; }
|
||||
bool operator!=(int rhs) const { return get() != rhs; }
|
||||
// Catch bogus error checks (i.e.: "!fd" instead of "fd != -1").
|
||||
bool operator!() const = delete;
|
||||
int release() __attribute__((warn_unused_result)) {
|
||||
tag(fd_, this, nullptr);
|
||||
int ret = fd_;
|
||||
fd_ = -1;
|
||||
return ret;
|
||||
}
|
||||
private:
|
||||
void reset(int new_value, void* previous_tag) {
|
||||
int previous_errno = errno;
|
||||
if (fd_ != -1) {
|
||||
close(fd_, this);
|
||||
}
|
||||
fd_ = new_value;
|
||||
if (new_value != -1) {
|
||||
tag(new_value, previous_tag, this);
|
||||
}
|
||||
errno = previous_errno;
|
||||
}
|
||||
int fd_ = -1;
|
||||
// Template magic to use Closer::Tag if available, and do nothing if not.
|
||||
// If Closer::Tag exists, this implementation is preferred, because int is a better match.
|
||||
// If not, this implementation is SFINAEd away, and the no-op below is the only one that exists.
|
||||
template <typename T = Closer>
|
||||
static auto tag(int fd, void* old_tag, void* new_tag)
|
||||
-> decltype(T::Tag(fd, old_tag, new_tag), void()) {
|
||||
T::Tag(fd, old_tag, new_tag);
|
||||
}
|
||||
template <typename T = Closer>
|
||||
static void tag(long, void*, void*) {
|
||||
// No-op.
|
||||
}
|
||||
// Same as above, to select between Closer::Close(int) and Closer::Close(int, void*).
|
||||
template <typename T = Closer>
|
||||
static auto close(int fd, void* tag_value) -> decltype(T::Close(fd, tag_value), void()) {
|
||||
T::Close(fd, tag_value);
|
||||
}
|
||||
template <typename T = Closer>
|
||||
static auto close(int fd, void*) -> decltype(T::Close(fd), void()) {
|
||||
T::Close(fd);
|
||||
}
|
||||
unique_fd_impl(const unique_fd_impl&);
|
||||
void operator=(const unique_fd_impl&);
|
||||
};
|
||||
using unique_fd = unique_fd_impl<DefaultCloser>;
|
||||
#if !defined(_WIN32)
|
||||
// Inline functions, so that they can be used header-only.
|
||||
template <typename Closer>
|
||||
inline bool Pipe(unique_fd_impl<Closer>* read, unique_fd_impl<Closer>* write,
|
||||
int flags = O_CLOEXEC) {
|
||||
int pipefd[2];
|
||||
#if defined(__linux__)
|
||||
if (pipe2(pipefd, flags) != 0) {
|
||||
return false;
|
||||
}
|
||||
#else // defined(__APPLE__)
|
||||
if (flags & ~(O_CLOEXEC | O_NONBLOCK)) {
|
||||
return false;
|
||||
}
|
||||
if (pipe(pipefd) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (flags & O_CLOEXEC) {
|
||||
if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (flags & O_NONBLOCK) {
|
||||
if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 || fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
read->reset(pipefd[0]);
|
||||
write->reset(pipefd[1]);
|
||||
return true;
|
||||
}
|
||||
template <typename Closer>
|
||||
inline bool Socketpair(int domain, int type, int protocol, unique_fd_impl<Closer>* left,
|
||||
unique_fd_impl<Closer>* right) {
|
||||
int sockfd[2];
|
||||
if (socketpair(domain, type, protocol, sockfd) != 0) {
|
||||
return false;
|
||||
}
|
||||
left->reset(sockfd[0]);
|
||||
right->reset(sockfd[1]);
|
||||
return true;
|
||||
}
|
||||
template <typename Closer>
|
||||
inline bool Socketpair(int type, unique_fd_impl<Closer>* left, unique_fd_impl<Closer>* right) {
|
||||
return Socketpair(AF_UNIX, type, 0, left, right);
|
||||
}
|
||||
// Using fdopen with unique_fd correctly is more annoying than it should be,
|
||||
// because fdopen doesn't close the file descriptor received upon failure.
|
||||
inline FILE* Fdopen(unique_fd&& ufd, const char* mode) {
|
||||
int fd = ufd.release();
|
||||
FILE* file = fdopen(fd, mode);
|
||||
if (!file) {
|
||||
close(fd);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
// Using fdopendir with unique_fd correctly is more annoying than it should be,
|
||||
// because fdopen doesn't close the file descriptor received upon failure.
|
||||
inline DIR* Fdopendir(unique_fd&& ufd) {
|
||||
int fd = ufd.release();
|
||||
DIR* dir = fdopendir(fd);
|
||||
if (dir == nullptr) {
|
||||
close(fd);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
#endif // !defined(_WIN32)
|
||||
// A wrapper type that can be implicitly constructed from either int or unique_fd.
|
||||
struct borrowed_fd {
|
||||
/* implicit */ borrowed_fd(int fd) : fd_(fd) {}
|
||||
template <typename T>
|
||||
/* implicit */ borrowed_fd(const unique_fd_impl<T>& ufd) : fd_(ufd.get()) {}
|
||||
int get() const { return fd_; }
|
||||
bool operator>=(int rhs) const { return get() >= rhs; }
|
||||
bool operator<(int rhs) const { return get() < rhs; }
|
||||
bool operator==(int rhs) const { return get() == rhs; }
|
||||
bool operator!=(int rhs) const { return get() != rhs; }
|
||||
private:
|
||||
int fd_ = -1;
|
||||
};
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
template <typename T>
|
||||
int close(const android::base::unique_fd_impl<T>&)
|
||||
__attribute__((__unavailable__("close called on unique_fd")));
|
||||
template <typename T>
|
||||
FILE* fdopen(const android::base::unique_fd_impl<T>&, const char* mode)
|
||||
__attribute__((__unavailable__("fdopen takes ownership of the fd passed in; either dup the "
|
||||
"unique_fd, or use android::base::Fdopen to pass ownership")));
|
||||
template <typename T>
|
||||
DIR* fdopendir(const android::base::unique_fd_impl<T>&) __attribute__((
|
||||
__unavailable__("fdopendir takes ownership of the fd passed in; either dup the "
|
||||
"unique_fd, or use android::base::Fdopendir to pass ownership")));
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#else
|
||||
// Bring in prototypes for standard APIs so that we can import them into the utf8 namespace.
|
||||
#include <fcntl.h> // open
|
||||
#include <stdio.h> // fopen
|
||||
#include <sys/stat.h> // mkdir
|
||||
#include <unistd.h> // unlink
|
||||
#endif
|
||||
namespace android {
|
||||
namespace base {
|
||||
// Only available on Windows because this is only needed on Windows.
|
||||
#ifdef _WIN32
|
||||
// Convert size number of UTF-16 wchar_t's to UTF-8. Returns whether the
|
||||
// conversion was done successfully.
|
||||
bool WideToUTF8(const wchar_t* utf16, const size_t size, std::string* utf8);
|
||||
// Convert a NULL-terminated string of UTF-16 characters to UTF-8. Returns
|
||||
// whether the conversion was done successfully.
|
||||
bool WideToUTF8(const wchar_t* utf16, std::string* utf8);
|
||||
// Convert a UTF-16 std::wstring (including any embedded NULL characters) to
|
||||
// UTF-8. Returns whether the conversion was done successfully.
|
||||
bool WideToUTF8(const std::wstring& utf16, std::string* utf8);
|
||||
// Convert size number of UTF-8 char's to UTF-16. Returns whether the conversion
|
||||
// was done successfully.
|
||||
bool UTF8ToWide(const char* utf8, const size_t size, std::wstring* utf16);
|
||||
// Convert a NULL-terminated string of UTF-8 characters to UTF-16. Returns
|
||||
// whether the conversion was done successfully.
|
||||
bool UTF8ToWide(const char* utf8, std::wstring* utf16);
|
||||
// Convert a UTF-8 std::string (including any embedded NULL characters) to
|
||||
// UTF-16. Returns whether the conversion was done successfully.
|
||||
bool UTF8ToWide(const std::string& utf8, std::wstring* utf16);
|
||||
// Convert a file system path, represented as a NULL-terminated string of
|
||||
// UTF-8 characters, to a UTF-16 string representing the same file system
|
||||
// path using the Windows extended-lengh path representation.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#MAXPATH:
|
||||
// ```The Windows API has many functions that also have Unicode versions to
|
||||
// permit an extended-length path for a maximum total path length of 32,767
|
||||
// characters. To specify an extended-length path, use the "\\?\" prefix.
|
||||
// For example, "\\?\D:\very long path".```
|
||||
//
|
||||
// Returns whether the conversion was done successfully.
|
||||
bool UTF8PathToWindowsLongPath(const char* utf8, std::wstring* utf16);
|
||||
#endif
|
||||
// The functions in the utf8 namespace take UTF-8 strings. For Windows, these
|
||||
// are wrappers, for non-Windows these just expose existing APIs. To call these
|
||||
// functions, use:
|
||||
//
|
||||
// // anonymous namespace to avoid conflict with existing open(), unlink(), etc.
|
||||
// namespace {
|
||||
// // Import functions into anonymous namespace.
|
||||
// using namespace android::base::utf8;
|
||||
//
|
||||
// void SomeFunction(const char* name) {
|
||||
// int fd = open(name, ...); // Calls android::base::utf8::open().
|
||||
// ...
|
||||
// unlink(name); // Calls android::base::utf8::unlink().
|
||||
// }
|
||||
// }
|
||||
namespace utf8 {
|
||||
#ifdef _WIN32
|
||||
FILE* fopen(const char* name, const char* mode);
|
||||
int mkdir(const char* name, mode_t mode);
|
||||
int open(const char* name, int flags, ...);
|
||||
int unlink(const char* name);
|
||||
#else
|
||||
using ::fopen;
|
||||
using ::mkdir;
|
||||
using ::open;
|
||||
using ::unlink;
|
||||
#endif
|
||||
} // namespace utf8
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
__BEGIN_DECLS
|
||||
/*
|
||||
* Error checking for close(2).
|
||||
*
|
||||
* Mishandling of file descriptor ownership is a common source of errors that
|
||||
* can be extremely difficult to diagnose. Mistakes like the following can
|
||||
* result in seemingly 'impossible' failures showing up on other threads that
|
||||
* happened to try to open a file descriptor between the buggy code's close and
|
||||
* fclose:
|
||||
*
|
||||
* int print(int fd) {
|
||||
* int rc;
|
||||
* char buf[128];
|
||||
* while ((rc = read(fd, buf, sizeof(buf))) > 0) {
|
||||
* printf("%.*s", rc);
|
||||
* }
|
||||
* close(fd);
|
||||
* }
|
||||
*
|
||||
* int bug() {
|
||||
* FILE* f = fopen("foo", "r");
|
||||
* print(fileno(f));
|
||||
* fclose(f);
|
||||
* }
|
||||
*
|
||||
* To make it easier to find this class of bugs, bionic provides a method to
|
||||
* require that file descriptors are closed by their owners. File descriptors
|
||||
* can be associated with tags with which they must be closed. This allows
|
||||
* objects that conceptually own an fd (FILE*, unique_fd, etc.) to use their
|
||||
* own address at the tag, to enforce that closure of the fd must come as a
|
||||
* result of their own destruction (fclose, ~unique_fd, etc.)
|
||||
*
|
||||
* By default, a file descriptor's tag is 0, and close(fd) is equivalent to
|
||||
* closing fd with the tag 0.
|
||||
*/
|
||||
/*
|
||||
* For improved diagnostics, the type of a file descriptors owner can be
|
||||
* encoded in the most significant byte of the owner tag. Values of 0 and 0xff
|
||||
* are ignored, which allows for raw pointers to be used as owner tags without
|
||||
* modification.
|
||||
*/
|
||||
enum android_fdsan_owner_type {
|
||||
/*
|
||||
* Generic Java or native owners.
|
||||
*
|
||||
* Generic Java objects always use 255 as their type, using identityHashCode
|
||||
* as the value of the tag, leaving bits 33-56 unset. Native pointers are sign
|
||||
* extended from 48-bits of virtual address space, and so can have the MSB
|
||||
* set to 255 as well. Use the value of bits 49-56 to distinguish between
|
||||
* these cases.
|
||||
*/
|
||||
ANDROID_FDSAN_OWNER_TYPE_GENERIC_00 = 0,
|
||||
ANDROID_FDSAN_OWNER_TYPE_GENERIC_FF = 255,
|
||||
/* FILE* */
|
||||
ANDROID_FDSAN_OWNER_TYPE_FILE = 1,
|
||||
/* DIR* */
|
||||
ANDROID_FDSAN_OWNER_TYPE_DIR = 2,
|
||||
/* android::base::unique_fd */
|
||||
ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD = 3,
|
||||
/* sqlite-owned file descriptors */
|
||||
ANDROID_FDSAN_OWNER_TYPE_SQLITE = 4,
|
||||
/* java.io.FileInputStream */
|
||||
ANDROID_FDSAN_OWNER_TYPE_FILEINPUTSTREAM = 5,
|
||||
/* java.io.FileOutputStream */
|
||||
ANDROID_FDSAN_OWNER_TYPE_FILEOUTPUTSTREAM = 6,
|
||||
/* java.io.RandomAccessFile */
|
||||
ANDROID_FDSAN_OWNER_TYPE_RANDOMACCESSFILE = 7,
|
||||
/* android.os.ParcelFileDescriptor */
|
||||
ANDROID_FDSAN_OWNER_TYPE_PARCELFILEDESCRIPTOR = 8,
|
||||
/* ART FdFile */
|
||||
ANDROID_FDSAN_OWNER_TYPE_ART_FDFILE = 9,
|
||||
/* java.net.DatagramSocketImpl */
|
||||
ANDROID_FDSAN_OWNER_TYPE_DATAGRAMSOCKETIMPL = 10,
|
||||
/* java.net.SocketImpl */
|
||||
ANDROID_FDSAN_OWNER_TYPE_SOCKETIMPL = 11,
|
||||
/* libziparchive's ZipArchive */
|
||||
ANDROID_FDSAN_OWNER_TYPE_ZIPARCHIVE = 12,
|
||||
};
|
||||
/*
|
||||
* Create an owner tag with the specified type and least significant 56 bits of tag.
|
||||
*/
|
||||
uint64_t android_fdsan_create_owner_tag(enum android_fdsan_owner_type type, uint64_t tag) __INTRODUCED_IN(29) __attribute__((__weak__));
|
||||
/*
|
||||
* Exchange a file descriptor's tag.
|
||||
*
|
||||
* Logs and aborts if the fd's tag does not match expected_tag.
|
||||
*/
|
||||
void android_fdsan_exchange_owner_tag(int fd, uint64_t expected_tag, uint64_t new_tag) __INTRODUCED_IN(29) __attribute__((__weak__));
|
||||
/*
|
||||
* Close a file descriptor with a tag, and resets the tag to 0.
|
||||
*
|
||||
* Logs and aborts if the tag is incorrect.
|
||||
*/
|
||||
int android_fdsan_close_with_tag(int fd, uint64_t tag) __INTRODUCED_IN(29) __attribute__((__weak__));
|
||||
/*
|
||||
* Get a file descriptor's current owner tag.
|
||||
*
|
||||
* Returns 0 for untagged and invalid file descriptors.
|
||||
*/
|
||||
uint64_t android_fdsan_get_owner_tag(int fd) __INTRODUCED_IN(29);
|
||||
/*
|
||||
* Get an owner tag's string representation.
|
||||
*
|
||||
* The return value points to memory with static lifetime, do not attempt to modify it.
|
||||
*/
|
||||
const char* android_fdsan_get_tag_type(uint64_t tag) __INTRODUCED_IN(29);
|
||||
/*
|
||||
* Get an owner tag's value, with the type masked off.
|
||||
*/
|
||||
uint64_t android_fdsan_get_tag_value(uint64_t tag) __INTRODUCED_IN(29);
|
||||
enum android_fdsan_error_level {
|
||||
// No errors.
|
||||
ANDROID_FDSAN_ERROR_LEVEL_DISABLED,
|
||||
// Warn once(ish) on error, and then downgrade to ANDROID_FDSAN_ERROR_LEVEL_DISABLED.
|
||||
ANDROID_FDSAN_ERROR_LEVEL_WARN_ONCE,
|
||||
// Warn always on error.
|
||||
ANDROID_FDSAN_ERROR_LEVEL_WARN_ALWAYS,
|
||||
// Abort on error.
|
||||
ANDROID_FDSAN_ERROR_LEVEL_FATAL,
|
||||
};
|
||||
/*
|
||||
* Get the error level.
|
||||
*/
|
||||
enum android_fdsan_error_level android_fdsan_get_error_level() __INTRODUCED_IN(29) __attribute__((__weak__));
|
||||
/*
|
||||
* Set the error level and return the previous state.
|
||||
*
|
||||
* Error checking is automatically disabled in the child of a fork, to maintain
|
||||
* compatibility with code that forks, blindly closes FDs, and then execs.
|
||||
*
|
||||
* In cases such as the zygote, where the child has no intention of calling
|
||||
* exec, call this function to reenable fdsan checks.
|
||||
*
|
||||
* This function is not thread-safe and does not synchronize with checks of the
|
||||
* value, and so should probably only be called in single-threaded contexts
|
||||
* (e.g. postfork).
|
||||
*/
|
||||
enum android_fdsan_error_level android_fdsan_set_error_level(enum android_fdsan_error_level new_level) __INTRODUCED_IN(29) __attribute__((__weak__));
|
||||
__END_DECLS
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stddef.h> // for size_t
|
||||
#include <unistd.h> // for TEMP_FAILURE_RETRY
|
||||
#include "android-base/macros.h"
|
||||
#include "android-base/thread_annotations.h"
|
||||
// Declare a friend relationship in a class with a test. Used rather that FRIEND_TEST to avoid
|
||||
// globally importing gtest/gtest.h into the main ART header files.
|
||||
#define ART_FRIEND_TEST(test_set_name, individual_test)\
|
||||
friend class test_set_name##_##individual_test##_Test
|
||||
// Declare a friend relationship in a class with a typed test.
|
||||
#define ART_FRIEND_TYPED_TEST(test_set_name, individual_test)\
|
||||
template<typename T> ART_FRIEND_TEST(test_set_name, individual_test)
|
||||
// A macro to disallow new and delete operators for a class. It goes in the private: declarations.
|
||||
// NOTE: Providing placement new (and matching delete) for constructing container elements.
|
||||
#define DISALLOW_ALLOCATION() \
|
||||
public: \
|
||||
NO_RETURN ALWAYS_INLINE void operator delete(void*, size_t) { UNREACHABLE(); } \
|
||||
ALWAYS_INLINE void* operator new(size_t, void* ptr) noexcept { return ptr; } \
|
||||
ALWAYS_INLINE void operator delete(void*, void*) noexcept { } \
|
||||
private: \
|
||||
void* operator new(size_t) = delete // NOLINT
|
||||
// offsetof is not defined by the spec on types with non-standard layout,
|
||||
// however it is implemented by compilers in practice.
|
||||
// (note that reinterpret_cast is not valid constexpr)
|
||||
//
|
||||
// Alternative approach would be something like:
|
||||
// #define OFFSETOF_HELPER(t, f) \
|
||||
// (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u))
|
||||
// #define OFFSETOF_MEMBER(t, f) \
|
||||
// (__builtin_constant_p(OFFSETOF_HELPER(t,f)) ? OFFSETOF_HELPER(t,f) : OFFSETOF_HELPER(t,f))
|
||||
#define OFFSETOF_MEMBER(t, f) offsetof(t, f)
|
||||
#define OFFSETOF_MEMBERPTR(t, f) \
|
||||
(reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - static_cast<uintptr_t>(16)) // NOLINT
|
||||
#define ALIGNED(x) __attribute__ ((__aligned__(x)))
|
||||
#define PACKED(x) __attribute__ ((__aligned__(x), __packed__))
|
||||
// Stringify the argument.
|
||||
#define QUOTE(x) #x
|
||||
#define STRINGIFY(x) QUOTE(x)
|
||||
// Append tokens after evaluating.
|
||||
#define APPEND_TOKENS_AFTER_EVAL_2(a, b) a ## b
|
||||
#define APPEND_TOKENS_AFTER_EVAL(a, b) APPEND_TOKENS_AFTER_EVAL_2(a, b)
|
||||
#ifndef NDEBUG
|
||||
#define ALWAYS_INLINE
|
||||
#else
|
||||
#define ALWAYS_INLINE __attribute__ ((always_inline))
|
||||
#endif
|
||||
// clang doesn't like attributes on lambda functions. It would be nice to say:
|
||||
// #define ALWAYS_INLINE_LAMBDA ALWAYS_INLINE
|
||||
#define ALWAYS_INLINE_LAMBDA
|
||||
#define NO_INLINE __attribute__ ((noinline))
|
||||
#if defined (__APPLE__)
|
||||
#define HOT_ATTR
|
||||
#define COLD_ATTR
|
||||
#else
|
||||
#define HOT_ATTR __attribute__ ((hot))
|
||||
#define COLD_ATTR __attribute__ ((cold))
|
||||
#endif
|
||||
#define PURE __attribute__ ((__pure__))
|
||||
// Define that a position within code is unreachable, for example:
|
||||
// int foo () { LOG(FATAL) << "Don't call me"; UNREACHABLE(); }
|
||||
// without the UNREACHABLE a return statement would be necessary.
|
||||
#define UNREACHABLE __builtin_unreachable
|
||||
// Add the C++11 noreturn attribute.
|
||||
#define NO_RETURN [[ noreturn ]] // NOLINT[whitespace/braces] [5]
|
||||
// Annotalysis thread-safety analysis support. Things that are not in base.
|
||||
#define LOCKABLE CAPABILITY("mutex")
|
||||
#define SHARED_LOCKABLE SHARED_CAPABILITY("mutex")
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include "android-base/logging.h"
|
||||
#include "nativehelper/scoped_local_ref.h"
|
||||
#include "art/base/macros.h"
|
||||
|
||||
namespace art {
|
||||
ALWAYS_INLINE inline void RegisterNativeMethodsWithClass(JNIEnv* env,
|
||||
jclass clazz,
|
||||
const JNINativeMethod* methods,
|
||||
jint method_count) {
|
||||
ScopedLocalRef<jclass> c(env, clazz);
|
||||
if (clazz == nullptr) {
|
||||
LOG(ERROR) << "clazz is null";
|
||||
return;
|
||||
}
|
||||
jint jni_result = env->RegisterNatives(c.get(), methods, method_count);
|
||||
CHECK_EQ(JNI_OK, jni_result);
|
||||
}
|
||||
ALWAYS_INLINE inline void RegisterNativeMethodsWithName(JNIEnv* env,
|
||||
const char* jni_class_name,
|
||||
const JNINativeMethod* methods,
|
||||
jint method_count) {
|
||||
ScopedLocalRef<jclass> clazz(env, env->FindClass(jni_class_name));
|
||||
if (clazz.get() == nullptr) {
|
||||
LOG(FATAL) << "Couldn't find class: " << jni_class_name;
|
||||
return;
|
||||
}
|
||||
RegisterNativeMethodsWithClass(env, clazz.get(), methods, method_count);
|
||||
}
|
||||
#define REGISTER_NATIVE_METHODS(jni_class_name) \
|
||||
RegisterNativeMethodsWithName(env, (jni_class_name), gMethods, arraysize(gMethods))
|
||||
|
||||
#define REGISTER_NATIVE_METHODS_WITH_CLASS(clazz) \
|
||||
RegisterNativeMethodsWithClass(env, (clazz), gMethods, arraysize(gMethods))
|
||||
} // namespace art
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* JNI helper macros.
|
||||
*
|
||||
* Only intended to be used in the platform.
|
||||
*/
|
||||
#pragma once
|
||||
// Intended to construct a JNINativeMethod.
|
||||
// (Assumes the C name is the ClassName_JavaMethodName).
|
||||
#ifndef NATIVE_METHOD
|
||||
#define NATIVE_METHOD(className, functionName, signature) \
|
||||
{ #functionName, \
|
||||
signature, \
|
||||
_NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## functionName) \
|
||||
}
|
||||
#endif
|
||||
// Intended to construct a JNINativeMethod (when the C name doesn't match the Java name).
|
||||
// (Assumes the C name is the ClassName_Identifier).
|
||||
#ifndef OVERLOADED_NATIVE_METHOD
|
||||
#define OVERLOADED_NATIVE_METHOD(className, functionName, signature, identifier) \
|
||||
{ #functionName, \
|
||||
signature, \
|
||||
_NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## identifier) \
|
||||
}
|
||||
#endif
|
||||
// Used for methods that are annotated with @FastNative on the managed side.
|
||||
// See NATIVE_METHOD for usage.
|
||||
#ifndef FAST_NATIVE_METHOD
|
||||
#define FAST_NATIVE_METHOD(className, functionName, signature) \
|
||||
{ #functionName, \
|
||||
signature, \
|
||||
_NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## functionName) \
|
||||
}
|
||||
#endif
|
||||
// Used for methods that are annotated with @FastNative on the managed side,
|
||||
// and when the C-name doesn't match the Java-name.
|
||||
//
|
||||
// See OVERLOADED_NATIVE_METHOD for usage.
|
||||
#ifndef OVERLOADED_FAST_NATIVE_METHOD
|
||||
#define OVERLOADED_FAST_NATIVE_METHOD(className, functionName, signature, identifier) \
|
||||
{ #functionName, \
|
||||
signature, \
|
||||
_NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## identifier) \
|
||||
}
|
||||
#endif
|
||||
////////////////////////////////////////////////////////
|
||||
// IMPLEMENTATION ONLY.
|
||||
// DO NOT USE DIRECTLY.
|
||||
////////////////////////////////////////////////////////
|
||||
// C-style cast for C, C++-style cast for C++ to avoid warnings/errors.
|
||||
#if defined(__cplusplus)
|
||||
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
|
||||
reinterpret_cast<to>
|
||||
#else
|
||||
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
|
||||
(to)
|
||||
#endif
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef __cplusplus
|
||||
#define MODULE_API extern "C"
|
||||
#else
|
||||
#define MODULE_API
|
||||
#endif // __cplusplus
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma
|
||||
#if defined(__cplusplus)
|
||||
#if !defined(DISALLOW_COPY_AND_ASSIGN)
|
||||
// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
|
||||
// declarations in a class.
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&) = delete; \
|
||||
void operator=(const TypeName&) = delete
|
||||
#endif // !defined(DISALLOW_COPY_AND_ASSIGN)
|
||||
#ifndef NATIVEHELPER_JNIHELP_H_
|
||||
// This seems a header-only include. Provide NPE throwing.
|
||||
static inline int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
|
||||
if (env->ExceptionCheck()) {
|
||||
// Drop any pending exception.
|
||||
env->ExceptionClear();
|
||||
}
|
||||
jclass e_class = env->FindClass("java/lang/NullPointerException");
|
||||
if (e_class == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
if (env->ThrowNew(e_class, msg) != JNI_OK) {
|
||||
env->DeleteLocalRef(e_class);
|
||||
return -1;
|
||||
}
|
||||
env->DeleteLocalRef(e_class);
|
||||
return 0;
|
||||
}
|
||||
#endif // NATIVEHELPER_JNIHELP_H_
|
||||
#endif // defined(__cplusplus)
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include "jni.h"
|
||||
#include "nativehelper_utils.h"
|
||||
// A smart pointer that deletes a JNI local reference when it goes out of scope.
|
||||
template<typename T>
|
||||
class ScopedLocalRef {
|
||||
public:
|
||||
ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) {
|
||||
}
|
||||
ScopedLocalRef(ScopedLocalRef&& s) noexcept : mEnv(s.mEnv), mLocalRef(s.release()) {
|
||||
}
|
||||
explicit ScopedLocalRef(JNIEnv* env) : mEnv(env), mLocalRef(nullptr) {
|
||||
}
|
||||
~ScopedLocalRef() {
|
||||
reset();
|
||||
}
|
||||
void reset(T ptr = NULL) {
|
||||
if (ptr != mLocalRef) {
|
||||
if (mLocalRef != NULL) {
|
||||
mEnv->DeleteLocalRef(mLocalRef);
|
||||
}
|
||||
mLocalRef = ptr;
|
||||
}
|
||||
}
|
||||
T release() __attribute__((warn_unused_result)) {
|
||||
T localRef = mLocalRef;
|
||||
mLocalRef = NULL;
|
||||
return localRef;
|
||||
}
|
||||
T get() const {
|
||||
return mLocalRef;
|
||||
}
|
||||
// We do not expose an empty constructor as it can easily lead to errors
|
||||
// using common idioms, e.g.:
|
||||
// ScopedLocalRef<...> ref;
|
||||
// ref.reset(...);
|
||||
// Move assignment operator.
|
||||
ScopedLocalRef& operator=(ScopedLocalRef&& s) noexcept {
|
||||
reset(s.release());
|
||||
mEnv = s.mEnv;
|
||||
return *this;
|
||||
}
|
||||
// Allows "if (scoped_ref == nullptr)"
|
||||
bool operator==(std::nullptr_t) const {
|
||||
return mLocalRef == nullptr;
|
||||
}
|
||||
// Allows "if (scoped_ref != nullptr)"
|
||||
bool operator!=(std::nullptr_t) const {
|
||||
return mLocalRef != nullptr;
|
||||
}
|
||||
private:
|
||||
JNIEnv* mEnv;
|
||||
T mLocalRef;
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
|
||||
};
|
||||
|
|
@ -2,17 +2,10 @@ cmake_minimum_required(VERSION 3.4.1)
|
|||
|
||||
set(CMAKE_ANDROID_STL_TYPE c++_static)
|
||||
|
||||
set(SRC_LIST
|
||||
main.cpp
|
||||
native_hook/native_hook.cpp
|
||||
native_hook/resource_hook.cpp
|
||||
native_hook/riru_hook.cpp
|
||||
java_hook/java_hook.cpp
|
||||
inject/framework_hook.cpp
|
||||
inject/config_manager.cpp
|
||||
)
|
||||
include_directories(include .)
|
||||
add_library(riru_edxp SHARED ${SRC_LIST})
|
||||
aux_source_directory(src SRC_LIST)
|
||||
aux_source_directory(src/jni SRC_JNI_LIST)
|
||||
include_directories(include src)
|
||||
add_library(riru_edxp SHARED ${SRC_LIST} ${SRC_JNI_LIST})
|
||||
|
||||
find_library(log-lib log)
|
||||
target_link_libraries(riru_edxp yahfa riru xhook substrate ${log-lib})
|
||||
target_link_libraries(riru_edxp yahfa riru xhook substrate android ${log-lib})
|
||||
|
|
@ -1,10 +1,35 @@
|
|||
#ifndef JNIHELPER_H
|
||||
#define JNIHELPER_H
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include <art/base/macros.h>
|
||||
#include "logging.h"
|
||||
|
||||
int ClearException(JNIEnv *env) {
|
||||
#define JNI_START JNIEnv *env, jclass clazz
|
||||
|
||||
ALWAYS_INLINE static void JNIExceptionClear(JNIEnv *env) {
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static bool JNIExceptionCheck(JNIEnv *env) {
|
||||
if (env->ExceptionCheck()) {
|
||||
jthrowable e = env->ExceptionOccurred();
|
||||
env->Throw(e);
|
||||
env->DeleteLocalRef(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static void JNIExceptionClearAndDescribe(JNIEnv *env) {
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static int ClearException(JNIEnv *env) {
|
||||
jthrowable exception = env->ExceptionOccurred();
|
||||
if (exception != nullptr) {
|
||||
env->ExceptionDescribe();
|
||||
|
|
@ -58,6 +83,10 @@ int ClearException(JNIEnv *env) {
|
|||
env->CallStaticVoidMethod(obj, __VA_ARGS__); \
|
||||
if (ClearException(env)) LOGE("CallStaticVoidMethod " #obj " " #__VA_ARGS__);
|
||||
|
||||
#define JNI_CallStaticObjectMethod(env, obj, ...) \
|
||||
env->CallStaticObjectMethod(obj, __VA_ARGS__); \
|
||||
if (ClearException(env)) LOGE("CallStaticVoidMethod " #obj " " #__VA_ARGS__);
|
||||
|
||||
#define JNI_GetArrayLength(env, array) \
|
||||
env->GetArrayLength(array); \
|
||||
if (ClearException(env)) LOGE("GetArrayLength " #array);
|
||||
|
|
@ -70,4 +99,3 @@ int ClearException(JNIEnv *env) {
|
|||
env->RegisterNatives(class, methods, size); \
|
||||
if (ClearException(env)) LOGE("RegisterNatives " #class);
|
||||
|
||||
#endif // JNIHELPER_H
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#define ANDROID_O 26
|
||||
#define ANDROID_O_MR1 27
|
||||
#define ANDROID_P 28
|
||||
#define ANDROID_Q 29
|
||||
|
||||
static inline int32_t GetAndroidApiLevel() {
|
||||
char prop_value[PROP_VALUE_MAX];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <JNIHelper.h>
|
||||
#include <base/object.h>
|
||||
|
||||
namespace art {
|
||||
|
||||
class ClassLinker : public edxp::HookedObject {
|
||||
|
||||
private:
|
||||
inline static ClassLinker *instance_;
|
||||
|
||||
CREATE_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, void *thiz, void *art_method) {
|
||||
if (LIKELY(SetEntryPointsToInterpreterSym))
|
||||
SetEntryPointsToInterpreterSym(thiz, art_method);
|
||||
}
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(void *, Constructor, void *thiz, void *intern_table) {
|
||||
if (LIKELY(instance_))
|
||||
instance_->Reset(thiz);
|
||||
else
|
||||
instance_ = new ClassLinker(thiz);
|
||||
return ConstructorBackup(thiz, intern_table);
|
||||
}
|
||||
|
||||
public:
|
||||
ClassLinker(void *thiz) : HookedObject(thiz) {}
|
||||
|
||||
static ClassLinker *Current() {
|
||||
return instance_;
|
||||
}
|
||||
|
||||
static void Setup(void *handle, HookFunType hook_func) {
|
||||
HOOK_FUNC(Constructor, "_ZN3art11ClassLinkerC2EPNS_11InternTableE");
|
||||
RETRIEVE_FUNC_SYMBOL(SetEntryPointsToInterpreter,
|
||||
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE");
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void SetEntryPointsToInterpreter(void *art_method) const {
|
||||
if (LIKELY(thiz_))
|
||||
SetEntryPointsToInterpreter(thiz_, art_method);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
|
||||
#define ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace art {
|
||||
namespace gc {
|
||||
namespace collector {
|
||||
// The type of collection to be performed. The ordering of the enum matters, it is used to
|
||||
// determine which GCs are run first.
|
||||
enum GcType {
|
||||
// Placeholder for when no GC has been performed.
|
||||
kGcTypeNone,
|
||||
// Sticky mark bits GC that attempts to only free objects allocated since the last GC.
|
||||
kGcTypeSticky,
|
||||
// Partial GC that marks the application heap but not the Zygote.
|
||||
kGcTypePartial,
|
||||
// Full GC that marks and frees in both the application and Zygote heap.
|
||||
kGcTypeFull,
|
||||
// Number of different GC types.
|
||||
kGcTypeMax,
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const GcType &policy);
|
||||
} // namespace collector
|
||||
} // namespace gc
|
||||
} // namespace art
|
||||
#endif // ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef ART_RUNTIME_GC_GC_CAUSE_H_
|
||||
#define ART_RUNTIME_GC_GC_CAUSE_H_
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace art {
|
||||
namespace gc {
|
||||
// What caused the GC?
|
||||
enum GcCause {
|
||||
// Invalid GC cause used as a placeholder.
|
||||
kGcCauseNone,
|
||||
// GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
|
||||
// retrying allocation.
|
||||
kGcCauseForAlloc,
|
||||
// A background GC trying to ensure there is free memory ahead of allocations.
|
||||
kGcCauseBackground,
|
||||
// An explicit System.gc() call.
|
||||
kGcCauseExplicit,
|
||||
// GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
|
||||
// (This may be a blocking GC depending on whether we run a non-concurrent collector).
|
||||
kGcCauseForNativeAlloc,
|
||||
// GC triggered for a collector transition.
|
||||
kGcCauseCollectorTransition,
|
||||
// Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical).
|
||||
kGcCauseDisableMovingGc,
|
||||
// Not a real GC cause, used when we trim the heap.
|
||||
kGcCauseTrim,
|
||||
// Not a real GC cause, used to implement exclusion between GC and instrumentation.
|
||||
kGcCauseInstrumentation,
|
||||
// Not a real GC cause, used to add or remove app image spaces.
|
||||
kGcCauseAddRemoveAppImageSpace,
|
||||
// Not a real GC cause, used to implement exclusion between GC and debugger.
|
||||
kGcCauseDebugger,
|
||||
// GC triggered for background transition when both foreground and background collector are CMS.
|
||||
kGcCauseHomogeneousSpaceCompact,
|
||||
// Class linker cause, used to guard filling art methods with special values.
|
||||
kGcCauseClassLinker,
|
||||
// Not a real GC cause, used to implement exclusion between code cache metadata and GC.
|
||||
kGcCauseJitCodeCache,
|
||||
// Not a real GC cause, used to add or remove system-weak holders.
|
||||
kGcCauseAddRemoveSystemWeakHolder,
|
||||
// Not a real GC cause, used to prevent hprof running in the middle of GC.
|
||||
kGcCauseHprof,
|
||||
// Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
|
||||
kGcCauseGetObjectsAllocated,
|
||||
// GC cause for the profile saver.
|
||||
kGcCauseProfileSaver,
|
||||
};
|
||||
} // namespace gc
|
||||
} // namespace art
|
||||
#endif // ART_RUNTIME_GC_GC_CAUSE_H_
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <base/object.h>
|
||||
#include "collector/gc_type.h"
|
||||
#include "gc_cause.h"
|
||||
#include "../thread.h"
|
||||
|
||||
namespace art {
|
||||
|
||||
namespace gc {
|
||||
|
||||
class Heap : public edxp::HookedObject {
|
||||
|
||||
private:
|
||||
inline static Heap *instance_;
|
||||
|
||||
CREATE_FUNC_SYMBOL_ENTRY(collector::GcType, WaitForGcToComplete,
|
||||
void *thiz, GcCause cause, void *threadSelf) {
|
||||
if (LIKELY(WaitForGcToCompleteSym))
|
||||
return WaitForGcToCompleteSym(thiz, cause, threadSelf);
|
||||
return art::gc::collector::GcType::kGcTypeNone;
|
||||
}
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(void, PreZygoteFork, void *thiz) {
|
||||
if (instance_)
|
||||
instance_->Reset(thiz);
|
||||
else
|
||||
instance_ = new Heap(thiz);
|
||||
PreZygoteForkBackup(thiz);
|
||||
}
|
||||
|
||||
public:
|
||||
Heap(void *thiz) : HookedObject(thiz) {}
|
||||
|
||||
static Heap *Current() {
|
||||
return instance_;
|
||||
}
|
||||
|
||||
static void Setup(void *handle, HookFunType hook_func) {
|
||||
HOOK_FUNC(PreZygoteFork, "_ZN3art2gc4Heap13PreZygoteForkEv");
|
||||
RETRIEVE_FUNC_SYMBOL(WaitForGcToComplete,
|
||||
"_ZN3art2gc4Heap19WaitForGcToCompleteENS0_7GcCauseEPNS_6ThreadE");
|
||||
}
|
||||
|
||||
ALWAYS_INLINE collector::GcType
|
||||
WaitForGcToComplete(GcCause cause, void *threadSelf) const {
|
||||
if (LIKELY(thiz_))
|
||||
return WaitForGcToComplete(thiz_, cause, threadSelf);
|
||||
return art::gc::collector::GcType::kGcTypeNone;
|
||||
}
|
||||
|
||||
};
|
||||
} // namespace gc
|
||||
} // namespace art
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "base/object.h"
|
||||
|
||||
namespace art {
|
||||
|
||||
namespace hidden_api {
|
||||
|
||||
enum Action {
|
||||
kAllow,
|
||||
kAllowButWarn,
|
||||
kAllowButWarnAndToast,
|
||||
kDeny
|
||||
};
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(Action, GetMethodActionImpl) {
|
||||
return Action::kAllow;
|
||||
}
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(Action, GetFieldActionImpl) {
|
||||
return Action::kAllow;
|
||||
}
|
||||
|
||||
static void DisableHiddenApi(void *handle, HookFunType hook_func) {
|
||||
if (GetAndroidApiLevel() < ANDROID_P) {
|
||||
return;
|
||||
}
|
||||
HOOK_FUNC(GetMethodActionImpl,
|
||||
"_ZN3art9hiddenapi6detail19GetMemberActionImplINS_9ArtMethodEEENS0_"
|
||||
"6ActionEPT_NS_20HiddenApiAccessFlags7ApiListES4_NS0_12AccessMethodE");
|
||||
HOOK_FUNC(GetFieldActionImpl,
|
||||
"_ZN3art9hiddenapi6detail19GetMemberActionImplINS_8ArtFieldEEENS0_"
|
||||
"6ActionEPT_NS_20HiddenApiAccessFlags7ApiListES4_NS0_12AccessMethodE");
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "base/object.h"
|
||||
|
||||
namespace art {
|
||||
namespace mirror {
|
||||
|
||||
using namespace std;
|
||||
using namespace edxp;
|
||||
|
||||
class Class : public HookedObject {
|
||||
|
||||
private:
|
||||
|
||||
CREATE_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, void *thiz,
|
||||
std::string *storage) {
|
||||
return GetDescriptorSym(thiz, storage);
|
||||
}
|
||||
|
||||
|
||||
CREATE_ORIGINAL_ENTRY(bool, IsInSamePackage, void *thiz, void *that) {
|
||||
if (IsInSamePackageBackup) {
|
||||
return IsInSamePackageBackup(thiz, that);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(bool, IsInSamePackage, void *thiz, void *that) {
|
||||
std::string storage1, storage2;
|
||||
const char *thisDesc = GetDescriptor(thiz, &storage1);
|
||||
const char *thatDesc = GetDescriptor(that, &storage2);
|
||||
// Note: these identifiers should be consistent with those in Java layer
|
||||
if (strstr(thisDesc, "EdHooker_") != nullptr
|
||||
|| strstr(thatDesc, "EdHooker_") != nullptr
|
||||
|| strstr(thisDesc, "com/elderdrivers/riru/") != nullptr
|
||||
|| strstr(thatDesc, "com/elderdrivers/riru/") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
// for MIUI resources hooking
|
||||
if (strstr(thisDesc, "android/content/res/MiuiTypedArray") != nullptr
|
||||
|| strstr(thatDesc, "android/content/res/MiuiTypedArray") != nullptr
|
||||
|| strstr(thisDesc, "android/content/res/XResources$XTypedArray") != nullptr
|
||||
|| strstr(thatDesc, "android/content/res/XResources$XTypedArray") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
return IsInSamePackage(thiz, that);
|
||||
}
|
||||
|
||||
public:
|
||||
Class(void *thiz) : HookedObject(thiz) {}
|
||||
|
||||
static void Setup(void *handle, HookFunType hook_func) {
|
||||
RETRIEVE_FUNC_SYMBOL(GetDescriptor, "_ZN3art6mirror5Class13GetDescriptorEPNSt3__112"
|
||||
"basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE");
|
||||
|
||||
// RETRIEVE_FIELD_SYMBOL(mutator_lock_, "_ZN3art5Locks13mutator_lock_E");
|
||||
// LOGE("mutator_lock_: %p", mutator_lock_);
|
||||
|
||||
HOOK_FUNC(IsInSamePackage,
|
||||
"_ZN3art6mirror5Class15IsInSamePackageENS_6ObjPtrIS1_EE", //8.0-
|
||||
"_ZN3art6mirror5Class15IsInSamePackageEPS1_"); //5.0-7.1
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mirror
|
||||
} // namespace art
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <base/object.h>
|
||||
|
||||
namespace art {
|
||||
|
||||
class Runtime : public edxp::HookedObject {
|
||||
|
||||
private:
|
||||
inline static Runtime *instance_;
|
||||
|
||||
CREATE_FUNC_SYMBOL_ENTRY(void, DeoptimizeBootImage, void *thiz) {
|
||||
if (LIKELY(DeoptimizeBootImageSym))
|
||||
DeoptimizeBootImageSym(thiz);
|
||||
}
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(bool, Init, void *thiz, void *runtime_options) {
|
||||
if (LIKELY(instance_))
|
||||
instance_->Reset(thiz);
|
||||
else
|
||||
instance_ = new Runtime(thiz);
|
||||
bool success = InitBackup(thiz, runtime_options);
|
||||
if (edxp::ConfigManager::GetInstance()->IsDeoptBootImageEnabled()) {
|
||||
DeoptimizeBootImage(thiz);
|
||||
LOGI("DeoptimizeBootImage done");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public:
|
||||
Runtime(void *thiz) : HookedObject(thiz) {}
|
||||
|
||||
static Runtime *Current() {
|
||||
return instance_;
|
||||
}
|
||||
|
||||
static void Setup(void *handle, HookFunType hook_func) {
|
||||
HOOK_FUNC(Init, "_ZN3art7Runtime4InitEONS_18RuntimeArgumentMapE");
|
||||
RETRIEVE_FUNC_SYMBOL(DeoptimizeBootImage,
|
||||
"_ZN3art7Runtime19DeoptimizeBootImageEv");
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void DeoptimizeBootImage() const {
|
||||
if (LIKELY(thiz_))
|
||||
DeoptimizeBootImage(thiz_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <base/object.h>
|
||||
|
||||
namespace art {
|
||||
|
||||
class Thread : public edxp::HookedObject {
|
||||
|
||||
public:
|
||||
Thread(void *thiz) : HookedObject(thiz) {}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <art/base/macros.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
typedef void (*HookFunType)(void *, void *, void **);
|
||||
|
||||
#define HOOK_FUNC(func, ...) \
|
||||
edxp::HookSyms(handle, hook_func, \
|
||||
reinterpret_cast<void *>(func##Replace), \
|
||||
reinterpret_cast<void **>(&func##Backup), \
|
||||
__VA_ARGS__)
|
||||
|
||||
#define CREATE_HOOK_STUB_ENTRIES(ret, func, ...) \
|
||||
inline static ret (*func##Backup)(__VA_ARGS__); \
|
||||
static ret func##Replace(__VA_ARGS__)
|
||||
|
||||
#define CREATE_ORIGINAL_ENTRY(ret, func, ...) \
|
||||
static ret func(__VA_ARGS__)
|
||||
|
||||
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
|
||||
name##Sym = reinterpret_cast<name##Type>( \
|
||||
edxp::Dlsym(handle, __VA_ARGS__))
|
||||
|
||||
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
|
||||
void *name = edxp::Dlsym(handle, __VA_ARGS__)
|
||||
|
||||
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
|
||||
typedef ret (*func##Type)(__VA_ARGS__); \
|
||||
inline static ret (*func##Sym)(__VA_ARGS__); \
|
||||
ALWAYS_INLINE static ret func(__VA_ARGS__)
|
||||
|
||||
namespace edxp {
|
||||
|
||||
class ShadowObject {
|
||||
|
||||
public:
|
||||
ShadowObject(void *thiz) : thiz_(thiz) {
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline void *Get() {
|
||||
return thiz_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline void Reset(void *thiz) {
|
||||
thiz_ = thiz;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline operator bool() const {
|
||||
return thiz_ != nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
void *thiz_;
|
||||
};
|
||||
|
||||
class HookedObject : public ShadowObject {
|
||||
|
||||
public:
|
||||
|
||||
HookedObject(void *thiz) : ShadowObject(thiz) {}
|
||||
|
||||
static void SetupSymbols(void *handle) {
|
||||
|
||||
}
|
||||
|
||||
static void SetupHooks(void *handle, HookFunType hook_fun) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
ALWAYS_INLINE static void *Dlsym(void *handle, const char *name) {
|
||||
return dlsym(handle, name);
|
||||
}
|
||||
|
||||
template<class T, class ... Args>
|
||||
static void *Dlsym(void *handle, T first, Args... last) {
|
||||
auto ret = Dlsym(handle, first);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
return Dlsym(handle, last...);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline static void HookFunction(HookFunType hook_fun, void *original,
|
||||
void *replace, void **backup) {
|
||||
hook_fun(original, replace, backup);
|
||||
}
|
||||
|
||||
inline static void *HookSym(void *handle, HookFunType hook_fun, const char *sym,
|
||||
void *replace, void **backup) {
|
||||
auto original = Dlsym(handle, sym);
|
||||
if (original) {
|
||||
HookFunction(hook_fun, original, replace, backup);
|
||||
} else {
|
||||
LOGW("%s not found", sym);
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
template<class T, class ... Args>
|
||||
inline static void *HookSyms(void *handle, HookFunType hook_fun,
|
||||
void *replace, void **backup, T first, Args... last) {
|
||||
auto original = Dlsym(handle, first, last...);
|
||||
if (original) {
|
||||
HookFunction(hook_fun, original, replace, backup);
|
||||
return original;
|
||||
} else {
|
||||
LOGW("%s not found", first);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace edxp
|
||||
|
|
@ -1,19 +1,36 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#include "art/base/macros.h"
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
namespace edxp {
|
||||
|
||||
//#define LOG_DISABLED
|
||||
//#define DEBUG
|
||||
|
||||
#define INJECT_DEX_PATH \
|
||||
"/system/framework/edxp.jar:/system/framework/eddalvikdx.jar:/system/framework/eddexmaker.jar"
|
||||
#if defined(__LP64__)
|
||||
# define LP_SELECT(lp32, lp64) (lp64)
|
||||
#else
|
||||
# define LP_SELECT(lp32, lp64) (lp32)
|
||||
#endif
|
||||
|
||||
#define ENTRY_CLASS_NAME "com.elderdrivers.riru.edxp.Main"
|
||||
static constexpr const char *kInjectDexPath = "/system/framework/edxp.jar:"
|
||||
"/system/framework/eddalvikdx.jar:"
|
||||
"/system/framework/eddexmaker.jar";
|
||||
static constexpr const char *kEntryClassName = "com.elderdrivers.riru.edxp.Main";
|
||||
static constexpr const char *kSandHookClassName = "com.swift.sandhook.SandHook";
|
||||
static constexpr const char *kSandHookNeverCallClassName = "com.swift.sandhook.ClassNeverCall";
|
||||
|
||||
#define CLASS_SAND_HOOK "com.swift.sandhook.SandHook"
|
||||
static const std::string kLibBasePath = LP_SELECT("/system/lib/", "/system/lib64/");
|
||||
static const std::string kLibArtPath = kLibBasePath + "libart.so";
|
||||
static const std::string kLibWhalePath = kLibBasePath + "libwhale.edxp.so";
|
||||
static const std::string kLibSandHookPath = kLibBasePath + "libsandhook.edxp.so";
|
||||
static const std::string kLibFwPath = kLibBasePath + "libandroidfw.so";
|
||||
|
||||
#define CLASS_NEVER_CALL "com.swift.sandhook.ClassNeverCall"
|
||||
|
||||
#endif //CONFIG_H
|
||||
inline const char *const BoolToString(bool b) {
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
inline static void *DlOpen(const char *file) {
|
||||
void *handle = dlopen(file, RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (!handle) {
|
||||
LOGE("dlopen(%s) failed: %s", file, dlerror());
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline static T DlSym(void *handle, const char *sym_name) {
|
||||
if (!handle) {
|
||||
LOGE("dlsym(%s) failed: handle is null", sym_name);
|
||||
}
|
||||
T symbol = reinterpret_cast<T>(dlsym(handle, sym_name));
|
||||
if (!symbol) {
|
||||
LOGE("dlsym(%s) failed: %s", sym_name, dlerror());
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
class ScopedDlHandle {
|
||||
|
||||
public:
|
||||
ScopedDlHandle(const char *file) {
|
||||
handle_ = DlOpen(file);
|
||||
}
|
||||
|
||||
~ScopedDlHandle() {
|
||||
if (handle_) {
|
||||
dlclose(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
void *Get() const {
|
||||
return handle_;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T DlSym(const char *sym_name) const {
|
||||
return edxp::DlSym<T>(handle_, sym_name);
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return handle_ != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void *handle_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace android {
|
||||
|
||||
typedef int32_t status_t;
|
||||
|
||||
enum {
|
||||
RES_NULL_TYPE = 0x0000,
|
||||
RES_STRING_POOL_TYPE = 0x0001,
|
||||
RES_TABLE_TYPE = 0x0002,
|
||||
RES_XML_TYPE = 0x0003,
|
||||
// Chunk types in RES_XML_TYPE
|
||||
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
|
||||
RES_XML_START_NAMESPACE_TYPE = 0x0100,
|
||||
RES_XML_END_NAMESPACE_TYPE = 0x0101,
|
||||
RES_XML_START_ELEMENT_TYPE = 0x0102,
|
||||
RES_XML_END_ELEMENT_TYPE = 0x0103,
|
||||
RES_XML_CDATA_TYPE = 0x0104,
|
||||
RES_XML_LAST_CHUNK_TYPE = 0x017f,
|
||||
// This contains a uint32_t array mapping strings in the string
|
||||
// pool back to resource identifiers. It is optional.
|
||||
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
|
||||
// Chunk types in RES_TABLE_TYPE
|
||||
RES_TABLE_PACKAGE_TYPE = 0x0200,
|
||||
RES_TABLE_TYPE_TYPE = 0x0201,
|
||||
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
|
||||
RES_TABLE_LIBRARY_TYPE = 0x0203
|
||||
};
|
||||
|
||||
struct ResXMLTree_node {
|
||||
void *header;
|
||||
// Line number in original source file at which this element appeared.
|
||||
uint32_t lineNumber;
|
||||
// Optional XML comment that was associated with this element; -1 if none.
|
||||
void *comment;
|
||||
};
|
||||
|
||||
class ResXMLTree;
|
||||
|
||||
class ResXMLParser {
|
||||
|
||||
public:
|
||||
enum event_code_t {
|
||||
BAD_DOCUMENT = -1,
|
||||
START_DOCUMENT = 0,
|
||||
END_DOCUMENT = 1,
|
||||
|
||||
FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE,
|
||||
|
||||
START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE,
|
||||
END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE,
|
||||
START_TAG = RES_XML_START_ELEMENT_TYPE,
|
||||
END_TAG = RES_XML_END_ELEMENT_TYPE,
|
||||
TEXT = RES_XML_CDATA_TYPE
|
||||
};
|
||||
|
||||
const ResXMLTree &mTree;
|
||||
event_code_t mEventCode;
|
||||
const ResXMLTree_node *mCurNode;
|
||||
const void *mCurExt;
|
||||
};
|
||||
|
||||
class ResStringPool {
|
||||
|
||||
public:
|
||||
status_t mError;
|
||||
void *mOwnedData;
|
||||
const void *mHeader;
|
||||
size_t mSize;
|
||||
mutable pthread_mutex_t mDecodeLock;
|
||||
const uint32_t *mEntries;
|
||||
const uint32_t *mEntryStyles;
|
||||
const void *mStrings;
|
||||
char16_t mutable **mCache;
|
||||
uint32_t mStringPoolSize; // number of uint16_t
|
||||
const uint32_t *mStyles;
|
||||
uint32_t mStylePoolSize; // number of uint32_t
|
||||
};
|
||||
|
||||
|
||||
class ResXMLTree : public ResXMLParser {
|
||||
|
||||
public:
|
||||
void *mDynamicRefTable;
|
||||
status_t mError;
|
||||
void *mOwnedData;
|
||||
const void *mHeader;
|
||||
size_t mSize;
|
||||
const uint8_t *mDataEnd;
|
||||
ResStringPool mStrings;
|
||||
const uint32_t *mResIds;
|
||||
size_t mNumResIds;
|
||||
const ResXMLTree_node *mRootNode;
|
||||
const void *mRootExt;
|
||||
event_code_t mRootCode;
|
||||
};
|
||||
|
||||
struct ResStringPool_ref {
|
||||
|
||||
// Index into the string pool table (uint32_t-offset from the indices
|
||||
// immediately after ResStringPool_header) at which to find the location
|
||||
// of the string data in the pool.
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
struct ResXMLTree_attrExt {
|
||||
|
||||
// String of the full namespace of this element.
|
||||
struct ResStringPool_ref ns;
|
||||
|
||||
// String name of this node if it is an ELEMENT; the raw
|
||||
// character data if this is a CDATA node.
|
||||
struct ResStringPool_ref name;
|
||||
|
||||
// Byte offset from the start of this structure where the attributes start.
|
||||
uint16_t attributeStart;
|
||||
|
||||
// Size of the ResXMLTree_attribute structures that follow.
|
||||
uint16_t attributeSize;
|
||||
|
||||
// Number of attributes associated with an ELEMENT. These are
|
||||
// available as an array of ResXMLTree_attribute structures
|
||||
// immediately following this node.
|
||||
uint16_t attributeCount;
|
||||
|
||||
// Index (1-based) of the "id" attribute. 0 if none.
|
||||
uint16_t idIndex;
|
||||
|
||||
// Index (1-based) of the "class" attribute. 0 if none.
|
||||
uint16_t classIndex;
|
||||
|
||||
// Index (1-based) of the "style" attribute. 0 if none.
|
||||
uint16_t styleIndex;
|
||||
};
|
||||
|
||||
struct Res_value {
|
||||
|
||||
// Number of bytes in this structure.
|
||||
uint16_t size;
|
||||
// Always set to 0.
|
||||
uint8_t res0;
|
||||
|
||||
// Type of the data value.
|
||||
enum : uint8_t {
|
||||
// The 'data' is either 0 or 1, specifying this resource is either
|
||||
// undefined or empty, respectively.
|
||||
TYPE_NULL = 0x00,
|
||||
// The 'data' holds a ResTable_ref, a reference to another resource
|
||||
// table entry.
|
||||
TYPE_REFERENCE = 0x01,
|
||||
// The 'data' holds an attribute resource identifier.
|
||||
TYPE_ATTRIBUTE = 0x02,
|
||||
// The 'data' holds an index into the containing resource table's
|
||||
// global value string pool.
|
||||
TYPE_STRING = 0x03,
|
||||
// The 'data' holds a single-precision floating point number.
|
||||
TYPE_FLOAT = 0x04,
|
||||
// The 'data' holds a complex number encoding a dimension value,
|
||||
// such as "100in".
|
||||
TYPE_DIMENSION = 0x05,
|
||||
// The 'data' holds a complex number encoding a fraction of a
|
||||
// container.
|
||||
TYPE_FRACTION = 0x06,
|
||||
// The 'data' holds a dynamic ResTable_ref, which needs to be
|
||||
// resolved before it can be used like a TYPE_REFERENCE.
|
||||
TYPE_DYNAMIC_REFERENCE = 0x07,
|
||||
// The 'data' holds an attribute resource identifier, which needs to be resolved
|
||||
// before it can be used like a TYPE_ATTRIBUTE.
|
||||
TYPE_DYNAMIC_ATTRIBUTE = 0x08,
|
||||
// Beginning of integer flavors...
|
||||
TYPE_FIRST_INT = 0x10,
|
||||
// The 'data' is a raw integer value of the form n..n.
|
||||
TYPE_INT_DEC = 0x10,
|
||||
// The 'data' is a raw integer value of the form 0xn..n.
|
||||
TYPE_INT_HEX = 0x11,
|
||||
// The 'data' is either 0 or 1, for input "false" or "true" respectively.
|
||||
TYPE_INT_BOOLEAN = 0x12,
|
||||
// Beginning of color integer flavors...
|
||||
TYPE_FIRST_COLOR_INT = 0x1c,
|
||||
// The 'data' is a raw integer value of the form #aarrggbb.
|
||||
TYPE_INT_COLOR_ARGB8 = 0x1c,
|
||||
// The 'data' is a raw integer value of the form #rrggbb.
|
||||
TYPE_INT_COLOR_RGB8 = 0x1d,
|
||||
// The 'data' is a raw integer value of the form #argb.
|
||||
TYPE_INT_COLOR_ARGB4 = 0x1e,
|
||||
// The 'data' is a raw integer value of the form #rgb.
|
||||
TYPE_INT_COLOR_RGB4 = 0x1f,
|
||||
// ...end of integer flavors.
|
||||
TYPE_LAST_COLOR_INT = 0x1f,
|
||||
// ...end of integer flavors.
|
||||
TYPE_LAST_INT = 0x1f
|
||||
};
|
||||
uint8_t dataType;
|
||||
// Structure of complex data values (TYPE_UNIT and TYPE_FRACTION)
|
||||
enum {
|
||||
// Where the unit type information is. This gives us 16 possible
|
||||
// types, as defined below.
|
||||
COMPLEX_UNIT_SHIFT = 0,
|
||||
COMPLEX_UNIT_MASK = 0xf,
|
||||
// TYPE_DIMENSION: Value is raw pixels.
|
||||
COMPLEX_UNIT_PX = 0,
|
||||
// TYPE_DIMENSION: Value is Device Independent Pixels.
|
||||
COMPLEX_UNIT_DIP = 1,
|
||||
// TYPE_DIMENSION: Value is a Scaled device independent Pixels.
|
||||
COMPLEX_UNIT_SP = 2,
|
||||
// TYPE_DIMENSION: Value is in points.
|
||||
COMPLEX_UNIT_PT = 3,
|
||||
// TYPE_DIMENSION: Value is in inches.
|
||||
COMPLEX_UNIT_IN = 4,
|
||||
// TYPE_DIMENSION: Value is in millimeters.
|
||||
COMPLEX_UNIT_MM = 5,
|
||||
// TYPE_FRACTION: A basic fraction of the overall size.
|
||||
COMPLEX_UNIT_FRACTION = 0,
|
||||
// TYPE_FRACTION: A fraction of the parent size.
|
||||
COMPLEX_UNIT_FRACTION_PARENT = 1,
|
||||
// Where the radix information is, telling where the decimal place
|
||||
// appears in the mantissa. This give us 4 possible fixed point
|
||||
// representations as defined below.
|
||||
COMPLEX_RADIX_SHIFT = 4,
|
||||
COMPLEX_RADIX_MASK = 0x3,
|
||||
// The mantissa is an integral number -- i.e., 0xnnnnnn.0
|
||||
COMPLEX_RADIX_23p0 = 0,
|
||||
// The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
|
||||
COMPLEX_RADIX_16p7 = 1,
|
||||
// The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
|
||||
COMPLEX_RADIX_8p15 = 2,
|
||||
// The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
|
||||
COMPLEX_RADIX_0p23 = 3,
|
||||
// Where the actual value is. This gives us 23 bits of
|
||||
// precision. The top bit is the sign.
|
||||
COMPLEX_MANTISSA_SHIFT = 8,
|
||||
COMPLEX_MANTISSA_MASK = 0xffffff
|
||||
};
|
||||
// Possible data values for TYPE_NULL.
|
||||
enum {
|
||||
// The value is not defined.
|
||||
DATA_NULL_UNDEFINED = 0,
|
||||
// The value is explicitly defined as empty.
|
||||
DATA_NULL_EMPTY = 1
|
||||
};
|
||||
// The data for this item, as interpreted according to dataType.
|
||||
typedef uint32_t data_type;
|
||||
data_type data;
|
||||
};
|
||||
|
||||
struct ResXMLTree_attribute {
|
||||
// Namespace of this attribute.
|
||||
struct ResStringPool_ref ns;
|
||||
|
||||
// Name of this attribute.
|
||||
struct ResStringPool_ref name;
|
||||
|
||||
// The original raw string value of this attribute.
|
||||
struct ResStringPool_ref rawValue;
|
||||
|
||||
// Processesd typed value of this attribute.
|
||||
struct Res_value typedValue;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
|
|
@ -31,20 +33,21 @@
|
|||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char* kPathPrefixWhitelist[] = {
|
||||
static const char *kPathPrefixWhitelist[] = {
|
||||
"/data/app/",
|
||||
"/data/app-private/"
|
||||
};
|
||||
|
||||
static const char* kFdPath = "/proc/self/fd";
|
||||
static const char *kFdPath = "/proc/self/fd";
|
||||
|
||||
// todo stay up to date
|
||||
// Keeps track of all relevant information (flags, offset etc.) of an
|
||||
// open zygote file descriptor.
|
||||
class FileDescriptorInfo {
|
||||
public:
|
||||
// Create a FileDescriptorInfo for a given file descriptor. Returns
|
||||
// |NULL| if an error occurred.
|
||||
static FileDescriptorInfo* createFromFd(int fd) {
|
||||
static FileDescriptorInfo *createFromFd(int fd) {
|
||||
struct stat f_stat;
|
||||
// This should never happen; the zygote should always have the right set
|
||||
// of permissions required to stat all its open files.
|
||||
|
|
@ -128,7 +131,8 @@ public:
|
|||
int open_flags = fs_flags & (kOpenFlags);
|
||||
fs_flags = fs_flags & (~(kOpenFlags));
|
||||
|
||||
return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
|
||||
return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags,
|
||||
offset);
|
||||
}
|
||||
|
||||
bool Detach() const {
|
||||
|
|
@ -215,7 +219,7 @@ private:
|
|||
is_sock(true) {
|
||||
}
|
||||
|
||||
FileDescriptorInfo(struct stat pstat, const std::string& pfile_path, int pfd, int popen_flags,
|
||||
FileDescriptorInfo(struct stat pstat, const std::string &pfile_path, int pfd, int popen_flags,
|
||||
int pfd_flags, int pfs_flags, off_t poffset) :
|
||||
fd(pfd),
|
||||
stat(pstat),
|
||||
|
|
@ -228,8 +232,9 @@ private:
|
|||
}
|
||||
|
||||
// Returns true iff. a given path is whitelisted.
|
||||
static bool IsWhitelisted(const std::string& path) {
|
||||
for (size_t i = 0; i < (sizeof(kPathPrefixWhitelist) / sizeof(kPathPrefixWhitelist[0])); ++i) {
|
||||
static bool IsWhitelisted(const std::string &path) {
|
||||
for (size_t i = 0;
|
||||
i < (sizeof(kPathPrefixWhitelist) / sizeof(kPathPrefixWhitelist[0])); ++i) {
|
||||
if (path.compare(0, strlen(kPathPrefixWhitelist[i]), kPathPrefixWhitelist[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -238,7 +243,7 @@ private:
|
|||
}
|
||||
|
||||
// TODO: Call android::base::Readlink instead of copying the code here.
|
||||
static bool Readlink(const int fd, std::string* result) {
|
||||
static bool Readlink(const int fd, std::string *result) {
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
|
||||
|
||||
|
|
@ -249,7 +254,7 @@ private:
|
|||
// We could call lstat first, but that would introduce a race condition that
|
||||
// we couldn't detect.
|
||||
// ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here.
|
||||
char* buf = new char[4096];
|
||||
char *buf = new char[4096];
|
||||
ssize_t len = readlink(path, buf, 4096);
|
||||
if (len == -1) {
|
||||
delete[] buf;
|
||||
|
|
@ -268,9 +273,9 @@ private:
|
|||
// - the length of the path is greater than zero (i.e, not an unnamed socket).
|
||||
// - the first byte of the path isn't zero (i.e, not a socket with an abstract
|
||||
// address).
|
||||
static bool GetSocketName(const int fd, std::string* result) {
|
||||
static bool GetSocketName(const int fd, std::string *result) {
|
||||
sockaddr_storage ss;
|
||||
sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
|
||||
sockaddr *addr = reinterpret_cast<sockaddr *>(&ss);
|
||||
socklen_t addr_len = sizeof(ss);
|
||||
|
||||
if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
|
||||
|
|
@ -290,7 +295,7 @@ private:
|
|||
return false;
|
||||
}
|
||||
|
||||
const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss);
|
||||
const sockaddr_un *unix_addr = reinterpret_cast<const sockaddr_un *>(&ss);
|
||||
|
||||
size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
|
||||
// This is an unnamed local socket, we do not accept it.
|
||||
|
|
@ -308,7 +313,7 @@ private:
|
|||
// If we're here, sun_path must refer to a null terminated filesystem
|
||||
// pathname (man 7 unix). Remove the terminator before assigning it to an
|
||||
// std::string.
|
||||
if (unix_addr->sun_path[path_len - 1] == '\0') {
|
||||
if (unix_addr->sun_path[path_len - 1] == '\0') {
|
||||
--path_len;
|
||||
}
|
||||
|
||||
|
|
@ -318,8 +323,9 @@ private:
|
|||
|
||||
|
||||
// DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
|
||||
FileDescriptorInfo(const FileDescriptorInfo&);
|
||||
void operator=(const FileDescriptorInfo&);
|
||||
FileDescriptorInfo(const FileDescriptorInfo &);
|
||||
|
||||
void operator=(const FileDescriptorInfo &);
|
||||
};
|
||||
|
||||
// A FileDescriptorTable is a collection of FileDescriptorInfo objects
|
||||
|
|
@ -329,23 +335,23 @@ public:
|
|||
// Creates a new FileDescriptorTable. This function scans
|
||||
// /proc/self/fd for the list of open file descriptors and collects
|
||||
// information about them. Returns NULL if an error occurs.
|
||||
static FileDescriptorTable* Create() {
|
||||
DIR* d = opendir(kFdPath);
|
||||
static FileDescriptorTable *Create() {
|
||||
DIR *d = opendir(kFdPath);
|
||||
if (d == NULL) {
|
||||
LOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
int dir_fd = dirfd(d);
|
||||
dirent* e;
|
||||
dirent *e;
|
||||
|
||||
std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
|
||||
std::unordered_map<int, FileDescriptorInfo *> open_fd_map;
|
||||
while ((e = readdir(d)) != NULL) {
|
||||
const int fd = ParseFd(e, dir_fd);
|
||||
if (fd == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd);
|
||||
FileDescriptorInfo *info = FileDescriptorInfo::createFromFd(fd);
|
||||
if (info == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -362,9 +368,9 @@ public:
|
|||
|
||||
// Reopens all file descriptors that are contained in the table.
|
||||
void Reopen() {
|
||||
std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
|
||||
std::unordered_map<int, FileDescriptorInfo *>::const_iterator it;
|
||||
for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
|
||||
const FileDescriptorInfo* info = it->second;
|
||||
const FileDescriptorInfo *info = it->second;
|
||||
if (info != NULL) {
|
||||
info->Reopen();
|
||||
delete info;
|
||||
|
|
@ -373,12 +379,12 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map)
|
||||
FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo *> &map)
|
||||
: open_fd_map_(map) {
|
||||
}
|
||||
|
||||
static int ParseFd(dirent* e, int dir_fd) {
|
||||
char* end;
|
||||
static int ParseFd(dirent *e, int dir_fd) {
|
||||
char *end;
|
||||
const int fd = strtol(e->d_name, &end, 10);
|
||||
if ((*end) != '\0') {
|
||||
return -1;
|
||||
|
|
@ -394,9 +400,10 @@ private:
|
|||
}
|
||||
|
||||
// Invariant: All values in this unordered_map are non-NULL.
|
||||
std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
|
||||
std::unordered_map<int, FileDescriptorInfo *> open_fd_map_;
|
||||
|
||||
// DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable);
|
||||
FileDescriptorTable(const FileDescriptorTable&);
|
||||
void operator=(const FileDescriptorTable&);
|
||||
FileDescriptorTable(const FileDescriptorTable &);
|
||||
|
||||
void operator=(const FileDescriptorTable &);
|
||||
};
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#include "android/log.h"
|
||||
|
||||
#ifndef LOG_TAG
|
||||
#define LOG_TAG "EdXposed-Core-Native"
|
||||
#define LOG_TAG "EdXposed"
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mntent.h>
|
||||
#include <jni.h>
|
||||
#include <asm/fcntl.h>
|
||||
#include <fcntl.h>
|
||||
#include <dlfcn.h>
|
||||
#include "java_hook/java_hook.h"
|
||||
#include "include/logging.h"
|
||||
#include "misc.h"
|
||||
|
||||
ssize_t fdgets(char *buf, const size_t size, int fd) {
|
||||
ssize_t len = 0;
|
||||
buf[0] = '\0';
|
||||
while (len < size - 1) {
|
||||
ssize_t ret = read(fd, buf + len, 1);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
break;
|
||||
if (buf[len] == '\0' || buf[len++] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf[len] = '\0';
|
||||
buf[size - 1] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
char *get_cmdline_from_pid(pid_t pid, char *buf, size_t len) {
|
||||
char filename[32];
|
||||
if (pid < 1 || buf == NULL) {
|
||||
printf("%s: illegal para\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(filename, 32, "/proc/%d/cmdline", pid);
|
||||
int read_ret = read_to_buf(filename, buf, len);
|
||||
if (read_ret < 0)
|
||||
return NULL;
|
||||
|
||||
if (buf[read_ret - 1] == '\n')
|
||||
buf[--read_ret] = 0;
|
||||
|
||||
char *name = buf;
|
||||
while (read_ret) {
|
||||
if (((unsigned char) *name) < ' ')
|
||||
*name = ' ';
|
||||
name++;
|
||||
read_ret--;
|
||||
}
|
||||
*name = 0;
|
||||
name = NULL;
|
||||
|
||||
LOGV("cmdline:%s\n", buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int read_to_buf(const char *filename, void *buf, size_t len) {
|
||||
int fd;
|
||||
int ret;
|
||||
if (buf == NULL) {
|
||||
printf("%s: illegal para\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
memset(buf, 0, len);
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
return -1;
|
||||
}
|
||||
ret = (int) read(fd, buf, len);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *getAppId(char *application_id, size_t size) {
|
||||
pid_t pid = getpid();
|
||||
// LOGV("process new id %d", pid);
|
||||
char path[64] = {0};
|
||||
sprintf(path, "/proc/%d/cmdline", pid);
|
||||
FILE *cmdline = fopen(path, "r");
|
||||
if (cmdline) {
|
||||
fread(application_id, size, 1, cmdline);
|
||||
// LOGV("application id %s", application_id);
|
||||
fclose(cmdline);
|
||||
}
|
||||
return application_id;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
ssize_t fdgets(char *buf, const size_t size, int fd);
|
||||
|
||||
char *get_cmdline_from_pid(pid_t pid, char *buf, size_t len);
|
||||
|
||||
int read_to_buf(const char *filename, void *buf, size_t len);
|
||||
|
||||
char *getAppId(char *application_id, size_t size);
|
||||
|
||||
#endif // MISC_H
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-value"
|
||||
#pragma once
|
||||
|
||||
#include <edxp_context.h>
|
||||
#include <art/base/macros.h>
|
||||
#include <nativehelper/scoped_local_ref.h>
|
||||
#include <android-base/logging.h>
|
||||
#include "JNIHelper.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
ALWAYS_INLINE inline void RegisterNativeMethodsInternal(JNIEnv *env,
|
||||
const char *class_name,
|
||||
const JNINativeMethod *methods,
|
||||
jint method_count) {
|
||||
|
||||
ScopedLocalRef<jclass> clazz(env,
|
||||
Context::GetInstance()->FindClassFromLoader(env, class_name));
|
||||
if (clazz.get() == nullptr) {
|
||||
LOG(FATAL) << "Couldn't find class: " << class_name;
|
||||
return;
|
||||
}
|
||||
jint jni_result = JNI_RegisterNatives(env, clazz.get(), methods, method_count);
|
||||
CHECK_EQ(JNI_OK, jni_result);
|
||||
}
|
||||
|
||||
#define REGISTER_EDXP_NATIVE_METHODS(class_name) \
|
||||
RegisterNativeMethodsInternal(env, (class_name), gMethods, arraysize(gMethods))
|
||||
|
||||
} // namespace edxp
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
//
|
||||
// Created by Solo on 2019/1/27.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <jni.h>
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <include/android_build.h>
|
||||
#include <include/logging.h>
|
||||
#include <linux/limits.h>
|
||||
#include "config_manager.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define PRIMARY_INSTALLER_PKG_NAME "com.solohsu.android.edxp.manager"
|
||||
#define SECONDARY_INSTALLER_PKG_NAME "org.meowcat.edxposed.manager"
|
||||
#define LEGACY_INSTALLER_PKG_NAME "de.robv.android.xposed.installer"
|
||||
|
||||
static bool use_prot_storage = GetAndroidApiLevel() >= ANDROID_N;
|
||||
static const char *data_path_prefix = use_prot_storage ? "/data/user_de/0/" : "/data/user/0/";
|
||||
static const char *config_path_tpl = "%s/%s/conf/%s";
|
||||
static char data_test_path[PATH_MAX];
|
||||
static char base_config_path[PATH_MAX];
|
||||
static char blacklist_path[PATH_MAX];
|
||||
static char whitelist_path[PATH_MAX];
|
||||
static char use_whitelist_path[PATH_MAX];
|
||||
static char black_white_list_path[PATH_MAX];
|
||||
static char dynamic_modules_path[PATH_MAX];
|
||||
static char deopt_boot_image_path[PATH_MAX];
|
||||
static char resources_hook_disable_path[PATH_MAX];
|
||||
|
||||
static const char *installer_package_name;
|
||||
static bool black_white_list_enabled = false;
|
||||
static bool dynamic_modules_enabled = false;
|
||||
static bool deopt_boot_image_enabled = false;
|
||||
static bool resources_hook_enabled = true;
|
||||
static bool inited = false;
|
||||
// snapshot at boot
|
||||
static bool use_white_list_snapshot = false;
|
||||
static vector<string> white_list_default;
|
||||
static vector<string> black_list_default;
|
||||
|
||||
static const char *get_installer_package_name() {
|
||||
snprintf(data_test_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
PRIMARY_INSTALLER_PKG_NAME, "/");
|
||||
if (access(data_test_path, F_OK) == 0) {
|
||||
LOGI("using installer "
|
||||
PRIMARY_INSTALLER_PKG_NAME);
|
||||
return PRIMARY_INSTALLER_PKG_NAME;
|
||||
}
|
||||
snprintf(data_test_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
SECONDARY_INSTALLER_PKG_NAME, "/");
|
||||
if (access(data_test_path, F_OK) == 0) {
|
||||
LOGI("using installer "
|
||||
SECONDARY_INSTALLER_PKG_NAME);
|
||||
return SECONDARY_INSTALLER_PKG_NAME;
|
||||
}
|
||||
snprintf(data_test_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
LEGACY_INSTALLER_PKG_NAME, "/");
|
||||
if (access(data_test_path, F_OK) == 0) {
|
||||
LOGI("using installer "
|
||||
LEGACY_INSTALLER_PKG_NAME);
|
||||
return LEGACY_INSTALLER_PKG_NAME;
|
||||
}
|
||||
LOGE("no supported installer app found, using primary as default");
|
||||
return PRIMARY_INSTALLER_PKG_NAME;
|
||||
}
|
||||
|
||||
static void snapshotBlackWhiteList() {
|
||||
DIR *dir;
|
||||
struct dirent *dent;
|
||||
dir = opendir(whitelist_path);
|
||||
if (dir != nullptr) {
|
||||
while ((dent = readdir(dir)) != nullptr) {
|
||||
if (dent->d_type == DT_REG) {
|
||||
const char *fileName = dent->d_name;
|
||||
LOGI("whitelist: %s", fileName);
|
||||
white_list_default.emplace_back(fileName);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
dir = opendir(blacklist_path);
|
||||
if (dir != nullptr) {
|
||||
while ((dent = readdir(dir)) != nullptr) {
|
||||
if (dent->d_type == DT_REG) {
|
||||
const char *fileName = dent->d_name;
|
||||
LOGI("blacklist: %s", fileName);
|
||||
black_list_default.emplace_back(fileName);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void init_once() {
|
||||
if (!inited) {
|
||||
installer_package_name = get_installer_package_name();
|
||||
snprintf(base_config_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "");
|
||||
snprintf(blacklist_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "blacklist/");
|
||||
snprintf(whitelist_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "whitelist/");
|
||||
snprintf(use_whitelist_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "usewhitelist");
|
||||
snprintf(black_white_list_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "blackwhitelist");
|
||||
snprintf(dynamic_modules_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "dynamicmodules");
|
||||
snprintf(deopt_boot_image_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "deoptbootimage");
|
||||
snprintf(resources_hook_disable_path, PATH_MAX, config_path_tpl, data_path_prefix,
|
||||
installer_package_name, "disable_resources");
|
||||
dynamic_modules_enabled = access(dynamic_modules_path, F_OK) == 0;
|
||||
black_white_list_enabled = access(black_white_list_path, F_OK) == 0;
|
||||
// use_white_list snapshot
|
||||
use_white_list_snapshot = access(use_whitelist_path, F_OK) == 0;
|
||||
deopt_boot_image_enabled = access(deopt_boot_image_path, F_OK) == 0;
|
||||
resources_hook_enabled = access(resources_hook_disable_path, F_OK) != 0;
|
||||
LOGI("black/white list mode: %d, using whitelist: %d", black_white_list_enabled,
|
||||
use_white_list_snapshot);
|
||||
LOGI("dynamic modules mode: %d", dynamic_modules_enabled);
|
||||
LOGI("resources hook: %d", resources_hook_enabled);
|
||||
if (black_white_list_enabled) {
|
||||
snapshotBlackWhiteList();
|
||||
}
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
static char package_name[256];
|
||||
|
||||
bool is_app_need_hook(JNIEnv *env, jclass, jstring appDataDir) {
|
||||
init_once();
|
||||
if (!black_white_list_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *app_data_dir = env->GetStringUTFChars(appDataDir, nullptr);
|
||||
bool can_access_app_data = access(base_config_path, F_OK) == 0;
|
||||
bool use_white_list;
|
||||
if (can_access_app_data) {
|
||||
use_white_list = access(use_whitelist_path, F_OK) == 0;
|
||||
} else {
|
||||
LOGE("can't access config path, using snapshot use_white_list: %s", app_data_dir);
|
||||
use_white_list = use_white_list_snapshot;
|
||||
}
|
||||
int user = 0;
|
||||
if (sscanf(app_data_dir, "/data/%*[^/]/%d/%s", &user, package_name) != 2) {
|
||||
if (sscanf(app_data_dir, "/data/%*[^/]/%s", package_name) != 1) {
|
||||
package_name[0] = '\0';
|
||||
LOGE("can't parse %s", app_data_dir);
|
||||
env->ReleaseStringUTFChars(appDataDir, app_data_dir);
|
||||
return !use_white_list;
|
||||
}
|
||||
}
|
||||
if (strcmp(package_name, "com.solohsu.android.edxp.manager") == 0
|
||||
|| strcmp(package_name, "org.meowcat.edxposed.manager") == 0
|
||||
|| strcmp(package_name, "de.robv.android.xposed.installer") == 0) {
|
||||
// always hook installer apps
|
||||
env->ReleaseStringUTFChars(appDataDir, app_data_dir);
|
||||
return true;
|
||||
}
|
||||
if (use_white_list) {
|
||||
if (!can_access_app_data) {
|
||||
LOGE("can't access config path, using snapshot white list: %s", app_data_dir);
|
||||
return find(white_list_default.begin(), white_list_default.end(), package_name) !=
|
||||
white_list_default.end();
|
||||
}
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, "%s%s", whitelist_path, package_name);
|
||||
bool res = access(path, F_OK) == 0;
|
||||
LOGD("using whitelist, %s -> %d", app_data_dir, res);
|
||||
env->ReleaseStringUTFChars(appDataDir, app_data_dir);
|
||||
return res;
|
||||
} else {
|
||||
if (!can_access_app_data) {
|
||||
LOGE("can't access config path, using snapshot black list: %s", app_data_dir);
|
||||
return !(find(black_list_default.begin(), black_list_default.end(), package_name) !=
|
||||
black_list_default.end());
|
||||
}
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, "%s%s", blacklist_path, package_name);
|
||||
bool res = access(path, F_OK) != 0;
|
||||
LOGD("using blacklist, %s -> %d", app_data_dir, res);
|
||||
env->ReleaseStringUTFChars(appDataDir, app_data_dir);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_black_white_list_enabled() {
|
||||
init_once();
|
||||
return black_white_list_enabled;
|
||||
}
|
||||
|
||||
bool is_dynamic_modules_enabled() {
|
||||
init_once();
|
||||
return dynamic_modules_enabled;
|
||||
}
|
||||
|
||||
bool is_resources_hook_enabled() {
|
||||
init_once();
|
||||
return resources_hook_enabled;
|
||||
}
|
||||
|
||||
bool is_deopt_boot_image_enabled() {
|
||||
init_once();
|
||||
return deopt_boot_image_enabled;
|
||||
}
|
||||
|
||||
jstring get_installer_pkg_name(JNIEnv *env, jclass) {
|
||||
init_once();
|
||||
return env->NewStringUTF(installer_package_name);
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// Created by Solo on 2019/1/27.
|
||||
//
|
||||
|
||||
#ifndef EDXPOSED_CONFIG_MANAGER_H
|
||||
#define EDXPOSED_CONFIG_MANAGER_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
bool is_app_need_hook(JNIEnv *env, jclass, jstring appDataDir);
|
||||
|
||||
bool is_black_white_list_enabled();
|
||||
|
||||
bool is_dynamic_modules_enabled();
|
||||
|
||||
bool is_resources_hook_enabled();
|
||||
|
||||
bool is_deopt_boot_image_enabled();
|
||||
|
||||
jstring get_installer_pkg_name(JNIEnv *env, jclass clazz);
|
||||
|
||||
#endif //EDXPOSED_CONFIG_MANAGER_H
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
|
||||
|
||||
#include <java_hook/java_hook.h>
|
||||
#include <unistd.h>
|
||||
#include <include/logging.h>
|
||||
#include "framework_hook.h"
|
||||
#include "include/misc.h"
|
||||
#include "config_manager.h"
|
||||
|
||||
#define SYSTEM_SERVER_DATA_DIR "/data/user/0/android"
|
||||
|
||||
static jclass sEntryClass;
|
||||
static jstring sAppDataDir;
|
||||
static jstring sNiceName;
|
||||
|
||||
void prepareJavaEnv(JNIEnv *env) {
|
||||
loadDexAndInit(env, INJECT_DEX_PATH);
|
||||
sEntryClass = findClassFromLoader(env, gInjectDexClassLoader, ENTRY_CLASS_NAME);
|
||||
}
|
||||
|
||||
void findAndCall(JNIEnv *env, const char *methodName, const char *methodSig, ...) {
|
||||
if (!sEntryClass) {
|
||||
LOGE("cannot call method %s, entry class is null", methodName);
|
||||
return;
|
||||
}
|
||||
jmethodID mid = env->GetStaticMethodID(sEntryClass, methodName, methodSig);
|
||||
if (env->ExceptionOccurred()) {
|
||||
env->ExceptionClear();
|
||||
LOGE("method %s not found in entry class", methodName);
|
||||
mid = NULL;
|
||||
}
|
||||
if (mid) {
|
||||
va_list args;
|
||||
va_start(args, methodSig);
|
||||
env->functions->CallStaticVoidMethodV(env, sEntryClass, mid, args);
|
||||
va_end(args);
|
||||
} else {
|
||||
LOGE("method %s id is null", methodName);
|
||||
}
|
||||
}
|
||||
|
||||
void onNativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids,
|
||||
jint runtime_flags, jobjectArray rlimits,
|
||||
jlong permittedCapabilities, jlong effectiveCapabilities) {
|
||||
sAppDataDir = env->NewStringUTF(SYSTEM_SERVER_DATA_DIR);
|
||||
bool is_black_white_list_mode = is_black_white_list_enabled();
|
||||
bool is_dynamic_modules_mode = is_dynamic_modules_enabled();
|
||||
if (is_black_white_list_mode && is_dynamic_modules_mode) {
|
||||
// when black/white list is on, never inject into zygote if dynamic modules mode is on
|
||||
return;
|
||||
}
|
||||
prepareJavaEnv(env);
|
||||
// jump to java code
|
||||
findAndCall(env, "forkSystemServerPre", "(II[II[[IJJ)V", uid, gid, gids, runtime_flags,
|
||||
rlimits, permittedCapabilities, effectiveCapabilities);
|
||||
}
|
||||
|
||||
|
||||
int onNativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res) {
|
||||
if (res == 0) {
|
||||
prepareJavaEnv(env);
|
||||
// only do work in child since findAndCall would print log
|
||||
findAndCall(env, "forkSystemServerPost", "(I)V", res);
|
||||
} else {
|
||||
// in zygote process, res is child zygote pid
|
||||
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void onNativeForkAndSpecializePre(JNIEnv *env, jclass clazz,
|
||||
jint uid, jint gid,
|
||||
jintArray gids,
|
||||
jint runtime_flags,
|
||||
jobjectArray rlimits,
|
||||
jint _mount_external,
|
||||
jstring se_info,
|
||||
jstring se_name,
|
||||
jintArray fdsToClose,
|
||||
jintArray fdsToIgnore,
|
||||
jboolean is_child_zygote,
|
||||
jstring instructionSet,
|
||||
jstring appDataDir) {
|
||||
sAppDataDir = appDataDir;
|
||||
sNiceName = se_name;
|
||||
bool is_black_white_list_mode = is_black_white_list_enabled();
|
||||
bool is_dynamic_modules_mode = is_dynamic_modules_enabled();
|
||||
if (is_black_white_list_mode && is_dynamic_modules_mode) {
|
||||
// when black/white list is on, never inject into zygote if dynamic modules mode is on
|
||||
return;
|
||||
}
|
||||
prepareJavaEnv(env);
|
||||
findAndCall(env, "forkAndSpecializePre",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)V",
|
||||
uid, gid, gids, runtime_flags, rlimits,
|
||||
_mount_external, se_info, se_name, fdsToClose, fdsToIgnore,
|
||||
is_child_zygote, instructionSet, appDataDir);
|
||||
}
|
||||
|
||||
int onNativeForkAndSpecializePost(JNIEnv *env, jclass clazz, jint res) {
|
||||
if (res == 0) {
|
||||
prepareJavaEnv(env);
|
||||
findAndCall(env, "forkAndSpecializePost", "(ILjava/lang/String;Ljava/lang/String;)V", res, sAppDataDir, sNiceName);
|
||||
} else {
|
||||
// in zygote process, res is child zygote pid
|
||||
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
|
||||
#ifndef RIRU_FRAMEWORK_HOOK_H
|
||||
#define RIRU_FRAMEWORK_HOOK_H
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
void onNativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids,
|
||||
jint runtime_flags, jobjectArray rlimits,
|
||||
jlong permittedCapabilities, jlong effectiveCapabilities);
|
||||
|
||||
int onNativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res);
|
||||
|
||||
void onNativeForkAndSpecializePre(JNIEnv *env, jclass clazz,
|
||||
jint _uid, jint gid,
|
||||
jintArray gids,
|
||||
jint runtime_flags,
|
||||
jobjectArray rlimits,
|
||||
jint _mount_external,
|
||||
jstring se_info,
|
||||
jstring se_name,
|
||||
jintArray fdsToClose,
|
||||
jintArray fdsToIgnore,
|
||||
jboolean is_child_zygote,
|
||||
jstring instructionSet,
|
||||
jstring appDataDir);
|
||||
|
||||
int onNativeForkAndSpecializePost(JNIEnv *env, jclass clazz, jint res);
|
||||
|
||||
#endif //RIRU_FRAMEWORK_HOOK_H
|
||||
|
|
@ -1,274 +0,0 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <unistd.h>
|
||||
#include <mntent.h>
|
||||
#include <jni.h>
|
||||
#include <fcntl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <inject/config_manager.h>
|
||||
#include <native_hook/native_hook.h>
|
||||
#include <native_hook/resource_hook.h>
|
||||
#include "java_hook/java_hook.h"
|
||||
#include "include/logging.h"
|
||||
#include "include/fd_utils-inl.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "HookMain.h"
|
||||
}
|
||||
|
||||
jobject gInjectDexClassLoader;
|
||||
|
||||
static bool isInited = false;
|
||||
|
||||
static FileDescriptorTable *gClosedFdTable = nullptr;
|
||||
|
||||
void closeFilesBeforeForkNative(JNIEnv *, jclass) {
|
||||
// FIXME what if gClosedFdTable is not null
|
||||
gClosedFdTable = FileDescriptorTable::Create();
|
||||
}
|
||||
|
||||
void reopenFilesAfterForkNative(JNIEnv *, jclass) {
|
||||
if (!gClosedFdTable) {
|
||||
LOGE("gClosedFdTable is null when reopening files");
|
||||
return;
|
||||
}
|
||||
gClosedFdTable->Reopen();
|
||||
delete gClosedFdTable;
|
||||
gClosedFdTable = nullptr;
|
||||
}
|
||||
|
||||
jlong suspendAllThreads(JNIEnv *, jclass) {
|
||||
if (!suspendAll) {
|
||||
return 0;
|
||||
}
|
||||
ScopedSuspendAll *suspendAllObj = (ScopedSuspendAll *) malloc(sizeof(ScopedSuspendAll));
|
||||
suspendAll(suspendAllObj, "edxp_stop_gc", false);
|
||||
return reinterpret_cast<jlong>(suspendAllObj);
|
||||
}
|
||||
|
||||
void resumeAllThreads(JNIEnv *, jclass, jlong obj) {
|
||||
if (!resumeAll) {
|
||||
return;
|
||||
}
|
||||
resumeAll(reinterpret_cast<ScopedSuspendAll *>(obj));
|
||||
}
|
||||
|
||||
int waitForGcToComplete(JNIEnv *, jclass, jlong thread) {
|
||||
// if waitGc succeeded, it should return one of enum collector::GcType
|
||||
int gcType = waitGc(0, reinterpret_cast<void *>(thread));
|
||||
return gcType;
|
||||
}
|
||||
|
||||
void setMethodNonCompilable(JNIEnv *env, jclass, jobject member) {
|
||||
if (!member) {
|
||||
LOGE("setNonCompilableNative: member is null");
|
||||
return;
|
||||
}
|
||||
void *artMethod = env->FromReflectedMethod(member);
|
||||
if (!artMethod) {
|
||||
LOGE("setNonCompilableNative: artMethod is null");
|
||||
return;
|
||||
}
|
||||
setNonCompilable(artMethod);
|
||||
}
|
||||
|
||||
static constexpr uint32_t kAccFinal = 0x0010;
|
||||
|
||||
jboolean removeFinalFlag(JNIEnv *env, jclass, jclass clazz) {
|
||||
if (clazz) {
|
||||
jfieldID java_lang_Class_accessFlags = env->GetFieldID(
|
||||
env->FindClass("java/lang/Class"), "accessFlags", "I");
|
||||
jint access_flags = env->GetIntField(clazz, java_lang_Class_accessFlags);
|
||||
env->SetIntField(clazz, java_lang_Class_accessFlags, access_flags & ~kAccFinal);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static JNINativeMethod hookMethods[] = {
|
||||
{
|
||||
"init",
|
||||
"(I)V",
|
||||
(void *) Java_lab_galaxy_yahfa_HookMain_init
|
||||
},
|
||||
{
|
||||
"backupAndHookNative",
|
||||
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z",
|
||||
(void *) Java_lab_galaxy_yahfa_HookMain_backupAndHookNative
|
||||
},
|
||||
{
|
||||
"setMethodNonCompilable", "(Ljava/lang/Object;)V", (void *) setMethodNonCompilable
|
||||
},
|
||||
{
|
||||
"findMethodNative",
|
||||
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;",
|
||||
(void *) Java_lab_galaxy_yahfa_HookMain_findMethodNative
|
||||
},
|
||||
{
|
||||
"ensureMethodCached",
|
||||
"(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V",
|
||||
(void *) Java_lab_galaxy_yahfa_HookMain_ensureMethodCached
|
||||
},
|
||||
{
|
||||
"isBlackWhiteListEnabled", "()Z", (void *) is_black_white_list_enabled
|
||||
},
|
||||
{
|
||||
"isDynamicModulesEnabled", "()Z", (void *) is_dynamic_modules_enabled
|
||||
},
|
||||
{
|
||||
"isResourcesHookEnabled", "()Z", (void *) is_resources_hook_enabled
|
||||
},
|
||||
{
|
||||
"isAppNeedHook", "(Ljava/lang/String;)Z", (void *) is_app_need_hook
|
||||
},
|
||||
{
|
||||
"getInstallerPkgName", "()Ljava/lang/String;", (void *) get_installer_pkg_name
|
||||
},
|
||||
{
|
||||
"closeFilesBeforeForkNative", "()V", (void *) closeFilesBeforeForkNative
|
||||
},
|
||||
{
|
||||
"reopenFilesAfterForkNative", "()V", (void *) reopenFilesAfterForkNative
|
||||
},
|
||||
{
|
||||
"deoptMethodNative", "(Ljava/lang/Object;)V", (void *) deoptimize_method
|
||||
},
|
||||
{
|
||||
"suspendAllThreads", "()J", (void *) suspendAllThreads
|
||||
},
|
||||
{
|
||||
"resumeAllThreads", "(J)V", (void *) resumeAllThreads
|
||||
},
|
||||
{
|
||||
"waitForGcToComplete", "(J)I", (void *) waitForGcToComplete
|
||||
},
|
||||
{
|
||||
"initXResourcesNative", "()Z", (void *) XposedBridge_initXResourcesNative
|
||||
},
|
||||
{
|
||||
"removeFinalFlagNative", "(Ljava/lang/Class;)Z", (void *) removeFinalFlag
|
||||
}
|
||||
};
|
||||
|
||||
void loadDexAndInit(JNIEnv *env, const char *dexPath) {
|
||||
if (isInited) {
|
||||
return;
|
||||
}
|
||||
jclass clzClassLoader = env->FindClass("java/lang/ClassLoader");
|
||||
LOGD("java/lang/ClassLoader: %p", clzClassLoader);
|
||||
jmethodID mdgetSystemClassLoader = env->GetStaticMethodID(clzClassLoader,
|
||||
"getSystemClassLoader",
|
||||
"()Ljava/lang/ClassLoader;");
|
||||
LOGD("java/lang/ClassLoader.getSystemClassLoader method: %p", mdgetSystemClassLoader);
|
||||
jobject systemClassLoader = env->CallStaticObjectMethod(clzClassLoader, mdgetSystemClassLoader);
|
||||
LOGD("java/lang/ClassLoader.getSystemClassLoader method result: %p", systemClassLoader);
|
||||
if (NULL == systemClassLoader) {
|
||||
LOGE("getSystemClassLoader failed!!!");
|
||||
return;
|
||||
}
|
||||
jclass clzPathClassLoader = env->FindClass("dalvik/system/PathClassLoader");
|
||||
LOGD("dalvik/system/PathClassLoader: %p", clzClassLoader);
|
||||
jmethodID mdinitPathCL = env->GetMethodID(clzPathClassLoader, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
|
||||
LOGD("dalvik/system/PathClassLoader.<init>: %p", clzClassLoader);
|
||||
jstring jarpath_str = env->NewStringUTF(dexPath);
|
||||
jobject myClassLoader = env->NewObject(clzPathClassLoader, mdinitPathCL,
|
||||
jarpath_str, NULL, systemClassLoader);
|
||||
if (NULL == myClassLoader) {
|
||||
LOGE("PathClassLoader creation failed!!!");
|
||||
return;
|
||||
}
|
||||
gInjectDexClassLoader = env->NewGlobalRef(myClassLoader);
|
||||
LOGD("PathClassLoader created: %p", myClassLoader);
|
||||
LOGD("PathClassLoader loading dexPath[%s]\n", dexPath);
|
||||
jclass entry_class = findClassFromLoader(env, myClassLoader, ENTRY_CLASS_NAME);
|
||||
if (NULL != entry_class) {
|
||||
LOGD("HookEntry Class %p", entry_class);
|
||||
env->RegisterNatives(entry_class, hookMethods, NELEM(hookMethods));
|
||||
isInited = true;
|
||||
LOGD("RegisterNatives succeed for HookEntry.");
|
||||
} else {
|
||||
LOGE("HookEntry class is null. %d", getpid());
|
||||
}
|
||||
|
||||
//load lib sandhook
|
||||
void* lib_sandhook;
|
||||
if (sizeof(void*) == 8) {
|
||||
lib_sandhook = dlopen("/system/lib64/libsandhook.edxp.so", RTLD_NOW);
|
||||
} else {
|
||||
lib_sandhook = dlopen("/system/lib/libsandhook.edxp.so", RTLD_NOW);
|
||||
}
|
||||
if (!lib_sandhook) {
|
||||
LOGW("libsandhook open failed. %s", dlerror());
|
||||
return;
|
||||
}
|
||||
bool* (*jni_load)(JNIEnv*, jclass, jclass) = reinterpret_cast<bool *(*)(JNIEnv *, jclass,
|
||||
jclass)>(dlsym(lib_sandhook, "JNI_Load_Ex"));
|
||||
|
||||
jclass sandhook_class = findClassFromLoader(env, myClassLoader, CLASS_SAND_HOOK);
|
||||
jclass nevercall_class = findClassFromLoader(env, myClassLoader, CLASS_NEVER_CALL);
|
||||
if (!sandhook_class || !nevercall_class) { // fail-fast
|
||||
return;
|
||||
}
|
||||
if (!jni_load(env, sandhook_class, nevercall_class)) {
|
||||
LOGE("SandHook: HookEntry class error. %d", getpid());
|
||||
}
|
||||
}
|
||||
|
||||
jstring getThrowableMessage(JNIEnv *env, jobject throwable) {
|
||||
if (!throwable) {
|
||||
LOGE("throwable is null.");
|
||||
return NULL;
|
||||
}
|
||||
jclass jthrowableClass = env->GetObjectClass(throwable);
|
||||
jmethodID getMsgMid = env->GetMethodID(jthrowableClass, "getMessage", "()Ljava/lang/String;");
|
||||
if (getMsgMid == 0) {
|
||||
LOGE("get Throwable.getMessage method id failed.");
|
||||
return NULL;
|
||||
}
|
||||
return (jstring) env->CallObjectMethod(throwable, getMsgMid);
|
||||
}
|
||||
|
||||
jclass findClassFromLoader(JNIEnv *env, jobject classLoader, const char *className) {
|
||||
jclass clz = env->GetObjectClass(classLoader);
|
||||
jmethodID mid = env->GetMethodID(clz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (env->ExceptionOccurred()) {
|
||||
LOGE("loadClass method not found");
|
||||
env->ExceptionClear();
|
||||
} else {
|
||||
LOGD("loadClass method %p", mid);
|
||||
}
|
||||
jclass ret = NULL;
|
||||
if (!mid) {
|
||||
mid = env->GetMethodID(clz, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (env->ExceptionOccurred()) {
|
||||
LOGE("findClass method not found");
|
||||
env->ExceptionClear();
|
||||
} else {
|
||||
LOGD("findClass method %p", mid);
|
||||
}
|
||||
}
|
||||
if (mid) {
|
||||
jstring className_str = env->NewStringUTF(className);
|
||||
jobject tmp = env->CallObjectMethod(classLoader, mid, className_str);
|
||||
jthrowable exception = env->ExceptionOccurred();
|
||||
if (exception) {
|
||||
jstring message = getThrowableMessage(env, exception);
|
||||
const char *message_char = env->GetStringUTFChars(message, JNI_FALSE);
|
||||
LOGE("Error when findClass %s: %s", className, message_char);
|
||||
env->ReleaseStringUTFChars(message, message_char);
|
||||
env->ExceptionClear();
|
||||
}
|
||||
if (NULL != tmp) {
|
||||
LOGD("findClassFromLoader %p", tmp);
|
||||
ret = (jclass) tmp;
|
||||
}
|
||||
} else {
|
||||
LOGE("no method found");
|
||||
}
|
||||
if (ret == NULL) {
|
||||
LOGE("class %s not found.", className);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#ifndef _JAVAHOOK_H
|
||||
#define _JAVAHOOK_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NELEM(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
extern jobject gInjectDexClassLoader;
|
||||
|
||||
void loadDexAndInit(JNIEnv *env, const char *dexPath);
|
||||
|
||||
jclass findClassFromLoader(JNIEnv *env, jobject classLoader, const char *className);
|
||||
|
||||
#endif // _JAVAHOOK_H
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <jni.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <sys/mman.h>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include "inject/framework_hook.h"
|
||||
#include "native_hook/native_hook.h"
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "include/misc.h"
|
||||
|
||||
#include "include/config.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((visibility("default"))) void onModuleLoaded() {
|
||||
LOGI("onModuleLoaded: welcome to EdXposed!");
|
||||
install_inline_hooks();
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) int shouldSkipUid(int uid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) void
|
||||
nativeForkAndSpecializePre(JNIEnv *env, jclass clazz, jint *_uid, jint *gid, jintArray *gids,
|
||||
jint *runtime_flags,
|
||||
jobjectArray *rlimits, jint *_mount_external, jstring *se_info,
|
||||
jstring *se_name,
|
||||
jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote,
|
||||
jstring *instructionSet, jstring *appDataDir, jstring *packageName,
|
||||
jobjectArray *packagesForUID, jstring *sandboxId) {
|
||||
onNativeForkAndSpecializePre(env, clazz, *_uid, *gid, *gids, *runtime_flags, *rlimits,
|
||||
*_mount_external, *se_info, *se_name, *fdsToClose, *fdsToIgnore,
|
||||
*is_child_zygote, *instructionSet, *appDataDir);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) int nativeForkAndSpecializePost(JNIEnv *env, jclass clazz,
|
||||
jint res) {
|
||||
return onNativeForkAndSpecializePost(env, clazz, res);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void nativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t *uid, gid_t *gid, jintArray *gids,
|
||||
jint *runtime_flags,
|
||||
jobjectArray *rlimits, jlong *permittedCapabilities,
|
||||
jlong *effectiveCapabilities) {
|
||||
onNativeForkSystemServerPre(env, clazz, *uid, *gid, *gids, *runtime_flags, *rlimits,
|
||||
*permittedCapabilities, *effectiveCapabilities);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int nativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res) {
|
||||
return onNativeForkSystemServerPost(env, clazz, res);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) void specializeAppProcessPre(
|
||||
JNIEnv *env, jclass clazz, jint *_uid, jint *gid, jintArray *gids, jint *runtimeFlags,
|
||||
jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,
|
||||
jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir,
|
||||
jstring *packageName, jobjectArray *packagesForUID, jstring *sandboxId) {
|
||||
onNativeForkAndSpecializePre(env, clazz, *_uid, *gid, *gids, *runtimeFlags, *rlimits,
|
||||
*mountExternal, *seInfo, *niceName, nullptr, nullptr,
|
||||
*startChildZygote, *instructionSet, *appDataDir);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) int specializeAppProcessPost(
|
||||
JNIEnv *env, jclass clazz) {
|
||||
return onNativeForkAndSpecializePost(env, clazz, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
|
||||
#include <dlfcn.h>
|
||||
#include <include/android_build.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <inject/config_manager.h>
|
||||
#include <SubstrateHook.h>
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "native_hook.h"
|
||||
#include "riru_hook.h"
|
||||
|
||||
static bool inlineHooksInstalled = false;
|
||||
|
||||
static const char *(*getDesc)(void *, std::string *);
|
||||
|
||||
static bool (*isInSamePackageBackup)(void *, void *) = nullptr;
|
||||
|
||||
// runtime
|
||||
void *runtime_ = nullptr;
|
||||
|
||||
void (*deoptBootImage)(void *runtime) = nullptr;
|
||||
|
||||
bool (*runtimeInitBackup)(void *runtime, void *mapAddr) = nullptr;
|
||||
|
||||
void *class_linker_ = nullptr;
|
||||
|
||||
static void *(*classLinkerCstBackup)(void *, void *) = nullptr;
|
||||
|
||||
void (*deoptMethod)(void *, void *) = nullptr;
|
||||
|
||||
static void (*heapPreForkBackup)(void *) = nullptr;
|
||||
|
||||
bool my_runtimeInit(void *runtime, void *mapAddr) {
|
||||
if (!runtimeInitBackup) {
|
||||
LOGE("runtimeInitBackup is null");
|
||||
return false;
|
||||
}
|
||||
LOGI("runtimeInit starts");
|
||||
bool result = runtimeInitBackup(runtime, mapAddr);
|
||||
if (!deoptBootImage) {
|
||||
LOGE("deoptBootImageSym is null, skip deoptBootImage");
|
||||
} else {
|
||||
LOGI("deoptBootImage starts");
|
||||
deoptBootImage(runtime);
|
||||
LOGI("deoptBootImage finishes");
|
||||
}
|
||||
LOGI("runtimeInit finishes");
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool onIsInSamePackageCalled(void *thiz, void *that) {
|
||||
std::string storage1, storage2;
|
||||
const char *thisDesc = getDesc(thiz, &storage1);
|
||||
const char *thatDesc = getDesc(that, &storage2);
|
||||
// Note: these identifiers should be consistent with those in Java layer
|
||||
if (strstr(thisDesc, "EdHooker_") != nullptr
|
||||
|| strstr(thatDesc, "EdHooker_") != nullptr
|
||||
|| strstr(thisDesc, "com/elderdrivers/riru/") != nullptr
|
||||
|| strstr(thatDesc, "com/elderdrivers/riru/") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
// for MIUI resources hooking
|
||||
if (strstr(thisDesc, "android/content/res/MiuiTypedArray") != nullptr
|
||||
|| strstr(thatDesc, "android/content/res/MiuiTypedArray") != nullptr
|
||||
|| strstr(thisDesc, "android/content/res/XResources$XTypedArray") != nullptr
|
||||
|| strstr(thatDesc, "android/content/res/XResources$XTypedArray") != nullptr) {
|
||||
return true;
|
||||
}
|
||||
return (*isInSamePackageBackup)(thiz, that);
|
||||
}
|
||||
|
||||
static bool onInvokeHiddenAPI() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTICE:
|
||||
* After Android Q(10.0), GetMemberActionImpl has been renamed to ShouldDenyAccessToMemberImpl,
|
||||
* But we don't know the symbols until it's published.
|
||||
* @author asLody
|
||||
*/
|
||||
static bool disableHiddenAPIPolicyImpl(int api_level, void *artHandle,
|
||||
void (*hookFun)(void *, void *, void **)) {
|
||||
if (api_level < ANDROID_P) {
|
||||
return true;
|
||||
}
|
||||
void *symbol = nullptr;
|
||||
// Android P : Preview 1 ~ 4 version
|
||||
symbol = dlsym(artHandle,
|
||||
"_ZN3art9hiddenapi25ShouldBlockAccessToMemberINS_8ArtFieldEEEbPT_PNS_6ThreadENSt3__18functionIFbS6_EEENS0_12AccessMethodE");
|
||||
if (symbol) {
|
||||
hookFun(symbol, reinterpret_cast<void *>(onInvokeHiddenAPI), nullptr);
|
||||
}
|
||||
symbol = dlsym(artHandle,
|
||||
"_ZN3art9hiddenapi25ShouldBlockAccessToMemberINS_9ArtMethodEEEbPT_PNS_6ThreadENSt3__18functionIFbS6_EEENS0_12AccessMethodE"
|
||||
);
|
||||
|
||||
if (symbol) {
|
||||
hookFun(symbol, reinterpret_cast<void *>(onInvokeHiddenAPI), nullptr);
|
||||
return true;
|
||||
}
|
||||
// Android P : Release version
|
||||
symbol = dlsym(artHandle,
|
||||
"_ZN3art9hiddenapi6detail19GetMemberActionImplINS_8ArtFieldEEENS0_6ActionEPT_NS_20HiddenApiAccessFlags7ApiListES4_NS0_12AccessMethodE"
|
||||
);
|
||||
if (symbol) {
|
||||
hookFun(symbol, reinterpret_cast<void *>(onInvokeHiddenAPI), nullptr);
|
||||
}
|
||||
symbol = dlsym(artHandle,
|
||||
"_ZN3art9hiddenapi6detail19GetMemberActionImplINS_9ArtMethodEEENS0_6ActionEPT_NS_20HiddenApiAccessFlags7ApiListES4_NS0_12AccessMethodE"
|
||||
);
|
||||
if (symbol) {
|
||||
hookFun(symbol, reinterpret_cast<void *>(onInvokeHiddenAPI), nullptr);
|
||||
}
|
||||
return symbol != nullptr;
|
||||
}
|
||||
|
||||
static void hookIsInSamePackage(int api_level, void *artHandle,
|
||||
void (*hookFun)(void *, void *, void **)) {
|
||||
// 5.0 - 7.1
|
||||
const char *isInSamePackageSym = "_ZN3art6mirror5Class15IsInSamePackageEPS1_";
|
||||
const char *getDescriptorSym = "_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE";
|
||||
if (api_level >= ANDROID_O) {
|
||||
// 8.0 and later
|
||||
isInSamePackageSym = "_ZN3art6mirror5Class15IsInSamePackageENS_6ObjPtrIS1_EE";
|
||||
}
|
||||
void *original = dlsym(artHandle, isInSamePackageSym);
|
||||
if (!original) {
|
||||
LOGE("can't get isInSamePackageSym: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
void *getDescSym = dlsym(artHandle, getDescriptorSym);
|
||||
if (!getDescSym) {
|
||||
LOGE("can't get GetDescriptorSym: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
getDesc = reinterpret_cast<const char *(*)(void *, std::string *)>(getDescSym);
|
||||
hookFun(original, reinterpret_cast<void *>(onIsInSamePackageCalled),
|
||||
reinterpret_cast<void **>(&isInSamePackageBackup));
|
||||
}
|
||||
|
||||
void *my_classLinkerCst(void *classLinker, void *internTable) {
|
||||
LOGI("classLinkerCst starts");
|
||||
void *result = classLinkerCstBackup(classLinker, internTable);
|
||||
if (class_linker_ != classLinker) {
|
||||
LOGI("class_linker_ changed from %p to %p", class_linker_, classLinker);
|
||||
class_linker_ = classLinker;
|
||||
}
|
||||
LOGI("classLinkerCst finishes");
|
||||
return result;
|
||||
}
|
||||
|
||||
void hookInstrumentation(int api_level, void *artHandle, void (*hookFun)(void *, void *, void **)) {
|
||||
if (api_level < ANDROID_M) {
|
||||
// 5.x not supported
|
||||
return;
|
||||
}
|
||||
void *classLinkerCstSym = dlsym(artHandle,
|
||||
"_ZN3art11ClassLinkerC2EPNS_11InternTableE");
|
||||
if (!classLinkerCstSym) {
|
||||
LOGE("can't get classLinkerCstSym: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
deoptMethod = reinterpret_cast<void (*)(void *, void *)>(
|
||||
dlsym(artHandle,
|
||||
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE"));
|
||||
if (!deoptMethod) {
|
||||
LOGE("can't get deoptMethodSym: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
hookFun(classLinkerCstSym, reinterpret_cast<void *>(my_classLinkerCst),
|
||||
reinterpret_cast<void **>(&classLinkerCstBackup));
|
||||
LOGI("classLinkerCst hooked");
|
||||
}
|
||||
|
||||
std::vector<void *> deoptedMethods;
|
||||
|
||||
void deoptimize_method(JNIEnv *env, jclass clazz, jobject method) {
|
||||
if (!deoptMethod) {
|
||||
LOGE("deoptMethodSym is null, skip deopt");
|
||||
return;
|
||||
}
|
||||
if (!class_linker_) {
|
||||
LOGE("class_linker_ is null, skip deopt");
|
||||
return;
|
||||
}
|
||||
void *reflected_method = env->FromReflectedMethod(method);
|
||||
if (std::find(deoptedMethods.begin(), deoptedMethods.end(), reflected_method) !=
|
||||
deoptedMethods.end()) {
|
||||
LOGD("method %p has been deopted before, skip...", reflected_method);
|
||||
return;
|
||||
}
|
||||
LOGD("deoptimizing method: %p", reflected_method);
|
||||
deoptMethod(class_linker_, reflected_method);
|
||||
deoptedMethods.push_back(reflected_method);
|
||||
LOGD("method deoptimized: %p", reflected_method);
|
||||
}
|
||||
|
||||
void hookRuntime(int api_level, void *artHandle, void (*hookFun)(void *, void *, void **)) {
|
||||
if (!is_deopt_boot_image_enabled()) {
|
||||
return;
|
||||
}
|
||||
void *runtimeInitSym = nullptr;
|
||||
if (api_level >= ANDROID_O) {
|
||||
// only oreo has deoptBootImageSym in Runtime
|
||||
runtime_ = dlsym(artHandle, "_ZN3art7Runtime9instance_E");
|
||||
if (!runtime_) { LOGW("runtime instance not found"); }
|
||||
runtimeInitSym = dlsym(artHandle, "_ZN3art7Runtime4InitEONS_18RuntimeArgumentMapE");
|
||||
if (!runtimeInitSym) {
|
||||
LOGE("can't find runtimeInitSym: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
deoptBootImage = reinterpret_cast<void (*)(void *)>(dlsym(artHandle,
|
||||
"_ZN3art7Runtime19DeoptimizeBootImageEv"));
|
||||
if (!deoptBootImage) {
|
||||
LOGE("can't find deoptBootImageSym: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
LOGI("start to hook runtimeInitSym");
|
||||
hookFun(runtimeInitSym, reinterpret_cast<void *>(my_runtimeInit),
|
||||
reinterpret_cast<void **>(&runtimeInitBackup));
|
||||
LOGI("runtimeInitSym hooked");
|
||||
} else {
|
||||
// TODO support deoptBootImage for Android 7.1 and before?
|
||||
LOGI("hooking Runtime skipped");
|
||||
}
|
||||
}
|
||||
|
||||
void (*suspendAll)(ScopedSuspendAll *, const char *, bool) = nullptr;
|
||||
|
||||
void (*resumeAll)(ScopedSuspendAll *) = nullptr;
|
||||
|
||||
int (*waitGcInternal)(void *, int, void *) = nullptr;
|
||||
|
||||
void *heap_ = nullptr;
|
||||
|
||||
int waitGc(int gcCause, void *thread) {
|
||||
if (!heap_) {
|
||||
LOGE("heap_ is null");
|
||||
return -1;
|
||||
}
|
||||
return waitGcInternal(heap_, gcCause, thread);
|
||||
}
|
||||
|
||||
static void myHeapPreFork(void *heap) {
|
||||
heap_ = heap;
|
||||
heapPreForkBackup(heap);
|
||||
}
|
||||
|
||||
void getSuspendSyms(int api_level, void *artHandle, void (*hookFun)(void *, void *, void **)) {
|
||||
if (api_level >= ANDROID_LOLLIPOP) {
|
||||
waitGcInternal = reinterpret_cast<int (*)(void *, int, void *)>(dlsym(artHandle,
|
||||
"_ZN3art2gc4Heap19WaitForGcToCompleteENS0_7GcCauseEPNS_6ThreadE"));
|
||||
void *heapPreFork = dlsym(artHandle, "_ZN3art2gc4Heap13PreZygoteForkEv");
|
||||
if (!heapPreFork) {
|
||||
LOGE("can't find heapPreFork: %s", dlerror());
|
||||
} else {
|
||||
// a chance to get pointer of the heap
|
||||
hookFun(heapPreFork, reinterpret_cast<void *>(myHeapPreFork),
|
||||
reinterpret_cast<void **>(&heapPreForkBackup));
|
||||
LOGI("heapPreFork hooked.");
|
||||
}
|
||||
}
|
||||
if (api_level >= ANDROID_N) {
|
||||
suspendAll = reinterpret_cast<void (*)(ScopedSuspendAll *, const char *, bool)>(dlsym(
|
||||
artHandle,
|
||||
"_ZN3art16ScopedSuspendAllC2EPKcb"));
|
||||
resumeAll = reinterpret_cast<void (*)(ScopedSuspendAll *)>(dlsym(artHandle,
|
||||
"_ZN3art16ScopedSuspendAllD2Ev"));
|
||||
}
|
||||
}
|
||||
|
||||
void install_inline_hooks() {
|
||||
if (inlineHooksInstalled) {
|
||||
LOGI("inline hooks installed, skip");
|
||||
return;
|
||||
}
|
||||
LOGI("start to install inline hooks");
|
||||
int api_level = GetAndroidApiLevel();
|
||||
if (api_level < ANDROID_LOLLIPOP) {
|
||||
LOGE("api level not supported: %d, skip", api_level);
|
||||
return;
|
||||
}
|
||||
install_riru_hooks();
|
||||
LOGI("using api level %d", api_level);
|
||||
#ifdef __LP64__
|
||||
void *whaleHandle = dlopen(kLibWhalePath, RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (!whaleHandle) {
|
||||
LOGE("can't open libwhale: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
void *hookFunSym = dlsym(whaleHandle, "WInlineHookFunction");
|
||||
#else
|
||||
void *hookFunSym = (void *)(MSHookFunction);
|
||||
#endif
|
||||
if (!hookFunSym) {
|
||||
LOGE("can't get WInlineHookFunction: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
void (*hookFun)(void *, void *, void **) = reinterpret_cast<void (*)(void *, void *,
|
||||
void **)>(hookFunSym);
|
||||
void *artHandle = dlopen(kLibArtPath, RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (!artHandle) {
|
||||
LOGE("can't open libart: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
hookRuntime(api_level, artHandle, hookFun);
|
||||
hookInstrumentation(api_level, artHandle, hookFun);
|
||||
getSuspendSyms(api_level, artHandle, hookFun);
|
||||
hookIsInSamePackage(api_level, artHandle, hookFun);
|
||||
if (disableHiddenAPIPolicyImpl(api_level, artHandle, hookFun)) {
|
||||
LOGI("disableHiddenAPIPolicyImpl done.");
|
||||
} else {
|
||||
LOGE("disableHiddenAPIPolicyImpl failed.");
|
||||
}
|
||||
#ifdef __LP64__
|
||||
dlclose(whaleHandle);
|
||||
#endif
|
||||
dlclose(artHandle);
|
||||
LOGI("install inline hooks done");
|
||||
}
|
||||
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#ifndef HOOK_H
|
||||
#define HOOK_H
|
||||
|
||||
#include <xhook.h>
|
||||
|
||||
#if defined(__LP64__)
|
||||
static constexpr const char *kLibArtPath = "/system/lib64/libart.so";
|
||||
static constexpr const char *kLibWhalePath = "/system/lib64/libwhale.edxp.so";
|
||||
#else
|
||||
static constexpr const char *kLibArtPath = "/system/lib/libart.so";
|
||||
static constexpr const char *kLibWhalePath = "/system/lib/libwhale.edxp.so";
|
||||
#endif
|
||||
|
||||
#define XHOOK_REGISTER(NAME) \
|
||||
if (xhook_register(".*", #NAME, (void*) new_##NAME, (void **) &old_##NAME) != 0) \
|
||||
LOGE("failed to register hook " #NAME "."); \
|
||||
|
||||
#define NEW_FUNC_DEF(ret, func, ...) \
|
||||
static ret (*old_##func)(__VA_ARGS__); \
|
||||
static ret new_##func(__VA_ARGS__)
|
||||
|
||||
class ScopedSuspendAll {
|
||||
};
|
||||
|
||||
extern void (*suspendAll)(ScopedSuspendAll *, const char *, bool);
|
||||
|
||||
extern void (*resumeAll)(ScopedSuspendAll *);
|
||||
|
||||
extern int waitGc(int, void *);
|
||||
|
||||
void install_inline_hooks();
|
||||
|
||||
void deoptimize_method(JNIEnv *env, jclass clazz, jobject method);
|
||||
|
||||
#endif // HOOK_H
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
//
|
||||
// Created by solo on 2019/3/24.
|
||||
//
|
||||
|
||||
#include <jni.h>
|
||||
#include <include/ByteOrder.h>
|
||||
#include <include/logging.h>
|
||||
#include <dlfcn.h>
|
||||
#include <java_hook/java_hook.h>
|
||||
#include "resource_hook.h"
|
||||
|
||||
#define CLASS_XRESOURCES "android/content/res/XResources"
|
||||
|
||||
jclass classXResources;
|
||||
jmethodID methodXResourcesTranslateAttrId;
|
||||
jmethodID methodXResourcesTranslateResId;
|
||||
|
||||
int32_t (*ResXMLParser_next)(void *);
|
||||
|
||||
void (*ResXMLParser_restart)(void *);
|
||||
|
||||
int32_t (*ResXMLParser_getAttributeNameID)(void *, int);
|
||||
|
||||
char16_t *(*ResStringPool_stringAt)(const void *, int32_t, size_t *);
|
||||
|
||||
bool prepareSymbols() {
|
||||
void *fwHandle = dlopen(kLibFwPath, RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (!fwHandle) {
|
||||
LOGE("can't open libandroidfw: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
ResXMLParser_next = reinterpret_cast<int32_t (*)(void *)>(dlsym(fwHandle,
|
||||
"_ZN7android12ResXMLParser4nextEv"));
|
||||
if (!ResXMLParser_next) {
|
||||
LOGE("can't get ResXMLParser_next: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
ResXMLParser_restart = reinterpret_cast<void (*)(void *)>(dlsym(fwHandle,
|
||||
"_ZN7android12ResXMLParser7restartEv"));
|
||||
if (!ResXMLParser_restart) {
|
||||
LOGE("can't get ResXMLParser_restart: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
ResXMLParser_getAttributeNameID = reinterpret_cast<int32_t (*)(void *, int)>(dlsym(fwHandle,
|
||||
#if defined(__LP64__)
|
||||
"_ZNK7android12ResXMLParser18getAttributeNameIDEm"
|
||||
#else
|
||||
"_ZNK7android12ResXMLParser18getAttributeNameIDEj"
|
||||
#endif
|
||||
));
|
||||
if (!ResXMLParser_getAttributeNameID) {
|
||||
LOGE("can't get ResXMLParser_getAttributeNameID: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
ResStringPool_stringAt = reinterpret_cast<char16_t *(*)(const void *, int32_t, size_t *)>(dlsym(
|
||||
fwHandle,
|
||||
#if defined(__LP64__)
|
||||
"_ZNK7android13ResStringPool8stringAtEmPm"
|
||||
#else
|
||||
"_ZNK7android13ResStringPool8stringAtEjPj"
|
||||
#endif
|
||||
));
|
||||
if (!ResStringPool_stringAt) {
|
||||
LOGE("can't get ResStringPool_stringAt: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int register_natives_XResources(JNIEnv *env, jclass clazz) {
|
||||
const JNINativeMethod methods[] = {
|
||||
{"rewriteXmlReferencesNative",
|
||||
"(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V",
|
||||
(void *) XResources_rewriteXmlReferencesNative},
|
||||
};
|
||||
return env->RegisterNatives(clazz, methods, NELEM(methods));
|
||||
}
|
||||
|
||||
jboolean XposedBridge_initXResourcesNative(JNIEnv *env, jclass) {
|
||||
classXResources = env->FindClass(CLASS_XRESOURCES);
|
||||
if (classXResources == NULL) {
|
||||
LOGE("Error while loading XResources class '%s':", CLASS_XRESOURCES);
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
classXResources = reinterpret_cast<jclass>(env->NewGlobalRef(classXResources));
|
||||
|
||||
if (register_natives_XResources(env, classXResources) != JNI_OK) {
|
||||
LOGE("Could not register natives for '%s'", CLASS_XRESOURCES);
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
|
||||
methodXResourcesTranslateResId = env->GetStaticMethodID(classXResources, "translateResId",
|
||||
"(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I");
|
||||
if (methodXResourcesTranslateResId == NULL) {
|
||||
LOGE("ERROR: could not find method %s.translateResId(int, XResources, Resources)",
|
||||
CLASS_XRESOURCES);
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
|
||||
methodXResourcesTranslateAttrId = env->GetStaticMethodID(classXResources, "translateAttrId",
|
||||
"(Ljava/lang/String;Landroid/content/res/XResources;)I");
|
||||
if (methodXResourcesTranslateAttrId == NULL) {
|
||||
LOGE("ERROR: could not find method %s.findAttrId(String, XResources)", CLASS_XRESOURCES);
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return prepareSymbols();
|
||||
}
|
||||
|
||||
void XResources_rewriteXmlReferencesNative(JNIEnv *env, jclass,
|
||||
jlong parserPtr, jobject origRes, jobject repRes) {
|
||||
|
||||
ResXMLParser *parser = (ResXMLParser *) parserPtr;
|
||||
|
||||
if (parser == nullptr)
|
||||
return;
|
||||
|
||||
const ResXMLTree &mTree = parser->mTree;
|
||||
uint32_t *mResIds = (uint32_t *) mTree.mResIds;
|
||||
ResXMLTree_attrExt *tag;
|
||||
int attrCount;
|
||||
|
||||
do {
|
||||
switch (ResXMLParser_next(parser)) {
|
||||
case ResXMLParser::START_TAG:
|
||||
tag = (ResXMLTree_attrExt *) parser->mCurExt;
|
||||
attrCount = dtohs(tag->attributeCount);
|
||||
for (int idx = 0; idx < attrCount; idx++) {
|
||||
ResXMLTree_attribute *attr = (ResXMLTree_attribute *)
|
||||
(((const uint8_t *) tag)
|
||||
+ dtohs(tag->attributeStart)
|
||||
+ (dtohs(tag->attributeSize) * idx));
|
||||
|
||||
// find resource IDs for attribute names
|
||||
int32_t attrNameID = ResXMLParser_getAttributeNameID(parser, idx);
|
||||
// only replace attribute name IDs for app packages
|
||||
if (attrNameID >= 0 && (size_t) attrNameID < mTree.mNumResIds &&
|
||||
dtohl(mResIds[attrNameID]) >= 0x7f000000) {
|
||||
size_t attNameLen;
|
||||
const char16_t *attrName = ResStringPool_stringAt(&(mTree.mStrings),
|
||||
attrNameID, &attNameLen);
|
||||
jint attrResID = env->CallStaticIntMethod(classXResources,
|
||||
methodXResourcesTranslateAttrId,
|
||||
env->NewString(
|
||||
(const jchar *) attrName,
|
||||
attNameLen), origRes);
|
||||
if (env->ExceptionCheck())
|
||||
goto leave;
|
||||
|
||||
mResIds[attrNameID] = htodl(attrResID);
|
||||
}
|
||||
|
||||
// find original resource IDs for reference values (app packages only)
|
||||
if (attr->typedValue.dataType != Res_value::TYPE_REFERENCE)
|
||||
continue;
|
||||
|
||||
jint oldValue = dtohl(attr->typedValue.data);
|
||||
if (oldValue < 0x7f000000)
|
||||
continue;
|
||||
|
||||
jint newValue = env->CallStaticIntMethod(classXResources,
|
||||
methodXResourcesTranslateResId,
|
||||
oldValue, origRes, repRes);
|
||||
if (env->ExceptionCheck())
|
||||
goto leave;
|
||||
|
||||
if (newValue != oldValue)
|
||||
attr->typedValue.data = htodl(newValue);
|
||||
}
|
||||
continue;
|
||||
case ResXMLParser::END_DOCUMENT:
|
||||
case ResXMLParser::BAD_DOCUMENT:
|
||||
goto leave;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
leave:
|
||||
ResXMLParser_restart(parser);
|
||||
}
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
//
|
||||
// Created by solo on 2019/3/24.
|
||||
//
|
||||
|
||||
#ifndef EDXPOSED_TEMP_RESOURCE_HOOK_H
|
||||
#define EDXPOSED_TEMP_RESOURCE_HOOK_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(__LP64__)
|
||||
static constexpr const char *kLibFwPath = "/system/lib64/libandroidfw.so";
|
||||
#else
|
||||
static constexpr const char *kLibFwPath = "/system/lib/libandroidfw.so";
|
||||
#endif
|
||||
|
||||
jboolean XposedBridge_initXResourcesNative(JNIEnv *env, jclass);
|
||||
|
||||
void XResources_rewriteXmlReferencesNative(JNIEnv *env, jclass,
|
||||
jlong parserPtr, jobject origRes, jobject repRes);
|
||||
|
||||
typedef int32_t status_t;
|
||||
|
||||
enum {
|
||||
RES_NULL_TYPE = 0x0000,
|
||||
RES_STRING_POOL_TYPE = 0x0001,
|
||||
RES_TABLE_TYPE = 0x0002,
|
||||
RES_XML_TYPE = 0x0003,
|
||||
// Chunk types in RES_XML_TYPE
|
||||
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
|
||||
RES_XML_START_NAMESPACE_TYPE = 0x0100,
|
||||
RES_XML_END_NAMESPACE_TYPE = 0x0101,
|
||||
RES_XML_START_ELEMENT_TYPE = 0x0102,
|
||||
RES_XML_END_ELEMENT_TYPE = 0x0103,
|
||||
RES_XML_CDATA_TYPE = 0x0104,
|
||||
RES_XML_LAST_CHUNK_TYPE = 0x017f,
|
||||
// This contains a uint32_t array mapping strings in the string
|
||||
// pool back to resource identifiers. It is optional.
|
||||
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
|
||||
// Chunk types in RES_TABLE_TYPE
|
||||
RES_TABLE_PACKAGE_TYPE = 0x0200,
|
||||
RES_TABLE_TYPE_TYPE = 0x0201,
|
||||
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
|
||||
RES_TABLE_LIBRARY_TYPE = 0x0203
|
||||
};
|
||||
|
||||
struct ResXMLTree_node {
|
||||
void *header;
|
||||
// Line number in original source file at which this element appeared.
|
||||
uint32_t lineNumber;
|
||||
// Optional XML comment that was associated with this element; -1 if none.
|
||||
void *comment;
|
||||
};
|
||||
|
||||
class ResXMLTree;
|
||||
|
||||
class ResXMLParser {
|
||||
|
||||
public:
|
||||
enum event_code_t {
|
||||
BAD_DOCUMENT = -1,
|
||||
START_DOCUMENT = 0,
|
||||
END_DOCUMENT = 1,
|
||||
|
||||
FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE,
|
||||
|
||||
START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE,
|
||||
END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE,
|
||||
START_TAG = RES_XML_START_ELEMENT_TYPE,
|
||||
END_TAG = RES_XML_END_ELEMENT_TYPE,
|
||||
TEXT = RES_XML_CDATA_TYPE
|
||||
};
|
||||
|
||||
const ResXMLTree &mTree;
|
||||
event_code_t mEventCode;
|
||||
const ResXMLTree_node *mCurNode;
|
||||
const void *mCurExt;
|
||||
};
|
||||
|
||||
class ResStringPool {
|
||||
|
||||
public:
|
||||
status_t mError;
|
||||
void *mOwnedData;
|
||||
const void *mHeader;
|
||||
size_t mSize;
|
||||
mutable pthread_mutex_t mDecodeLock;
|
||||
const uint32_t *mEntries;
|
||||
const uint32_t *mEntryStyles;
|
||||
const void *mStrings;
|
||||
char16_t mutable **mCache;
|
||||
uint32_t mStringPoolSize; // number of uint16_t
|
||||
const uint32_t *mStyles;
|
||||
uint32_t mStylePoolSize; // number of uint32_t
|
||||
};
|
||||
|
||||
|
||||
class ResXMLTree : public ResXMLParser {
|
||||
|
||||
public:
|
||||
void *mDynamicRefTable;
|
||||
status_t mError;
|
||||
void *mOwnedData;
|
||||
const void *mHeader;
|
||||
size_t mSize;
|
||||
const uint8_t *mDataEnd;
|
||||
ResStringPool mStrings;
|
||||
const uint32_t *mResIds;
|
||||
size_t mNumResIds;
|
||||
const ResXMLTree_node *mRootNode;
|
||||
const void *mRootExt;
|
||||
event_code_t mRootCode;
|
||||
};
|
||||
|
||||
struct ResStringPool_ref {
|
||||
|
||||
// Index into the string pool table (uint32_t-offset from the indices
|
||||
// immediately after ResStringPool_header) at which to find the location
|
||||
// of the string data in the pool.
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
struct ResXMLTree_attrExt {
|
||||
|
||||
// String of the full namespace of this element.
|
||||
struct ResStringPool_ref ns;
|
||||
|
||||
// String name of this node if it is an ELEMENT; the raw
|
||||
// character data if this is a CDATA node.
|
||||
struct ResStringPool_ref name;
|
||||
|
||||
// Byte offset from the start of this structure where the attributes start.
|
||||
uint16_t attributeStart;
|
||||
|
||||
// Size of the ResXMLTree_attribute structures that follow.
|
||||
uint16_t attributeSize;
|
||||
|
||||
// Number of attributes associated with an ELEMENT. These are
|
||||
// available as an array of ResXMLTree_attribute structures
|
||||
// immediately following this node.
|
||||
uint16_t attributeCount;
|
||||
|
||||
// Index (1-based) of the "id" attribute. 0 if none.
|
||||
uint16_t idIndex;
|
||||
|
||||
// Index (1-based) of the "class" attribute. 0 if none.
|
||||
uint16_t classIndex;
|
||||
|
||||
// Index (1-based) of the "style" attribute. 0 if none.
|
||||
uint16_t styleIndex;
|
||||
};
|
||||
|
||||
struct Res_value {
|
||||
|
||||
// Number of bytes in this structure.
|
||||
uint16_t size;
|
||||
// Always set to 0.
|
||||
uint8_t res0;
|
||||
|
||||
// Type of the data value.
|
||||
enum : uint8_t {
|
||||
// The 'data' is either 0 or 1, specifying this resource is either
|
||||
// undefined or empty, respectively.
|
||||
TYPE_NULL = 0x00,
|
||||
// The 'data' holds a ResTable_ref, a reference to another resource
|
||||
// table entry.
|
||||
TYPE_REFERENCE = 0x01,
|
||||
// The 'data' holds an attribute resource identifier.
|
||||
TYPE_ATTRIBUTE = 0x02,
|
||||
// The 'data' holds an index into the containing resource table's
|
||||
// global value string pool.
|
||||
TYPE_STRING = 0x03,
|
||||
// The 'data' holds a single-precision floating point number.
|
||||
TYPE_FLOAT = 0x04,
|
||||
// The 'data' holds a complex number encoding a dimension value,
|
||||
// such as "100in".
|
||||
TYPE_DIMENSION = 0x05,
|
||||
// The 'data' holds a complex number encoding a fraction of a
|
||||
// container.
|
||||
TYPE_FRACTION = 0x06,
|
||||
// The 'data' holds a dynamic ResTable_ref, which needs to be
|
||||
// resolved before it can be used like a TYPE_REFERENCE.
|
||||
TYPE_DYNAMIC_REFERENCE = 0x07,
|
||||
// The 'data' holds an attribute resource identifier, which needs to be resolved
|
||||
// before it can be used like a TYPE_ATTRIBUTE.
|
||||
TYPE_DYNAMIC_ATTRIBUTE = 0x08,
|
||||
// Beginning of integer flavors...
|
||||
TYPE_FIRST_INT = 0x10,
|
||||
// The 'data' is a raw integer value of the form n..n.
|
||||
TYPE_INT_DEC = 0x10,
|
||||
// The 'data' is a raw integer value of the form 0xn..n.
|
||||
TYPE_INT_HEX = 0x11,
|
||||
// The 'data' is either 0 or 1, for input "false" or "true" respectively.
|
||||
TYPE_INT_BOOLEAN = 0x12,
|
||||
// Beginning of color integer flavors...
|
||||
TYPE_FIRST_COLOR_INT = 0x1c,
|
||||
// The 'data' is a raw integer value of the form #aarrggbb.
|
||||
TYPE_INT_COLOR_ARGB8 = 0x1c,
|
||||
// The 'data' is a raw integer value of the form #rrggbb.
|
||||
TYPE_INT_COLOR_RGB8 = 0x1d,
|
||||
// The 'data' is a raw integer value of the form #argb.
|
||||
TYPE_INT_COLOR_ARGB4 = 0x1e,
|
||||
// The 'data' is a raw integer value of the form #rgb.
|
||||
TYPE_INT_COLOR_RGB4 = 0x1f,
|
||||
// ...end of integer flavors.
|
||||
TYPE_LAST_COLOR_INT = 0x1f,
|
||||
// ...end of integer flavors.
|
||||
TYPE_LAST_INT = 0x1f
|
||||
};
|
||||
uint8_t dataType;
|
||||
// Structure of complex data values (TYPE_UNIT and TYPE_FRACTION)
|
||||
enum {
|
||||
// Where the unit type information is. This gives us 16 possible
|
||||
// types, as defined below.
|
||||
COMPLEX_UNIT_SHIFT = 0,
|
||||
COMPLEX_UNIT_MASK = 0xf,
|
||||
// TYPE_DIMENSION: Value is raw pixels.
|
||||
COMPLEX_UNIT_PX = 0,
|
||||
// TYPE_DIMENSION: Value is Device Independent Pixels.
|
||||
COMPLEX_UNIT_DIP = 1,
|
||||
// TYPE_DIMENSION: Value is a Scaled device independent Pixels.
|
||||
COMPLEX_UNIT_SP = 2,
|
||||
// TYPE_DIMENSION: Value is in points.
|
||||
COMPLEX_UNIT_PT = 3,
|
||||
// TYPE_DIMENSION: Value is in inches.
|
||||
COMPLEX_UNIT_IN = 4,
|
||||
// TYPE_DIMENSION: Value is in millimeters.
|
||||
COMPLEX_UNIT_MM = 5,
|
||||
// TYPE_FRACTION: A basic fraction of the overall size.
|
||||
COMPLEX_UNIT_FRACTION = 0,
|
||||
// TYPE_FRACTION: A fraction of the parent size.
|
||||
COMPLEX_UNIT_FRACTION_PARENT = 1,
|
||||
// Where the radix information is, telling where the decimal place
|
||||
// appears in the mantissa. This give us 4 possible fixed point
|
||||
// representations as defined below.
|
||||
COMPLEX_RADIX_SHIFT = 4,
|
||||
COMPLEX_RADIX_MASK = 0x3,
|
||||
// The mantissa is an integral number -- i.e., 0xnnnnnn.0
|
||||
COMPLEX_RADIX_23p0 = 0,
|
||||
// The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
|
||||
COMPLEX_RADIX_16p7 = 1,
|
||||
// The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
|
||||
COMPLEX_RADIX_8p15 = 2,
|
||||
// The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
|
||||
COMPLEX_RADIX_0p23 = 3,
|
||||
// Where the actual value is. This gives us 23 bits of
|
||||
// precision. The top bit is the sign.
|
||||
COMPLEX_MANTISSA_SHIFT = 8,
|
||||
COMPLEX_MANTISSA_MASK = 0xffffff
|
||||
};
|
||||
// Possible data values for TYPE_NULL.
|
||||
enum {
|
||||
// The value is not defined.
|
||||
DATA_NULL_UNDEFINED = 0,
|
||||
// The value is explicitly defined as empty.
|
||||
DATA_NULL_EMPTY = 1
|
||||
};
|
||||
// The data for this item, as interpreted according to dataType.
|
||||
typedef uint32_t data_type;
|
||||
data_type data;
|
||||
};
|
||||
|
||||
struct ResXMLTree_attribute {
|
||||
// Namespace of this attribute.
|
||||
struct ResStringPool_ref ns;
|
||||
|
||||
// Name of this attribute.
|
||||
struct ResStringPool_ref name;
|
||||
|
||||
// The original raw string value of this attribute.
|
||||
struct ResStringPool_ref rawValue;
|
||||
|
||||
// Processesd typed value of this attribute.
|
||||
struct Res_value typedValue;
|
||||
};
|
||||
|
||||
#endif //EDXPOSED_TEMP_RESOURCE_HOOK_H
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
//
|
||||
// Created by solo on 2019/3/16.
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <riru.h>
|
||||
#include <xhook.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <include/logging.h>
|
||||
#include <include/android_build.h>
|
||||
#include "riru_hook.h"
|
||||
|
||||
int api_level = 0;
|
||||
|
||||
#define PROP_KEY_COMPILER_FILTER "dalvik.vm.dex2oat-filter"
|
||||
#define PROP_KEY_COMPILER_FLAGS "dalvik.vm.dex2oat-flags"
|
||||
#define PROP_KEY_USEJITPROFILES "dalvik.vm.usejitprofiles"
|
||||
#define PROP_KEY_PM_BG_DEXOPT "pm.dexopt.bg-dexopt"
|
||||
#define PROP_VALUE_COMPILER_FILTER "quicken"
|
||||
#define PROP_VALUE_COMPILER_FLAGS "--inline-max-code-units=0"
|
||||
#define PROP_VALUE_PM_BG_DEXOPT "speed"
|
||||
|
||||
#define XHOOK_REGISTER(NAME) \
|
||||
if (xhook_register(".*", #NAME, (void*) new_##NAME, (void **) &old_##NAME) == 0) { \
|
||||
if (riru_get_version() >= 8) { \
|
||||
void *f = riru_get_func(#NAME); \
|
||||
if (f != nullptr) { \
|
||||
memcpy(&old_##NAME, &f, sizeof(void *)); \
|
||||
} \
|
||||
riru_set_func(#NAME, (void *) new_##NAME); \
|
||||
} \
|
||||
} else { \
|
||||
LOGE("failed to register riru hook " #NAME "."); \
|
||||
}
|
||||
|
||||
#define NEW_FUNC_DEF(ret, func, ...) \
|
||||
static ret (*old_##func)(__VA_ARGS__); \
|
||||
static ret new_##func(__VA_ARGS__)
|
||||
|
||||
NEW_FUNC_DEF(int, __system_property_get, const char *key, char *value) {
|
||||
int res = old___system_property_get(key, value);
|
||||
if (key) {
|
||||
if (strcmp(PROP_KEY_COMPILER_FILTER, key) == 0) {
|
||||
strcpy(value, PROP_VALUE_COMPILER_FILTER);
|
||||
LOGI("system_property_get: %s -> %s", key, value);
|
||||
} else if (strcmp(PROP_KEY_COMPILER_FLAGS, key) == 0) {
|
||||
strcpy(value, PROP_VALUE_COMPILER_FLAGS);
|
||||
LOGI("system_property_get: %s -> %s", key, value);
|
||||
}
|
||||
if (api_level == ANDROID_O_MR1) {
|
||||
// https://android.googlesource.com/platform/art/+/f5516d38736fb97bfd0435ad03bbab17ddabbe4e
|
||||
// Android 8.1 add a fatal check for debugging (removed in Android 9.0),
|
||||
// which will be triggered by EdXposed in cases where target method is hooked
|
||||
// (native flag set) after it has been called several times(getCounter() return positive number)
|
||||
if (strcmp(PROP_KEY_USEJITPROFILES, key) == 0) {
|
||||
strcpy(value, "false");
|
||||
} else if (strcmp(PROP_KEY_PM_BG_DEXOPT, key) == 0) {
|
||||
// use speed as bg-dexopt filter since that speed-profile won't work after
|
||||
// jit profiles is disabled
|
||||
strcpy(value, PROP_VALUE_PM_BG_DEXOPT);
|
||||
}
|
||||
LOGD("system_property_get: %s -> %s", key, value);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
NEW_FUNC_DEF(std::string,
|
||||
_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_,
|
||||
const std::string &key, const std::string &default_value) {
|
||||
std::string res = old__ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_(
|
||||
key, default_value);
|
||||
if (strcmp(PROP_KEY_COMPILER_FILTER, key.c_str()) == 0) {
|
||||
res = PROP_VALUE_COMPILER_FILTER;
|
||||
LOGI("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str());
|
||||
} else if (strcmp(PROP_KEY_COMPILER_FLAGS, key.c_str()) == 0) {
|
||||
res = PROP_VALUE_COMPILER_FLAGS;
|
||||
LOGI("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str());
|
||||
}
|
||||
if (api_level == ANDROID_O_MR1) {
|
||||
// see __system_property_get hook above for explanations
|
||||
if (strcmp(PROP_KEY_USEJITPROFILES, key.c_str()) == 0) {
|
||||
res = "false";
|
||||
} else if (strcmp(PROP_KEY_PM_BG_DEXOPT, key.c_str()) == 0) {
|
||||
res = PROP_VALUE_PM_BG_DEXOPT;
|
||||
}
|
||||
LOGD("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void install_riru_hooks() {
|
||||
|
||||
LOGI("install riru hook");
|
||||
|
||||
api_level = GetAndroidApiLevel();
|
||||
|
||||
XHOOK_REGISTER(__system_property_get);
|
||||
|
||||
if (GetAndroidApiLevel() >= ANDROID_P) {
|
||||
XHOOK_REGISTER(
|
||||
_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_);
|
||||
}
|
||||
|
||||
if (xhook_refresh(0) == 0) {
|
||||
xhook_clear();
|
||||
LOGI("riru hooks installed");
|
||||
} else {
|
||||
LOGE("failed to install riru hooks");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
//
|
||||
// Created by solo on 2019/3/16.
|
||||
//
|
||||
|
||||
#ifndef EDXPOSED_RIRU_HOOK_H
|
||||
#define EDXPOSED_RIRU_HOOK_H
|
||||
|
||||
void install_riru_hooks();
|
||||
|
||||
#endif //EDXPOSED_RIRU_HOOK_H
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
//
|
||||
// Created by solo on 2019/5/31.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <jni.h>
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <android_build.h>
|
||||
#include <logging.h>
|
||||
#include <linux/limits.h>
|
||||
#include <JNIHelper.h>
|
||||
#include "art/runtime/native/native_util.h"
|
||||
#include "config_manager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace art;
|
||||
|
||||
namespace edxp {
|
||||
|
||||
std::string ConfigManager::RetrieveInstallerPkgName() const {
|
||||
std::string data_test_path = data_path_prefix_ + kPrimaryInstallerPkgName;
|
||||
if (access(data_test_path.c_str(), F_OK) == 0) {
|
||||
LOGI("using installer %s", kPrimaryInstallerPkgName);
|
||||
return kPrimaryInstallerPkgName;
|
||||
}
|
||||
data_test_path = data_path_prefix_ + kSecondaryInstallerPkgName;
|
||||
if (access(data_test_path.c_str(), F_OK) == 0) {
|
||||
LOGI("using installer %s", kSecondaryInstallerPkgName);
|
||||
return kSecondaryInstallerPkgName;
|
||||
}
|
||||
data_test_path = data_path_prefix_ + kLegacyInstallerPkgName;
|
||||
if (access(data_test_path.c_str(), F_OK) == 0) {
|
||||
LOGI("using installer %s", kLegacyInstallerPkgName);
|
||||
return kLegacyInstallerPkgName;
|
||||
}
|
||||
LOGE("no supported installer app found, using primary as default %s",
|
||||
kPrimaryInstallerPkgName);
|
||||
return kPrimaryInstallerPkgName;
|
||||
}
|
||||
|
||||
void ConfigManager::SnapshotBlackWhiteList() {
|
||||
DIR *dir;
|
||||
struct dirent *dent;
|
||||
dir = opendir(whitelist_path_.c_str());
|
||||
if (dir != nullptr) {
|
||||
while ((dent = readdir(dir)) != nullptr) {
|
||||
if (dent->d_type == DT_REG) {
|
||||
const char *fileName = dent->d_name;
|
||||
LOGI("whitelist: %s", fileName);
|
||||
white_list_default_.emplace_back(fileName);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
dir = opendir(blacklist_path_.c_str());
|
||||
if (dir != nullptr) {
|
||||
while ((dent = readdir(dir)) != nullptr) {
|
||||
if (dent->d_type == DT_REG) {
|
||||
const char *fileName = dent->d_name;
|
||||
LOGI("blacklist: %s", fileName);
|
||||
black_list_default_.emplace_back(fileName);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::InitOnce() {
|
||||
if (!initialized_) {
|
||||
use_prot_storage_ = GetAndroidApiLevel() >= ANDROID_N;
|
||||
data_path_prefix_ = use_prot_storage_ ? "/data/user_de/0/" : "/data/user/0/";
|
||||
|
||||
installer_pkg_name_ = RetrieveInstallerPkgName();
|
||||
base_config_path_ = GetConfigPath("");
|
||||
blacklist_path_ = GetConfigPath("blacklist/");
|
||||
whitelist_path_ = GetConfigPath("whitelist/");
|
||||
use_whitelist_path_ = GetConfigPath("usewhitelist");
|
||||
|
||||
dynamic_modules_enabled_ = access(GetConfigPath("dynamicmodules").c_str(), F_OK) == 0;
|
||||
black_white_list_enabled_ = access(GetConfigPath("blackwhitelist").c_str(), F_OK) == 0;
|
||||
deopt_boot_image_enabled_ = access(GetConfigPath("deoptbootimage").c_str(), F_OK) == 0;
|
||||
resources_hook_enabled_ = access(GetConfigPath("disable_resources").c_str(), F_OK) != 0;
|
||||
|
||||
// use_white_list snapshot
|
||||
use_white_list_snapshot_ = access(use_whitelist_path_.c_str(), F_OK) == 0;
|
||||
LOGI("black/white list mode: %s, using whitelist: %s",
|
||||
BoolToString(black_white_list_enabled_), BoolToString(use_white_list_snapshot_));
|
||||
LOGI("dynamic modules mode: %s", BoolToString(dynamic_modules_enabled_));
|
||||
LOGI("resources hook: %s", BoolToString(resources_hook_enabled_));
|
||||
LOGI("deopt boot image: %s", BoolToString(deopt_boot_image_enabled_));
|
||||
if (black_white_list_enabled_) {
|
||||
SnapshotBlackWhiteList();
|
||||
}
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigManager::IsAppNeedHook(const std::string &app_data_dir) const {
|
||||
if (!black_white_list_enabled_) {
|
||||
return true;
|
||||
}
|
||||
bool can_access_app_data = access(base_config_path_.c_str(), F_OK) == 0;
|
||||
bool use_white_list;
|
||||
if (can_access_app_data) {
|
||||
use_white_list = access(use_whitelist_path_.c_str(), F_OK) == 0;
|
||||
} else {
|
||||
LOGE("can't access config path, using snapshot use_white_list: %s",
|
||||
app_data_dir.c_str());
|
||||
use_white_list = use_white_list_snapshot_;
|
||||
}
|
||||
int user = 0;
|
||||
char package_name[PATH_MAX];
|
||||
if (sscanf(app_data_dir.c_str(), "/data/%*[^/]/%d/%s", &user, package_name) != 2) {
|
||||
if (sscanf(app_data_dir.c_str(), "/data/%*[^/]/%s", package_name) != 1) {
|
||||
package_name[0] = '\0';
|
||||
LOGE("can't parse %s", app_data_dir.c_str());
|
||||
return !use_white_list;
|
||||
}
|
||||
}
|
||||
if (strcmp(package_name, kPrimaryInstallerPkgName) == 0
|
||||
|| strcmp(package_name, kSecondaryInstallerPkgName) == 0
|
||||
|| strcmp(package_name, kLegacyInstallerPkgName) == 0) {
|
||||
// always hook installer apps
|
||||
return true;
|
||||
}
|
||||
if (use_white_list) {
|
||||
if (!can_access_app_data) {
|
||||
LOGE("can't access config path, using snapshot white list: %s",
|
||||
app_data_dir.c_str());
|
||||
return !(find(white_list_default_.begin(), white_list_default_.end(),
|
||||
package_name) ==
|
||||
white_list_default_.end());
|
||||
}
|
||||
std::string target_path = whitelist_path_ + package_name;
|
||||
bool res = access(target_path.c_str(), F_OK) == 0;
|
||||
LOGD("using whitelist, %s -> %d", app_data_dir.c_str(), res);
|
||||
return res;
|
||||
} else {
|
||||
if (!can_access_app_data) {
|
||||
LOGE("can't access config path, using snapshot black list: %s",
|
||||
app_data_dir.c_str());
|
||||
return find(black_list_default_.begin(), black_list_default_.end(), package_name) ==
|
||||
black_list_default_.end();
|
||||
}
|
||||
std::string target_path = blacklist_path_ + package_name;
|
||||
bool res = access(target_path.c_str(), F_OK) != 0;
|
||||
LOGD("using blacklist, %s -> %d", app_data_dir.c_str(), res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool ConfigManager::IsBlackWhiteListEnabled() const {
|
||||
return black_white_list_enabled_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool ConfigManager::IsDynamicModulesEnabled() const {
|
||||
return dynamic_modules_enabled_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool ConfigManager::IsResourcesHookEnabled() const {
|
||||
return resources_hook_enabled_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool ConfigManager::IsDeoptBootImageEnabled() const {
|
||||
return deopt_boot_image_enabled_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE std::string ConfigManager::GetInstallerPkgName() const {
|
||||
return installer_pkg_name_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE std::string ConfigManager::GetConfigPath(const std::string &suffix) const {
|
||||
return data_path_prefix_ + installer_pkg_name_ + "/conf/" + suffix;
|
||||
};
|
||||
|
||||
ConfigManager::ConfigManager() {
|
||||
InitOnce();
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager() {
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <JNIHelper.h>
|
||||
#include <art/runtime/native/native_util.h>
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static constexpr const char *kPrimaryInstallerPkgName = "com.solohsu.android.edxp.manager";
|
||||
static constexpr const char *kSecondaryInstallerPkgName = "org.meowcat.edxposed.manager";
|
||||
static constexpr const char *kLegacyInstallerPkgName = "de.robv.android.xposed.installer";
|
||||
|
||||
class ConfigManager {
|
||||
public:
|
||||
|
||||
static ConfigManager *GetInstance() {
|
||||
if (instance_ == 0) {
|
||||
instance_ = new ConfigManager();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
bool IsBlackWhiteListEnabled() const;
|
||||
|
||||
bool IsDynamicModulesEnabled() const;
|
||||
|
||||
bool IsResourcesHookEnabled() const;
|
||||
|
||||
bool IsDeoptBootImageEnabled() const;
|
||||
|
||||
std::string GetInstallerPkgName() const;
|
||||
|
||||
bool IsAppNeedHook(const std::string &app_data_dir) const;
|
||||
|
||||
private:
|
||||
inline static ConfigManager *instance_;
|
||||
bool initialized_ = false;
|
||||
bool use_prot_storage_ = true;
|
||||
std::string data_path_prefix_;
|
||||
std::string installer_pkg_name_;
|
||||
std::string base_config_path_;
|
||||
std::string blacklist_path_;
|
||||
std::string whitelist_path_;
|
||||
std::string use_whitelist_path_;
|
||||
bool black_white_list_enabled_ = false;
|
||||
bool dynamic_modules_enabled_ = false;
|
||||
bool deopt_boot_image_enabled_ = false;
|
||||
bool resources_hook_enabled_ = true;
|
||||
// snapshot at boot
|
||||
bool use_white_list_snapshot_ = false;
|
||||
std::vector<std::string> white_list_default_;
|
||||
std::vector<std::string> black_list_default_;
|
||||
|
||||
ConfigManager();
|
||||
|
||||
~ConfigManager();
|
||||
|
||||
void InitOnce();
|
||||
|
||||
void SnapshotBlackWhiteList();
|
||||
|
||||
std::string RetrieveInstallerPkgName() const;
|
||||
|
||||
std::string GetConfigPath(const std::string &suffix) const;
|
||||
};
|
||||
|
||||
|
||||
} // namespace edxp
|
||||
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
|
||||
#include <jni.h>
|
||||
#include <android-base/macros.h>
|
||||
#include <JNIHelper.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <jni/edxp_config_manager.h>
|
||||
#include <jni/art_class_linker.h>
|
||||
#include <jni/art_heap.h>
|
||||
#include <jni/edxp_yahfa.h>
|
||||
#include <dlfcn.h>
|
||||
#include <native_hook.h>
|
||||
#include <jni/framework_zygote.h>
|
||||
#include <jni/edxp_resources_hook.h>
|
||||
#include <dl_util.h>
|
||||
#include "edxp_context.h"
|
||||
#include "config_manager.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-value"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
Context *Context::instance_ = nullptr;
|
||||
|
||||
Context *Context::GetInstance() {
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new Context();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE inline jobject Context::GetCurrentClassLoader() const {
|
||||
return inject_class_loader_;
|
||||
}
|
||||
|
||||
void Context::LoadDexAndInit(JNIEnv *env, const char *dex_path) {
|
||||
if (LIKELY(initialized_)) {
|
||||
return;
|
||||
}
|
||||
jclass classloader = JNI_FindClass(env, "java/lang/ClassLoader");
|
||||
jmethodID getsyscl_mid = JNI_GetStaticMethodID(
|
||||
env, classloader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
|
||||
jobject sys_classloader = JNI_CallStaticObjectMethod(env, classloader, getsyscl_mid);
|
||||
if (UNLIKELY(!sys_classloader)) {
|
||||
LOG(ERROR) << "getSystemClassLoader failed!!!";
|
||||
return;
|
||||
}
|
||||
jclass path_classloader = JNI_FindClass(env, "dalvik/system/PathClassLoader");
|
||||
jmethodID initMid = JNI_GetMethodID(env, path_classloader, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
|
||||
jobject my_cl = JNI_NewObject(env, path_classloader, initMid, env->NewStringUTF(dex_path),
|
||||
nullptr, sys_classloader);
|
||||
if (UNLIKELY(!my_cl)) {
|
||||
LOG(ERROR) << "PathClassLoader creation failed!!!";
|
||||
return;
|
||||
}
|
||||
inject_class_loader_ = env->NewGlobalRef(my_cl);
|
||||
entry_class_ = (jclass) (env->NewGlobalRef(
|
||||
FindClassFromLoader(env, GetCurrentClassLoader(), kEntryClassName)));
|
||||
|
||||
RegisterEdxpResourcesHook(env);
|
||||
RegisterFrameworkZygote(env);
|
||||
RegisterConfigManagerMethods(env);
|
||||
RegisterArtClassLinker(env);
|
||||
RegisterArtHeap(env);
|
||||
RegisterEdxpYahfa(env);
|
||||
|
||||
initialized_ = true;
|
||||
|
||||
//for SandHook variant
|
||||
ScopedDlHandle sandhook_handle(kLibSandHookPath.c_str());
|
||||
if (!sandhook_handle.IsValid()) {
|
||||
return;
|
||||
}
|
||||
typedef bool *(*TYPE_JNI_LOAD)(JNIEnv *, jclass, jclass);
|
||||
auto jni_load = sandhook_handle.DlSym<TYPE_JNI_LOAD>("JNI_Load_Ex");
|
||||
jclass sandhook_class = FindClassFromLoader(env, kSandHookClassName);
|
||||
jclass nevercall_class = FindClassFromLoader(env, kSandHookNeverCallClassName);
|
||||
if (!sandhook_class || !nevercall_class) { // fail-fast
|
||||
return;
|
||||
}
|
||||
if (!jni_load(env, sandhook_class, nevercall_class)) {
|
||||
LOGE("SandHook: HookEntry class error. %d", getpid());
|
||||
}
|
||||
}
|
||||
|
||||
jclass
|
||||
Context::FindClassFromLoader(JNIEnv *env, jobject class_loader, const char *class_name) const {
|
||||
jclass clz = JNI_GetObjectClass(env, class_loader);
|
||||
jmethodID mid = JNI_GetMethodID(env, clz, "loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
jclass ret = nullptr;
|
||||
if (!mid) {
|
||||
mid = JNI_GetMethodID(env, clz, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
}
|
||||
if (LIKELY(mid)) {
|
||||
jobject target = JNI_CallObjectMethod(env, class_loader, mid,
|
||||
env->NewStringUTF(class_name));
|
||||
if (target) {
|
||||
return (jclass) target;
|
||||
}
|
||||
} else {
|
||||
LOG(ERROR) << "No loadClass/findClass method found";
|
||||
}
|
||||
LOG(ERROR) << "Class %s not found: " << class_name;
|
||||
return ret;
|
||||
}
|
||||
|
||||
jclass Context::FindClassFromLoader(JNIEnv *env, const char *className) const {
|
||||
return FindClassFromLoader(env, GetCurrentClassLoader(), className);
|
||||
}
|
||||
|
||||
inline void Context::PrepareJavaEnv(JNIEnv *env) {
|
||||
LoadDexAndInit(env, kInjectDexPath);
|
||||
}
|
||||
|
||||
inline void Context::FindAndCall(JNIEnv *env, const char *method_name,
|
||||
const char *method_sig, ...) const {
|
||||
if (!entry_class_) {
|
||||
LOGE("cannot call method %s, entry class is null", method_name);
|
||||
return;
|
||||
}
|
||||
jmethodID mid = JNI_GetStaticMethodID(env, entry_class_, method_name, method_sig);
|
||||
if (LIKELY(mid)) {
|
||||
va_list args;
|
||||
va_start(args, method_sig);
|
||||
env->functions->CallStaticVoidMethodV(env, entry_class_, mid, args);
|
||||
va_end(args);
|
||||
} else {
|
||||
LOGE("method %s id is null", method_name);
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void Context::SetAppDataDir(jstring app_data_dir) {
|
||||
app_data_dir_ = app_data_dir;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE jstring Context::GetAppDataDir() const {
|
||||
return app_data_dir_;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void Context::SetNiceName(jstring nice_name) {
|
||||
nice_name_ = nice_name;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE jstring Context::GetNiceName() const {
|
||||
return nice_name_;
|
||||
}
|
||||
|
||||
void
|
||||
Context::OnNativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t uid, gid_t gid,
|
||||
jintArray gids,
|
||||
jint runtime_flags, jobjectArray rlimits,
|
||||
jlong permitted_capabilities,
|
||||
jlong effective_capabilities) {
|
||||
app_data_dir_ = env->NewStringUTF(SYSTEM_SERVER_DATA_DIR);
|
||||
bool is_black_white_list_mode = ConfigManager::GetInstance()->IsBlackWhiteListEnabled();
|
||||
bool is_dynamic_modules_mode = ConfigManager::GetInstance()->IsDynamicModulesEnabled();
|
||||
if (is_black_white_list_mode && is_dynamic_modules_mode) {
|
||||
// when black/white list is on, never inject into zygote if dynamic modules mode is on
|
||||
return;
|
||||
}
|
||||
PrepareJavaEnv(env);
|
||||
// jump to java code
|
||||
FindAndCall(env, "forkSystemServerPre", "(II[II[[IJJ)V", uid, gid, gids, runtime_flags,
|
||||
rlimits, permitted_capabilities, effective_capabilities);
|
||||
}
|
||||
|
||||
|
||||
int Context::OnNativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res) {
|
||||
if (res == 0) {
|
||||
PrepareJavaEnv(env);
|
||||
// only do work in child since FindAndCall would print log
|
||||
FindAndCall(env, "forkSystemServerPost", "(I)V", res);
|
||||
} else {
|
||||
// in zygote process, res is child zygote pid
|
||||
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Context::OnNativeForkAndSpecializePre(JNIEnv *env, jclass clazz,
|
||||
jint uid, jint gid,
|
||||
jintArray gids,
|
||||
jint runtime_flags,
|
||||
jobjectArray rlimits,
|
||||
jint mount_external,
|
||||
jstring se_info,
|
||||
jstring se_name,
|
||||
jintArray fds_to_close,
|
||||
jintArray fds_to_ignore,
|
||||
jboolean is_child_zygote,
|
||||
jstring instruction_set,
|
||||
jstring app_data_dir) {
|
||||
app_data_dir_ = app_data_dir;
|
||||
nice_name_ = se_name;
|
||||
if (ConfigManager::GetInstance()->IsBlackWhiteListEnabled() &&
|
||||
ConfigManager::GetInstance()->IsDynamicModulesEnabled()) {
|
||||
// when black/white list is on, never inject into zygote if dynamic modules mode is on
|
||||
return;
|
||||
}
|
||||
PrepareJavaEnv(env);
|
||||
FindAndCall(env, "forkAndSpecializePre",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)V",
|
||||
uid, gid, gids, runtime_flags, rlimits,
|
||||
mount_external, se_info, se_name, fds_to_close, fds_to_ignore,
|
||||
is_child_zygote, instruction_set, app_data_dir);
|
||||
}
|
||||
|
||||
int Context::OnNativeForkAndSpecializePost(JNIEnv *env, jclass clazz, jint res) {
|
||||
if (res == 0) {
|
||||
PrepareJavaEnv(env);
|
||||
FindAndCall(env, "forkAndSpecializePost", "(ILjava/lang/String;Ljava/lang/String;)V",
|
||||
res,
|
||||
app_data_dir_, nice_name_);
|
||||
} else {
|
||||
// in zygote process, res is child zygote pid
|
||||
// don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace edxp {
|
||||
|
||||
#define SYSTEM_SERVER_DATA_DIR "/data/user/0/android"
|
||||
|
||||
class Context {
|
||||
|
||||
public:
|
||||
static Context *GetInstance();
|
||||
|
||||
jobject GetCurrentClassLoader() const;
|
||||
|
||||
void PrepareJavaEnv(JNIEnv *env);
|
||||
|
||||
void FindAndCall(JNIEnv *env, const char *method_name, const char *method_sig, ...) const;
|
||||
|
||||
void SetAppDataDir(jstring app_data_dir);
|
||||
|
||||
void SetNiceName(jstring nice_name);
|
||||
|
||||
jstring GetAppDataDir() const;
|
||||
|
||||
jstring GetNiceName() const;
|
||||
|
||||
jclass FindClassFromLoader(JNIEnv *env, const char *className) const;
|
||||
|
||||
void OnNativeForkAndSpecializePre(JNIEnv *env, jclass clazz, jint uid, jint gid,
|
||||
jintArray gids, jint runtime_flags, jobjectArray rlimits,
|
||||
jint mount_external,
|
||||
jstring se_info, jstring se_name, jintArray fds_to_close,
|
||||
jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir);
|
||||
|
||||
int OnNativeForkAndSpecializePost(JNIEnv *env, jclass clazz, jint res);
|
||||
|
||||
int OnNativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res);
|
||||
|
||||
void OnNativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t uid, gid_t gid,
|
||||
jintArray gids, jint runtime_flags, jobjectArray rlimits,
|
||||
jlong permitted_capabilities,
|
||||
jlong effective_capabilities);
|
||||
|
||||
private:
|
||||
static Context *instance_;
|
||||
bool initialized_ = false;
|
||||
jobject inject_class_loader_ = nullptr;
|
||||
jclass entry_class_ = nullptr;
|
||||
jstring app_data_dir_ = nullptr;
|
||||
jstring nice_name_ = nullptr;
|
||||
|
||||
Context() {}
|
||||
|
||||
~Context() {}
|
||||
|
||||
void LoadDexAndInit(JNIEnv *env, const char *dex_path);
|
||||
|
||||
jclass FindClassFromLoader(JNIEnv *env, jobject class_loader, const char *class_name) const;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
#include <jni.h>
|
||||
#include <native_util.h>
|
||||
#include <art/runtime/class_linker.h>
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include <vector>
|
||||
#include "art_class_linker.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static std::vector<void *> deopted_methods;
|
||||
|
||||
static void ClassLinker_setEntryPointsToInterpreter(JNI_START, jobject method) {
|
||||
void *reflected_method = env->FromReflectedMethod(method);
|
||||
if (std::find(deopted_methods.begin(), deopted_methods.end(), reflected_method) !=
|
||||
deopted_methods.end()) {
|
||||
LOGD("method %p has been deopted before, skip...", reflected_method);
|
||||
return;
|
||||
}
|
||||
LOGD("deoptimizing method: %p", reflected_method);
|
||||
art::ClassLinker::Current()->SetEntryPointsToInterpreter(reflected_method);
|
||||
deopted_methods.push_back(reflected_method);
|
||||
LOGD("method deoptimized: %p", reflected_method);
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(ClassLinker, setEntryPointsToInterpreter, "(Ljava/lang/reflect/Member;)V")
|
||||
};
|
||||
|
||||
void RegisterArtClassLinker(JNIEnv *env) {
|
||||
REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.art.ClassLinker");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
namespace edxp {
|
||||
|
||||
void RegisterArtClassLinker(JNIEnv *);
|
||||
|
||||
} // namespace edxp
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#include <jni.h>
|
||||
#include <native_util.h>
|
||||
#include <art/runtime/gc/collector/gc_type.h>
|
||||
#include <art/runtime/gc/heap.h>
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include "art_heap.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
|
||||
static jint Heap_waitForGcToComplete(JNI_START, jlong thread) {
|
||||
art::gc::collector::GcType gcType = art::gc::Heap::Current()->WaitForGcToComplete(
|
||||
art::gc::GcCause::kGcCauseNone, reinterpret_cast<void *>(thread));
|
||||
return gcType;
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(Heap, waitForGcToComplete, "(J)I")
|
||||
};
|
||||
|
||||
void RegisterArtHeap(JNIEnv *env) {
|
||||
REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.art.Heap");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
namespace edxp {
|
||||
|
||||
void RegisterArtHeap(JNIEnv *);
|
||||
|
||||
} // namespace edxp
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
#include <config_manager.h>
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include <native_util.h>
|
||||
#include "edxp_config_manager.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static jboolean ConfigManager_isBlackWhiteListEnabled(JNI_START) {
|
||||
return (jboolean) ConfigManager::GetInstance()->IsBlackWhiteListEnabled();
|
||||
}
|
||||
|
||||
static jboolean ConfigManager_isDynamicModulesEnabled(JNI_START) {
|
||||
return (jboolean) ConfigManager::GetInstance()->IsDynamicModulesEnabled();
|
||||
}
|
||||
|
||||
static jboolean ConfigManager_isResourcesHookEnabled(JNI_START) {
|
||||
return (jboolean) ConfigManager::GetInstance()->IsResourcesHookEnabled();
|
||||
}
|
||||
|
||||
static jboolean ConfigManager_isDeoptBootImageEnabled(JNI_START) {
|
||||
return (jboolean) ConfigManager::GetInstance()->IsDeoptBootImageEnabled();
|
||||
}
|
||||
|
||||
static jstring ConfigManager_getInstallerPackageName(JNI_START) {
|
||||
return env->NewStringUTF(ConfigManager::GetInstance()->GetInstallerPkgName().c_str());
|
||||
}
|
||||
|
||||
static jboolean ConfigManager_isAppNeedHook(JNI_START, jstring appDataDir) {
|
||||
const char *app_data_dir = env->GetStringUTFChars(appDataDir, JNI_FALSE);
|
||||
auto result = (jboolean) ConfigManager::GetInstance()->IsAppNeedHook(app_data_dir);
|
||||
env->ReleaseStringUTFChars(appDataDir, app_data_dir);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(ConfigManager, isBlackWhiteListEnabled, "()Z"),
|
||||
NATIVE_METHOD(ConfigManager, isDynamicModulesEnabled, "()Z"),
|
||||
NATIVE_METHOD(ConfigManager, isResourcesHookEnabled, "()Z"),
|
||||
NATIVE_METHOD(ConfigManager, isDeoptBootImageEnabled, "()Z"),
|
||||
NATIVE_METHOD(ConfigManager, getInstallerPackageName, "()Ljava/lang/String;"),
|
||||
NATIVE_METHOD(ConfigManager, isAppNeedHook, "(Ljava/lang/String;)Z"),
|
||||
};
|
||||
|
||||
void RegisterConfigManagerMethods(JNIEnv *env) {
|
||||
REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.config.ConfigManager");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
void RegisterConfigManagerMethods(JNIEnv *env);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
#include <jni.h>
|
||||
#include <native_util.h>
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include <resource_hook.h>
|
||||
#include "edxp_resources_hook.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static jboolean ResourcesHook_initXResourcesNative(JNI_START) {
|
||||
return XposedBridge_initXResourcesNative(env, clazz);
|
||||
}
|
||||
|
||||
static jboolean ResourcesHook_removeFinalFlagNative(JNI_START, jclass target_class) {
|
||||
if (target_class) {
|
||||
jclass class_clazz = JNI_FindClass(env, "java/lang/Class");
|
||||
jfieldID java_lang_Class_accessFlags = JNI_GetFieldID(
|
||||
env, class_clazz, "accessFlags", "I");
|
||||
jint access_flags = env->GetIntField(clazz, java_lang_Class_accessFlags);
|
||||
env->SetIntField(clazz, java_lang_Class_accessFlags, access_flags & ~kAccFinal);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(ResourcesHook, initXResourcesNative, "()Z"),
|
||||
NATIVE_METHOD(ResourcesHook, removeFinalFlagNative, "(Ljava/lang/Class;)Z"),
|
||||
};
|
||||
|
||||
void RegisterEdxpResourcesHook(JNIEnv *env) {
|
||||
REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.core.ResourcesHook");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static constexpr uint32_t kAccFinal = 0x0010;
|
||||
|
||||
void RegisterEdxpResourcesHook(JNIEnv *);
|
||||
|
||||
} // namespace edxp
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
extern "C"
|
||||
{
|
||||
#include "HookMain.h"
|
||||
}
|
||||
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include "jni.h"
|
||||
#include "native_util.h"
|
||||
#include "edxp_yahfa.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static void Yahfa_init(JNI_START, jint sdkVersion) {
|
||||
Java_lab_galaxy_yahfa_HookMain_init(env, clazz, sdkVersion);
|
||||
}
|
||||
|
||||
static jobject Yahfa_findMethodNative(JNI_START, jclass targetClass,
|
||||
jstring methodName, jstring methodSig) {
|
||||
return Java_lab_galaxy_yahfa_HookMain_findMethodNative(env, clazz, targetClass, methodName,
|
||||
methodSig);
|
||||
}
|
||||
|
||||
static jboolean Yahfa_backupAndHookNative(JNI_START, jobject target,
|
||||
jobject hook, jobject backup) {
|
||||
return Java_lab_galaxy_yahfa_HookMain_backupAndHookNative(env, clazz, target, hook, backup);
|
||||
}
|
||||
|
||||
static void Yahfa_ensureMethodCached(JNI_START, jobject hook, jobject backup) {
|
||||
Java_lab_galaxy_yahfa_HookMain_ensureMethodCached(env, clazz, hook, backup);
|
||||
}
|
||||
|
||||
static void Yahfa_setMethodNonCompilable(JNI_START, jobject member) {
|
||||
if (!member) {
|
||||
LOGE("setNonCompilableNative: member is null");
|
||||
return;
|
||||
}
|
||||
void *art_method = env->FromReflectedMethod(member);
|
||||
if (!art_method) {
|
||||
LOGE("setNonCompilableNative: art_method is null");
|
||||
return;
|
||||
}
|
||||
setNonCompilable(art_method);
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(Yahfa, init, "(I)V"),
|
||||
NATIVE_METHOD(Yahfa, findMethodNative,
|
||||
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;"),
|
||||
NATIVE_METHOD(Yahfa, backupAndHookNative,
|
||||
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
|
||||
NATIVE_METHOD(Yahfa, ensureMethodCached,
|
||||
"(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V"),
|
||||
NATIVE_METHOD(Yahfa, setMethodNonCompilable, "(Ljava/lang/reflect/Member;)V"),
|
||||
};
|
||||
|
||||
void RegisterEdxpYahfa(JNIEnv *env) {
|
||||
REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.core.Yahfa");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
void RegisterEdxpYahfa(JNIEnv *);
|
||||
|
||||
} // namespace edxp
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
#include <jni.h>
|
||||
#include <native_util.h>
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include <framework/fd_utils-inl.h>
|
||||
#include "framework_zygote.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static FileDescriptorTable *gClosedFdTable = nullptr;
|
||||
|
||||
static void Zygote_closeFilesBeforeFork(JNI_START) {
|
||||
// FIXME what if gClosedFdTable is not null
|
||||
gClosedFdTable = FileDescriptorTable::Create();
|
||||
}
|
||||
|
||||
static void Zygote_reopenFilesAfterFork(JNI_START) {
|
||||
if (!gClosedFdTable) {
|
||||
LOGE("gClosedFdTable is null when reopening files");
|
||||
return;
|
||||
}
|
||||
gClosedFdTable->Reopen();
|
||||
delete gClosedFdTable;
|
||||
gClosedFdTable = nullptr;
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(Zygote, closeFilesBeforeFork, "()V"),
|
||||
NATIVE_METHOD(Zygote, reopenFilesAfterFork, "()V")
|
||||
};
|
||||
|
||||
void RegisterFrameworkZygote(JNIEnv *env) {
|
||||
REGISTER_EDXP_NATIVE_METHODS("com.elderdrivers.riru.edxp.framework.Zygote");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
void RegisterFrameworkZygote(JNIEnv *);
|
||||
|
||||
} // namespace edxp
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <jni.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <sys/mman.h>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <android-base/logging.h>
|
||||
#include "native_hook.h"
|
||||
#include "logging.h"
|
||||
#include "config.h"
|
||||
#include "edxp_context.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-value"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((visibility("default"))) void onModuleLoaded() {
|
||||
LOG(INFO) << "onModuleLoaded: welcome to EdXposed!";
|
||||
InstallInlineHooks();
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) int shouldSkipUid(int uid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) void
|
||||
nativeForkAndSpecializePre(JNIEnv *env, jclass clazz, jint *_uid, jint *gid, jintArray *gids,
|
||||
jint *runtime_flags,
|
||||
jobjectArray *rlimits, jint *mount_external, jstring *se_info,
|
||||
jstring *se_name,
|
||||
jintArray *fds_to_close, jintArray *fds_to_ignore,
|
||||
jboolean *is_child_zygote,
|
||||
jstring *instruction_set, jstring *app_data_dir,
|
||||
jstring *package_name,
|
||||
jobjectArray *packages_for_uid, jstring *sandbox_id) {
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(env, clazz, *_uid, *gid, *gids,
|
||||
*runtime_flags, *rlimits,
|
||||
*mount_external, *se_info, *se_name,
|
||||
*fds_to_close,
|
||||
*fds_to_ignore,
|
||||
*is_child_zygote, *instruction_set,
|
||||
*app_data_dir);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) int
|
||||
nativeForkAndSpecializePost(JNIEnv *env, jclass clazz,
|
||||
jint res) {
|
||||
return Context::GetInstance()->OnNativeForkAndSpecializePost(env, clazz, res);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void
|
||||
nativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t *uid, gid_t *gid, jintArray *gids,
|
||||
jint *runtime_flags,
|
||||
jobjectArray *rlimits, jlong *permitted_capabilities,
|
||||
jlong *effective_capabilities) {
|
||||
Context::GetInstance()->OnNativeForkSystemServerPre(env, clazz, *uid, *gid, *gids,
|
||||
*runtime_flags, *rlimits,
|
||||
*permitted_capabilities,
|
||||
*effective_capabilities);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int nativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res) {
|
||||
return Context::GetInstance()->OnNativeForkSystemServerPost(env, clazz, res);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) void specializeAppProcessPre(
|
||||
JNIEnv *env, jclass clazz, jint *_uid, jint *gid, jintArray *gids, jint *runtime_flags,
|
||||
jobjectArray *rlimits, jint *mount_external, jstring *se_info, jstring *nice_name,
|
||||
jboolean *start_child_zygote, jstring *instruction_set, jstring *app_data_dir,
|
||||
jstring *package_name, jobjectArray *packages_for_uid, jstring *sandbox_id) {
|
||||
Context::GetInstance()->OnNativeForkAndSpecializePre(env, clazz, *_uid, *gid, *gids,
|
||||
*runtime_flags, *rlimits,
|
||||
*mount_external, *se_info, *nice_name,
|
||||
nullptr, nullptr,
|
||||
*start_child_zygote, *instruction_set,
|
||||
*app_data_dir);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) int specializeAppProcessPost(
|
||||
JNIEnv *env, jclass clazz) {
|
||||
return Context::GetInstance()->OnNativeForkAndSpecializePost(env, clazz, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
#include <dlfcn.h>
|
||||
#include <android_build.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <SubstrateHook.h>
|
||||
#include <config_manager.h>
|
||||
#include <art/runtime/runtime.h>
|
||||
#include <dl_util.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "native_hook.h"
|
||||
#include "riru_hook.h"
|
||||
#include "art/runtime/mirror/class.h"
|
||||
#include "art/runtime/class_linker.h"
|
||||
#include "art/runtime/gc/heap.h"
|
||||
#include "art/runtime/hidden_api.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static bool installed = false;
|
||||
|
||||
void InstallInlineHooks() {
|
||||
if (installed) {
|
||||
LOGI("Inline hooks have been installed, skip");
|
||||
return;
|
||||
}
|
||||
LOGI("Start to install inline hooks");
|
||||
int api_level = GetAndroidApiLevel();
|
||||
if (UNLIKELY(api_level < ANDROID_LOLLIPOP)) {
|
||||
LOGE("API level not supported: %d, skip inline hooks", api_level);
|
||||
return;
|
||||
}
|
||||
LOGI("Using api level %d", api_level);
|
||||
InstallRiruHooks();
|
||||
#ifdef __LP64__
|
||||
ScopedDlHandle whale_handle(kLibWhalePath.c_str());
|
||||
if (!whale_handle.IsValid()) {
|
||||
return;
|
||||
}
|
||||
void *hook_func_symbol = whale_handle.DlSym<void *>("WInlineHookFunction");
|
||||
#else
|
||||
void *hook_func_symbol = (void *) MSHookFunction;
|
||||
#endif
|
||||
if (!hook_func_symbol) {
|
||||
return;
|
||||
}
|
||||
auto hook_func = reinterpret_cast<HookFunType>(hook_func_symbol);
|
||||
ScopedDlHandle art_handle(kLibArtPath.c_str());
|
||||
if (!art_handle.IsValid()) {
|
||||
return;
|
||||
}
|
||||
art::hidden_api::DisableHiddenApi(art_handle.Get(), hook_func);
|
||||
art::Runtime::Setup(art_handle.Get(), hook_func);
|
||||
art::gc::Heap::Setup(art_handle.Get(), hook_func);
|
||||
art::ClassLinker::Setup(art_handle.Get(), hook_func);
|
||||
art::mirror::Class::Setup(art_handle.Get(), hook_func);
|
||||
LOGI("Inline hooks installed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <xhook.h>
|
||||
|
||||
namespace edxp {
|
||||
|
||||
void InstallInlineHooks();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
//
|
||||
// Created by solo on 2019/3/24.
|
||||
//
|
||||
|
||||
#include <jni.h>
|
||||
#include <ByteOrder.h>
|
||||
#include <logging.h>
|
||||
#include <dlfcn.h>
|
||||
#include <android-base/macros.h>
|
||||
#include <edxp_context.h>
|
||||
#include <art/runtime/native/native_util.h>
|
||||
#include <nativehelper/jni_macros.h>
|
||||
#include <JNIHelper.h>
|
||||
#include "framework/androidfw/ResourceTypes.h"
|
||||
#include "resource_hook.h"
|
||||
#include "dl_util.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static constexpr const char *kXResourcesClassName = "android/content/res/XResources";
|
||||
|
||||
typedef int32_t (*TYPE_GET_ATTR_NAME_ID)(void *, int);
|
||||
|
||||
typedef char16_t *(*TYPE_STRING_AT)(const void *, int32_t, size_t *);
|
||||
|
||||
typedef void(*TYPE_RESTART)(void *);
|
||||
|
||||
typedef int32_t (*TYPE_NEXT)(void *);
|
||||
|
||||
static jclass classXResources;
|
||||
static jmethodID methodXResourcesTranslateAttrId;
|
||||
static jmethodID methodXResourcesTranslateResId;
|
||||
|
||||
static TYPE_NEXT ResXMLParser_next = nullptr;
|
||||
static TYPE_RESTART ResXMLParser_restart = nullptr;
|
||||
static TYPE_GET_ATTR_NAME_ID ResXMLParser_getAttributeNameID = nullptr;
|
||||
static TYPE_STRING_AT ResStringPool_stringAt = nullptr;
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
NATIVE_METHOD(XResources, rewriteXmlReferencesNative,
|
||||
"(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V")
|
||||
};
|
||||
|
||||
static bool register_natives_XResources(JNIEnv *env, jclass clazz) {
|
||||
jint result = JNI_RegisterNatives(env, clazz, gMethods, arraysize(gMethods));
|
||||
return result == JNI_OK;
|
||||
}
|
||||
|
||||
static bool PrepareSymbols() {
|
||||
ScopedDlHandle fw_handle(kLibFwPath.c_str());
|
||||
if (!fw_handle.IsValid()) {
|
||||
return false;
|
||||
};
|
||||
if (!(ResXMLParser_next = fw_handle.DlSym<TYPE_NEXT>(
|
||||
"_ZN7android12ResXMLParser4nextEv"))) {
|
||||
return false;
|
||||
}
|
||||
if (!(ResXMLParser_restart = fw_handle.DlSym<TYPE_RESTART>(
|
||||
"_ZN7android12ResXMLParser7restartEv"))) {
|
||||
return false;
|
||||
};
|
||||
if (!(ResXMLParser_getAttributeNameID = fw_handle.DlSym<TYPE_GET_ATTR_NAME_ID>(
|
||||
LP_SELECT("_ZNK7android12ResXMLParser18getAttributeNameIDEj",
|
||||
"_ZNK7android12ResXMLParser18getAttributeNameIDEm")))) {
|
||||
return false;
|
||||
}
|
||||
return (ResStringPool_stringAt = fw_handle.DlSym<TYPE_STRING_AT>(
|
||||
LP_SELECT("_ZNK7android13ResStringPool8stringAtEjPj",
|
||||
"_ZNK7android13ResStringPool8stringAtEmPm"))) != nullptr;
|
||||
}
|
||||
|
||||
jboolean XposedBridge_initXResourcesNative(JNIEnv *env, jclass) {
|
||||
classXResources = Context::GetInstance()->FindClassFromLoader(env, kXResourcesClassName);
|
||||
if (!classXResources) {
|
||||
LOGE("Error while loading XResources class '%s':", kXResourcesClassName);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (!register_natives_XResources(env, classXResources)) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
methodXResourcesTranslateResId = JNI_GetStaticMethodID(
|
||||
env, classXResources, "translateResId",
|
||||
"(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I");
|
||||
if (!methodXResourcesTranslateResId) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
methodXResourcesTranslateAttrId = JNI_GetStaticMethodID(
|
||||
env, classXResources, "translateAttrId",
|
||||
"(Ljava/lang/String;Landroid/content/res/XResources;)I");
|
||||
if (!methodXResourcesTranslateAttrId) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (!PrepareSymbols()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
classXResources = reinterpret_cast<jclass>(env->NewGlobalRef(classXResources));
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
void XResources_rewriteXmlReferencesNative(JNIEnv *env, jclass,
|
||||
jlong parserPtr, jobject origRes, jobject repRes) {
|
||||
|
||||
auto parser = (android::ResXMLParser *) parserPtr;
|
||||
|
||||
if (parser == nullptr)
|
||||
return;
|
||||
|
||||
const android::ResXMLTree &mTree = parser->mTree;
|
||||
auto mResIds = (uint32_t *) mTree.mResIds;
|
||||
android::ResXMLTree_attrExt *tag;
|
||||
int attrCount;
|
||||
|
||||
do {
|
||||
switch (ResXMLParser_next(parser)) {
|
||||
case android::ResXMLParser::START_TAG:
|
||||
tag = (android::ResXMLTree_attrExt *) parser->mCurExt;
|
||||
attrCount = dtohs(tag->attributeCount);
|
||||
for (int idx = 0; idx < attrCount; idx++) {
|
||||
auto attr = (android::ResXMLTree_attribute *)
|
||||
(((const uint8_t *) tag)
|
||||
+ dtohs(tag->attributeStart)
|
||||
+ (dtohs(tag->attributeSize) * idx));
|
||||
|
||||
// find resource IDs for attribute names
|
||||
int32_t attrNameID = ResXMLParser_getAttributeNameID(parser, idx);
|
||||
// only replace attribute name IDs for app packages
|
||||
if (attrNameID >= 0 && (size_t) attrNameID < mTree.mNumResIds &&
|
||||
dtohl(mResIds[attrNameID]) >= 0x7f000000) {
|
||||
size_t attNameLen;
|
||||
const char16_t *attrName = ResStringPool_stringAt(&(mTree.mStrings),
|
||||
attrNameID,
|
||||
&attNameLen);
|
||||
jint attrResID = env->CallStaticIntMethod(classXResources,
|
||||
methodXResourcesTranslateAttrId,
|
||||
env->NewString(
|
||||
(const jchar *) attrName,
|
||||
attNameLen), origRes);
|
||||
if (env->ExceptionCheck())
|
||||
goto leave;
|
||||
|
||||
mResIds[attrNameID] = htodl(attrResID);
|
||||
}
|
||||
|
||||
// find original resource IDs for reference values (app packages only)
|
||||
if (attr->typedValue.dataType != android::Res_value::TYPE_REFERENCE)
|
||||
continue;
|
||||
|
||||
jint oldValue = dtohl(attr->typedValue.data);
|
||||
if (oldValue < 0x7f000000)
|
||||
continue;
|
||||
|
||||
jint newValue = env->CallStaticIntMethod(classXResources,
|
||||
methodXResourcesTranslateResId,
|
||||
oldValue, origRes, repRes);
|
||||
if (env->ExceptionCheck())
|
||||
goto leave;
|
||||
|
||||
if (newValue != oldValue)
|
||||
attr->typedValue.data = htodl(newValue);
|
||||
}
|
||||
continue;
|
||||
case android::ResXMLParser::END_DOCUMENT:
|
||||
case android::ResXMLParser::BAD_DOCUMENT:
|
||||
goto leave;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
leave:
|
||||
ResXMLParser_restart(parser);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace edxp {
|
||||
|
||||
jboolean XposedBridge_initXResourcesNative(JNIEnv *env, jclass);
|
||||
|
||||
void XResources_rewriteXmlReferencesNative(JNIEnv *env, jclass,
|
||||
jlong parserPtr, jobject origRes, jobject repRes);
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Created by solo on 2019/3/16.
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <riru.h>
|
||||
#include <xhook.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <logging.h>
|
||||
#include <android_build.h>
|
||||
#include "riru_hook.h"
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static int api_level = 0;
|
||||
|
||||
NEW_FUNC_DEF(int, __system_property_get, const char *key, char *value) {
|
||||
int res = old___system_property_get(key, value);
|
||||
if (key) {
|
||||
if (strcmp(kPropKeyCompilerFilter, key) == 0) {
|
||||
strcpy(value, kPropValueCompilerFilter);
|
||||
LOGI("system_property_get: %s -> %s", key, value);
|
||||
} else if (strcmp(kPropKeyCompilerFlags, key) == 0) {
|
||||
strcpy(value, kPropValueCompilerFlags);
|
||||
LOGI("system_property_get: %s -> %s", key, value);
|
||||
}
|
||||
if (api_level == ANDROID_O_MR1) {
|
||||
// https://android.googlesource.com/platform/art/+/f5516d38736fb97bfd0435ad03bbab17ddabbe4e
|
||||
// Android 8.1 add a fatal check for debugging (removed in Android 9.0),
|
||||
// which will be triggered by EdXposed in cases where target method is hooked
|
||||
// (native flag set) after it has been called several times(getCounter() return positive number)
|
||||
if (strcmp(kPropKeyUseJitProfiles, key) == 0) {
|
||||
strcpy(value, "false");
|
||||
} else if (strcmp(kPropKeyPmBgDexopt, key) == 0) {
|
||||
// use speed as bg-dexopt filter since that speed-profile won't work after
|
||||
// jit profiles is disabled
|
||||
strcpy(value, kPropValuePmBgDexopt);
|
||||
}
|
||||
LOGD("system_property_get: %s -> %s", key, value);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
NEW_FUNC_DEF(std::string,
|
||||
_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_,
|
||||
const std::string &key, const std::string &default_value) {
|
||||
std::string res = old__ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_(
|
||||
key, default_value);
|
||||
if (strcmp(kPropKeyCompilerFilter, key.c_str()) == 0) {
|
||||
res = kPropValueCompilerFilter;
|
||||
LOGI("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str());
|
||||
} else if (strcmp(kPropKeyCompilerFlags, key.c_str()) == 0) {
|
||||
res = kPropValueCompilerFlags;
|
||||
LOGI("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str());
|
||||
}
|
||||
if (api_level == ANDROID_O_MR1) {
|
||||
// see __system_property_get hook above for explanations
|
||||
if (strcmp(kPropKeyUseJitProfiles, key.c_str()) == 0) {
|
||||
res = "false";
|
||||
} else if (strcmp(kPropKeyPmBgDexopt, key.c_str()) == 0) {
|
||||
res = kPropValuePmBgDexopt;
|
||||
}
|
||||
LOGD("android::base::GetProperty: %s -> %s", key.c_str(), res.c_str());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void InstallRiruHooks() {
|
||||
|
||||
LOGI("Start to install Riru hook");
|
||||
|
||||
api_level = GetAndroidApiLevel();
|
||||
|
||||
XHOOK_REGISTER(__system_property_get);
|
||||
|
||||
if (GetAndroidApiLevel() >= ANDROID_P) {
|
||||
XHOOK_REGISTER(
|
||||
_ZN7android4base11GetPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_);
|
||||
}
|
||||
|
||||
if (xhook_refresh(0) == 0) {
|
||||
xhook_clear();
|
||||
LOGI("Riru hooks installed");
|
||||
} else {
|
||||
LOGE("Failed to install riru hooks");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#define XHOOK_REGISTER(NAME) \
|
||||
if (xhook_register(".*", #NAME, (void*) new_##NAME, (void **) &old_##NAME) == 0) { \
|
||||
if (riru_get_version() >= 8) { \
|
||||
void *f = riru_get_func(#NAME); \
|
||||
if (f != nullptr) { \
|
||||
memcpy(&old_##NAME, &f, sizeof(void *)); \
|
||||
} \
|
||||
riru_set_func(#NAME, (void *) new_##NAME); \
|
||||
} \
|
||||
} else { \
|
||||
LOGE("failed to register riru hook " #NAME "."); \
|
||||
}
|
||||
|
||||
#define NEW_FUNC_DEF(ret, func, ...) \
|
||||
static ret (*old_##func)(__VA_ARGS__); \
|
||||
static ret new_##func(__VA_ARGS__)
|
||||
|
||||
namespace edxp {
|
||||
|
||||
static constexpr const char *kPropKeyCompilerFilter = "dalvik.vm.dex2oat-filter";
|
||||
static constexpr const char *kPropKeyCompilerFlags = "dalvik.vm.dex2oat-flags";
|
||||
static constexpr const char *kPropKeyUseJitProfiles = "dalvik.vm.usejitprofiles";
|
||||
static constexpr const char *kPropKeyPmBgDexopt = "pm.dexopt.bg-dexopt";
|
||||
|
||||
static constexpr const char *kPropValueCompilerFilter = "quicken";
|
||||
static constexpr const char *kPropValuePmBgDexopt = "speed";
|
||||
static constexpr const char *kPropValueCompilerFlags = "--inline-max-code-units=0";
|
||||
|
||||
|
||||
void InstallRiruHooks();
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/system/bin/sh
|
||||
|
||||
EDXP_VERSION="0.4.2.3_alpha (4230)"
|
||||
EDXP_VERSION="0.4.3.0_alpha (4300)"
|
||||
ANDROID_SDK=`getprop ro.build.version.sdk`
|
||||
BUILD_DESC=`getprop ro.build.description`
|
||||
PRODUCT=`getprop ro.build.product`
|
||||
|
|
@ -79,7 +79,7 @@ start_log_cather () {
|
|||
}
|
||||
|
||||
start_verbose_log_catcher () {
|
||||
start_log_cather all.log "EdXposed-Fwk:V EdXposed-dexmaker:V XSharedPreferences:V EdXposed-Bridge:V EdXposed-YAHFA:V EdXposed-Core-Native:V EdXposed-Manager:V XposedInstaller:V" true ${LOG_VERBOSE}
|
||||
start_log_cather all.log "EdXposed:V EdXposed-Fwk:V EdXposed-dexmaker:V XSharedPreferences:V EdXposed-Bridge:V EdXposed-YAHFA:V EdXposed-Manager:V XposedInstaller:V" true ${LOG_VERBOSE}
|
||||
}
|
||||
|
||||
start_bridge_log_catcher () {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
sourceCompatibility = "7"
|
||||
targetCompatibility = "7"
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
|
|
|
|||
|
|
@ -30,4 +30,8 @@
|
|||
-keep class * implements com.elderdrivers.riru.common.KeepAll { *; }
|
||||
-keepclassmembers class * implements com.elderdrivers.riru.common.KeepMembers { *; }
|
||||
|
||||
-keep class com.swift.sandhook.** {*;}
|
||||
-keep class com.swift.sandhook.** {*;}
|
||||
|
||||
-keepclasseswithmember class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ import android.os.Build;
|
|||
import android.os.Process;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepAll;
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.config.InstallerChooser;
|
||||
import com.elderdrivers.riru.edxp.core.Yahfa;
|
||||
import com.elderdrivers.riru.edxp.sandhook.BuildConfig;
|
||||
import com.elderdrivers.riru.edxp.sandhook.core.HookMethodResolver;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
|
|
@ -15,7 +17,6 @@ import com.elderdrivers.riru.edxp.util.Utils;
|
|||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
|
|
@ -28,10 +29,10 @@ public class Main implements KeepAll {
|
|||
private static String forkSystemServerPramsStr = "";
|
||||
|
||||
static {
|
||||
init(Build.VERSION.SDK_INT);
|
||||
Yahfa.init(Build.VERSION.SDK_INT);
|
||||
HookMethodResolver.init();
|
||||
Router.injectConfig();
|
||||
InstallerChooser.setInstallerPackageName(getInstallerPkgName());
|
||||
InstallerChooser.setInstallerPackageName(ConfigManager.getInstallerPackageName());
|
||||
SandHookXposedBridge.init();
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ public class Main implements KeepAll {
|
|||
mountExternal, seInfo, niceName, Arrays.toString(fdsToClose),
|
||||
Arrays.toString(fdsToIgnore), startChildZygote, instructionSet, appDataDir);
|
||||
}
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkAndSpecializePre(uid, gid, gids, debugFlags, rlimits,
|
||||
mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote,
|
||||
instructionSet, appDataDir);
|
||||
|
|
@ -70,7 +71,7 @@ public class Main implements KeepAll {
|
|||
public static void forkAndSpecializePost(int pid, String appDataDir, String niceName) {
|
||||
if (pid == 0) {
|
||||
Utils.logD(forkAndSpecializePramsStr + " = " + Process.myPid());
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkAndSpecializePost(pid, appDataDir, niceName);
|
||||
} else {
|
||||
NormalProxy.forkAndSpecializePost(pid, appDataDir, niceName);
|
||||
|
|
@ -88,7 +89,7 @@ public class Main implements KeepAll {
|
|||
uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits),
|
||||
permittedCapabilities, effectiveCapabilities);
|
||||
}
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkSystemServerPre(uid, gid, gids, debugFlags, rlimits,
|
||||
permittedCapabilities, effectiveCapabilities);
|
||||
} else {
|
||||
|
|
@ -100,7 +101,7 @@ public class Main implements KeepAll {
|
|||
public static void forkSystemServerPost(int pid) {
|
||||
if (pid == 0) {
|
||||
Utils.logD(forkSystemServerPramsStr + " = " + Process.myPid());
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkSystemServerPost(pid);
|
||||
} else {
|
||||
NormalProxy.forkSystemServerPost(pid);
|
||||
|
|
@ -111,46 +112,4 @@ public class Main implements KeepAll {
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// native methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static native boolean backupAndHookNative(Object target, Method hook, Method backup);
|
||||
|
||||
public static native void setMethodNonCompilable(Object member);
|
||||
|
||||
public static native void ensureMethodCached(Method hook, Method backup);
|
||||
|
||||
// JNI.ToReflectedMethod() could return either Method or Constructor
|
||||
public static native Object findMethodNative(Class targetClass, String methodName, String methodSig);
|
||||
|
||||
private static native void init(int SDK_version);
|
||||
|
||||
public static native String getInstallerPkgName();
|
||||
|
||||
public static native boolean isBlackWhiteListEnabled();
|
||||
|
||||
public static native boolean isDynamicModulesEnabled();
|
||||
|
||||
public static native boolean isAppNeedHook(String appDataDir);
|
||||
|
||||
// prevent from fatal error caused by holding not whitelisted file descriptors when forking zygote
|
||||
// https://github.com/rovo89/Xposed/commit/b3ba245ad04cd485699fb1d2ebde7117e58214ff
|
||||
public static native void closeFilesBeforeForkNative();
|
||||
|
||||
public static native void reopenFilesAfterForkNative();
|
||||
|
||||
public static native void deoptMethodNative(Object object);
|
||||
|
||||
public static native long suspendAllThreads();
|
||||
|
||||
public static native void resumeAllThreads(long obj);
|
||||
|
||||
public static native int waitForGcToComplete(long thread);
|
||||
|
||||
public static native boolean initXResourcesNative();
|
||||
|
||||
public static native boolean removeFinalFlagNative(Class clazz);
|
||||
|
||||
public static native boolean isResourcesHookEnabled();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.config;
|
||||
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.config.EdXpConfig;
|
||||
import com.elderdrivers.riru.edxp.config.InstallerChooser;
|
||||
import com.elderdrivers.riru.edxp.hooker.XposedBlackListHooker;
|
||||
|
|
@ -18,11 +19,11 @@ public class SandHookEdxpConfig implements EdXpConfig {
|
|||
|
||||
@Override
|
||||
public boolean isDynamicModulesMode() {
|
||||
return Main.isDynamicModulesEnabled();
|
||||
return ConfigManager.isDynamicModulesEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourcesHookEnabled() {
|
||||
return Main.isResourcesHookEnabled();
|
||||
return ConfigManager.isResourcesHookEnabled();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ package com.elderdrivers.riru.edxp.sandhook.config;
|
|||
import android.util.Log;
|
||||
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.art.ClassLinker;
|
||||
import com.elderdrivers.riru.edxp.config.BaseHookProvider;
|
||||
import com.elderdrivers.riru.edxp.core.ResourcesHook;
|
||||
import com.elderdrivers.riru.edxp.core.Yahfa;
|
||||
import com.elderdrivers.riru.edxp.sandhook.dexmaker.DynamicBridge;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge;
|
||||
|
|
@ -46,12 +49,12 @@ public class SandHookProvider extends BaseHookProvider {
|
|||
|
||||
@Override
|
||||
public Object findMethodNative(Class clazz, String methodName, String methodSig) {
|
||||
return Main.findMethodNative(clazz, methodName, methodSig);
|
||||
return Yahfa.findMethodNative(clazz, methodName, methodSig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deoptMethodNative(Object method) {
|
||||
Main.deoptMethodNative(method);
|
||||
ClassLinker.setEntryPointsToInterpreter((Member) method);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -61,11 +64,11 @@ public class SandHookProvider extends BaseHookProvider {
|
|||
|
||||
@Override
|
||||
public boolean initXResourcesNative() {
|
||||
return Main.initXResourcesNative();
|
||||
return ResourcesHook.initXResourcesNative();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeFinalFlagNative(Class clazz) {
|
||||
return Main.removeFinalFlagNative(clazz);
|
||||
return ResourcesHook.removeFinalFlagNative(clazz);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.elderdrivers.riru.edxp.sandhook.core;
|
||||
|
||||
import com.elderdrivers.riru.edxp.art.Heap;
|
||||
import com.elderdrivers.riru.edxp.core.Yahfa;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.sandhook._hooker.OnePlusWorkAroundHooker;
|
||||
|
|
@ -14,9 +16,6 @@ import java.util.Set;
|
|||
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.Main.backupAndHookNative;
|
||||
import static com.elderdrivers.riru.edxp.Main.findMethodNative;
|
||||
|
||||
public class HookMain {
|
||||
|
||||
private static Set<String> hookItemWhiteList = Collections.singleton(OnePlusWorkAroundHooker.class.getName());
|
||||
|
|
@ -116,13 +115,13 @@ public class HookMain {
|
|||
}
|
||||
// make sure GC completed before hook
|
||||
Thread currentThread = Thread.currentThread();
|
||||
int lastGcType = Main.waitForGcToComplete(
|
||||
int lastGcType = Heap.waitForGcToComplete(
|
||||
XposedHelpers.getLongField(currentThread, "nativePeer"));
|
||||
if (lastGcType < 0) {
|
||||
Utils.logW("waitForGcToComplete failed, using fallback");
|
||||
Runtime.getRuntime().gc();
|
||||
}
|
||||
if (!backupAndHookNative(target, hook, backup)) {
|
||||
if (!Yahfa.backupAndHookNative(target, hook, backup)) {
|
||||
throw new RuntimeException("Failed to hook " + target + " with " + hook);
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +136,7 @@ public class HookMain {
|
|||
if (methodSig == null) {
|
||||
throw new IllegalArgumentException("null method signature");
|
||||
}
|
||||
return findMethodNative(cls, methodName, methodSig);
|
||||
return Yahfa.findMethodNative(cls, methodName, methodSig);
|
||||
}
|
||||
|
||||
private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.elderdrivers.riru.edxp.sandhook.core;
|
|||
|
||||
import android.os.Build;
|
||||
|
||||
import com.elderdrivers.riru.edxp.core.Yahfa;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
|
||||
|
|
@ -122,7 +123,7 @@ public class HookMethodResolver {
|
|||
}
|
||||
|
||||
private static void resolveInNative(Method hook, Method backup) {
|
||||
Main.ensureMethodCached(hook, backup);
|
||||
Yahfa.ensureMethodCached(hook, backup);
|
||||
}
|
||||
|
||||
public static Field getField(Class topClass, String fieldName) throws NoSuchFieldException {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.os.Build;
|
|||
import android.text.TextUtils;
|
||||
|
||||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.core.Yahfa;
|
||||
import com.elderdrivers.riru.edxp.sandhook.core.HookMain;
|
||||
|
||||
import java.io.File;
|
||||
|
|
@ -219,7 +220,7 @@ public class HookerDexMaker {
|
|||
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
|
||||
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
|
||||
Main.setMethodNonCompilable(mCallBackupMethod);
|
||||
Yahfa.setMethodNonCompilable(mCallBackupMethod);
|
||||
HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import android.text.TextUtils;
|
|||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter;
|
||||
import com.elderdrivers.riru.edxp.framework.Zygote;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
import com.elderdrivers.riru.edxp.util.ProcessUtils;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.Main.isAppNeedHook;
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||
|
||||
/**
|
||||
|
|
@ -40,7 +40,7 @@ public class BlackWhiteListProxy {
|
|||
String niceName, int[] fdsToClose, int[] fdsToIgnore,
|
||||
boolean startChildZygote, String instructionSet,
|
||||
String appDataDir) {
|
||||
final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled();
|
||||
final boolean isDynamicModulesMode = ConfigManager.isDynamicModulesEnabled();
|
||||
if (isDynamicModulesMode) {
|
||||
// should never happen
|
||||
return;
|
||||
|
|
@ -56,7 +56,7 @@ public class BlackWhiteListProxy {
|
|||
public static void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags,
|
||||
int[][] rlimits, long permittedCapabilities,
|
||||
long effectiveCapabilities) {
|
||||
final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled();
|
||||
final boolean isDynamicModulesMode = ConfigManager.isDynamicModulesEnabled();
|
||||
if (isDynamicModulesMode) {
|
||||
// should never happen
|
||||
return;
|
||||
|
|
@ -75,7 +75,6 @@ public class BlackWhiteListProxy {
|
|||
private static void onForkPreForNonDynamicMode(boolean isSystemServer) {
|
||||
Router.onForkStart();
|
||||
Router.initResourcesHook();
|
||||
ConfigManager.setDynamicModulesMode(false);
|
||||
// set startsSystemServer flag used when loadModules
|
||||
Router.prepare(isSystemServer);
|
||||
// deoptBootMethods once for all child processes of zygote
|
||||
|
|
@ -86,16 +85,15 @@ public class BlackWhiteListProxy {
|
|||
// loadModules once for all child processes of zygote
|
||||
// TODO maybe just save initZygote callbacks and call them when whitelisted process forked?
|
||||
Router.loadModulesSafely(true);
|
||||
Main.closeFilesBeforeForkNative();
|
||||
Zygote.closeFilesBeforeFork();
|
||||
}
|
||||
|
||||
private static void onForkPostCommon(boolean isSystemServer, String appDataDir, String niceName) {
|
||||
Main.setAppDataDir(appDataDir);
|
||||
Main.niceName = niceName;
|
||||
final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled();
|
||||
ConfigManager.setDynamicModulesMode(isDynamicModulesMode);
|
||||
final boolean isDynamicModulesMode = ConfigManager.isDynamicModulesEnabled();
|
||||
if (!isDynamicModulesMode) {
|
||||
Main.reopenFilesAfterForkNative();
|
||||
Zygote.reopenFilesAfterFork();
|
||||
}
|
||||
Router.onEnterChildProcess();
|
||||
if (!checkNeedHook(appDataDir, niceName)) {
|
||||
|
|
@ -123,7 +121,7 @@ public class BlackWhiteListProxy {
|
|||
needHook = false;
|
||||
} else {
|
||||
// FIXME some process cannot read app_data_file because of MLS, e.g. bluetooth
|
||||
needHook = isAppNeedHook(appDataDir);
|
||||
needHook = ConfigManager.isAppNeedHook(appDataDir);
|
||||
}
|
||||
if (!needHook) {
|
||||
// clean up the scene
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.elderdrivers.riru.edxp.sandhook.proxy;
|
|||
import com.elderdrivers.riru.edxp.Main;
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter;
|
||||
import com.elderdrivers.riru.edxp.framework.Zygote;
|
||||
import com.elderdrivers.riru.edxp.sandhook.entry.Router;
|
||||
|
||||
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix;
|
||||
|
|
@ -17,8 +18,6 @@ public class NormalProxy {
|
|||
// mainly for secondary zygote
|
||||
Router.onForkStart();
|
||||
Router.initResourcesHook();
|
||||
final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled();
|
||||
ConfigManager.setDynamicModulesMode(isDynamicModulesMode);
|
||||
// call this to ensure the flag is set to false ASAP
|
||||
Router.prepare(false);
|
||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
||||
|
|
@ -26,7 +25,7 @@ public class NormalProxy {
|
|||
Router.installBootstrapHooks(false);
|
||||
// only load modules for secondary zygote
|
||||
Router.loadModulesSafely(true);
|
||||
Main.closeFilesBeforeForkNative();
|
||||
Zygote.closeFilesBeforeFork();
|
||||
}
|
||||
|
||||
public static void forkAndSpecializePost(int pid, String appDataDir, String niceName) {
|
||||
|
|
@ -34,7 +33,7 @@ public class NormalProxy {
|
|||
Main.setAppDataDir(appDataDir);
|
||||
Main.niceName = niceName;
|
||||
Router.prepare(false);
|
||||
Main.reopenFilesAfterForkNative();
|
||||
Zygote.reopenFilesAfterFork();
|
||||
Router.onEnterChildProcess();
|
||||
// load modules for each app process on its forked if dynamic modules mode is on
|
||||
Router.loadModulesSafely(false);
|
||||
|
|
@ -45,8 +44,6 @@ public class NormalProxy {
|
|||
long permittedCapabilities, long effectiveCapabilities) {
|
||||
Router.onForkStart();
|
||||
Router.initResourcesHook();
|
||||
final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled();
|
||||
ConfigManager.setDynamicModulesMode(isDynamicModulesMode);
|
||||
// set startsSystemServer flag used when loadModules
|
||||
Router.prepare(true);
|
||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for main zygote
|
||||
|
|
@ -58,7 +55,7 @@ public class NormalProxy {
|
|||
// because if not global hooks installed in initZygote might not be
|
||||
// propagated to processes not forked via forkAndSpecialize
|
||||
Router.loadModulesSafely(true);
|
||||
Main.closeFilesBeforeForkNative();
|
||||
Zygote.closeFilesBeforeFork();
|
||||
}
|
||||
|
||||
public static void forkSystemServerPost(int pid) {
|
||||
|
|
@ -66,7 +63,7 @@ public class NormalProxy {
|
|||
Main.setAppDataDir(getDataPathPrefix() + "android");
|
||||
Main.niceName = "system_server";
|
||||
Router.prepare(true);
|
||||
Main.reopenFilesAfterForkNative();
|
||||
Zygote.reopenFilesAfterFork();
|
||||
Router.onEnterChildProcess();
|
||||
// reload module list if dynamic mode is on
|
||||
Router.loadModulesSafely(false);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
sourceCompatibility = "7"
|
||||
targetCompatibility = "7"
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
|
|
|
|||
|
|
@ -30,4 +30,8 @@
|
|||
-keep class * implements com.elderdrivers.riru.common.KeepAll { *; }
|
||||
-keepclassmembers class * implements com.elderdrivers.riru.common.KeepMembers { *; }
|
||||
|
||||
-keep class com.lody.** {*;}
|
||||
-keep class com.lody.** {*;}
|
||||
|
||||
-keepclasseswithmember class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
|
@ -5,14 +5,15 @@ import android.os.Build;
|
|||
import android.os.Process;
|
||||
|
||||
import com.elderdrivers.riru.common.KeepAll;
|
||||
import com.elderdrivers.riru.edxp.config.ConfigManager;
|
||||
import com.elderdrivers.riru.edxp.config.InstallerChooser;
|
||||
import com.elderdrivers.riru.edxp.core.Yahfa;
|
||||
import com.elderdrivers.riru.edxp.util.Utils;
|
||||
import com.elderdrivers.riru.edxp.whale.core.HookMethodResolver;
|
||||
import com.elderdrivers.riru.edxp.whale.entry.Router;
|
||||
import com.elderdrivers.riru.edxp.whale.proxy.BlackWhiteListProxy;
|
||||
import com.elderdrivers.riru.edxp.whale.proxy.NormalProxy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
|
|
@ -25,10 +26,10 @@ public class Main implements KeepAll {
|
|||
private static String forkSystemServerPramsStr = "";
|
||||
|
||||
static {
|
||||
init(Build.VERSION.SDK_INT);
|
||||
Yahfa.init(Build.VERSION.SDK_INT);
|
||||
HookMethodResolver.init();
|
||||
Router.injectConfig();
|
||||
InstallerChooser.setInstallerPackageName(getInstallerPkgName());
|
||||
InstallerChooser.setInstallerPackageName(ConfigManager.getInstallerPackageName());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -47,7 +48,7 @@ public class Main implements KeepAll {
|
|||
mountExternal, seInfo, niceName, Arrays.toString(fdsToClose),
|
||||
Arrays.toString(fdsToIgnore), startChildZygote, instructionSet, appDataDir);
|
||||
}
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkAndSpecializePre(uid, gid, gids, debugFlags, rlimits,
|
||||
mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, startChildZygote,
|
||||
instructionSet, appDataDir);
|
||||
|
|
@ -61,7 +62,7 @@ public class Main implements KeepAll {
|
|||
public static void forkAndSpecializePost(int pid, String appDataDir, String niceName) {
|
||||
if (pid == 0) {
|
||||
Utils.logD(forkAndSpecializePramsStr + " = " + Process.myPid());
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkAndSpecializePost(pid, appDataDir, niceName);
|
||||
} else {
|
||||
NormalProxy.forkAndSpecializePost(pid, appDataDir, niceName);
|
||||
|
|
@ -79,7 +80,7 @@ public class Main implements KeepAll {
|
|||
uid, gid, Arrays.toString(gids), debugFlags, Arrays.toString(rlimits),
|
||||
permittedCapabilities, effectiveCapabilities);
|
||||
}
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkSystemServerPre(uid, gid, gids, debugFlags, rlimits,
|
||||
permittedCapabilities, effectiveCapabilities);
|
||||
} else {
|
||||
|
|
@ -91,7 +92,7 @@ public class Main implements KeepAll {
|
|||
public static void forkSystemServerPost(int pid) {
|
||||
if (pid == 0) {
|
||||
Utils.logD(forkSystemServerPramsStr + " = " + Process.myPid());
|
||||
if (isBlackWhiteListEnabled()) {
|
||||
if (ConfigManager.isBlackWhiteListEnabled()) {
|
||||
BlackWhiteListProxy.forkSystemServerPost(pid);
|
||||
} else {
|
||||
NormalProxy.forkSystemServerPost(pid);
|
||||
|
|
@ -102,46 +103,4 @@ public class Main implements KeepAll {
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// native methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static native boolean backupAndHookNative(Object target, Method hook, Method backup);
|
||||
|
||||
public static native void setMethodNonCompilable(Object member);
|
||||
|
||||
public static native void ensureMethodCached(Method hook, Method backup);
|
||||
|
||||
// JNI.ToReflectedMethod() could return either Method or Constructor
|
||||
public static native Object findMethodNative(Class targetClass, String methodName, String methodSig);
|
||||
|
||||
private static native void init(int SDK_version);
|
||||
|
||||
public static native String getInstallerPkgName();
|
||||
|
||||
public static native boolean isBlackWhiteListEnabled();
|
||||
|
||||
public static native boolean isDynamicModulesEnabled();
|
||||
|
||||
public static native boolean isAppNeedHook(String appDataDir);
|
||||
|
||||
// prevent from fatal error caused by holding not whitelisted file descriptors when forking zygote
|
||||
// https://github.com/rovo89/Xposed/commit/b3ba245ad04cd485699fb1d2ebde7117e58214ff
|
||||
public static native void closeFilesBeforeForkNative();
|
||||
|
||||
public static native void reopenFilesAfterForkNative();
|
||||
|
||||
public static native void deoptMethodNative(Object object);
|
||||
|
||||
public static native long suspendAllThreads();
|
||||
|
||||
public static native void resumeAllThreads(long obj);
|
||||
|
||||
public static native int waitForGcToComplete(long thread);
|
||||
|
||||
public static native boolean initXResourcesNative();
|
||||
|
||||
public static native boolean removeFinalFlagNative(Class clazz);
|
||||
|
||||
public static native boolean isResourcesHookEnabled();
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue