Test that relro sharing actually saves memory.

Spawn 20 child processes, have them all load the library, and compare
the total PSS used in the case where we use dlopen() and the case where
we use android_dlopen_ext() with relro sharing. We assume we will save
at least 10% of the memory; in practise this example saves 40% or more
so this should be a reasonable threshold.

Bug: 14299541
Change-Id: Idccf6b8b0eb137abae2200f1ce68fb76b3cbdd75
This commit is contained in:
Torne (Richard Coles) 2014-05-02 14:57:42 +01:00
parent 9b5ee4aa85
commit 2605261691
2 changed files with 127 additions and 0 deletions

View file

@ -252,8 +252,12 @@ bionic-unit-tests_ldflags := \
-Wl,--export-dynamic \
-Wl,-u,DlSymTestFunction \
bionic-unit-tests_c_includes := \
$(call include-path-for, libpagemap) \
bionic-unit-tests_shared_libraries_target := \
libdl \
libpagemap \
module := bionic-unit-tests
module_tag := optional

View file

@ -24,8 +24,11 @@
#include <unistd.h>
#include <android/dlext.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pagemap/pagemap.h>
#define ASSERT_DL_NOTNULL(ptr) \
ASSERT_TRUE(ptr != NULL) << "dlerror: " << dlerror()
@ -209,6 +212,8 @@ protected:
EXPECT_EQ(4, f());
}
void SpawnChildrenAndMeasurePss(const char* lib, bool share_relro, size_t* pss_out);
android_dlextinfo extinfo_;
char relro_file_[PATH_MAX];
};
@ -230,3 +235,121 @@ TEST_F(DlExtRelroSharingTest, RelroFileEmpty) {
ASSERT_NO_FATAL_FAILURE(TryUsingRelro(LIBNAME));
}
TEST_F(DlExtRelroSharingTest, VerifyMemorySaving) {
ASSERT_NO_FATAL_FAILURE(CreateRelroFile(LIBNAME));
int relro_fd = open(relro_file_, O_RDONLY);
ASSERT_NOERROR(relro_fd);
extinfo_.flags |= ANDROID_DLEXT_USE_RELRO;
extinfo_.relro_fd = relro_fd;
int pipefd[2];
ASSERT_NOERROR(pipe(pipefd));
size_t without_sharing, with_sharing;
ASSERT_NO_FATAL_FAILURE(SpawnChildrenAndMeasurePss(LIBNAME, false, &without_sharing));
ASSERT_NO_FATAL_FAILURE(SpawnChildrenAndMeasurePss(LIBNAME, true, &with_sharing));
// We expect the sharing to save at least 10% of the total PSS. In practice
// it saves 40%+ for this test.
size_t expected_size = without_sharing - (without_sharing/10);
EXPECT_LT(with_sharing, expected_size);
}
void getPss(pid_t pid, size_t* pss_out) {
pm_kernel_t* kernel;
ASSERT_EQ(0, pm_kernel_create(&kernel));
pm_process_t* process;
ASSERT_EQ(0, pm_process_create(kernel, pid, &process));
pm_map_t** maps;
size_t num_maps;
ASSERT_EQ(0, pm_process_maps(process, &maps, &num_maps));
size_t total_pss = 0;
for (size_t i = 0; i < num_maps; i++) {
pm_memusage_t usage;
ASSERT_EQ(0, pm_map_usage(maps[i], &usage));
total_pss += usage.pss;
}
*pss_out = total_pss;
free(maps);
pm_process_destroy(process);
pm_kernel_destroy(kernel);
}
void DlExtRelroSharingTest::SpawnChildrenAndMeasurePss(const char* lib, bool share_relro,
size_t* pss_out) {
const int CHILDREN = 20;
// Create children
pid_t childpid[CHILDREN];
int childpipe[CHILDREN];
for (int i=0; i<CHILDREN; ++i) {
char read_buf;
int child_done_pipe[2], parent_done_pipe[2];
ASSERT_NOERROR(pipe(child_done_pipe));
ASSERT_NOERROR(pipe(parent_done_pipe));
pid_t child = fork();
if (child == 0) {
// close the 'wrong' ends of the pipes in the child
close(child_done_pipe[0]);
close(parent_done_pipe[1]);
// open the library
void* handle;
if (share_relro) {
handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo_);
} else {
handle = dlopen(lib, RTLD_NOW);
}
if (handle == NULL) {
fprintf(stderr, "in child: %s\n", dlerror());
exit(1);
}
// close write end of child_done_pipe to signal the parent that we're done.
close(child_done_pipe[1]);
// wait for the parent to close parent_done_pipe, then exit
read(parent_done_pipe[0], &read_buf, 1);
exit(0);
}
ASSERT_NOERROR(child);
// close the 'wrong' ends of the pipes in the parent
close(child_done_pipe[1]);
close(parent_done_pipe[0]);
// wait for the child to be done
read(child_done_pipe[0], &read_buf, 1);
close(child_done_pipe[0]);
// save the child's pid and the parent_done_pipe
childpid[i] = child;
childpipe[i] = parent_done_pipe[1];
}
// Sum the PSS of all the children
size_t total_pss = 0;
for (int i=0; i<CHILDREN; ++i) {
size_t child_pss;
ASSERT_NO_FATAL_FAILURE(getPss(childpid[i], &child_pss));
total_pss += child_pss;
}
*pss_out = total_pss;
// Close pipes and wait for children to exit
for (int i=0; i<CHILDREN; ++i) {
ASSERT_NOERROR(close(childpipe[i]));
}
for (int i=0; i<CHILDREN; ++i) {
int status;
ASSERT_EQ(childpid[i], waitpid(childpid[i], &status, 0));
ASSERT_TRUE(WIFEXITED(status));
ASSERT_EQ(0, WEXITSTATUS(status));
}
}