diff options
-rw-r--r-- | adb/daemon/set_verity_enable_state_service.cpp | 13 | ||||
-rwxr-xr-x | bootstat/boot_reason_test.sh | 1 | ||||
-rw-r--r-- | bootstat/bootstat.cpp | 56 | ||||
-rw-r--r-- | init/reboot.cpp | 14 | ||||
-rw-r--r-- | libmemunreachable/Android.bp | 1 | ||||
-rw-r--r-- | libunwindstack/ElfInterfaceArm.cpp | 13 | ||||
-rw-r--r-- | libunwindstack/ElfInterfaceArm.h | 3 | ||||
-rw-r--r-- | libunwindstack/tests/UnwindOfflineTest.cpp | 4 | ||||
-rw-r--r-- | libunwindstack/tools/unwind_info.cpp | 3 | ||||
-rw-r--r-- | libutils/Android.bp | 1 | ||||
-rw-r--r-- | lmkd/Android.bp | 15 | ||||
-rw-r--r-- | lmkd/include/liblmkd_utils.h | 54 | ||||
-rw-r--r-- | lmkd/include/lmkd.h | 147 | ||||
-rw-r--r-- | lmkd/liblmkd_utils.c | 76 | ||||
-rw-r--r-- | lmkd/lmkd.c | 291 | ||||
-rw-r--r-- | lmkd/tests/Android.bp | 40 | ||||
-rw-r--r-- | lmkd/tests/lmkd_test.cpp | 368 | ||||
-rw-r--r-- | rootdir/etc/ld.config.txt | 6 | ||||
-rw-r--r-- | rootdir/init.rc | 3 |
19 files changed, 963 insertions, 146 deletions
diff --git a/adb/daemon/set_verity_enable_state_service.cpp b/adb/daemon/set_verity_enable_state_service.cpp index 49e0363a1..0fcf89b7c 100644 --- a/adb/daemon/set_verity_enable_state_service.cpp +++ b/adb/daemon/set_verity_enable_state_service.cpp @@ -98,13 +98,22 @@ static std::string get_ab_suffix() { return android::base::GetProperty("ro.boot.slot_suffix", ""); } +static bool is_avb_device_locked() { + return android::base::GetProperty("ro.boot.vbmeta.device_state", "") == "locked"; +} + /* Use AVB to turn verity on/off */ static bool set_avb_verity_enabled_state(int fd, AvbOps* ops, bool enable_verity) { std::string ab_suffix = get_ab_suffix(); - bool verity_enabled; + + if (is_avb_device_locked()) { + WriteFdFmt(fd, "Device is locked. Please unlock the device first\n"); + return false; + } + if (!avb_user_verity_get(ops, ab_suffix.c_str(), &verity_enabled)) { - WriteFdFmt(fd, "Error getting verity state\n"); + WriteFdFmt(fd, "Error getting verity state. Try adb root first?\n"); return false; } diff --git a/bootstat/boot_reason_test.sh b/bootstat/boot_reason_test.sh index 79702a6c0..f5d789cb6 100755 --- a/bootstat/boot_reason_test.sh +++ b/bootstat/boot_reason_test.sh @@ -287,6 +287,7 @@ bootstat: Failed to parse boot time record: /data/misc/bootstat/post_decrypt_tim bootstat: Service started: /system/bin/bootstat --record_boot_reason bootstat: Service started: /system/bin/bootstat --record_time_since_factory_reset bootstat: Service started: /system/bin/bootstat -l +bootstat: Service started: /system/bin/bootstat --set_system_boot_reason --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l bootstat: Battery level at shutdown 100% bootstat: Battery level at startup 100% init : Parsing file /system/etc/init/bootstat.rc... diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp index a1fe6ed4b..4ba430ce3 100644 --- a/bootstat/bootstat.cpp +++ b/bootstat/bootstat.cpp @@ -30,6 +30,7 @@ #include <map> #include <memory> #include <string> +#include <utility> #include <vector> #include <android-base/chrono_utils.h> @@ -138,7 +139,7 @@ constexpr int32_t kUnknownBootReason = 1; // values. const std::map<std::string, int32_t> kBootReasonMap = { {"empty", kEmptyBootReason}, - {"unknown", kUnknownBootReason}, + {"__BOOTSTAT_UNKNOWN__", kUnknownBootReason}, {"normal", 2}, {"recovery", 3}, {"reboot", 4}, @@ -191,12 +192,14 @@ const std::map<std::string, int32_t> kBootReasonMap = { {"s3_wakeup", 51}, {"kernel_panic,sysrq", 52}, {"kernel_panic,NULL", 53}, + {"kernel_panic,null", 53}, {"kernel_panic,BUG", 54}, + {"kernel_panic,bug", 54}, {"bootloader", 55}, {"cold", 56}, {"hard", 57}, {"warm", 58}, - {"recovery", 59}, + // {"recovery", 59}, // Duplicate of enum 3 above. Immediate reuse possible. {"thermal-shutdown", 60}, {"shutdown,thermal", 61}, {"shutdown,battery", 62}, @@ -227,7 +230,7 @@ const std::map<std::string, int32_t> kBootReasonMap = { {"shutdown,thermal,battery", 87}, {"reboot,its_just_so_hard", 88}, // produced by boot_reason_test {"reboot,Its Just So Hard", 89}, // produced by boot_reason_test - {"usb", 90}, + // {"usb", 90}, // Duplicate of enum 80 above. Immediate reuse possible. {"charge", 91}, {"oem_tz_crash", 92}, {"uvlo", 93}, @@ -465,7 +468,7 @@ class pstoreConsole { // If bit error match to needle, correct it. // Return true if any corrections were discovered and applied. -bool correctForBer(std::string& reason, const std::string& needle) { +bool correctForBitError(std::string& reason, const std::string& needle) { bool corrected = false; if (reason.length() < needle.length()) return corrected; const pstoreConsole console(reason); @@ -483,20 +486,35 @@ bool correctForBer(std::string& reason, const std::string& needle) { return corrected; } +// If bit error match to needle, correct it. +// Return true if any corrections were discovered and applied. +// Try again if we can replace underline with spaces. +bool correctForBitErrorOrUnderline(std::string& reason, const std::string& needle) { + bool corrected = correctForBitError(reason, needle); + std::string _needle(needle); + std::transform(_needle.begin(), _needle.end(), _needle.begin(), + [](char c) { return (c == '_') ? ' ' : c; }); + if (needle != _needle) { + corrected |= correctForBitError(reason, _needle); + } + return corrected; +} + bool addKernelPanicSubReason(const pstoreConsole& console, std::string& ret) { // Check for kernel panic types to refine information - if (console.rfind("SysRq : Trigger a crash") != std::string::npos) { + if ((console.rfind("SysRq : Trigger a crash") != std::string::npos) || + (console.rfind("PC is at sysrq_handle_crash+") != std::string::npos)) { // Can not happen, except on userdebug, during testing/debugging. ret = "kernel_panic,sysrq"; return true; } if (console.rfind("Unable to handle kernel NULL pointer dereference at virtual address") != std::string::npos) { - ret = "kernel_panic,NULL"; + ret = "kernel_panic,null"; return true; } if (console.rfind("Kernel BUG at ") != std::string::npos) { - ret = "kernel_panic,BUG"; + ret = "kernel_panic,bug"; return true; } return false; @@ -506,22 +524,14 @@ bool addKernelPanicSubReason(const std::string& content, std::string& ret) { return addKernelPanicSubReason(pstoreConsole(content), ret); } -// std::transform Helper callback functions: // Converts a string value representing the reason the system booted to a // string complying with Android system standard reason. -char tounderline(char c) { - return ::isblank(c) ? '_' : c; -} - -char toprintable(char c) { - return ::isprint(c) ? c : '?'; -} - -// Cleanup boot_reason regarding acceptable character set void transformReason(std::string& reason) { std::transform(reason.begin(), reason.end(), reason.begin(), ::tolower); - std::transform(reason.begin(), reason.end(), reason.begin(), tounderline); - std::transform(reason.begin(), reason.end(), reason.begin(), toprintable); + std::transform(reason.begin(), reason.end(), reason.begin(), + [](char c) { return ::isblank(c) ? '_' : c; }); + std::transform(reason.begin(), reason.end(), reason.begin(), + [](char c) { return ::isprint(c) ? c : '?'; }); } const char system_reboot_reason_property[] = "sys.boot.reason"; @@ -628,14 +638,14 @@ std::string BootReasonStrToReason(const std::string& boot_reason) { std::string subReason(content.substr(pos, max_reason_length)); // Correct against any known strings that Bit Error Match for (const auto& s : knownReasons) { - correctForBer(subReason, s); + correctForBitErrorOrUnderline(subReason, s); } for (const auto& m : kBootReasonMap) { if (m.first.length() <= strlen("cold")) continue; // too short? - if (correctForBer(subReason, m.first + "'")) continue; + if (correctForBitErrorOrUnderline(subReason, m.first + "'")) continue; if (m.first.length() <= strlen("reboot,cold")) continue; // short? if (!android::base::StartsWith(m.first, "reboot,")) continue; - correctForBer(subReason, m.first.substr(strlen("reboot,")) + "'"); + correctForBitErrorOrUnderline(subReason, m.first.substr(strlen("reboot,")) + "'"); } for (pos = 0; pos < subReason.length(); ++pos) { char c = subReason[pos]; @@ -683,7 +693,7 @@ std::string BootReasonStrToReason(const std::string& boot_reason) { if (pos != std::string::npos) { digits = content.substr(pos + strlen(battery), strlen("100 ")); // correct common errors - correctForBer(digits, "100 "); + correctForBitError(digits, "100 "); if (digits[0] == '!') digits[0] = '1'; if (digits[1] == '!') digits[1] = '1'; } diff --git a/init/reboot.cpp b/init/reboot.cpp index 03ed55a1b..bb87f1292 100644 --- a/init/reboot.cpp +++ b/init/reboot.cpp @@ -429,10 +429,20 @@ void RebootThread(unsigned int cmd, std::chrono::milliseconds shutdown_timeout, if (kill_after_apps.count(s->name())) s->Stop(); } // 4. sync, try umount, and optionally run fsck for user shutdown - sync(); + { + Timer sync_timer; + LOG(INFO) << "sync() before umount..."; + sync(); + LOG(INFO) << "sync() before umount took" << sync_timer; + } UmountStat stat = TryUmountAndFsck(runFsck, shutdown_timeout - t.duration()); // Follow what linux shutdown is doing: one more sync with little bit delay - sync(); + { + Timer sync_timer; + LOG(INFO) << "sync() after umount..."; + sync(); + LOG(INFO) << "sync() after umount took" << sync_timer; + } if (cmd != ANDROID_RB_THERMOFF) std::this_thread::sleep_for(100ms); LogShutdownTime(stat, &t); diff --git a/libmemunreachable/Android.bp b/libmemunreachable/Android.bp index caca3774e..f872d0fd0 100644 --- a/libmemunreachable/Android.bp +++ b/libmemunreachable/Android.bp @@ -88,7 +88,6 @@ cc_test { cc_test { name: "memunreachable_binder_test", defaults: ["libmemunreachable_defaults"], - test_suites: ["vts"], srcs: [ "tests/Binder_test.cpp", ], diff --git a/libunwindstack/ElfInterfaceArm.cpp b/libunwindstack/ElfInterfaceArm.cpp index dfb8e8f91..a5afc7e5d 100644 --- a/libunwindstack/ElfInterfaceArm.cpp +++ b/libunwindstack/ElfInterfaceArm.cpp @@ -171,4 +171,17 @@ bool ElfInterfaceArm::StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Mem return return_value; } +bool ElfInterfaceArm::GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name, + uint64_t* offset) { + // For ARM, thumb function symbols have bit 0 set, but the address passed + // in here might not have this bit set and result in a failure to find + // the thumb function names. Adjust the address and offset to account + // for this possible case. + if (ElfInterface32::GetFunctionName(addr | 1, load_bias, name, offset)) { + *offset &= ~1; + return true; + } + return false; +} + } // namespace unwindstack diff --git a/libunwindstack/ElfInterfaceArm.h b/libunwindstack/ElfInterfaceArm.h index 9c067ba3a..c1597ce3a 100644 --- a/libunwindstack/ElfInterfaceArm.h +++ b/libunwindstack/ElfInterfaceArm.h @@ -76,6 +76,9 @@ class ElfInterfaceArm : public ElfInterface32 { bool StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory, bool* finished); + bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name, + uint64_t* offset) override; + uint64_t start_offset() { return start_offset_; } size_t total_entries() { return total_entries_; } diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp index af4a5b59e..515bc8cc7 100644 --- a/libunwindstack/tests/UnwindOfflineTest.cpp +++ b/libunwindstack/tests/UnwindOfflineTest.cpp @@ -188,7 +188,7 @@ TEST_F(UnwindOfflineTest, pc_straddle_arm) { std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(4U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; EXPECT_EQ( - " #00 pc 0001a9f8 libc.so (abort+63)\n" + " #00 pc 0001a9f8 libc.so (abort+64)\n" " #01 pc 00006a1b libbase.so (_ZN7android4base14DefaultAborterEPKc+6)\n" " #02 pc 00007441 libbase.so (_ZN7android4base10LogMessageD2Ev+748)\n" " #03 pc 00015147 /does/not/exist/libhidlbase.so\n", @@ -575,7 +575,7 @@ TEST_F(UnwindOfflineTest, jit_debug_arm) { std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(76U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; EXPECT_EQ( - " #00 pc 00018a5e libarttestd.so (Java_Main_unwindInProcess+865)\n" + " #00 pc 00018a5e libarttestd.so (Java_Main_unwindInProcess+866)\n" " #01 pc 0000212d (offset 0x2000) 137-cfi.odex (boolean Main.unwindInProcess(boolean, int, " "boolean)+92)\n" " #02 pc 00011cb1 anonymous:e2796000 (boolean Main.bar(boolean)+72)\n" diff --git a/libunwindstack/tools/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp index a0abccae2..5a8edfdf3 100644 --- a/libunwindstack/tools/unwind_info.cpp +++ b/libunwindstack/tools/unwind_info.cpp @@ -53,8 +53,7 @@ void DumpArm(ElfInterfaceArm* interface) { printf(" PC 0x%" PRIx64, addr + load_bias); uint64_t func_offset; uint64_t pc = addr + load_bias; - // This might be a thumb function, so set the low bit. - if (interface->GetFunctionName(pc | 1, load_bias, &name, &func_offset) && !name.empty()) { + if (interface->GetFunctionName(pc, load_bias, &name, &func_offset) && !name.empty()) { printf(" <%s>", name.c_str()); } printf("\n"); diff --git a/libutils/Android.bp b/libutils/Android.bp index 32caa69bb..0d7925a4a 100644 --- a/libutils/Android.bp +++ b/libutils/Android.bp @@ -159,7 +159,6 @@ cc_library { cc_library { name: "libutilscallstack", defaults: ["libutils_defaults"], - vendor_available: false, srcs: [ "CallStack.cpp", diff --git a/lmkd/Android.bp b/lmkd/Android.bp index 76d308a3c..d172755bd 100644 --- a/lmkd/Android.bp +++ b/lmkd/Android.bp @@ -6,6 +6,7 @@ cc_binary { "liblog", "libcutils", ], + local_include_dirs: ["include"], cflags: ["-Werror"], init_rc: ["lmkd.rc"], @@ -18,3 +19,17 @@ cc_binary { }, }, } + +cc_library_static { + name: "liblmkd_utils", + srcs: ["liblmkd_utils.c"], + shared_libs: [ + "libcutils", + ], + export_include_dirs: ["include"], + cppflags: [ + "-g", + "-Wall", + "-Werror", + ] +} diff --git a/lmkd/include/liblmkd_utils.h b/lmkd/include/liblmkd_utils.h new file mode 100644 index 000000000..72e3f4a2b --- /dev/null +++ b/lmkd/include/liblmkd_utils.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Google, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBLMKD_UTILS_H_ +#define _LIBLMKD_UTILS_H_ + +#include <sys/cdefs.h> +#include <sys/types.h> + +#include <lmkd.h> + +__BEGIN_DECLS + +/* + * Connects to lmkd process and returns socket handle. + * On success returns socket handle. + * On error, -1 is returned, and errno is set appropriately. + */ +int lmkd_connect(); + +/* + * Registers a process with lmkd and sets its oomadj score. + * On success returns 0. + * On error, -1 is returned. + * In the case of error errno is set appropriately. + */ +int lmkd_register_proc(int sock, struct lmk_procprio *params); + +/* + * Creates memcg directory for given process. + * On success returns 0. + * -1 is returned if path creation failed. + * -2 is returned if tasks file open operation failed. + * -3 is returned if tasks file write operation failed. + * In the case of error errno is set appropriately. + */ +int create_memcg(uid_t uid, pid_t pid); + +__END_DECLS + +#endif /* _LIBLMKD_UTILS_H_ */ diff --git a/lmkd/include/lmkd.h b/lmkd/include/lmkd.h new file mode 100644 index 000000000..fe6364d82 --- /dev/null +++ b/lmkd/include/lmkd.h @@ -0,0 +1,147 @@ +/* + * Copyright 2018 Google, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LMKD_H_ +#define _LMKD_H_ + +#include <arpa/inet.h> +#include <sys/cdefs.h> +#include <sys/types.h> + +__BEGIN_DECLS + +/* + * Supported LMKD commands + */ +enum lmk_cmd { + LMK_TARGET = 0, /* Associate minfree with oom_adj_score */ + LMK_PROCPRIO, /* Register a process and set its oom_adj_score */ + LMK_PROCREMOVE, /* Unregister a process */ +}; + +/* + * Max number of targets in LMK_TARGET command. + */ +#define MAX_TARGETS 6 + +/* + * Max packet length in bytes. + * Longest packet is LMK_TARGET followed by MAX_TARGETS + * of minfree and oom_adj_score values + */ +#define CTRL_PACKET_MAX_SIZE (sizeof(int) * (MAX_TARGETS * 2 + 1)) + +/* LMKD packet - first int is lmk_cmd followed by payload */ +typedef int LMKD_CTRL_PACKET[CTRL_PACKET_MAX_SIZE / sizeof(int)]; + +/* Get LMKD packet command */ +inline enum lmk_cmd lmkd_pack_get_cmd(LMKD_CTRL_PACKET pack) { + return (enum lmk_cmd)ntohl(pack[0]); +} + +/* LMK_TARGET packet payload */ +struct lmk_target { + int minfree; + int oom_adj_score; +}; + +/* + * For LMK_TARGET packet get target_idx-th payload. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline void lmkd_pack_get_target(LMKD_CTRL_PACKET packet, + int target_idx, struct lmk_target *target) { + target->minfree = ntohl(packet[target_idx * 2 + 1]); + target->oom_adj_score = ntohl(packet[target_idx * 2 + 2]); +} + +/* + * Prepare LMK_TARGET packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline size_t lmkd_pack_set_target(LMKD_CTRL_PACKET packet, + struct lmk_target *targets, + size_t target_cnt) { + int idx = 0; + packet[idx++] = htonl(LMK_TARGET); + while (target_cnt) { + packet[idx++] = htonl(targets->minfree); + packet[idx++] = htonl(targets->oom_adj_score); + targets++; + target_cnt--; + } + return idx * sizeof(int); +} + +/* LMK_PROCPRIO packet payload */ +struct lmk_procprio { + pid_t pid; + uid_t uid; + int oomadj; +}; + +/* + * For LMK_PROCPRIO packet get its payload. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline void lmkd_pack_get_procprio(LMKD_CTRL_PACKET packet, + struct lmk_procprio *params) { + params->pid = (pid_t)ntohl(packet[1]); + params->uid = (uid_t)ntohl(packet[2]); + params->oomadj = ntohl(packet[3]); +} + +/* + * Prepare LMK_PROCPRIO packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline size_t lmkd_pack_set_procprio(LMKD_CTRL_PACKET packet, + struct lmk_procprio *params) { + packet[0] = htonl(LMK_PROCPRIO); + packet[1] = htonl(params->pid); + packet[2] = htonl(params->uid); + packet[3] = htonl(params->oomadj); + return 4 * sizeof(int); +} + +/* LMK_PROCREMOVE packet payload */ +struct lmk_procremove { + pid_t pid; +}; + +/* + * For LMK_PROCREMOVE packet get its payload. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline void lmkd_pack_get_procremove(LMKD_CTRL_PACKET packet, + struct lmk_procremove *params) { + params->pid = (pid_t)ntohl(packet[1]); +} + +/* + * Prepare LMK_PROCREMOVE packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline size_t lmkd_pack_set_procremove(LMKD_CTRL_PACKET packet, + struct lmk_procprio *params) { + packet[0] = htonl(LMK_PROCREMOVE); + packet[1] = htonl(params->pid); + return 2 * sizeof(int); +} + +__END_DECLS + +#endif /* _LMKD_H_ */ diff --git a/lmkd/liblmkd_utils.c b/lmkd/liblmkd_utils.c new file mode 100644 index 000000000..fa3b7a920 --- /dev/null +++ b/lmkd/liblmkd_utils.c @@ -0,0 +1,76 @@ +/* + * Copyright 2018 Google, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <sys/cdefs.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> + +#include <liblmkd_utils.h> +#include <cutils/sockets.h> + +int lmkd_connect() { + return socket_local_client("lmkd", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_SEQPACKET); +} + +int lmkd_register_proc(int sock, struct lmk_procprio *params) { + LMKD_CTRL_PACKET packet; + size_t size; + int ret; + + size = lmkd_pack_set_procprio(packet, params); + ret = TEMP_FAILURE_RETRY(write(sock, packet, size)); + + return (ret < 0) ? -1 : 0; +} + +int create_memcg(uid_t uid, pid_t pid) { + char buf[256]; + int tasks_file; + int written; + + snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u", uid); + if (mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && + errno != EEXIST) { + return -1; + } + + snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u/pid_%u", uid, pid); + if (mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && + errno != EEXIST) { + return -1; + } + + snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u/pid_%u/tasks", uid, pid); + tasks_file = open(buf, O_WRONLY); + if (tasks_file < 0) { + return -2; + } + written = snprintf(buf, sizeof(buf), "%u", pid); + if (__predict_false(written >= (int)sizeof(buf))) { + written = sizeof(buf) - 1; + } + written = TEMP_FAILURE_RETRY(write(tasks_file, buf, written)); + close(tasks_file); + + return (written < 0) ? -3 : 0; +} + diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c index 338e5fa20..45fa863b2 100644 --- a/lmkd/lmkd.c +++ b/lmkd/lmkd.c @@ -16,7 +16,6 @@ #define LOG_TAG "lowmemorykiller" -#include <arpa/inet.h> #include <errno.h> #include <inttypes.h> #include <sched.h> @@ -34,6 +33,7 @@ #include <cutils/properties.h> #include <cutils/sockets.h> +#include <lmkd.h> #include <log/log.h> /* @@ -71,19 +71,6 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) #define EIGHT_MEGA (1 << 23) -enum lmk_cmd { - LMK_TARGET, - LMK_PROCPRIO, - LMK_PROCREMOVE, -}; - -#define MAX_TARGETS 6 -/* - * longest is LMK_TARGET followed by MAX_TARGETS each minfree and minkillprio - * values - */ -#define CTRL_PACKET_MAX (sizeof(int) * (MAX_TARGETS * 2 + 1)) - /* default to old in-kernel interface if no memory pressure events */ static int use_inkernel_interface = 1; static bool has_inkernel_module; @@ -122,13 +109,30 @@ static bool is_go_device; static bool kill_heaviest_task; static unsigned long kill_timeout_ms; -/* control socket listen and data */ -static int ctrl_lfd; -static int ctrl_dfd = -1; -static int ctrl_dfd_reopened; /* did we reopen ctrl conn on this loop? */ +/* data required to handle events */ +struct event_handler_info { + int data; + void (*handler)(int data, uint32_t events); +}; + +/* data required to handle socket events */ +struct sock_event_handler_info { + int sock; + struct event_handler_info handler_info; +}; + +/* max supported number of data connections */ +#define MAX_DATA_CONN 2 -/* 3 memory pressure levels, 1 ctrl listen socket, 1 ctrl data socket */ -#define MAX_EPOLL_EVENTS 5 +/* socket event handler data */ +static struct sock_event_handler_info ctrl_sock; +static struct sock_event_handler_info data_sock[MAX_DATA_CONN]; + +/* vmpressure event handler data */ +static struct event_handler_info vmpressure_hinfo[VMPRESS_LEVEL_COUNT]; + +/* 3 memory pressure levels, 1 ctrl listen socket, 2 ctrl data socket */ +#define MAX_EPOLL_EVENTS (1 + MAX_DATA_CONN + VMPRESS_LEVEL_COUNT) static int epollfd; static int maxevents; @@ -283,45 +287,49 @@ static void writefilestring(const char *path, char *s) { close(fd); } -static void cmd_procprio(int pid, int uid, int oomadj) { +static void cmd_procprio(LMKD_CTRL_PACKET packet) { struct proc *procp; char path[80]; char val[20]; int soft_limit_mult; + struct lmk_procprio params; + + lmkd_pack_get_procprio(packet, ¶ms); - if (oomadj < OOM_SCORE_ADJ_MIN || oomadj > OOM_SCORE_ADJ_MAX) { - ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj); + if (params.oomadj < OOM_SCORE_ADJ_MIN || + params.oomadj > OOM_SCORE_ADJ_MAX) { + ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj); return; } - snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid); - snprintf(val, sizeof(val), "%d", oomadj); + snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid); + snprintf(val, sizeof(val), "%d", params.oomadj); writefilestring(path, val); if (use_inkernel_interface) return; - if (oomadj >= 900) { + if (params.oomadj >= 900) { soft_limit_mult = 0; - } else if (oomadj >= 800) { + } else if (params.oomadj >= 800) { soft_limit_mult = 0; - } else if (oomadj >= 700) { + } else if (params.oomadj >= 700) { soft_limit_mult = 0; - } else if (oomadj >= 600) { + } else if (params.oomadj >= 600) { // Launcher should be perceptible, don't kill it. - oomadj = 200; + params.oomadj = 200; soft_limit_mult = 1; - } else if (oomadj >= 500) { + } else if (params.oomadj >= 500) { soft_limit_mult = 0; - } else if (oomadj >= 400) { + } else if (params.oomadj >= 400) { soft_limit_mult = 0; - } else if (oomadj >= 300) { + } else if (params.oomadj >= 300) { soft_limit_mult = 1; - } else if (oomadj >= 200) { + } else if (params.oomadj >= 200) { soft_limit_mult = 2; - } else if (oomadj >= 100) { + } else if (params.oomadj >= 100) { soft_limit_mult = 10; - } else if (oomadj >= 0) { + } else if (params.oomadj >= 0) { soft_limit_mult = 20; } else { // Persistent processes will have a large @@ -329,11 +337,13 @@ static void cmd_procprio(int pid, int uid, int oomadj) { soft_limit_mult = 64; } - snprintf(path, sizeof(path), "/dev/memcg/apps/uid_%d/pid_%d/memory.soft_limit_in_bytes", uid, pid); + snprintf(path, sizeof(path), + "/dev/memcg/apps/uid_%d/pid_%d/memory.soft_limit_in_bytes", + params.uid, params.pid); snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA); writefilestring(path, val); - procp = pid_lookup(pid); + procp = pid_lookup(params.pid); if (!procp) { procp = malloc(sizeof(struct proc)); if (!procp) { @@ -341,33 +351,38 @@ static void cmd_procprio(int pid, int uid, int oomadj) { return; } - procp->pid = pid; - procp->uid = uid; - procp->oomadj = oomadj; + procp->pid = params.pid; + procp->uid = params.uid; + procp->oomadj = params.oomadj; proc_insert(procp); } else { proc_unslot(procp); - procp->oomadj = oomadj; + procp->oomadj = params.oomadj; proc_slot(procp); } } -static void cmd_procremove(int pid) { +static void cmd_procremove(LMKD_CTRL_PACKET packet) { + struct lmk_procremove params; + if (use_inkernel_interface) return; - pid_remove(pid); + lmkd_pack_get_procremove(packet, ¶ms); + pid_remove(params.pid); } -static void cmd_target(int ntargets, int *params) { +static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) { int i; + struct lmk_target target; if (ntargets > (int)ARRAY_SIZE(lowmem_adj)) return; for (i = 0; i < ntargets; i++) { - lowmem_minfree[i] = ntohl(*params++); - lowmem_adj[i] = ntohl(*params++); + lmkd_pack_get_target(packet, i, &target); + lowmem_minfree[i] = target.minfree; + lowmem_adj[i] = target.oom_adj_score; } lowmem_targets_size = ntargets; @@ -398,17 +413,24 @@ static void cmd_target(int ntargets, int *params) { } } -static void ctrl_data_close(void) { - ALOGI("Closing Activity Manager data connection"); - close(ctrl_dfd); - ctrl_dfd = -1; +static void ctrl_data_close(int dsock_idx) { + struct epoll_event epev; + + ALOGI("closing lmkd data connection"); + if (epoll_ctl(epollfd, EPOLL_CTL_DEL, data_sock[dsock_idx].sock, &epev) == -1) { + // Log a warning and keep going + ALOGW("epoll_ctl for data connection socket failed; errno=%d", errno); + } maxevents--; + + close(data_sock[dsock_idx].sock); + data_sock[dsock_idx].sock = -1; } -static int ctrl_data_read(char *buf, size_t bufsz) { +static int ctrl_data_read(int dsock_idx, char *buf, size_t bufsz) { int ret = 0; - ret = read(ctrl_dfd, buf, bufsz); + ret = read(data_sock[dsock_idx].sock, buf, bufsz); if (ret == -1) { ALOGE("control data socket read failed; errno=%d", errno); @@ -420,39 +442,43 @@ static int ctrl_data_read(char *buf, size_t bufsz) { return ret; } -static void ctrl_command_handler(void) { - int ibuf[CTRL_PACKET_MAX / sizeof(int)]; +static void ctrl_command_handler(int dsock_idx) { + LMKD_CTRL_PACKET packet; int len; - int cmd = -1; + enum lmk_cmd cmd; int nargs; int targets; - len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); + len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE); if (len <= 0) return; + if (len < (int)sizeof(int)) { + ALOGE("Wrong control socket read length len=%d", len); + return; + } + + cmd = lmkd_pack_get_cmd(packet); nargs = len / sizeof(int) - 1; if (nargs < 0) goto wronglen; - cmd = ntohl(ibuf[0]); - switch(cmd) { case LMK_TARGET: targets = nargs / 2; if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj)) goto wronglen; - cmd_target(targets, &ibuf[1]); + cmd_target(targets, packet); break; case LMK_PROCPRIO: if (nargs != 3) goto wronglen; - cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3])); + cmd_procprio(packet); break; case LMK_PROCREMOVE: if (nargs != 1) goto wronglen; - cmd_procremove(ntohl(ibuf[1])); + cmd_procremove(packet); break; default: ALOGE("Received unknown command code %d", cmd); @@ -465,40 +491,57 @@ wronglen: ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len); } -static void ctrl_data_handler(uint32_t events) { - if (events & EPOLLHUP) { - ALOGI("ActivityManager disconnected"); - if (!ctrl_dfd_reopened) - ctrl_data_close(); - } else if (events & EPOLLIN) { - ctrl_command_handler(); +static void ctrl_data_handler(int data, uint32_t events) { + if (events & EPOLLIN) { + ctrl_command_handler(data); } } -static void ctrl_connect_handler(uint32_t events __unused) { - struct epoll_event epev; - - if (ctrl_dfd >= 0) { - ctrl_data_close(); - ctrl_dfd_reopened = 1; +static int get_free_dsock() { + for (int i = 0; i < MAX_DATA_CONN; i++) { + if (data_sock[i].sock < 0) { + return i; + } } + return -1; +} - ctrl_dfd = accept(ctrl_lfd, NULL, NULL); +static void ctrl_connect_handler(int data __unused, uint32_t events __unused) { + struct epoll_event epev; + int free_dscock_idx = get_free_dsock(); + + if (free_dscock_idx < 0) { + /* + * Number of data connections exceeded max supported. This should not + * happen but if it does we drop all existing connections and accept + * the new one. This prevents inactive connections from monopolizing + * data socket and if we drop ActivityManager connection it will + * immediately reconnect. + */ + for (int i = 0; i < MAX_DATA_CONN; i++) { + ctrl_data_close(i); + } + free_dscock_idx = 0; + } - if (ctrl_dfd < 0) { + data_sock[free_dscock_idx].sock = accept(ctrl_sock.sock, NULL, NULL); + if (data_sock[free_dscock_idx].sock < 0) { ALOGE("lmkd control socket accept failed; errno=%d", errno); return; } - ALOGI("ActivityManager connected"); - maxevents++; + ALOGI("lmkd data connection established"); + /* use data to store data connection idx */ + data_sock[free_dscock_idx].handler_info.data = free_dscock_idx; + data_sock[free_dscock_idx].handler_info.handler = ctrl_data_handler; epev.events = EPOLLIN; - epev.data.ptr = (void *)ctrl_data_handler; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_dfd, &epev) == -1) { + epev.data.ptr = (void *)&(data_sock[free_dscock_idx].handler_info); + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, data_sock[free_dscock_idx].sock, &epev) == -1) { ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno); - ctrl_data_close(); + ctrl_data_close(free_dscock_idx); return; } + maxevents++; } static int zoneinfo_parse_protection(char *cp) { @@ -802,7 +845,7 @@ static inline unsigned long get_time_diff_ms(struct timeval *from, (to->tv_usec - from->tv_usec) / 1000; } -static void mp_event_common(enum vmpressure_level level) { +static void mp_event_common(int data, uint32_t events __unused) { int ret; unsigned long long evcount; int64_t mem_usage, memsw_usage; @@ -811,6 +854,7 @@ static void mp_event_common(enum vmpressure_level level) { struct mem_size free_mem; static struct timeval last_report_tm; static unsigned long skip_count = 0; + enum vmpressure_level level = (enum vmpressure_level)data; /* * Check all event counters from low to critical @@ -927,26 +971,15 @@ do_kill: } } -static void mp_event_low(uint32_t events __unused) { - mp_event_common(VMPRESS_LEVEL_LOW); -} - -static void mp_event_medium(uint32_t events __unused) { - mp_event_common(VMPRESS_LEVEL_MEDIUM); -} - -static void mp_event_critical(uint32_t events __unused) { - mp_event_common(VMPRESS_LEVEL_CRITICAL); -} - -static bool init_mp_common(void *event_handler, enum vmpressure_level level) { +static bool init_mp_common(enum vmpressure_level level) { int mpfd; int evfd; int evctlfd; char buf[256]; struct epoll_event epev; int ret; - const char *levelstr = level_name[level]; + int level_idx = (int)level; + const char *levelstr = level_name[level_idx]; mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC); if (mpfd < 0) { @@ -972,7 +1005,7 @@ static bool init_mp_common(void *event_handler, enum vmpressure_level level) { goto err; } - ret = write(evctlfd, buf, strlen(buf) + 1); + ret = TEMP_FAILURE_RETRY(write(evctlfd, buf, strlen(buf) + 1)); if (ret == -1) { ALOGE("cgroup.event_control write failed for level %s; errno=%d", levelstr, errno); @@ -980,7 +1013,10 @@ static bool init_mp_common(void *event_handler, enum vmpressure_level level) { } epev.events = EPOLLIN; - epev.data.ptr = event_handler; + /* use data to store event level */ + vmpressure_hinfo[level_idx].data = level_idx; + vmpressure_hinfo[level_idx].handler = mp_event_common; + epev.data.ptr = (void *)&vmpressure_hinfo[level_idx]; ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev); if (ret == -1) { ALOGE("epoll_ctl for level %s failed; errno=%d", levelstr, errno); @@ -1017,21 +1053,27 @@ static int init(void) { return -1; } - ctrl_lfd = android_get_control_socket("lmkd"); - if (ctrl_lfd < 0) { + // mark data connections as not connected + for (int i = 0; i < MAX_DATA_CONN; i++) { + data_sock[i].sock = -1; + } + + ctrl_sock.sock = android_get_control_socket("lmkd"); + if (ctrl_sock.sock < 0) { ALOGE("get lmkd control socket failed"); return -1; } - ret = listen(ctrl_lfd, 1); + ret = listen(ctrl_sock.sock, MAX_DATA_CONN); if (ret < 0) { ALOGE("lmkd control socket listen failed (errno=%d)", errno); return -1; } epev.events = EPOLLIN; - epev.data.ptr = (void *)ctrl_connect_handler; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) { + ctrl_sock.handler_info.handler = ctrl_connect_handler; + epev.data.ptr = (void *)&(ctrl_sock.handler_info); + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) { ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno); return -1; } @@ -1043,10 +1085,9 @@ static int init(void) { if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface"); } else { - if (!init_mp_common((void *)&mp_event_low, VMPRESS_LEVEL_LOW) || - !init_mp_common((void *)&mp_event_medium, VMPRESS_LEVEL_MEDIUM) || - !init_mp_common((void *)&mp_event_critical, - VMPRESS_LEVEL_CRITICAL)) { + if (!init_mp_common(VMPRESS_LEVEL_LOW) || + !init_mp_common(VMPRESS_LEVEL_MEDIUM) || + !init_mp_common(VMPRESS_LEVEL_CRITICAL)) { ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer"); return -1; } @@ -1061,12 +1102,14 @@ static int init(void) { } static void mainloop(void) { + struct event_handler_info* handler_info; + struct epoll_event *evt; + while (1) { struct epoll_event events[maxevents]; int nevents; int i; - ctrl_dfd_reopened = 0; nevents = epoll_wait(epollfd, events, maxevents, -1); if (nevents == -1) { @@ -1076,11 +1119,33 @@ static void mainloop(void) { continue; } - for (i = 0; i < nevents; ++i) { - if (events[i].events & EPOLLERR) + /* + * First pass to see if any data socket connections were dropped. + * Dropped connection should be handled before any other events + * to deallocate data connection and correctly handle cases when + * connection gets dropped and reestablished in the same epoll cycle. + * In such cases it's essential to handle connection closures first. + */ + for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) { + if ((evt->events & EPOLLHUP) && evt->data.ptr) { + ALOGI("lmkd data connection dropped"); + handler_info = (struct event_handler_info*)evt->data.ptr; + ctrl_data_close(handler_info->data); + } + } + + /* Second pass to handle all other events */ + for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) { + if (evt->events & EPOLLERR) ALOGD("EPOLLERR on event #%d", i); - if (events[i].data.ptr) - (*(void (*)(uint32_t))events[i].data.ptr)(events[i].events); + if (evt->events & EPOLLHUP) { + /* This case was handled in the first pass */ + continue; + } + if (evt->data.ptr) { + handler_info = (struct event_handler_info*)evt->data.ptr; + handler_info->handler(handler_info->data, evt->events); + } } } } diff --git a/lmkd/tests/Android.bp b/lmkd/tests/Android.bp new file mode 100644 index 000000000..cbf44e9fc --- /dev/null +++ b/lmkd/tests/Android.bp @@ -0,0 +1,40 @@ +// 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. + +cc_test { + name: "lmkd_unit_test", + + shared_libs: [ + "libbase", + "liblog", + ], + + static_libs: [ + "liblmkd_utils", + ], + + target: { + android: { + srcs: ["lmkd_test.cpp"], + }, + }, + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + + compile_multilib: "first", +} diff --git a/lmkd/tests/lmkd_test.cpp b/lmkd/tests/lmkd_test.cpp new file mode 100644 index 000000000..f17512daf --- /dev/null +++ b/lmkd/tests/lmkd_test.cpp @@ -0,0 +1,368 @@ +/* + * 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 <sstream> +#include <stdio.h> +#include <string.h> +#include <string> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> +#include <gtest/gtest.h> +#include <lmkd.h> +#include <liblmkd_utils.h> +#include <log/log_properties.h> +#include <private/android_filesystem_config.h> + +using namespace android::base; + +#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree" +#define LMKDTEST_RESPAWN_FLAG "LMKDTEST_RESPAWN" + +#define LMKD_LOGCAT_MARKER "lowmemorykiller" +#define LMKD_KILL_MARKER_TEMPLATE LMKD_LOGCAT_MARKER ": Killing '%s'" +#define OOM_MARKER "Out of memory" +#define OOM_KILL_MARKER "Killed process" +#define MIN_LOG_SIZE 100 + +#define ONE_MB (1 << 20) + +/* Test constant parameters */ +#define OOM_ADJ_MAX 1000 +#define OOM_ADJ_MIN 0 +#define OOM_ADJ_STEP 100 +#define STEP_COUNT ((OOM_ADJ_MAX - OOM_ADJ_MIN) / OOM_ADJ_STEP + 1) + +#define ALLOC_STEP (ONE_MB) +#define ALLOC_DELAY 1000 + +/* Utility functions */ +std::string readCommand(const std::string& command) { + FILE* fp = popen(command.c_str(), "r"); + std::string content; + ReadFdToString(fileno(fp), &content); + pclose(fp); + return content; +} + +std::string readLogcat(const std::string& marker) { + std::string content = readCommand("logcat -d -b all"); + size_t pos = content.find(marker); + if (pos == std::string::npos) return ""; + content.erase(0, pos); + return content; +} + +bool writeFile(const std::string& file, const std::string& string) { + if (getuid() == static_cast<unsigned>(AID_ROOT)) { + return WriteStringToFile(string, file); + } + return string == readCommand( + "echo -n '" + string + "' | su root tee " + file + " 2>&1"); +} + +bool writeKmsg(const std::string& marker) { + return writeFile("/dev/kmsg", marker); +} + +std::string getTextAround(const std::string& text, size_t pos, + size_t lines_before, size_t lines_after) { + size_t start_pos = pos; + + // find start position + // move up lines_before number of lines + while (lines_before > 0 && + (start_pos = text.rfind('\n', start_pos)) != std::string::npos) { + lines_before--; + } + // move to the beginning of the line + start_pos = text.rfind('\n', start_pos); + start_pos = (start_pos == std::string::npos) ? 0 : start_pos + 1; + + // find end position + // move down lines_after number of lines + while (lines_after > 0 && + (pos = text.find('\n', pos)) != std::string::npos) { + pos++; + lines_after--; + } + return text.substr(start_pos, (pos == std::string::npos) ? + std::string::npos : pos - start_pos); +} + +bool getExecPath(std::string &path) { + // exec path as utf8z c_str(). + // std::string contains _all_ nul terminated argv[] strings. + return android::base::ReadFileToString("/proc/self/cmdline", &path); +} + +/* Child synchronization primitives */ +#define STATE_INIT 0 +#define STATE_CHILD_READY 1 +#define STATE_PARENT_READY 2 + +struct state_sync { + pthread_mutex_t mutex; + pthread_cond_t condition; + int state; +}; + +struct state_sync * init_state_sync_obj() { + struct state_sync *ssync; + + ssync = (struct state_sync*)mmap(NULL, sizeof(struct state_sync), + PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (ssync == MAP_FAILED) { + return NULL; + } + + pthread_mutexattr_t mattr; + pthread_mutexattr_init(&mattr); + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&ssync->mutex, &mattr); + + pthread_condattr_t cattr; + pthread_condattr_init(&cattr); + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); + pthread_cond_init(&ssync->condition, &cattr); + + ssync->state = STATE_INIT; + return ssync; +} + +void destroy_state_sync_obj(struct state_sync *ssync) { + pthread_cond_destroy(&ssync->condition); + pthread_mutex_destroy(&ssync->mutex); + munmap(ssync, sizeof(struct state_sync)); +} + +void signal_state(struct state_sync *ssync, int state) { + pthread_mutex_lock(&ssync->mutex); + ssync->state = state; + pthread_cond_signal(&ssync->condition); + pthread_mutex_unlock(&ssync->mutex); +} + +void wait_for_state(struct state_sync *ssync, int state) { + pthread_mutex_lock(&ssync->mutex); + while (ssync->state != state) { + pthread_cond_wait(&ssync->condition, &ssync->mutex); + } + pthread_mutex_unlock(&ssync->mutex); +} + +/* Memory allocation and data sharing */ +struct shared_data { + size_t allocated; + bool finished; + size_t total_size; + size_t step_size; + size_t step_delay; + int oomadj; +}; + +volatile void *gptr; +void add_pressure(struct shared_data *data) { + volatile void *ptr; + size_t allocated_size = 0; + + data->finished = false; + while (allocated_size < data->total_size) { + ptr = mmap(NULL, data->step_size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (ptr != MAP_FAILED) { + /* create ptr aliasing to prevent compiler optimizing the access */ + gptr = ptr; + /* make data non-zero */ + memset((void*)ptr, (int)(allocated_size + 1), data->step_size); + allocated_size += data->step_size; + data->allocated = allocated_size; + } + usleep(data->step_delay); + } + data->finished = (allocated_size >= data->total_size); +} + +/* Memory stress test main body */ +void runMemStressTest() { + struct shared_data *data; + struct state_sync *ssync; + int sock; + pid_t pid; + uid_t uid = getuid(); + + ASSERT_FALSE((sock = lmkd_connect()) < 0) + << "Failed to connect to lmkd process, err=" << strerror(errno); + + /* allocate shared memory to communicate params with a child */ + data = (struct shared_data*)mmap(NULL, sizeof(struct shared_data), + PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + ASSERT_FALSE(data == MAP_FAILED) << "Memory allocation failure"; + data->total_size = (size_t)-1; /* allocate until killed */ + data->step_size = ALLOC_STEP; + data->step_delay = ALLOC_DELAY; + + /* allocate state sync object */ + ASSERT_FALSE((ssync = init_state_sync_obj()) == NULL) + << "Memory allocation failure"; + + /* run the test gradually decreasing oomadj */ + data->oomadj = OOM_ADJ_MAX; + while (data->oomadj >= OOM_ADJ_MIN) { + ASSERT_FALSE((pid = fork()) < 0) + << "Failed to spawn a child process, err=" << strerror(errno); + if (pid != 0) { + /* Parent */ + struct lmk_procprio params; + /* wait for child to start and get ready */ + wait_for_state(ssync, STATE_CHILD_READY); + params.pid = pid; + params.uid = uid; + params.oomadj = data->oomadj; + ASSERT_FALSE(lmkd_register_proc(sock, ¶ms) < 0) + << "Failed to communicate with lmkd, err=" << strerror(errno); + // signal the child it can proceed + signal_state(ssync, STATE_PARENT_READY); + waitpid(pid, NULL, 0); + if (data->finished) { + GTEST_LOG_(INFO) << "Child [pid=" << pid << "] allocated " + << data->allocated / ONE_MB << "MB"; + } else { + GTEST_LOG_(INFO) << "Child [pid=" << pid << "] allocated " + << data->allocated / ONE_MB + << "MB before being killed"; + } + data->oomadj -= OOM_ADJ_STEP; + } else { + /* Child */ + pid = getpid(); + GTEST_LOG_(INFO) << "Child [pid=" << pid + << "] is running at oomadj=" + << data->oomadj; + data->allocated = 0; + data->finished = false; + ASSERT_FALSE(create_memcg(uid, pid) != 0) + << "Child [pid=" << pid << "] failed to create a cgroup"; + signal_state(ssync, STATE_CHILD_READY); + wait_for_state(ssync, STATE_PARENT_READY); + add_pressure(data); + /* should not reach here, child should be killed by OOM/LMK */ + FAIL() << "Child [pid=" << pid << "] was not killed"; + break; + } + } + destroy_state_sync_obj(ssync); + munmap(data, sizeof(struct shared_data)); + close(sock); +} + +TEST(lmkd, check_for_oom) { + // test requirements + // userdebug build + if (!__android_log_is_debuggable()) { + GTEST_LOG_(INFO) << "Must be userdebug build, terminating test"; + return; + } + // check if in-kernel LMK driver is present + if (!access(INKERNEL_MINFREE_PATH, W_OK)) { + GTEST_LOG_(INFO) << "Must not have kernel lowmemorykiller driver," + << " terminating test"; + return; + } + + // if respawned test process then run the test and exit (no analysis) + if (getenv(LMKDTEST_RESPAWN_FLAG) != NULL) { + runMemStressTest(); + return; + } + + // Main test process + // mark the beginning of the test + std::string marker = StringPrintf( + "LMKD test start %lu\n", static_cast<unsigned long>(time(nullptr))); + ASSERT_TRUE(writeKmsg(marker)); + + // get executable complete path + std::string test_path; + ASSERT_TRUE(getExecPath(test_path)); + + std::string test_output; + if (getuid() != static_cast<unsigned>(AID_ROOT)) { + // if not root respawn itself as root and capture output + std::string command = StringPrintf( + "%s=true su root %s --gtest_filter=lmkd.check_for_oom 2>&1", + LMKDTEST_RESPAWN_FLAG, test_path.c_str()); + std::string test_output = readCommand(command); + GTEST_LOG_(INFO) << test_output; + } else { + // main test process is root, run the test + runMemStressTest(); + } + + // Analyze results + // capture logcat containind kernel logs + std::string logcat_out = readLogcat(marker); + + // 1. extract LMKD kills from logcat output, count kills + std::stringstream kill_logs; + int hit_count = 0; + size_t pos = 0; + marker = StringPrintf(LMKD_KILL_MARKER_TEMPLATE, test_path.c_str()); + + while (true) { + if ((pos = logcat_out.find(marker, pos)) != std::string::npos) { + kill_logs << getTextAround(logcat_out, pos, 0, 1); + pos += marker.length(); + hit_count++; + } else { + break; + } + } + GTEST_LOG_(INFO) << "====Logged kills====" << std::endl + << kill_logs.str(); + EXPECT_TRUE(hit_count == STEP_COUNT) << "Number of kills " << hit_count + << " is less than expected " + << STEP_COUNT; + + // 2. check kernel logs for OOM kills + pos = logcat_out.find(OOM_MARKER); + bool oom_detected = (pos != std::string::npos); + bool oom_kill_detected = (oom_detected && + logcat_out.find(OOM_KILL_MARKER, pos) != std::string::npos); + + EXPECT_FALSE(oom_kill_detected) << "OOM kill is detected!"; + if (oom_detected || oom_kill_detected) { + // capture logcat with logs around all OOMs + pos = 0; + while ((pos = logcat_out.find(OOM_MARKER, pos)) != std::string::npos) { + GTEST_LOG_(INFO) << "====Logs around OOM====" << std::endl + << getTextAround(logcat_out, pos, + MIN_LOG_SIZE / 2, MIN_LOG_SIZE / 2); + pos += strlen(OOM_MARKER); + } + } + + // output complete logcat with kernel (might get truncated) + GTEST_LOG_(INFO) << "====Complete logcat output====" << std::endl + << logcat_out; +} + diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt index c8d87c8f0..6e46295d5 100644 --- a/rootdir/etc/ld.config.txt +++ b/rootdir/etc/ld.config.txt @@ -54,6 +54,9 @@ namespace.default.permitted.paths += /system/priv-app namespace.default.permitted.paths += /vendor/framework namespace.default.permitted.paths += /vendor/app namespace.default.permitted.paths += /vendor/priv-app +namespace.default.permitted.paths += /odm/framework +namespace.default.permitted.paths += /odm/app +namespace.default.permitted.paths += /odm/priv-app namespace.default.permitted.paths += /oem/app namespace.default.permitted.paths += /product/framework namespace.default.permitted.paths += /product/app @@ -74,6 +77,9 @@ namespace.default.asan.permitted.paths += /system/priv-app namespace.default.asan.permitted.paths += /vendor/framework namespace.default.asan.permitted.paths += /vendor/app namespace.default.asan.permitted.paths += /vendor/priv-app +namespace.default.asan.permitted.paths += /odm/framework +namespace.default.asan.permitted.paths += /odm/app +namespace.default.asan.permitted.paths += /odm/priv-app namespace.default.asan.permitted.paths += /oem/app namespace.default.asan.permitted.paths += /product/framework namespace.default.asan.permitted.paths += /product/app diff --git a/rootdir/init.rc b/rootdir/init.rc index a213ffb39..146257040 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -573,6 +573,9 @@ on boot hostname localhost domainname localdomain + # IPsec SA default expiration length + write /proc/sys/net/core/xfrm_acq_expires 3600 + # Memory management. Basic kernel parameters, and allow the high # level system server to be able to adjust the kernel OOM driver # parameters to match how it is managing things. |