Merge "healthd: Remove access to /sys/class/power_supply/battery/batt_vol and batt_temp"
diff --git a/adb/Android.mk b/adb/Android.mk
index 0eeafb6..e52f0cb 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -11,6 +11,7 @@
adb_target_sanitize :=
ADB_COMMON_CFLAGS := \
+ -frtti \
-Wall -Wextra -Werror \
-Wno-unused-parameter \
-Wno-missing-field-initializers \
diff --git a/adb/adb.h b/adb/adb.h
index 21e5d4b..b5d6bcb 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -136,9 +136,6 @@
int adb_server_main(int is_daemon, const std::string& socket_spec, int ack_reply_fd);
/* initialize a transport object's func pointers and state */
-#if ADB_HOST
-int get_available_local_transport_index();
-#endif
int init_socket_transport(atransport* t, int s, int port, int local);
void init_usb_transport(atransport* t, usb_handle* usb);
diff --git a/adb/services.cpp b/adb/services.cpp
index aff7012..6dc71cf 100644
--- a/adb/services.cpp
+++ b/adb/services.cpp
@@ -407,14 +407,6 @@
return;
}
- // Check if more emulators can be registered. Similar unproblematic
- // race condition as above.
- int candidate_slot = get_available_local_transport_index();
- if (candidate_slot < 0) {
- *response = "Cannot accept more emulators";
- return;
- }
-
// Preconditions met, try to connect to the emulator.
std::string error;
if (!local_connect_arbitrary_ports(console_port, adb_port, &error)) {
diff --git a/adb/transport.cpp b/adb/transport.cpp
index f221785..5acaaec 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -41,6 +41,7 @@
#include "adb.h"
#include "adb_auth.h"
+#include "adb_io.h"
#include "adb_trace.h"
#include "adb_utils.h"
#include "diagnose_usb.h"
@@ -65,6 +66,36 @@
return next++;
}
+bool FdConnection::Read(apacket* packet) {
+ if (!ReadFdExactly(fd_.get(), &packet->msg, sizeof(amessage))) {
+ D("remote local: read terminated (message)");
+ return false;
+ }
+
+ if (!ReadFdExactly(fd_.get(), &packet->data, packet->msg.data_length)) {
+ D("remote local: terminated (data)");
+ return false;
+ }
+
+ return true;
+}
+
+bool FdConnection::Write(apacket* packet) {
+ uint32_t length = packet->msg.data_length;
+
+ if (!WriteFdExactly(fd_.get(), &packet->msg, sizeof(amessage) + length)) {
+ D("remote local: write terminated");
+ return false;
+ }
+
+ return true;
+}
+
+void FdConnection::Close() {
+ adb_shutdown(fd_.get());
+ fd_.reset();
+}
+
static std::string dump_packet(const char* name, const char* func, apacket* p) {
unsigned command = p->msg.command;
int len = p->msg.data_length;
@@ -220,13 +251,21 @@
{
ATRACE_NAME("read_transport read_remote");
- if (t->read_from_remote(p, t) != 0) {
+ if (!t->connection->Read(p)) {
D("%s: remote read failed for transport", t->serial);
put_apacket(p);
break;
}
+
+ if (!check_header(p, t)) {
+ D("%s: remote read: bad header", t->serial);
+ put_apacket(p);
+ break;
+ }
+
#if ADB_HOST
if (p->msg.command == 0) {
+ put_apacket(p);
continue;
}
#endif
@@ -625,7 +664,7 @@
t->ref_count--;
if (t->ref_count == 0) {
D("transport: %s unref (kicking and closing)", t->serial);
- t->close(t);
+ t->connection->Close();
remove_transport(t);
} else {
D("transport: %s unref (count=%zu)", t->serial, t->ref_count);
@@ -753,14 +792,14 @@
}
int atransport::Write(apacket* p) {
- return write_func_(p, this);
+ return this->connection->Write(p) ? 0 : -1;
}
void atransport::Kick() {
if (!kicked_) {
+ D("kicking transport %s", this->serial);
kicked_ = true;
- CHECK(kick_func_ != nullptr);
- kick_func_(this);
+ this->connection->Close();
}
}
@@ -1082,8 +1121,12 @@
// This should only be used for transports with connection_state == kCsNoPerm.
void unregister_usb_transport(usb_handle* usb) {
std::lock_guard<std::recursive_mutex> lock(transport_lock);
- transport_list.remove_if(
- [usb](atransport* t) { return t->usb == usb && t->GetConnectionState() == kCsNoPerm; });
+ transport_list.remove_if([usb](atransport* t) {
+ if (auto connection = dynamic_cast<UsbConnection*>(t->connection.get())) {
+ return connection->handle_ == usb && t->GetConnectionState() == kCsNoPerm;
+ }
+ return false;
+ });
}
bool check_header(apacket* p, atransport* t) {
diff --git a/adb/transport.h b/adb/transport.h
index 86cd992..9700f44 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -28,10 +28,11 @@
#include <string>
#include <unordered_set>
-#include "adb.h"
-
#include <openssl/rsa.h>
+#include "adb.h"
+#include "adb_unique_fd.h"
+
typedef std::unordered_set<std::string> FeatureSet;
const FeatureSet& supported_features();
@@ -56,6 +57,50 @@
TransportId NextTransportId();
+// Abstraction for a blocking packet transport.
+struct Connection {
+ Connection() = default;
+ Connection(const Connection& copy) = delete;
+ Connection(Connection&& move) = delete;
+
+ // Destroy a Connection. Formerly known as 'Close' in atransport.
+ virtual ~Connection() = default;
+
+ // Read/Write a packet. These functions are concurrently called from a transport's reader/writer
+ // threads.
+ virtual bool Read(apacket* packet) = 0;
+ virtual bool Write(apacket* packet) = 0;
+
+ // Terminate a connection.
+ // This method must be thread-safe, and must cause concurrent Reads/Writes to terminate.
+ // Formerly known as 'Kick' in atransport.
+ virtual void Close() = 0;
+};
+
+struct FdConnection : public Connection {
+ explicit FdConnection(unique_fd fd) : fd_(std::move(fd)) {}
+
+ bool Read(apacket* packet) override final;
+ bool Write(apacket* packet) override final;
+
+ void Close() override;
+
+ private:
+ unique_fd fd_;
+};
+
+struct UsbConnection : public Connection {
+ explicit UsbConnection(usb_handle* handle) : handle_(handle) {}
+ ~UsbConnection();
+
+ bool Read(apacket* packet) override final;
+ bool Write(apacket* packet) override final;
+
+ void Close() override final;
+
+ usb_handle* handle_;
+};
+
class atransport {
public:
// TODO(danalbert): We expose waaaaaaay too much stuff because this was
@@ -73,12 +118,6 @@
}
virtual ~atransport() {}
- int (*read_from_remote)(apacket* p, atransport* t) = nullptr;
- void (*close)(atransport* t) = nullptr;
-
- void SetWriteFunction(int (*write_func)(apacket*, atransport*)) { write_func_ = write_func; }
- void SetKickFunction(void (*kick_func)(atransport*)) { kick_func_ = kick_func; }
- bool IsKicked() { return kicked_; }
int Write(apacket* p);
void Kick();
@@ -95,9 +134,7 @@
bool online = false;
TransportType type = kTransportAny;
- // USB handle or socket fd as needed.
- usb_handle* usb = nullptr;
- int sfd = -1;
+ std::unique_ptr<Connection> connection;
// Used to identify transports for clients.
char* serial = nullptr;
@@ -105,22 +142,8 @@
char* model = nullptr;
char* device = nullptr;
char* devpath = nullptr;
- void SetLocalPortForEmulator(int port) {
- CHECK_EQ(local_port_for_emulator_, -1);
- local_port_for_emulator_ = port;
- }
- bool GetLocalPortForEmulator(int* port) const {
- if (type == kTransportLocal && local_port_for_emulator_ != -1) {
- *port = local_port_for_emulator_;
- return true;
- }
- return false;
- }
-
- bool IsTcpDevice() const {
- return type == kTransportLocal && local_port_for_emulator_ == -1;
- }
+ bool IsTcpDevice() const { return type == kTransportLocal; }
#if ADB_HOST
std::shared_ptr<RSA> NextKey();
@@ -165,10 +188,7 @@
bool MatchesTarget(const std::string& target) const;
private:
- int local_port_for_emulator_ = -1;
bool kicked_ = false;
- void (*kick_func_)(atransport*) = nullptr;
- int (*write_func_)(apacket*, atransport*) = nullptr;
// A set of features transmitted in the banner with the initial connection.
// This is stored in the banner as 'features=feature0,feature1,etc'.
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index d6c84da..560a031 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -28,10 +28,12 @@
#include <condition_variable>
#include <mutex>
#include <thread>
+#include <unordered_map>
#include <vector>
#include <android-base/parsenetaddress.h>
#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
#include <cutils/sockets.h>
#if !ADB_HOST
@@ -40,6 +42,7 @@
#include "adb.h"
#include "adb_io.h"
+#include "adb_unique_fd.h"
#include "adb_utils.h"
#include "sysdeps/chrono.h"
@@ -53,48 +56,15 @@
static std::mutex& local_transports_lock = *new std::mutex();
-/* we keep a list of opened transports. The atransport struct knows to which
- * local transport it is connected. The list is used to detect when we're
- * trying to connect twice to a given local transport.
- */
-static atransport* local_transports[ ADB_LOCAL_TRANSPORT_MAX ];
+// We keep a map from emulator port to transport.
+// TODO: weak_ptr?
+static auto& local_transports GUARDED_BY(local_transports_lock) =
+ *new std::unordered_map<int, atransport*>();
#endif /* ADB_HOST */
-static int remote_read(apacket *p, atransport *t)
-{
- if (!ReadFdExactly(t->sfd, &p->msg, sizeof(amessage))) {
- D("remote local: read terminated (message)");
- return -1;
- }
-
- if (!check_header(p, t)) {
- D("bad header: terminated (data)");
- return -1;
- }
-
- if (!ReadFdExactly(t->sfd, p->data, p->msg.data_length)) {
- D("remote local: terminated (data)");
- return -1;
- }
-
- return 0;
-}
-
-static int remote_write(apacket *p, atransport *t)
-{
- int length = p->msg.data_length;
-
- if(!WriteFdExactly(t->sfd, &p->msg, sizeof(amessage) + length)) {
- D("remote local: write terminated");
- return -1;
- }
-
- return 0;
-}
-
bool local_connect(int port) {
std::string dummy;
- return local_connect_arbitrary_ports(port-1, port, &dummy) == 0;
+ return local_connect_arbitrary_ports(port - 1, port, &dummy) == 0;
}
void connect_device(const std::string& address, std::string* response) {
@@ -423,130 +393,83 @@
std::thread(func, port).detach();
}
-static void remote_kick(atransport *t)
-{
- int fd = t->sfd;
- t->sfd = -1;
- adb_shutdown(fd);
- adb_close(fd);
-
#if ADB_HOST
- int nn;
- std::lock_guard<std::mutex> lock(local_transports_lock);
- for (nn = 0; nn < ADB_LOCAL_TRANSPORT_MAX; nn++) {
- if (local_transports[nn] == t) {
- local_transports[nn] = NULL;
- break;
- }
- }
-#endif
-}
+struct EmulatorConnection : public FdConnection {
+ EmulatorConnection(unique_fd fd, int local_port)
+ : FdConnection(std::move(fd)), local_port_(local_port) {}
-static void remote_close(atransport *t)
-{
- int fd = t->sfd;
- if (fd != -1) {
- t->sfd = -1;
- adb_close(fd);
- }
-#if ADB_HOST
- int local_port;
- if (t->GetLocalPortForEmulator(&local_port)) {
- VLOG(TRANSPORT) << "remote_close, local_port = " << local_port;
+ ~EmulatorConnection() {
+ VLOG(TRANSPORT) << "remote_close, local_port = " << local_port_;
std::unique_lock<std::mutex> lock(retry_ports_lock);
RetryPort port;
- port.port = local_port;
+ port.port = local_port_;
port.retry_count = LOCAL_PORT_RETRY_COUNT;
retry_ports.push_back(port);
retry_ports_cond.notify_one();
}
-#endif
-}
+ void Close() override {
+ std::lock_guard<std::mutex> lock(local_transports_lock);
+ local_transports.erase(local_port_);
+ FdConnection::Close();
+ }
-#if ADB_HOST
+ int local_port_;
+};
+
/* Only call this function if you already hold local_transports_lock. */
static atransport* find_emulator_transport_by_adb_port_locked(int adb_port)
-{
- int i;
- for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) {
- int local_port;
- if (local_transports[i] && local_transports[i]->GetLocalPortForEmulator(&local_port)) {
- if (local_port == adb_port) {
- return local_transports[i];
- }
- }
+ REQUIRES(local_transports_lock) {
+ auto it = local_transports.find(adb_port);
+ if (it == local_transports.end()) {
+ return nullptr;
}
- return NULL;
+ return it->second;
}
-std::string getEmulatorSerialString(int console_port)
-{
+std::string getEmulatorSerialString(int console_port) {
return android::base::StringPrintf("emulator-%d", console_port);
}
-atransport* find_emulator_transport_by_adb_port(int adb_port)
-{
+atransport* find_emulator_transport_by_adb_port(int adb_port) {
std::lock_guard<std::mutex> lock(local_transports_lock);
- atransport* result = find_emulator_transport_by_adb_port_locked(adb_port);
- return result;
+ return find_emulator_transport_by_adb_port_locked(adb_port);
}
-atransport* find_emulator_transport_by_console_port(int console_port)
-{
+atransport* find_emulator_transport_by_console_port(int console_port) {
return find_transport(getEmulatorSerialString(console_port).c_str());
}
-
-
-/* Only call this function if you already hold local_transports_lock. */
-int get_available_local_transport_index_locked()
-{
- int i;
- for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) {
- if (local_transports[i] == NULL) {
- return i;
- }
- }
- return -1;
-}
-
-int get_available_local_transport_index()
-{
- std::lock_guard<std::mutex> lock(local_transports_lock);
- int result = get_available_local_transport_index_locked();
- return result;
-}
#endif
-int init_socket_transport(atransport *t, int s, int adb_port, int local)
-{
- int fail = 0;
+int init_socket_transport(atransport* t, int s, int adb_port, int local) {
+ int fail = 0;
- t->SetKickFunction(remote_kick);
- t->SetWriteFunction(remote_write);
- t->close = remote_close;
- t->read_from_remote = remote_read;
- t->sfd = s;
+ unique_fd fd(s);
t->sync_token = 1;
t->type = kTransportLocal;
#if ADB_HOST
+ // Emulator connection.
if (local) {
+ t->connection.reset(new EmulatorConnection(std::move(fd), adb_port));
std::lock_guard<std::mutex> lock(local_transports_lock);
- t->SetLocalPortForEmulator(adb_port);
atransport* existing_transport = find_emulator_transport_by_adb_port_locked(adb_port);
- int index = get_available_local_transport_index_locked();
if (existing_transport != NULL) {
D("local transport for port %d already registered (%p)?", adb_port, existing_transport);
fail = -1;
- } else if (index < 0) {
+ } else if (local_transports.size() >= ADB_LOCAL_TRANSPORT_MAX) {
// Too many emulators.
D("cannot register more emulators. Maximum is %d", ADB_LOCAL_TRANSPORT_MAX);
fail = -1;
} else {
- local_transports[index] = t;
+ local_transports[adb_port] = t;
}
+
+ return fail;
}
#endif
+
+ // Regular tcp connection.
+ t->connection.reset(new FdConnection(std::move(fd)));
return fail;
}
diff --git a/adb/transport_test.cpp b/adb/transport_test.cpp
index 68689d4..d987d4f 100644
--- a/adb/transport_test.cpp
+++ b/adb/transport_test.cpp
@@ -20,22 +20,6 @@
#include "adb.h"
-TEST(transport, kick_transport) {
- atransport t;
- static size_t kick_count;
- kick_count = 0;
- // Mutate some member so we can test that the function is run.
- t.SetKickFunction([](atransport* trans) { kick_count++; });
- ASSERT_FALSE(t.IsKicked());
- t.Kick();
- ASSERT_TRUE(t.IsKicked());
- ASSERT_EQ(1u, kick_count);
- // A transport can only be kicked once.
- t.Kick();
- ASSERT_TRUE(t.IsKicked());
- ASSERT_EQ(1u, kick_count);
-}
-
static void DisconnectFunc(void* arg, atransport*) {
int* count = reinterpret_cast<int*>(arg);
++*count;
diff --git a/adb/transport_usb.cpp b/adb/transport_usb.cpp
index 3474820..73e8e15 100644
--- a/adb/transport_usb.cpp
+++ b/adb/transport_usb.cpp
@@ -80,25 +80,18 @@
#endif
}
-static int remote_read(apacket* p, atransport* t) {
- int n = UsbReadMessage(t->usb, &p->msg);
+static int remote_read(apacket* p, usb_handle* usb) {
+ int n = UsbReadMessage(usb, &p->msg);
if (n < 0) {
D("remote usb: read terminated (message)");
return -1;
}
- if (static_cast<size_t>(n) != sizeof(p->msg) || !check_header(p, t)) {
- D("remote usb: check_header failed, skip it");
- goto err_msg;
- }
- if (t->GetConnectionState() == kCsOffline) {
- // If we read a wrong msg header declaring a large message payload, don't read its payload.
- // Otherwise we may miss true messages from the device.
- if (p->msg.command != A_CNXN && p->msg.command != A_AUTH) {
- goto err_msg;
- }
+ if (static_cast<size_t>(n) != sizeof(p->msg)) {
+ D("remote usb: read received unexpected header length %d", n);
+ return -1;
}
if (p->msg.data_length) {
- n = UsbReadPayload(t->usb, p);
+ n = UsbReadPayload(usb, p);
if (n < 0) {
D("remote usb: terminated (data)");
return -1;
@@ -106,34 +99,24 @@
if (static_cast<uint32_t>(n) != p->msg.data_length) {
D("remote usb: read payload failed (need %u bytes, give %d bytes), skip it",
p->msg.data_length, n);
- goto err_msg;
+ return -1;
}
}
return 0;
-
-err_msg:
- p->msg.command = 0;
- return 0;
}
#else
// On Android devices, we rely on the kernel to provide buffered read.
// So we can recover automatically from EOVERFLOW.
-static int remote_read(apacket *p, atransport *t)
-{
- if (usb_read(t->usb, &p->msg, sizeof(amessage))) {
+static int remote_read(apacket* p, usb_handle* usb) {
+ if (usb_read(usb, &p->msg, sizeof(amessage))) {
PLOG(ERROR) << "remote usb: read terminated (message)";
return -1;
}
- if (!check_header(p, t)) {
- LOG(ERROR) << "remote usb: check_header failed";
- return -1;
- }
-
if (p->msg.data_length) {
- if (usb_read(t->usb, p->data, p->msg.data_length)) {
+ if (usb_read(usb, p->data, p->msg.data_length)) {
PLOG(ERROR) << "remote usb: terminated (data)";
return -1;
}
@@ -143,45 +126,43 @@
}
#endif
-static int remote_write(apacket *p, atransport *t)
-{
- unsigned size = p->msg.data_length;
+UsbConnection::~UsbConnection() {
+ usb_close(handle_);
+}
- if (usb_write(t->usb, &p->msg, sizeof(amessage))) {
+bool UsbConnection::Read(apacket* packet) {
+ int rc = remote_read(packet, handle_);
+ return rc == 0;
+}
+
+bool UsbConnection::Write(apacket* packet) {
+ unsigned size = packet->msg.data_length;
+
+ if (usb_write(handle_, &packet->msg, sizeof(packet->msg)) != 0) {
PLOG(ERROR) << "remote usb: 1 - write terminated";
- return -1;
+ return false;
}
- if (p->msg.data_length == 0) return 0;
- if (usb_write(t->usb, &p->data, size)) {
+
+ if (packet->msg.data_length != 0 && usb_write(handle_, &packet->data, size) != 0) {
PLOG(ERROR) << "remote usb: 2 - write terminated";
- return -1;
+ return false;
}
- return 0;
+ return true;
}
-static void remote_close(atransport* t) {
- usb_close(t->usb);
- t->usb = 0;
-}
-
-static void remote_kick(atransport* t) {
- usb_kick(t->usb);
+void UsbConnection::Close() {
+ usb_kick(handle_);
}
void init_usb_transport(atransport* t, usb_handle* h) {
D("transport: usb");
- t->close = remote_close;
- t->SetKickFunction(remote_kick);
- t->SetWriteFunction(remote_write);
- t->read_from_remote = remote_read;
+ t->connection.reset(new UsbConnection(h));
t->sync_token = 1;
t->type = kTransportUsb;
- t->usb = h;
}
-int is_adb_interface(int usb_class, int usb_subclass, int usb_protocol)
-{
+int is_adb_interface(int usb_class, int usb_subclass, int usb_protocol) {
return (usb_class == ADB_CLASS && usb_subclass == ADB_SUBCLASS && usb_protocol == ADB_PROTOCOL);
}
diff --git a/bootstat/Android.bp b/bootstat/Android.bp
index 2c87018..e530774 100644
--- a/bootstat/Android.bp
+++ b/bootstat/Android.bp
@@ -63,7 +63,10 @@
name: "bootstat",
defaults: ["bootstat_defaults"],
static_libs: ["libbootstat"],
- shared_libs: ["liblogcat"],
+ shared_libs: [
+ "liblogcat",
+ "libstatslog"
+ ],
init_rc: ["bootstat.rc"],
product_variables: {
debuggable: {
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index a1fe6ed..1ec5451 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -42,6 +42,7 @@
#include <cutils/properties.h>
#include <log/logcat.h>
#include <metricslogger/metrics_logger.h>
+#include <statslog.h>
#include "boot_event_record_store.h"
@@ -881,6 +882,16 @@
return timings;
}
+// Returns the total bootloader boot time from the ro.boot.boottime system property.
+int32_t GetBootloaderTime(const BootloaderTimingMap& bootloader_timings) {
+ int32_t total_time = 0;
+ for (const auto& timing : bootloader_timings) {
+ total_time += timing.second;
+ }
+
+ return total_time;
+}
+
// Parses and records the set of bootloader stages and associated boot times
// from the ro.boot.boottime system property.
void RecordBootloaderTimings(BootEventRecordStore* boot_event_store,
@@ -894,11 +905,10 @@
boot_event_store->AddBootEventWithValue("boottime.bootloader.total", total_time);
}
-// Records the closest estimation to the absolute device boot time, i.e.,
+// Returns the closest estimation to the absolute device boot time, i.e.,
// from power on to boot_complete, including bootloader times.
-void RecordAbsoluteBootTime(BootEventRecordStore* boot_event_store,
- const BootloaderTimingMap& bootloader_timings,
- std::chrono::milliseconds uptime) {
+std::chrono::milliseconds GetAbsoluteBootTime(const BootloaderTimingMap& bootloader_timings,
+ std::chrono::milliseconds uptime) {
int32_t bootloader_time_ms = 0;
for (const auto& timing : bootloader_timings) {
@@ -908,9 +918,36 @@
}
auto bootloader_duration = std::chrono::milliseconds(bootloader_time_ms);
- auto absolute_total =
- std::chrono::duration_cast<std::chrono::seconds>(bootloader_duration + uptime);
- boot_event_store->AddBootEventWithValue("absolute_boot_time", absolute_total.count());
+ return bootloader_duration + uptime;
+}
+
+// Records the closest estimation to the absolute device boot time in seconds.
+// i.e. from power on to boot_complete, including bootloader times.
+void RecordAbsoluteBootTime(BootEventRecordStore* boot_event_store,
+ std::chrono::milliseconds absolute_total) {
+ auto absolute_total_sec = std::chrono::duration_cast<std::chrono::seconds>(absolute_total);
+ boot_event_store->AddBootEventWithValue("absolute_boot_time", absolute_total_sec.count());
+}
+
+// Logs the total boot time and reason to statsd.
+void LogBootInfoToStatsd(std::chrono::milliseconds end_time,
+ std::chrono::milliseconds total_duration, int32_t bootloader_duration_ms,
+ double time_since_last_boot_sec) {
+ const std::string reason(GetProperty(bootloader_reboot_reason_property));
+
+ if (reason.empty()) {
+ android::util::stats_write(android::util::BOOT_SEQUENCE_REPORTED, "<EMPTY>", "<EMPTY>",
+ end_time.count(), total_duration.count(),
+ (int64_t)bootloader_duration_ms,
+ (int64_t)time_since_last_boot_sec * 1000);
+ return;
+ }
+
+ const std::string system_reason(BootReasonStrToReason(reason));
+ android::util::stats_write(android::util::BOOT_SEQUENCE_REPORTED, reason.c_str(),
+ system_reason.c_str(), end_time.count(), total_duration.count(),
+ (int64_t)bootloader_duration_ms,
+ (int64_t)time_since_last_boot_sec * 1000);
}
// Records several metrics related to the time it takes to boot the device,
@@ -922,10 +959,11 @@
auto time_since_epoch = android::base::boot_clock::now().time_since_epoch();
auto uptime = std::chrono::duration_cast<std::chrono::seconds>(time_since_epoch);
time_t current_time_utc = time(nullptr);
+ time_t time_since_last_boot = 0;
if (boot_event_store.GetBootEvent("last_boot_time_utc", &record)) {
time_t last_boot_time_utc = record.second;
- time_t time_since_last_boot = difftime(current_time_utc, last_boot_time_utc);
+ time_since_last_boot = difftime(current_time_utc, last_boot_time_utc);
boot_event_store.AddBootEventWithValue("time_since_last_boot", time_since_last_boot);
}
@@ -964,10 +1002,18 @@
RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init.cold_boot_wait");
const BootloaderTimingMap bootloader_timings = GetBootLoaderTimings();
+ int32_t bootloader_boot_duration = GetBootloaderTime(bootloader_timings);
RecordBootloaderTimings(&boot_event_store, bootloader_timings);
auto uptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_since_epoch);
- RecordAbsoluteBootTime(&boot_event_store, bootloader_timings, uptime_ms);
+ auto absolute_boot_time = GetAbsoluteBootTime(bootloader_timings, uptime_ms);
+ RecordAbsoluteBootTime(&boot_event_store, absolute_boot_time);
+
+ auto boot_end_time_point = std::chrono::system_clock::now().time_since_epoch();
+ auto boot_end_time = std::chrono::duration_cast<std::chrono::milliseconds>(boot_end_time_point);
+
+ LogBootInfoToStatsd(boot_end_time, absolute_boot_time, bootloader_boot_duration,
+ time_since_last_boot);
}
// Records the boot_reason metric by querying the ro.boot.bootreason system
diff --git a/init/stable_properties.h b/init/stable_properties.h
index 8219838..be35457 100644
--- a/init/stable_properties.h
+++ b/init/stable_properties.h
@@ -24,19 +24,27 @@
namespace init {
static constexpr const char* kPartnerPrefixes[] = {
- "init.svc.vendor.", "ro.vendor.", "persist.vendor.", "vendor.",
- "init.svc.odm.", "ro.odm.", "persist.odm.", "odm.",
+ "init.svc.vendor.", "ro.vendor.", "persist.vendor.", "vendor.", "init.svc.odm.", "ro.odm.",
+ "persist.odm.", "odm.", "ro.boot.",
};
static const std::set<std::string> kExportedActionableProperties = {
- "init.svc.zygote", "persist.bluetooth.btsnoopenable",
- "persist.sys.crash_rcu", "persist.sys.zram_enabled",
- "ro.boot.revision", "ro.bootmode",
- "ro.build.type", "sys.boot_completed",
- "sys.retaildemo.enabled", "sys.shutdown.requested",
- "sys.usb.config", "sys.usb.configfs",
- "sys.usb.ffs.mtp.ready", "sys.usb.ffs.ready",
- "sys.user.0.ce_available", "sys.vdso",
+ "init.svc.mediadrm",
+ "init.svc.zygote",
+ "persist.bluetooth.btsnoopenable",
+ "persist.sys.crash_rcu",
+ "persist.sys.zram_enabled",
+ "ro.bootmode",
+ "ro.build.type",
+ "sys.boot_completed",
+ "sys.retaildemo.enabled",
+ "sys.shutdown.requested",
+ "sys.usb.config",
+ "sys.usb.configfs",
+ "sys.usb.ffs.mtp.ready",
+ "sys.usb.ffs.ready",
+ "sys.user.0.ce_available",
+ "sys.vdso",
"vts.native_server.on",
};
diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp
index 14ae445..10a4e46 100644
--- a/libbacktrace/Android.bp
+++ b/libbacktrace/Android.bp
@@ -50,7 +50,6 @@
"BacktracePtrace.cpp",
"thread_utils.c",
"ThreadEntry.cpp",
- "UnwindDexFile.cpp",
"UnwindStack.cpp",
"UnwindStackMap.cpp",
]
@@ -110,10 +109,9 @@
static_libs: ["libasync_safe"],
},
vendor: {
- cflags: ["-DNO_LIBDEXFILE"],
- exclude_srcs: ["UnwindDexFile.cpp"],
+ cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
exclude_shared_libs: ["libdexfile"],
- },
+ }
},
whole_static_libs: ["libdemangle"],
}
@@ -145,8 +143,6 @@
"backtrace_test.cpp",
"GetPss.cpp",
"thread_utils.c",
-
- "unwind_dex_test.cpp",
],
cflags: [
diff --git a/libbacktrace/UnwindStack.cpp b/libbacktrace/UnwindStack.cpp
index 158467e..7e2e6d0 100644
--- a/libbacktrace/UnwindStack.cpp
+++ b/libbacktrace/UnwindStack.cpp
@@ -36,70 +36,15 @@
#include <unwindstack/Regs.h>
#include <unwindstack/RegsGetLocal.h>
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+#include <unwindstack/DexFiles.h>
+#endif
#include <unwindstack/Unwinder.h>
#include "BacktraceLog.h"
-#ifndef NO_LIBDEXFILE
-#include "UnwindDexFile.h"
-#endif
#include "UnwindStack.h"
#include "UnwindStackMap.h"
-static void FillInDexFrame(UnwindStackMap* stack_map, uint64_t dex_pc,
- backtrace_frame_data_t* frame) {
- // The DEX PC points into the .dex section within an ELF file.
- // However, this is a BBS section manually mmaped to a .vdex file,
- // so we need to get the following map to find the ELF data.
- unwindstack::Maps* maps = stack_map->stack_maps();
- auto it = maps->begin();
- uint64_t rel_dex_pc;
- unwindstack::MapInfo* info;
- for (; it != maps->end(); ++it) {
- auto entry = *it;
- if (dex_pc >= entry->start && dex_pc < entry->end) {
- info = entry;
- rel_dex_pc = dex_pc - entry->start;
- frame->map.start = entry->start;
- frame->map.end = entry->end;
- frame->map.offset = entry->offset;
- frame->map.load_bias = entry->load_bias;
- frame->map.flags = entry->flags;
- frame->map.name = entry->name;
- frame->rel_pc = rel_dex_pc;
- break;
- }
- }
- if (it == maps->end() || ++it == maps->end()) {
- return;
- }
-
- auto entry = *it;
- auto process_memory = stack_map->process_memory();
- unwindstack::Elf* elf = entry->GetElf(process_memory, true);
- if (!elf->valid()) {
- return;
- }
-
- // Adjust the relative dex by the offset.
- rel_dex_pc += entry->elf_offset;
-
- uint64_t dex_offset;
- if (!elf->GetFunctionName(rel_dex_pc, &frame->func_name, &dex_offset)) {
- return;
- }
- frame->func_offset = dex_offset;
- if (frame->func_name != "$dexfile") {
- return;
- }
-
-#ifndef NO_LIBDEXFILE
- UnwindDexFile* dex_file = stack_map->GetDexFile(dex_pc - dex_offset, info);
- if (dex_file != nullptr) {
- dex_file->GetMethodInformation(dex_offset, &frame->func_name, &frame->func_offset);
- }
-#endif
-}
-
bool Backtrace::Unwind(unwindstack::Regs* regs, BacktraceMap* back_map,
std::vector<backtrace_frame_data_t>* frames, size_t num_ignore_frames,
std::vector<std::string>* skip_names, BacktraceUnwindError* error) {
@@ -110,6 +55,11 @@
if (stack_map->GetJitDebug() != nullptr) {
unwinder.SetJitDebug(stack_map->GetJitDebug(), regs->Arch());
}
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ if (stack_map->GetDexFiles() != nullptr) {
+ unwinder.SetDexFiles(stack_map->GetDexFiles(), regs->Arch());
+ }
+#endif
unwinder.Unwind(skip_names, &stack_map->GetSuffixesToIgnore());
if (error != nullptr) {
switch (unwinder.LastErrorCode()) {
@@ -150,36 +100,11 @@
}
auto unwinder_frames = unwinder.frames();
- // Get the real number of frames we'll need.
- size_t total_frames = 0;
- for (size_t i = num_ignore_frames; i < unwinder.NumFrames(); i++, total_frames++) {
- if (unwinder_frames[i].dex_pc != 0) {
- total_frames++;
- }
- }
- frames->resize(total_frames);
+ frames->resize(unwinder.NumFrames() - num_ignore_frames);
size_t cur_frame = 0;
for (size_t i = num_ignore_frames; i < unwinder.NumFrames(); i++) {
auto frame = &unwinder_frames[i];
- // Inject extra 'virtual' frame that represents the dex pc data.
- // The dex pc is magic register defined in the Mterp interpreter,
- // and thus it will be restored/observed in the frame after it.
- // Adding the dex frame first here will create something like:
- // #7 pc 006b1ba1 libartd.so ExecuteMterpImpl+14625
- // #8 pc 0015fa20 core.vdex java.util.Arrays.binarySearch+8
- // #9 pc 0039a1ef libartd.so art::interpreter::Execute+719
- if (frame->dex_pc != 0) {
- backtrace_frame_data_t* dex_frame = &frames->at(cur_frame);
- dex_frame->num = cur_frame++;
- dex_frame->pc = frame->dex_pc;
- dex_frame->rel_pc = frame->dex_pc;
- dex_frame->sp = frame->sp;
- dex_frame->stack_size = 0;
- dex_frame->func_offset = 0;
- FillInDexFrame(stack_map, frame->dex_pc, dex_frame);
- }
-
backtrace_frame_data_t* back_frame = &frames->at(cur_frame);
back_frame->num = cur_frame++;
diff --git a/libbacktrace/UnwindStackMap.cpp b/libbacktrace/UnwindStackMap.cpp
index 97f8d78..1622e30 100644
--- a/libbacktrace/UnwindStackMap.cpp
+++ b/libbacktrace/UnwindStackMap.cpp
@@ -26,20 +26,11 @@
#include <unwindstack/MapInfo.h>
#include <unwindstack/Maps.h>
-#include "UnwindDexFile.h"
#include "UnwindStackMap.h"
//-------------------------------------------------------------------------
UnwindStackMap::UnwindStackMap(pid_t pid) : BacktraceMap(pid) {}
-UnwindStackMap::~UnwindStackMap() {
-#ifndef NO_LIBDEXFILE
- for (auto& entry : dex_files_) {
- delete entry.second;
- }
-#endif
-}
-
bool UnwindStackMap::Build() {
if (pid_ == 0) {
pid_ = getpid();
@@ -54,6 +45,9 @@
// Create a JitDebug object for getting jit unwind information.
std::vector<std::string> search_libs_{"libart.so", "libartd.so"};
jit_debug_.reset(new unwindstack::JitDebug(process_memory_, search_libs_));
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ dex_files_.reset(new unwindstack::DexFiles(process_memory_));
+#endif
if (!stack_maps_->Parse()) {
return false;
@@ -127,26 +121,6 @@
return process_memory_;
}
-#ifdef NO_LIBDEXFILE
-UnwindDexFile* UnwindStackMap::GetDexFile(uint64_t, unwindstack::MapInfo*) {
- return nullptr;
-}
-#else
-UnwindDexFile* UnwindStackMap::GetDexFile(uint64_t dex_file_offset, unwindstack::MapInfo* info) {
- // Lock while we get the data.
- std::lock_guard<std::mutex> guard(dex_lock_);
- UnwindDexFile* dex_file;
- auto entry = dex_files_.find(dex_file_offset);
- if (entry == dex_files_.end()) {
- dex_file = UnwindDexFile::Create(dex_file_offset, process_memory_.get(), info);
- dex_files_[dex_file_offset] = dex_file;
- } else {
- dex_file = entry->second;
- }
- return dex_file;
-}
-#endif
-
UnwindStackOfflineMap::UnwindStackOfflineMap(pid_t pid) : UnwindStackMap(pid) {}
bool UnwindStackOfflineMap::Build() {
diff --git a/libbacktrace/UnwindStackMap.h b/libbacktrace/UnwindStackMap.h
index be5c59e..94cbfb2 100644
--- a/libbacktrace/UnwindStackMap.h
+++ b/libbacktrace/UnwindStackMap.h
@@ -27,6 +27,9 @@
#include <backtrace/Backtrace.h>
#include <backtrace/BacktraceMap.h>
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+#include <unwindstack/DexFiles.h>
+#endif
#include <unwindstack/JitDebug.h>
#include <unwindstack/Maps.h>
@@ -36,7 +39,7 @@
class UnwindStackMap : public BacktraceMap {
public:
explicit UnwindStackMap(pid_t pid);
- ~UnwindStackMap();
+ ~UnwindStackMap() = default;
bool Build() override;
@@ -51,7 +54,9 @@
unwindstack::JitDebug* GetJitDebug() { return jit_debug_.get(); }
- UnwindDexFile* GetDexFile(uint64_t dex_file_offset, unwindstack::MapInfo* info);
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ unwindstack::DexFiles* GetDexFiles() { return dex_files_.get(); }
+#endif
protected:
uint64_t GetLoadBias(size_t index) override;
@@ -59,9 +64,8 @@
std::unique_ptr<unwindstack::Maps> stack_maps_;
std::shared_ptr<unwindstack::Memory> process_memory_;
std::unique_ptr<unwindstack::JitDebug> jit_debug_;
-#ifndef NO_LIBDEXFILE
- std::mutex dex_lock_;
- std::unordered_map<uint64_t, UnwindDexFile*> dex_files_;
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ std::unique_ptr<unwindstack::DexFiles> dex_files_;
#endif
};
diff --git a/libcutils/tests/AshmemTest.cpp b/libcutils/tests/AshmemTest.cpp
index a87e23e..b37d020 100644
--- a/libcutils/tests/AshmemTest.cpp
+++ b/libcutils/tests/AshmemTest.cpp
@@ -14,11 +14,18 @@
* limitations under the License.
*/
+#include <errno.h>
+#include <linux/fs.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/macros.h>
#include <android-base/unique_fd.h>
#include <cutils/ashmem.h>
#include <gtest/gtest.h>
-#include <linux/fs.h>
-#include <sys/mman.h>
using android::base::unique_fd;
@@ -31,15 +38,21 @@
}
void TestMmap(const unique_fd& fd, size_t size, int prot, void** region, off_t off = 0) {
+ ASSERT_TRUE(fd >= 0);
+ ASSERT_TRUE(ashmem_valid(fd));
*region = mmap(nullptr, size, prot, MAP_SHARED, fd, off);
ASSERT_NE(MAP_FAILED, *region);
}
void TestProtDenied(const unique_fd &fd, size_t size, int prot) {
+ ASSERT_TRUE(fd >= 0);
+ ASSERT_TRUE(ashmem_valid(fd));
EXPECT_EQ(MAP_FAILED, mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
}
void TestProtIs(const unique_fd& fd, int prot) {
+ ASSERT_TRUE(fd >= 0);
+ ASSERT_TRUE(ashmem_valid(fd));
EXPECT_EQ(prot, ioctl(fd, ASHMEM_GET_PROT_MASK));
}
@@ -86,18 +99,23 @@
ASSERT_EQ(0, memcmp(region1, &data, size));
EXPECT_EQ(0, munmap(region1, size));
- ASSERT_EXIT({
- void *region2 = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if (region2 == MAP_FAILED) {
- _exit(1);
- }
- if (memcmp(region2, &data, size) != 0) {
- _exit(2);
- }
- memset(region2, 0, size);
- munmap(region2, size);
- _exit(0);
- }, ::testing::ExitedWithCode(0),"");
+ ASSERT_EXIT(
+ {
+ if (!ashmem_valid(fd)) {
+ _exit(3);
+ }
+ void* region2 = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (region2 == MAP_FAILED) {
+ _exit(1);
+ }
+ if (memcmp(region2, &data, size) != 0) {
+ _exit(2);
+ }
+ memset(region2, 0, size);
+ munmap(region2, size);
+ _exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
memset(&data, 0, size);
void *region2;
@@ -146,6 +164,7 @@
};
for (const auto& cfg : seeks) {
errno = 0;
+ ASSERT_TRUE(ashmem_valid(fd));
auto off = lseek(fd, cfg.offset, cfg.whence);
ASSERT_EQ(cfg.ret, off) << "lseek(" << cfg.offset << ", " << cfg.whence << ") failed"
<< (errno ? ": " : "") << (errno ? strerror(errno) : "");
@@ -196,15 +215,19 @@
constexpr size_t size = PAGE_SIZE;
int protFlags[] = { PROT_READ, PROT_WRITE };
- for (int i = 0; i < 2; i++) {
+ for (size_t i = 0; i < arraysize(protFlags); i++) {
ASSERT_NO_FATAL_FAILURE(TestCreateRegion(size, fd, PROT_READ | PROT_WRITE));
- ASSERT_EXIT({
- if (ashmem_set_prot_region(fd, protFlags[i]) >= 0) {
- _exit(0);
- } else {
- _exit(1);
- }
- }, ::testing::ExitedWithCode(0), "");
+ ASSERT_EXIT(
+ {
+ if (!ashmem_valid(fd)) {
+ _exit(3);
+ } else if (ashmem_set_prot_region(fd, protFlags[i]) >= 0) {
+ _exit(0);
+ } else {
+ _exit(1);
+ }
+ },
+ ::testing::ExitedWithCode(0), "");
ASSERT_NO_FATAL_FAILURE(TestProtDenied(fd, size, protFlags[1-i]));
}
}
@@ -227,6 +250,9 @@
ASSERT_EXIT({
for (int i = 0; i < nRegions; i++) {
+ if (!ashmem_valid(fd[i])) {
+ _exit(3);
+ }
void *region = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd[i], 0);
if (region == MAP_FAILED) {
_exit(1);
diff --git a/libcutils/trace-dev.cpp b/libcutils/trace-dev.cpp
index 4da8215..27255c2 100644
--- a/libcutils/trace-dev.cpp
+++ b/libcutils/trace-dev.cpp
@@ -25,6 +25,7 @@
void atrace_set_tracing_enabled(bool enabled)
{
atomic_store_explicit(&atrace_is_enabled, enabled, memory_order_release);
+ atomic_store_explicit(&atrace_is_ready, false, memory_order_release);
atrace_update_tags();
}
@@ -34,18 +35,17 @@
if (atrace_marker_fd == -1) {
ALOGE("Error opening trace file: %s (%d)", strerror(errno), errno);
atrace_enabled_tags = 0;
- goto done;
+ return;
}
-
atrace_enabled_tags = atrace_get_property();
-
-done:
- atomic_store_explicit(&atrace_is_ready, true, memory_order_release);
}
void atrace_setup()
{
- pthread_once(&atrace_once_control, atrace_init_once);
+ if (atomic_load_explicit(&atrace_is_enabled, memory_order_acquire)) {
+ pthread_once(&atrace_once_control, atrace_init_once);
+ }
+ atomic_store_explicit(&atrace_is_ready, true, memory_order_release);
}
void atrace_begin_body(const char* name)
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 28d7e64..74dfaa5 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -80,7 +80,13 @@
host: {
cflags: ["-O0", "-g"],
},
+ vendor: {
+ cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
+ exclude_static_libs: ["libunwindstack_dex"],
+ exclude_shared_libs: ["libdexfile"],
+ },
},
+ whole_static_libs: ["libunwindstack_dex"],
arch: {
x86: {
@@ -99,14 +105,70 @@
shared_libs: [
"libbase",
+ "libdexfile",
"liblog",
"liblzma",
],
}
+// Isolate the dex file processing into a separate library. Currently,
+// it is necessary to add art include directories directly, which also
+// adds the art elf.h file in the include path, overriding the system one.
+// Work to isolate libdexfile is b/72216369.
+cc_library_static {
+ name: "libunwindstack_dex",
+ vendor_available: false,
+ defaults: ["libunwindstack_flags"],
+
+ cflags: [
+ "-Wexit-time-destructors",
+ ],
+
+ srcs: [
+ "DexFile.cpp",
+ "DexFiles.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libdexfile",
+ ],
+ local_include_dirs: ["include"],
+ allow_undefined_symbols: true,
+
+ // libdexfile will eventually properly export headers, for now include
+ // these directly.
+ include_dirs: [
+ "art/runtime",
+ ],
+}
+
//-------------------------------------------------------------------------
// Unit Tests
//-------------------------------------------------------------------------
+cc_test_library {
+ name: "libunwindstack_dex_test",
+ vendor_available: false,
+ defaults: ["libunwindstack_flags"],
+
+ srcs: [
+ "tests/DexFileTest.cpp",
+ ],
+ local_include_dirs: ["include"],
+ allow_undefined_symbols: true,
+
+ shared_libs: [
+ "libbase",
+ "libunwindstack",
+ ],
+
+ // libdexfile will eventually properly export headers, for now include
+ // these directly.
+ include_dirs: [
+ "art/runtime",
+ ],
+}
+
cc_test {
name: "libunwindstack_test",
defaults: ["libunwindstack_flags"],
@@ -168,6 +230,8 @@
"libgmock",
],
+ whole_static_libs: ["libunwindstack_dex_test"],
+
data: [
"tests/files/elf32.xz",
"tests/files/elf64.xz",
diff --git a/libbacktrace/UnwindDexFile.cpp b/libunwindstack/DexFile.cpp
similarity index 85%
rename from libbacktrace/UnwindDexFile.cpp
rename to libunwindstack/DexFile.cpp
index 5780fbb..be6c2f7 100644
--- a/libbacktrace/UnwindDexFile.cpp
+++ b/libunwindstack/DexFile.cpp
@@ -33,26 +33,27 @@
#include <unwindstack/MapInfo.h>
#include <unwindstack/Memory.h>
-#include "UnwindDexFile.h"
+#include "DexFile.h"
-UnwindDexFile* UnwindDexFile::Create(uint64_t dex_file_offset_in_memory,
- unwindstack::Memory* memory, unwindstack::MapInfo* info) {
+namespace unwindstack {
+
+DexFile* DexFile::Create(uint64_t dex_file_offset_in_memory, Memory* memory, MapInfo* info) {
if (!info->name.empty()) {
- std::unique_ptr<UnwindDexFileFromFile> dex_file(new UnwindDexFileFromFile);
+ std::unique_ptr<DexFileFromFile> dex_file(new DexFileFromFile);
if (dex_file->Open(dex_file_offset_in_memory - info->start + info->offset, info->name)) {
return dex_file.release();
}
}
- std::unique_ptr<UnwindDexFileFromMemory> dex_file(new UnwindDexFileFromMemory);
+ std::unique_ptr<DexFileFromMemory> dex_file(new DexFileFromMemory);
if (dex_file->Open(dex_file_offset_in_memory, memory)) {
return dex_file.release();
}
return nullptr;
}
-void UnwindDexFile::GetMethodInformation(uint64_t dex_offset, std::string* method_name,
- uint64_t* method_offset) {
+void DexFile::GetMethodInformation(uint64_t dex_offset, std::string* method_name,
+ uint64_t* method_offset) {
if (dex_file_ == nullptr) {
return;
}
@@ -87,13 +88,13 @@
}
}
-UnwindDexFileFromFile::~UnwindDexFileFromFile() {
+DexFileFromFile::~DexFileFromFile() {
if (size_ != 0) {
munmap(mapped_memory_, size_);
}
}
-bool UnwindDexFileFromFile::Open(uint64_t dex_file_offset_in_file, const std::string& file) {
+bool DexFileFromFile::Open(uint64_t dex_file_offset_in_file, const std::string& file) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd == -1) {
return false;
@@ -137,7 +138,7 @@
return dex_file_ != nullptr;
}
-bool UnwindDexFileFromMemory::Open(uint64_t dex_file_offset_in_memory, unwindstack::Memory* memory) {
+bool DexFileFromMemory::Open(uint64_t dex_file_offset_in_memory, Memory* memory) {
art::DexFile::Header header;
if (!memory->ReadFully(dex_file_offset_in_memory, &header, sizeof(header))) {
return false;
@@ -160,3 +161,5 @@
dex_file_.reset(dex.release());
return dex_file_ != nullptr;
}
+
+} // namespace unwindstack
diff --git a/libbacktrace/UnwindDexFile.h b/libunwindstack/DexFile.h
similarity index 60%
rename from libbacktrace/UnwindDexFile.h
rename to libunwindstack/DexFile.h
index dd70aba..22e98df 100644
--- a/libbacktrace/UnwindDexFile.h
+++ b/libunwindstack/DexFile.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef _LIBBACKTRACE_UNWIND_DEX_FILE_H
-#define _LIBBACKTRACE_UNWIND_DEX_FILE_H
+#ifndef _LIBUNWINDSTACK_DEX_FILE_H
+#define _LIBUNWINDSTACK_DEX_FILE_H
#include <stdint.h>
@@ -26,28 +26,24 @@
#include <dex/dex_file-inl.h>
namespace unwindstack {
-class Memory;
-struct MapInfo;
-} // namespace unwindstack
-class UnwindDexFile {
+class DexFile {
public:
- UnwindDexFile() = default;
- virtual ~UnwindDexFile() = default;
+ DexFile() = default;
+ virtual ~DexFile() = default;
void GetMethodInformation(uint64_t dex_offset, std::string* method_name, uint64_t* method_offset);
- static UnwindDexFile* Create(uint64_t dex_file_offset_in_memory, unwindstack::Memory* memory,
- unwindstack::MapInfo* info);
+ static DexFile* Create(uint64_t dex_file_offset_in_memory, Memory* memory, MapInfo* info);
protected:
std::unique_ptr<const art::DexFile> dex_file_;
};
-class UnwindDexFileFromFile : public UnwindDexFile {
+class DexFileFromFile : public DexFile {
public:
- UnwindDexFileFromFile() = default;
- virtual ~UnwindDexFileFromFile();
+ DexFileFromFile() = default;
+ virtual ~DexFileFromFile();
bool Open(uint64_t dex_file_offset_in_file, const std::string& name);
@@ -56,15 +52,17 @@
size_t size_ = 0;
};
-class UnwindDexFileFromMemory : public UnwindDexFile {
+class DexFileFromMemory : public DexFile {
public:
- UnwindDexFileFromMemory() = default;
- virtual ~UnwindDexFileFromMemory() = default;
+ DexFileFromMemory() = default;
+ virtual ~DexFileFromMemory() = default;
- bool Open(uint64_t dex_file_offset_in_memory, unwindstack::Memory* memory);
+ bool Open(uint64_t dex_file_offset_in_memory, Memory* memory);
private:
std::vector<uint8_t> memory_;
};
-#endif // _LIBBACKTRACE_UNWIND_DEX_FILE_H
+} // namespace unwindstack
+
+#endif // _LIBUNWINDSTACK_DEX_FILE_H
diff --git a/libunwindstack/DexFiles.cpp b/libunwindstack/DexFiles.cpp
new file mode 100644
index 0000000..3d67a6a
--- /dev/null
+++ b/libunwindstack/DexFiles.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 <stdint.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include <unwindstack/DexFiles.h>
+#include <unwindstack/MapInfo.h>
+#include <unwindstack/Memory.h>
+
+#include "DexFile.h"
+
+namespace unwindstack {
+
+DexFiles::DexFiles(std::shared_ptr<Memory>& memory) : memory_(memory) {}
+
+DexFiles::~DexFiles() {
+ for (auto& entry : files_) {
+ delete entry.second;
+ }
+}
+
+DexFile* DexFiles::GetDexFile(uint64_t dex_file_offset, MapInfo* info) {
+ // Lock while processing the data.
+ std::lock_guard<std::mutex> guard(files_lock_);
+ DexFile* dex_file;
+ auto entry = files_.find(dex_file_offset);
+ if (entry == files_.end()) {
+ dex_file = DexFile::Create(dex_file_offset, memory_.get(), info);
+ files_[dex_file_offset] = dex_file;
+ } else {
+ dex_file = entry->second;
+ }
+ return dex_file;
+}
+
+void DexFiles::GetMethodInformation(uint64_t dex_offset, MapInfo* info, std::string* method_name,
+ uint64_t* method_offset) {
+ DexFile* dex_file = GetDexFile(dex_offset, info);
+ if (dex_file != nullptr) {
+ dex_file->GetMethodInformation(dex_offset, method_name, method_offset);
+ }
+}
+
+void DexFiles::SetArch(ArchEnum) {}
+
+} // namespace unwindstack
diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp
index f70ed7b..d22e1e8 100644
--- a/libunwindstack/Unwinder.cpp
+++ b/libunwindstack/Unwinder.cpp
@@ -31,8 +31,74 @@
#include <unwindstack/MapInfo.h>
#include <unwindstack/Unwinder.h>
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+#include <unwindstack/DexFiles.h>
+#endif
+
namespace unwindstack {
+// Inject extra 'virtual' frame that represents the dex pc data.
+// The dex pc is a magic register defined in the Mterp interpreter,
+// and thus it will be restored/observed in the frame after it.
+// Adding the dex frame first here will create something like:
+// #7 pc 0015fa20 core.vdex java.util.Arrays.binarySearch+8
+// #8 pc 006b1ba1 libartd.so ExecuteMterpImpl+14625
+// #9 pc 0039a1ef libartd.so art::interpreter::Execute+719
+void Unwinder::FillInDexFrame() {
+ size_t frame_num = frames_.size();
+ frames_.resize(frame_num + 1);
+ FrameData* frame = &frames_.at(frame_num);
+
+ uint64_t dex_pc = regs_->dex_pc();
+ frame->pc = dex_pc;
+ frame->sp = regs_->sp();
+
+ auto it = maps_->begin();
+ uint64_t rel_dex_pc;
+ MapInfo* info;
+ for (; it != maps_->end(); ++it) {
+ auto entry = *it;
+ if (dex_pc >= entry->start && dex_pc < entry->end) {
+ info = entry;
+ rel_dex_pc = dex_pc - entry->start;
+ frame->map_start = entry->start;
+ frame->map_end = entry->end;
+ frame->map_offset = entry->offset;
+ frame->map_load_bias = entry->load_bias;
+ frame->map_flags = entry->flags;
+ frame->map_name = entry->name;
+ frame->rel_pc = rel_dex_pc;
+ break;
+ }
+ }
+
+ if (it == maps_->end() || ++it == maps_->end()) {
+ return;
+ }
+
+ auto entry = *it;
+ unwindstack::Elf* elf = entry->GetElf(process_memory_, true);
+ if (!elf->valid()) {
+ return;
+ }
+
+ // Adjust the relative dex by the offset.
+ rel_dex_pc += entry->elf_offset;
+
+ uint64_t dex_offset;
+ if (!elf->GetFunctionName(rel_dex_pc, &frame->function_name, &dex_offset)) {
+ return;
+ }
+ frame->function_offset = dex_offset;
+ if (frame->function_name != "$dexfile") {
+ return;
+ }
+
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ dex_files_->GetMethodInformation(dex_offset, info, &frame->function_name, &frame->function_offset);
+#endif
+}
+
void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc, uint64_t func_pc) {
size_t frame_num = frames_.size();
frames_.resize(frame_num + 1);
@@ -40,7 +106,6 @@
frame->num = frame_num;
frame->sp = regs_->sp();
frame->rel_pc = adjusted_rel_pc;
- frame->dex_pc = regs_->dex_pc();
if (map_info == nullptr) {
frame->pc = regs_->pc();
@@ -128,6 +193,11 @@
if (map_info == nullptr || initial_map_names_to_skip == nullptr ||
std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(),
basename(map_info->name.c_str())) == initial_map_names_to_skip->end()) {
+ if (regs_->dex_pc() != 0) {
+ // Add a frame to represent the dex file.
+ FillInDexFrame();
+ }
+
FillInFrame(map_info, elf, adjusted_rel_pc, adjusted_pc);
// Once a frame is added, stop skipping frames.
@@ -240,4 +310,11 @@
jit_debug_ = jit_debug;
}
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+void Unwinder::SetDexFiles(DexFiles* dex_files, ArchEnum arch) {
+ dex_files->SetArch(arch);
+ dex_files_ = dex_files;
+}
+#endif
+
} // namespace unwindstack
diff --git a/libunwindstack/include/unwindstack/DexFiles.h b/libunwindstack/include/unwindstack/DexFiles.h
new file mode 100644
index 0000000..d80e9b7
--- /dev/null
+++ b/libunwindstack/include/unwindstack/DexFiles.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef _LIBUNWINDSTACK_DEX_FILES_H
+#define _LIBUNWINDSTACK_DEX_FILES_H
+
+#include <stdint.h>
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+namespace unwindstack {
+
+// Forward declarations.
+class DexFile;
+class Maps;
+struct MapInfo;
+class Memory;
+enum ArchEnum : uint8_t;
+
+class DexFiles {
+ public:
+ explicit DexFiles(std::shared_ptr<Memory>& memory);
+ ~DexFiles();
+
+ DexFile* GetDexFile(uint64_t dex_offset, MapInfo* info);
+
+ void GetMethodInformation(uint64_t dex_offset, MapInfo* info, std::string* method_name,
+ uint64_t* method_offset);
+
+ void SetArch(ArchEnum arch);
+
+ private:
+ std::shared_ptr<Memory> memory_;
+ std::mutex files_lock_;
+ std::unordered_map<uint64_t, DexFile*> files_;
+};
+
+} // namespace unwindstack
+
+#endif // _LIBUNWINDSTACK_DEX_FILES_H
diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h
index a7b57e6..e8af8b4 100644
--- a/libunwindstack/include/unwindstack/Unwinder.h
+++ b/libunwindstack/include/unwindstack/Unwinder.h
@@ -32,6 +32,7 @@
namespace unwindstack {
// Forward declarations.
+class DexFiles;
class Elf;
class JitDebug;
enum ArchEnum : uint8_t;
@@ -42,7 +43,6 @@
uint64_t rel_pc;
uint64_t pc;
uint64_t sp;
- uint64_t dex_pc;
std::string function_name;
uint64_t function_offset;
@@ -75,10 +75,15 @@
void SetJitDebug(JitDebug* jit_debug, ArchEnum arch);
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ void SetDexFiles(DexFiles* dex_files, ArchEnum arch);
+#endif
+
ErrorCode LastErrorCode() { return last_error_.code; }
uint64_t LastErrorAddress() { return last_error_.address; }
private:
+ void FillInDexFrame();
void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc, uint64_t adjusted_pc);
size_t max_frames_;
@@ -87,6 +92,9 @@
std::vector<FrameData> frames_;
std::shared_ptr<Memory> process_memory_;
JitDebug* jit_debug_ = nullptr;
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+ DexFiles* dex_files_ = nullptr;
+#endif
ErrorData last_error_;
};
diff --git a/libbacktrace/unwind_dex_test.cpp b/libunwindstack/tests/DexFileTest.cpp
similarity index 66%
rename from libbacktrace/unwind_dex_test.cpp
rename to libunwindstack/tests/DexFileTest.cpp
index 449e662..d1338cb 100644
--- a/libbacktrace/unwind_dex_test.cpp
+++ b/libunwindstack/tests/DexFileTest.cpp
@@ -30,46 +30,11 @@
#include <gtest/gtest.h>
-#include "UnwindDexFile.h"
+#include "DexFile.h"
-class MemoryFake : public unwindstack::Memory {
- public:
- MemoryFake() = default;
- virtual ~MemoryFake() = default;
+#include "MemoryFake.h"
- size_t Read(uint64_t addr, void* buffer, size_t size) override;
-
- void SetMemory(uint64_t addr, const void* memory, size_t length);
-
- void Clear() { data_.clear(); }
-
- private:
- std::unordered_map<uint64_t, uint8_t> data_;
-};
-
-void MemoryFake::SetMemory(uint64_t addr, const void* memory, size_t length) {
- const uint8_t* src = reinterpret_cast<const uint8_t*>(memory);
- for (size_t i = 0; i < length; i++, addr++) {
- auto value = data_.find(addr);
- if (value != data_.end()) {
- value->second = src[i];
- } else {
- data_.insert({addr, src[i]});
- }
- }
-}
-
-size_t MemoryFake::Read(uint64_t addr, void* memory, size_t size) {
- uint8_t* dst = reinterpret_cast<uint8_t*>(memory);
- for (size_t i = 0; i < size; i++, addr++) {
- auto value = data_.find(addr);
- if (value == data_.end()) {
- return i;
- }
- dst[i] = value->second;
- }
- return size;
-}
+namespace unwindstack {
// Borrowed from art/dex/dex_file_test.cc.
static constexpr uint32_t kDexData[] = {
@@ -92,12 +57,12 @@
0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c,
};
-TEST(UnwindDexTest, from_file_open_non_exist) {
- UnwindDexFileFromFile dex_file;
+TEST(DexFileTest, from_file_open_non_exist) {
+ DexFileFromFile dex_file;
ASSERT_FALSE(dex_file.Open(0, "/file/does/not/exist"));
}
-TEST(UnwindDexTest, from_file_open_too_small) {
+TEST(DexFileTest, from_file_open_too_small) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
@@ -106,7 +71,7 @@
TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(art::DexFile::Header)) - 2)));
// Header too small.
- UnwindDexFileFromFile dex_file;
+ DexFileFromFile dex_file;
ASSERT_FALSE(dex_file.Open(0, tf.path));
// Header correct, file too small.
@@ -116,18 +81,18 @@
ASSERT_FALSE(dex_file.Open(0, tf.path));
}
-TEST(UnwindDexTest, from_file_open) {
+TEST(DexFileTest, from_file_open) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
ASSERT_EQ(sizeof(kDexData),
static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData)))));
- UnwindDexFileFromFile dex_file;
+ DexFileFromFile dex_file;
ASSERT_TRUE(dex_file.Open(0, tf.path));
}
-TEST(UnwindDexTest, from_file_open_non_zero_offset) {
+TEST(DexFileTest, from_file_open_non_zero_offset) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
@@ -135,38 +100,38 @@
ASSERT_EQ(sizeof(kDexData),
static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData)))));
- UnwindDexFileFromFile dex_file;
+ DexFileFromFile dex_file;
ASSERT_TRUE(dex_file.Open(0x100, tf.path));
}
-TEST(UnwindDexTest, from_memory_fail_too_small_for_header) {
+TEST(DexFileTest, from_memory_fail_too_small_for_header) {
MemoryFake memory;
memory.SetMemory(0x1000, kDexData, sizeof(art::DexFile::Header) - 1);
- UnwindDexFileFromMemory dex_file;
+ DexFileFromMemory dex_file;
ASSERT_FALSE(dex_file.Open(0x1000, &memory));
}
-TEST(UnwindDexTest, from_memory_fail_too_small_for_data) {
+TEST(DexFileTest, from_memory_fail_too_small_for_data) {
MemoryFake memory;
memory.SetMemory(0x1000, kDexData, sizeof(kDexData) - 2);
- UnwindDexFileFromMemory dex_file;
+ DexFileFromMemory dex_file;
ASSERT_FALSE(dex_file.Open(0x1000, &memory));
}
-TEST(UnwindDexTest, from_memory_open) {
+TEST(DexFileTest, from_memory_open) {
MemoryFake memory;
memory.SetMemory(0x1000, kDexData, sizeof(kDexData));
- UnwindDexFileFromMemory dex_file;
+ DexFileFromMemory dex_file;
ASSERT_TRUE(dex_file.Open(0x1000, &memory));
}
-TEST(UnwindDexTest, create_using_file) {
+TEST(DexFileTest, create_using_file) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
@@ -175,12 +140,12 @@
static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData)))));
MemoryFake memory;
- unwindstack::MapInfo info(0, 0x10000, 0, 0x5, tf.path);
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x500, &memory, &info));
+ MapInfo info(0, 0x10000, 0, 0x5, tf.path);
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x500, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
}
-TEST(UnwindDexTest, create_using_file_non_zero_start) {
+TEST(DexFileTest, create_using_file_non_zero_start) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
@@ -189,12 +154,12 @@
static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData)))));
MemoryFake memory;
- unwindstack::MapInfo info(0x100, 0x10000, 0, 0x5, tf.path);
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x600, &memory, &info));
+ MapInfo info(0x100, 0x10000, 0, 0x5, tf.path);
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x600, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
}
-TEST(UnwindDexTest, create_using_file_non_zero_offset) {
+TEST(DexFileTest, create_using_file_non_zero_offset) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
@@ -203,28 +168,28 @@
static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData)))));
MemoryFake memory;
- unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, tf.path);
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x400, &memory, &info));
+ MapInfo info(0x100, 0x10000, 0x200, 0x5, tf.path);
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x400, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
}
-TEST(UnwindDexTest, create_using_memory_empty_file) {
+TEST(DexFileTest, create_using_memory_empty_file) {
MemoryFake memory;
memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
- unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, "");
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x4000, &memory, &info));
+ MapInfo info(0x100, 0x10000, 0x200, 0x5, "");
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x4000, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
}
-TEST(UnwindDexTest, create_using_memory_file_does_not_exist) {
+TEST(DexFileTest, create_using_memory_file_does_not_exist) {
MemoryFake memory;
memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
- unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, "/does/not/exist");
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x4000, &memory, &info));
+ MapInfo info(0x100, 0x10000, 0x200, 0x5, "/does/not/exist");
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x4000, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
}
-TEST(UnwindDexTest, create_using_memory_file_is_malformed) {
+TEST(DexFileTest, create_using_memory_file_is_malformed) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
@@ -233,30 +198,30 @@
MemoryFake memory;
memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
- unwindstack::MapInfo info(0x4000, 0x10000, 0x200, 0x5, "/does/not/exist");
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x4000, &memory, &info));
+ MapInfo info(0x4000, 0x10000, 0x200, 0x5, "/does/not/exist");
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x4000, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
// Check it came from memory by clearing memory and verifying it fails.
memory.Clear();
- dex_file.reset(UnwindDexFile::Create(0x4000, &memory, &info));
+ dex_file.reset(DexFile::Create(0x4000, &memory, &info));
ASSERT_TRUE(dex_file == nullptr);
}
-TEST(UnwindDexTest, get_method_not_opened) {
+TEST(DexFileTest, get_method_not_opened) {
std::string method("something");
uint64_t method_offset = 100;
- UnwindDexFile dex_file;
+ DexFile dex_file;
dex_file.GetMethodInformation(0x100, &method, &method_offset);
EXPECT_EQ("something", method);
EXPECT_EQ(100U, method_offset);
}
-TEST(UnwindDexTest, get_method) {
+TEST(DexFileTest, get_method) {
MemoryFake memory;
memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
- unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, "");
- std::unique_ptr<UnwindDexFile> dex_file(UnwindDexFile::Create(0x4000, &memory, &info));
+ MapInfo info(0x100, 0x10000, 0x200, 0x5, "");
+ std::unique_ptr<DexFile> dex_file(DexFile::Create(0x4000, &memory, &info));
ASSERT_TRUE(dex_file != nullptr);
std::string method;
@@ -271,3 +236,5 @@
EXPECT_EQ("not_in_a_method", method);
EXPECT_EQ(0x123U, method_offset);
}
+
+} // namespace unwindstack
diff --git a/libunwindstack/tools/unwind.cpp b/libunwindstack/tools/unwind.cpp
index 33e5527..07e48af 100644
--- a/libunwindstack/tools/unwind.cpp
+++ b/libunwindstack/tools/unwind.cpp
@@ -96,13 +96,8 @@
unwinder.Unwind();
// Print the frames.
- const std::vector<unwindstack::FrameData>& frames = unwinder.frames();
for (size_t i = 0; i < unwinder.NumFrames(); i++) {
printf("%s\n", unwinder.FormatFrame(i).c_str());
- const unwindstack::FrameData* frame = &frames[i];
- if (frame->dex_pc != 0) {
- printf(" dex pc %" PRIx64 "\n", frame->dex_pc);
- }
}
}
diff --git a/lmkd/Android.bp b/lmkd/Android.bp
index e16db65..fc31693 100644
--- a/lmkd/Android.bp
+++ b/lmkd/Android.bp
@@ -6,6 +6,7 @@
"liblog",
"libcutils",
],
+ local_include_dirs: ["include"],
cflags: ["-Werror"],
init_rc: ["lmkd.rc"],
@@ -30,3 +31,17 @@
"liblog",
],
}
+
+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 0000000..72e3f4a
--- /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 0000000..fe6364d
--- /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 0000000..fa3b7a9
--- /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 838a5f4..42ec3ca 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>
/*
@@ -62,7 +62,6 @@
#define MEMCG_SYSFS_PATH "/dev/memcg/"
#define MEMCG_MEMORY_USAGE "/dev/memcg/memory.usage_in_bytes"
#define MEMCG_MEMORYSW_USAGE "/dev/memcg/memory.memsw.usage_in_bytes"
-#define ZONEINFO_PATH "/proc/zoneinfo"
#define LINE_MAX 128
#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
@@ -71,19 +70,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 +108,30 @@
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);
+};
-/* 3 memory pressure levels, 1 ctrl listen socket, 1 ctrl data socket */
-#define MAX_EPOLL_EVENTS 5
+/* 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
+
+/* 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 +286,49 @@
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;
- if (oomadj < OOM_SCORE_ADJ_MIN || oomadj > OOM_SCORE_ADJ_MAX) {
- ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj);
+ lmkd_pack_get_procprio(packet, ¶ms);
+
+ 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 +336,13 @@
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 +350,38 @@
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 +412,24 @@
}
}
-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 +441,43 @@
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,110 +490,57 @@
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;
+static int get_free_dsock() {
+ for (int i = 0; i < MAX_DATA_CONN; i++) {
+ if (data_sock[i].sock < 0) {
+ return i;
+ }
+ }
+ return -1;
+}
- if (ctrl_dfd >= 0) {
- ctrl_data_close();
- ctrl_dfd_reopened = 1;
+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;
}
- ctrl_dfd = accept(ctrl_lfd, NULL, NULL);
-
- 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;
}
-}
-
-static int zoneinfo_parse_protection(char *cp) {
- int max = 0;
- int zoneval;
- char *save_ptr;
-
- for (cp = strtok_r(cp, "(), ", &save_ptr); cp; cp = strtok_r(NULL, "), ", &save_ptr)) {
- zoneval = strtol(cp, &cp, 0);
- if (zoneval > max)
- max = zoneval;
- }
-
- return max;
-}
-
-static void zoneinfo_parse_line(char *line, struct sysmeminfo *mip) {
- char *cp = line;
- char *ap;
- char *save_ptr;
-
- cp = strtok_r(line, " ", &save_ptr);
- if (!cp)
- return;
-
- ap = strtok_r(NULL, " ", &save_ptr);
- if (!ap)
- return;
-
- if (!strcmp(cp, "nr_free_pages"))
- mip->nr_free_pages += strtol(ap, NULL, 0);
- else if (!strcmp(cp, "nr_file_pages"))
- mip->nr_file_pages += strtol(ap, NULL, 0);
- else if (!strcmp(cp, "nr_shmem"))
- mip->nr_shmem += strtol(ap, NULL, 0);
- else if (!strcmp(cp, "high"))
- mip->totalreserve_pages += strtol(ap, NULL, 0);
- else if (!strcmp(cp, "protection:"))
- mip->totalreserve_pages += zoneinfo_parse_protection(ap);
-}
-
-static int zoneinfo_parse(struct sysmeminfo *mip) {
- int fd;
- ssize_t size;
- char buf[PAGE_SIZE];
- char *save_ptr;
- char *line;
-
- memset(mip, 0, sizeof(struct sysmeminfo));
-
- fd = open(ZONEINFO_PATH, O_RDONLY | O_CLOEXEC);
- if (fd == -1) {
- ALOGE("%s open: errno=%d", ZONEINFO_PATH, errno);
- return -1;
- }
-
- size = read_all(fd, buf, sizeof(buf) - 1);
- if (size < 0) {
- ALOGE("%s read: errno=%d", ZONEINFO_PATH, errno);
- close(fd);
- return -1;
- }
- ALOG_ASSERT((size_t)size < sizeof(buf) - 1, "/proc/zoneinfo too large");
- buf[size] = 0;
-
- for (line = strtok_r(buf, "\n", &save_ptr); line; line = strtok_r(NULL, "\n", &save_ptr))
- zoneinfo_parse_line(line, mip);
-
- close(fd);
- return 0;
+ maxevents++;
}
static int get_free_memory(struct mem_size *ms) {
@@ -802,7 +774,7 @@
(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 +783,7 @@
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 +900,15 @@
}
}
-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 +934,7 @@
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 +942,10 @@
}
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 +982,27 @@
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 +1014,9 @@
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 +1031,14 @@
}
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 +1048,33 @@
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 0000000..cbf44e9
--- /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 0000000..4afaeb8
--- /dev/null
+++ b/lmkd/tests/lmkd_test.cpp
@@ -0,0 +1,373 @@
+/*
+ * 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) {
+ char buf[PATH_MAX + 1];
+ int ret = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
+ if (ret < 0) {
+ return false;
+ }
+ buf[ret] = '\0';
+ path = buf;
+ return true;
+}
+
+/* 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 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;
+}
+