blob: f501f2655f3148c73af6225183786ec7ef46c69e [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/object.h>
#include <mini-process/mini-process.h>
#include <unittest/unittest.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#define LOCAL_TRACE 0
#define LTRACEF(str, x...) \
do { \
if (LOCAL_TRACE) { \
printf("%s:%d: " str, __func__, __LINE__, ##x); \
} \
} while (0)
namespace {
// A function that returns a handle to get the info of.
// Typically get_test_process, get_test_job, zx_process_self, zx_job_default.
typedef zx_handle_t (*handle_source_fn)();
bool handle_valid_on_valid_handle_succeeds() {
BEGIN_TEST;
EXPECT_EQ(zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_VALID,
nullptr, 0, nullptr, nullptr),
ZX_OK);
END_TEST;
}
bool handle_valid_on_closed_handle_fails() {
BEGIN_TEST;
// Create an event and show that it's valid.
zx_handle_t event;
ASSERT_EQ(zx_event_create(0u, &event), ZX_OK);
EXPECT_EQ(zx_object_get_info(event, ZX_INFO_HANDLE_VALID,
nullptr, 0, nullptr, nullptr),
ZX_OK);
// Close the handle and show that it becomes invalid.
zx_handle_close(event);
EXPECT_NE(zx_object_get_info(event, ZX_INFO_HANDLE_VALID,
nullptr, 0, nullptr, nullptr),
ZX_OK);
END_TEST;
}
// Tests that ZX_INFO_TASK_STATS seems to work.
bool task_stats_smoke() {
BEGIN_TEST;
zx_info_task_stats_t info;
ASSERT_EQ(zx_object_get_info(zx_process_self(), ZX_INFO_TASK_STATS,
&info, sizeof(info), nullptr, nullptr),
ZX_OK);
ASSERT_GT(info.mem_private_bytes, 0u);
ASSERT_GT(info.mem_shared_bytes, 0u);
ASSERT_GE(info.mem_mapped_bytes,
info.mem_private_bytes + info.mem_shared_bytes);
ASSERT_GT(info.mem_scaled_shared_bytes, 0u);
ASSERT_GT(info.mem_shared_bytes, info.mem_scaled_shared_bytes);
END_TEST;
}
// Structs to keep track of VMARs/mappings in the test child process.
typedef struct test_mapping {
uintptr_t base;
size_t size;
uint32_t flags; // ZX_INFO_MAPS_MMU_FLAG_PERM_{READ,WRITE,EXECUTE}
} test_mapping_t;
// A VMO that the test process maps or has a handle to.
typedef struct test_vmo {
zx_koid_t koid;
size_t size;
uint32_t flags; // ZX_INFO_VMO_VIA_{HANDLE,MAPPING}
} test_vmo_t;
typedef struct test_mapping_info {
uintptr_t vmar_base;
size_t vmar_size;
size_t num_mappings;
test_mapping_t* mappings; // num_mappings entries
size_t num_vmos;
test_vmo_t* vmos; // num_vmos entries
} test_mapping_info_t;
// Gets the koid of the object pointed to by |handle|.
zx_status_t get_koid(zx_handle_t handle, zx_koid_t* koid) {
zx_info_handle_basic_t info;
zx_status_t s = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC,
&info, sizeof(info), nullptr, nullptr);
if (s == ZX_OK) {
*koid = info.koid;
}
return s;
}
// Returns a process singleton. ZX_INFO_PROCESS_MAPS can't run on the current
// process, so tests should use this instead.
// This handle is leaked, and we expect our process teardown to clean it up
// naturally.
zx_handle_t get_test_process_etc(const test_mapping_info_t** info) {
static zx_handle_t test_process = ZX_HANDLE_INVALID;
static test_mapping_info_t* test_info = nullptr;
if (info != nullptr) {
*info = nullptr;
}
if (test_process == ZX_HANDLE_INVALID) {
// Create a VMO whose handle we'll give to the test process.
// It will not be mapped into the test process's VMAR.
const size_t unmapped_vmo_size = PAGE_SIZE;
zx_handle_t unmapped_vmo;
zx_status_t s = zx_vmo_create(
unmapped_vmo_size, /* options */ 0u, &unmapped_vmo);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "zx_vmo_create"); // Poison the test.
return ZX_HANDLE_INVALID;
}
zx_koid_t unmapped_vmo_koid;
s = get_koid(unmapped_vmo, &unmapped_vmo_koid);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "get_koid");
return ZX_HANDLE_INVALID;
}
// Try to set the name, but ignore any errors.
static const char unmapped_vmo_name[] = "test:unmapped";
zx_object_set_property(unmapped_vmo, ZX_PROP_NAME,
unmapped_vmo_name, sizeof(unmapped_vmo_name));
// Failures from here on will start to leak handles, but they'll
// be cleaned up when this binary exits.
zx_handle_t process;
zx_handle_t vmar;
static const char pname[] = "object-info-minipr";
s = zx_process_create(zx_job_default(), pname, sizeof(pname),
/* options */ 0u, &process, &vmar);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "zx_process_create");
return ZX_HANDLE_INVALID;
}
zx_handle_t thread;
static const char tname[] = "object-info-minith";
s = zx_thread_create(process, tname, sizeof(tname),
/* options */ 0u, &thread);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "zx_thread_create");
return ZX_HANDLE_INVALID;
}
zx_handle_t minip_channel;
// Start the process before we mess with the VMAR,
// so we don't step on the mapping done by start_mini_process_etc.
s = start_mini_process_etc(process, thread, vmar, unmapped_vmo,
&minip_channel);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "start_mini_process_etc");
return ZX_HANDLE_INVALID;
}
unmapped_vmo = ZX_HANDLE_INVALID; // Transferred to the test process.
zx_handle_close(minip_channel);
// Create a child VMAR and a mapping under it, so we have
// something interesting to look at when getting the process's
// memory maps. After this, the process maps should at least contain:
//
// Root Aspace
// - Root VMAR
// - Code+stack mapping created by start_mini_process_etc
// - Sub VMAR created below
// - kNumMappings mappings created below
static const size_t kNumMappings = 8;
// Leaked on failure. Never freed on success.
test_mapping_info_t* ti = (test_mapping_info_t*)malloc(sizeof(*ti));
ti->num_mappings = kNumMappings;
ti->mappings =
(test_mapping_t*)malloc(kNumMappings * sizeof(test_mapping_t));
// Big enough to fit all of the mappings with some slop.
ti->vmar_size = PAGE_SIZE * kNumMappings * 16;
zx_handle_t sub_vmar;
s = zx_vmar_allocate(vmar, /* offset */ 0,
ti->vmar_size,
ZX_VM_FLAG_CAN_MAP_READ |
ZX_VM_FLAG_CAN_MAP_WRITE |
ZX_VM_FLAG_CAN_MAP_EXECUTE,
&sub_vmar, &ti->vmar_base);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "zx_vmar_allocate");
return ZX_HANDLE_INVALID;
}
zx_handle_t vmo;
const size_t vmo_size = PAGE_SIZE * kNumMappings;
s = zx_vmo_create(vmo_size, /* options */ 0u, &vmo);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "zx_vmo_create");
return ZX_HANDLE_INVALID;
}
zx_koid_t vmo_koid;
s = get_koid(vmo, &vmo_koid);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "get_koid");
return ZX_HANDLE_INVALID;
}
// Try to set the name, but ignore any errors.
static const char vmo_name[] = "test:mapped";
zx_object_set_property(vmo, ZX_PROP_NAME, vmo_name, sizeof(vmo_name));
// Record the VMOs now that we have both of them.
ti->num_vmos = 2;
ti->vmos = (test_vmo_t*)malloc(2 * sizeof(test_vmo_t));
ti->vmos[0].koid = unmapped_vmo_koid;
ti->vmos[0].size = unmapped_vmo_size;
ti->vmos[0].flags = ZX_INFO_VMO_VIA_HANDLE;
ti->vmos[1].koid = vmo_koid;
ti->vmos[1].size = vmo_size;
ti->vmos[1].flags = ZX_INFO_VMO_VIA_MAPPING;
// Map each page of the VMO to some arbitray location in the VMAR.
for (size_t i = 0; i < kNumMappings; i++) {
test_mapping_t* m = &ti->mappings[i];
m->size = PAGE_SIZE;
// Pick flags for this mapping; cycle through different
// combinations for the test. Must always have READ set
// to be mapped.
m->flags = ZX_VM_FLAG_PERM_READ;
if (i & 1) {
m->flags |= ZX_VM_FLAG_PERM_WRITE;
}
if (i & 2) {
m->flags |= ZX_VM_FLAG_PERM_EXECUTE;
}
s = zx_vmar_map(sub_vmar, /* vmar_offset (ignored) */ 0,
vmo, /* vmo_offset */ i * PAGE_SIZE,
/* len */ PAGE_SIZE,
m->flags,
&m->base);
if (s != ZX_OK) {
char msg[32];
snprintf(msg, sizeof(msg), "zx_vmar_map: [%zd]", i);
EXPECT_EQ(s, ZX_OK, msg);
return ZX_HANDLE_INVALID;
}
}
zx_handle_close(vmo); // Kept alive by the VMAR.
zx_handle_close(sub_vmar); // Kept alive by the process.
test_process = process;
test_info = ti;
}
if (info != nullptr) {
*info = test_info;
}
return test_process;
}
zx_handle_t get_test_process() {
return get_test_process_etc(nullptr);
}
// Tests that ZX_INFO_PROCESS_MAPS seems to work.
bool process_maps_smoke() {
BEGIN_TEST;
const test_mapping_info_t* test_info;
const zx_handle_t process = get_test_process_etc(&test_info);
ASSERT_NONNULL(test_info, "get_test_process_etc");
// Buffer big enough to read all of the test process's map entries.
const size_t bufsize = test_info->num_mappings * 4 * sizeof(zx_info_maps_t);
zx_info_maps_t* maps = (zx_info_maps_t*)malloc(bufsize);
// Read the map entries.
size_t actual;
size_t avail;
ASSERT_EQ(zx_object_get_info(process, ZX_INFO_PROCESS_MAPS,
maps, bufsize,
&actual, &avail),
ZX_OK);
EXPECT_EQ(actual, avail, "Should have read all entries");
// The first two entries should always be the ASpace and root VMAR.
ASSERT_GE(actual, 2u, "Root aspace/vmar missing?");
EXPECT_EQ(maps[0].type, (uint32_t)ZX_INFO_MAPS_TYPE_ASPACE);
EXPECT_EQ(maps[0].depth, 0u, "ASpace depth");
EXPECT_GT(maps[0].size, 1u * 1024 * 1024 * 1024 * 1024, "ASpace size");
EXPECT_EQ(maps[1].type, (uint32_t)ZX_INFO_MAPS_TYPE_VMAR);
EXPECT_EQ(maps[1].depth, 1u, "Root VMAR depth");
EXPECT_GT(maps[1].size, 1u * 1024 * 1024 * 1024 * 1024, "Root VMAR size");
// Look for the VMAR and all of the mappings we created.
bool saw_vmar = false; // Whether we've seen our VMAR.
bool under_vmar = false; // If we're looking at children of our VMAR.
size_t vmar_depth = 0;
uint32_t saw_mapping = 0u; // bitmask of mapping indices we've seen.
ASSERT_LT(test_info->num_mappings, 32u);
LTRACEF("\n");
for (size_t i = 2; i < actual; i++) {
zx_info_maps_t* entry = maps + i;
char msg[128];
snprintf(msg, sizeof(msg),
"[%2zd] %*stype:%u base:0x%" PRIx64 " size:%" PRIu64,
i, (int)(entry->depth - 2) * 2, "",
entry->type, entry->base, entry->size);
LTRACEF("%s\n", msg);
// All entries should be children of the root VMAR.
EXPECT_GT(entry->depth, 1u, msg);
EXPECT_TRUE(entry->type >= ZX_INFO_MAPS_TYPE_ASPACE &&
entry->type < ZX_INFO_MAPS_TYPE_LAST,
msg);
if (entry->type == ZX_INFO_MAPS_TYPE_VMAR &&
entry->base == test_info->vmar_base &&
entry->size == test_info->vmar_size) {
saw_vmar = true;
under_vmar = true;
vmar_depth = entry->depth;
} else if (under_vmar) {
if (entry->depth <= vmar_depth) {
under_vmar = false;
vmar_depth = 0;
} else {
// |entry| should be a child mapping of our VMAR.
EXPECT_EQ((uint32_t)ZX_INFO_MAPS_TYPE_MAPPING, entry->type,
msg);
// The mapping should fit inside the VMAR.
EXPECT_LE(test_info->vmar_base, entry->base, msg);
EXPECT_LE(entry->base + entry->size,
test_info->vmar_base + test_info->vmar_size,
msg);
// Look for it in the expected mappings.
bool found = false;
for (size_t j = 0; j < test_info->num_mappings; j++) {
const test_mapping_t* t = &test_info->mappings[j];
if (t->base == entry->base && t->size == entry->size) {
// Make sure we don't see duplicates.
EXPECT_EQ(0u, saw_mapping & (1 << j), msg);
saw_mapping |= 1 << j;
EXPECT_EQ(t->flags, entry->u.mapping.mmu_flags, msg);
found = true;
break;
}
}
EXPECT_TRUE(found, msg);
}
}
}
// Make sure we saw our VMAR and all of our mappings.
EXPECT_TRUE(saw_vmar);
EXPECT_EQ((uint32_t)(1 << test_info->num_mappings) - 1, saw_mapping);
// Do one more read with a short buffer to test actual < avail.
const size_t bufsize2 = actual * 3 / 4 * sizeof(zx_info_maps_t);
zx_info_maps_t* maps2 = (zx_info_maps_t*)malloc(bufsize2);
size_t actual2;
size_t avail2;
ASSERT_EQ(zx_object_get_info(process, ZX_INFO_PROCESS_MAPS,
maps2, bufsize2,
&actual2, &avail2),
ZX_OK);
EXPECT_LT(actual2, avail2);
// mini-process is very simple, and won't have modified its own memory
// maps since the previous dump. Its "committed_pages" values could be
// different, though.
EXPECT_EQ(avail, avail2);
LTRACEF("\n");
EXPECT_GT(actual2, 3u); // Make sure we're looking at something.
for (size_t i = 0; i < actual2; i++) {
zx_info_maps_t* e1 = maps + i;
zx_info_maps_t* e2 = maps2 + i;
char msg[128];
snprintf(msg, sizeof(msg),
"[%2zd] %*stype:%u/%u base:0x%" PRIx64 "/0x%" PRIx64
" size:%" PRIu64 "/%" PRIu64,
i, (int)e1->depth * 2, "",
e1->type, e2->type, e1->base, e2->base, e1->size, e2->size);
LTRACEF("%s\n", msg);
EXPECT_EQ(e1->base, e2->base, msg);
EXPECT_EQ(e1->size, e2->size, msg);
EXPECT_EQ(e1->depth, e2->depth, msg);
EXPECT_EQ(e1->type, e2->type, msg);
if (e1->type == e2->type && e2->type == ZX_INFO_MAPS_TYPE_MAPPING) {
EXPECT_EQ(e1->u.mapping.mmu_flags, e2->u.mapping.mmu_flags, msg);
}
}
free(maps);
free(maps2);
END_TEST;
}
template <uint32_t Topic, typename EntryType>
bool self_fails() {
BEGIN_TEST;
EntryType entries[2];
size_t actual;
size_t avail;
// It's illegal to look at your own entries, because the output buffer
// lives inside the address space that's being examined.
EXPECT_EQ(zx_object_get_info(zx_process_self(), Topic,
entries, sizeof(entries), &actual, &avail),
ZX_ERR_ACCESS_DENIED);
END_TEST;
}
template <uint32_t Topic, typename EntryType>
bool invalid_handle_fails() {
BEGIN_TEST;
EntryType entries[2];
size_t actual;
size_t avail;
// Passing ZX_HANDLE_INVALID should fail.
EXPECT_EQ(zx_object_get_info(ZX_HANDLE_INVALID, Topic,
entries, sizeof(entries), &actual, &avail),
ZX_ERR_BAD_HANDLE);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetWrongHandle>
bool wrong_handle_type_fails() {
BEGIN_TEST;
EntryType entries[2];
size_t actual;
size_t avail;
// Passing a handle to an unsupported object type should fail.
EXPECT_NE(zx_object_get_info(GetWrongHandle(), Topic,
entries, sizeof(entries), &actual, &avail),
ZX_OK);
END_TEST;
}
template <uint32_t Topic, typename EntryType,
handle_source_fn GetHandle, zx_rights_t MissingRights>
bool missing_rights_fails() {
BEGIN_TEST;
// Call should succeed with the default rights.
zx_handle_t obj = GetHandle();
EntryType entries[2];
size_t actual;
size_t avail;
EXPECT_EQ(zx_object_get_info(obj, Topic,
entries, sizeof(entries), &actual, &avail),
ZX_OK);
// Get the test object handle rights.
zx_info_handle_basic_t hi;
ASSERT_EQ(zx_object_get_info(obj, ZX_INFO_HANDLE_BASIC,
&hi, sizeof(hi), nullptr, nullptr),
ZX_OK);
char msg[32];
snprintf(msg, sizeof(msg), "rights 0x%" PRIx32, hi.rights);
EXPECT_EQ(hi.rights & MissingRights, MissingRights, msg);
// Create a handle without the important rights.
zx_handle_t handle;
ASSERT_EQ(zx_handle_duplicate(obj, hi.rights & ~MissingRights, &handle),
ZX_OK);
// Call should fail without these rights.
EXPECT_EQ(zx_object_get_info(handle, Topic,
entries, sizeof(entries), &actual, &avail),
ZX_ERR_ACCESS_DENIED);
zx_handle_close(handle);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool single_zero_buffer_fails() {
BEGIN_TEST;
EntryType entry;
size_t actual;
size_t avail;
// Passing a zero-sized buffer to a topic that expects a single
// in/out entry should fail.
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
&entry, // buffer
0, // len
&actual, &avail),
ZX_ERR_BUFFER_TOO_SMALL);
EXPECT_EQ(0u, actual);
EXPECT_GT(avail, 0u);
END_TEST;
}
template <uint32_t Topic, handle_source_fn GetHandle>
bool multi_zero_buffer_succeeds() {
BEGIN_TEST;
size_t actual;
size_t avail;
// Passing a zero-sized null buffer to a topic that can handle multiple
// in/out entries should succeed.
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
nullptr, // buffer
0, // len
&actual, &avail),
ZX_OK);
EXPECT_EQ(0u, actual);
EXPECT_GT(avail, 0u);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool short_buffer_succeeds() {
BEGIN_TEST;
EntryType entries[1];
size_t actual;
size_t avail;
// Passing a buffer shorter than avail should succeed.
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
entries,
sizeof(entries),
&actual, &avail),
ZX_OK);
EXPECT_EQ(1u, actual);
EXPECT_GT(avail, actual);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool null_avail_actual_succeeds() {
BEGIN_TEST;
EntryType entries[2];
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
entries, sizeof(entries),
nullptr, // actual
nullptr), // avail
ZX_OK);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool bad_buffer_fails() {
BEGIN_TEST;
size_t actual;
size_t avail;
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
// Bad buffer pointer value.
(EntryType*)1,
sizeof(EntryType),
&actual, &avail),
ZX_ERR_INVALID_ARGS);
END_TEST;
}
// Tests the behavior when passing a buffer that starts in mapped
// memory but crosses into unmapped memory.
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool partially_unmapped_buffer_fails() {
BEGIN_TEST;
// Create a two-page VMAR.
zx_handle_t vmar;
uintptr_t vmar_addr;
ASSERT_EQ(zx_vmar_allocate(zx_vmar_root_self(),
0, 2 * PAGE_SIZE,
ZX_VM_FLAG_CAN_MAP_READ |
ZX_VM_FLAG_CAN_MAP_WRITE |
ZX_VM_FLAG_CAN_MAP_SPECIFIC,
&vmar, &vmar_addr),
ZX_OK);
// Create a one-page VMO.
zx_handle_t vmo;
ASSERT_EQ(zx_vmo_create(PAGE_SIZE, 0, &vmo), ZX_OK);
// Map the first page of the VMAR.
uintptr_t vmo_addr;
ASSERT_EQ(zx_vmar_map(vmar, 0, vmo, 0, PAGE_SIZE,
ZX_VM_FLAG_SPECIFIC |
ZX_VM_FLAG_PERM_READ |
ZX_VM_FLAG_PERM_WRITE,
&vmo_addr),
ZX_OK);
ASSERT_EQ(vmar_addr, vmo_addr);
// Point to a spot in the mapped page just before the unmapped region:
// the first entry will hit mapped memory, the second entry will hit
// unmapped memory.
EntryType* entries = (EntryType*)(vmo_addr + PAGE_SIZE) - 1;
size_t actual;
size_t avail;
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
entries, sizeof(EntryType) * 4,
&actual, &avail),
// Bad user buffer should return ZX_ERR_INVALID_ARGS.
ZX_ERR_INVALID_ARGS);
zx_vmar_destroy(vmar);
zx_handle_close(vmar);
zx_handle_close(vmo);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool bad_actual_fails() {
BEGIN_TEST;
EntryType entries[2];
size_t avail;
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
entries, sizeof(entries),
// Bad actual pointer value.
(size_t*)1,
&avail),
ZX_ERR_INVALID_ARGS);
END_TEST;
}
template <uint32_t Topic, typename EntryType, handle_source_fn GetHandle>
bool bad_avail_fails() {
BEGIN_TEST;
EntryType entries[2];
size_t actual;
EXPECT_EQ(zx_object_get_info(GetHandle(), Topic,
entries, sizeof(entries), &actual,
// Bad available pointer value.
(size_t*)1),
ZX_ERR_INVALID_ARGS);
END_TEST;
}
// Tests that ZX_INFO_PROCESS_VMOS seems to work.
bool process_vmos_smoke() {
BEGIN_TEST;
const test_mapping_info_t* test_info;
const zx_handle_t process = get_test_process_etc(&test_info);
ASSERT_NONNULL(test_info, "get_test_process_etc");
// Buffer big enough to read all of the test process's VMO entries.
// There'll be one per mapping, one for the unmapped VMO, plus some
// extras (at least the vDSO and the mini-process stack).
const size_t bufsize =
(test_info->num_mappings + 1 + 8) * sizeof(zx_info_vmo_t);
zx_info_vmo_t* vmos = (zx_info_vmo_t*)malloc(bufsize);
// Read the VMO entries.
size_t actual;
size_t avail;
ASSERT_EQ(zx_object_get_info(process, ZX_INFO_PROCESS_VMOS,
vmos, bufsize,
&actual, &avail),
ZX_OK);
EXPECT_EQ(actual, avail, "Should have read all entries");
// Look for the expected VMOs.
uint32_t saw_vmo = 0u; // Bitmask of VMO indices we've seen
ASSERT_LT(test_info->num_vmos, 32u);
LTRACEF("\n");
for (size_t i = 0; i < actual; i++) {
zx_info_vmo_t* entry = vmos + i;
char msg[128];
snprintf(msg, sizeof(msg),
"[%2zd] koid:%" PRIu64 " name:'%s' size:%" PRIu64
" flags:0x%" PRIx32,
i, entry->koid, entry->name, entry->size_bytes, entry->flags);
LTRACEF("%s\n", msg);
// Look for it in the expected VMOs. We won't find all VMOs here,
// since we don't track the vDSO or mini-process stack.
for (size_t j = 0; j < test_info->num_vmos; j++) {
const test_vmo_t* t = &test_info->vmos[j];
if (t->koid == entry->koid && t->size == entry->size_bytes) {
// These checks aren't appropriate for all VMOs.
// The VMOs we track are:
// - Only mapped or via handle, not both
// - Not clones
// - Not shared
EXPECT_EQ(entry->parent_koid, 0u, msg);
EXPECT_EQ(entry->num_children, 0u, msg);
EXPECT_EQ(entry->share_count, 1u, msg);
EXPECT_EQ(t->flags & entry->flags, t->flags, msg);
if (entry->flags & ZX_INFO_VMO_VIA_HANDLE) {
EXPECT_EQ(entry->num_mappings, 0u, msg);
} else {
EXPECT_NE(entry->flags & ZX_INFO_VMO_VIA_MAPPING, 0u, msg);
EXPECT_EQ(
entry->num_mappings, test_info->num_mappings, msg);
}
EXPECT_EQ(entry->flags & ZX_INFO_VMO_IS_COW_CLONE, 0u, msg);
saw_vmo |= 1 << j; // Duplicates are fine and expected
break;
}
}
// All of our VMOs should be paged, not physical.
EXPECT_EQ(ZX_INFO_VMO_TYPE(entry->flags), ZX_INFO_VMO_TYPE_PAGED, msg);
// Each entry should be via either map or handle, but not both.
// NOTE: This could change in the future, but currently reflects
// the way things work.
const uint32_t kViaMask =
ZX_INFO_VMO_VIA_HANDLE | ZX_INFO_VMO_VIA_MAPPING;
EXPECT_NE(entry->flags & kViaMask, kViaMask, msg);
// TODO(dbort): Test more fields/flags of zx_info_vmo_t by adding some
// clones, shared VMOs, mapped+handle VMOs, physical VMOs if possible.
// All but committed_bytes should be predictable.
}
// Make sure we saw all of the expected VMOs.
EXPECT_EQ((uint32_t)(1 << test_info->num_vmos) - 1, saw_vmo);
// Do one more read with a short buffer to test actual < avail.
const size_t bufsize2 = actual * 3 / 4 * sizeof(zx_info_vmo_t);
zx_info_vmo_t* vmos2 = (zx_info_vmo_t*)malloc(bufsize2);
size_t actual2;
size_t avail2;
ASSERT_EQ(zx_object_get_info(process, ZX_INFO_PROCESS_VMOS,
vmos2, bufsize2,
&actual2, &avail2),
ZX_OK);
EXPECT_LT(actual2, avail2);
// mini-process is very simple, and won't have modified its own set of VMOs
// since the previous dump.
EXPECT_EQ(avail, avail2);
LTRACEF("\n");
EXPECT_GT(actual2, 3u); // Make sure we're looking at something.
for (size_t i = 0; i < actual2; i++) {
zx_info_vmo_t* e1 = vmos + i;
zx_info_vmo_t* e2 = vmos2 + i;
char msg[128];
snprintf(msg, sizeof(msg),
"[%2zd] koid:%" PRIu64 "/%" PRIu64 " name:'%s'/'%s' "
"size:%" PRIu64 "/%" PRIu64 " flags:0x%" PRIx32 "/0x%" PRIx32,
i, e1->koid, e2->koid, e1->name, e2->name,
e1->size_bytes, e2->size_bytes, e1->flags, e2->flags);
LTRACEF("%s\n", msg);
EXPECT_EQ(e1->koid, e2->koid, msg);
EXPECT_EQ(e1->size_bytes, e2->size_bytes, msg);
EXPECT_EQ(e1->flags, e2->flags, msg);
if (e1->flags == e2->flags && e2->flags & ZX_INFO_VMO_VIA_HANDLE) {
EXPECT_EQ(e1->handle_rights, e2->handle_rights, msg);
}
}
free(vmos);
free(vmos2);
END_TEST;
}
// ZX_INFO_JOB_PROCESS/ZX_INFO_JOB_CHILDREN tests
// Returns a job with the structure:
// - returned job
// - child process 1
// - child process 2
// - child process 3 (kTestJobChildProcs)
// - child job 1
// - grandchild process 1.1
// - grandchild job 1.1
// - child job 2 (kTestJobChildJobs)
// - grandchild process 2.1
// - grandchild job 2.1
const size_t kTestJobChildProcs = 3;
const size_t kTestJobChildJobs = 2;
zx_handle_t get_test_job() {
static zx_handle_t test_job = ZX_HANDLE_INVALID;
if (test_job == ZX_HANDLE_INVALID) {
char msg[64];
zx_handle_t root;
zx_status_t s = zx_job_create(zx_job_default(), 0, &root);
if (s != ZX_OK) {
EXPECT_EQ(s, ZX_OK, "zx_job_create"); // Poison the test.
return ZX_HANDLE_INVALID;
}
for (size_t i = 0; i < kTestJobChildProcs; i++) {
zx_handle_t proc;
zx_handle_t vmar;
s = zx_process_create(root, "child", 6, 0, &proc, &vmar);
if (s != ZX_OK) {
snprintf(msg, sizeof(msg), "zx_process_create(child %zu)", i);
goto fail;
}
}
for (size_t i = 0; i < kTestJobChildJobs; i++) {
zx_handle_t job;
s = zx_job_create(root, 0, &job);
if (s != ZX_OK) {
snprintf(msg, sizeof(msg), "zx_job_create(child %zu)", i);
goto fail;
}
zx_handle_t proc;
zx_handle_t vmar;
s = zx_process_create(job, "grandchild", 6, 0, &proc, &vmar);
if (s != ZX_OK) {
snprintf(msg, sizeof(msg), "zx_process_create(grandchild)");
goto fail;
}
zx_handle_t subjob;
s = zx_job_create(job, 0, &subjob);
if (s != ZX_OK) {
snprintf(msg, sizeof(msg), "zx_job_create(grandchild)");
goto fail;
}
}
if (false) {
fail:
EXPECT_EQ(s, ZX_OK, msg); // Poison the test
zx_task_kill(root); // Clean up all tasks; leaks handles
return ZX_HANDLE_INVALID;
}
test_job = root;
}
return test_job;
}
// The jobch_helper_* (job child helper) functions allow testing both
// ZX_INFO_JOB_PROCESS and ZX_INFO_JOB_CHILDREN.
bool jobch_helper_smoke(uint32_t topic, size_t expected_count) {
BEGIN_TEST;
zx_koid_t koids[32];
size_t actual;
size_t avail;
EXPECT_EQ(zx_object_get_info(get_test_job(), topic,
koids, sizeof(koids), &actual, &avail),
ZX_OK);
EXPECT_EQ(expected_count, actual);
EXPECT_EQ(expected_count, avail);
// All returned koids should produce a valid handle when passed to
// zx_object_get_child.
for (size_t i = 0; i < actual; i++) {
char msg[32];
snprintf(msg, sizeof(msg), "koid %zu", koids[i]);
zx_handle_t h = ZX_HANDLE_INVALID;
EXPECT_EQ(zx_object_get_child(get_test_job(), koids[i],
ZX_RIGHT_SAME_RIGHTS, &h),
ZX_OK, msg);
zx_handle_close(h);
}
END_TEST;
}
bool job_processes_smoke() {
return jobch_helper_smoke(ZX_INFO_JOB_PROCESSES, kTestJobChildProcs);
}
bool job_children_smoke() {
return jobch_helper_smoke(ZX_INFO_JOB_CHILDREN, kTestJobChildJobs);
}
} // namespace
// Tests that should pass for any topic. Use the wrappers below instead of
// calling this directly.
#define _RUN_COMMON_TESTS(topic, entry_type, get_handle) \
RUN_TEST((invalid_handle_fails<topic, entry_type>)); \
RUN_TEST((null_avail_actual_succeeds<topic, entry_type, get_handle>)); \
RUN_TEST((bad_buffer_fails<topic, entry_type, get_handle>)); \
RUN_TEST((bad_actual_fails<topic, entry_type, get_handle>)); \
RUN_TEST((bad_avail_fails<topic, entry_type, get_handle>))
// Tests that should pass for any topic that expects a single entry in its
// in/out buffer.
#define RUN_SINGLE_ENTRY_TESTS(topic, entry_type, get_handle) \
_RUN_COMMON_TESTS(topic, entry_type, get_handle); \
RUN_TEST((single_zero_buffer_fails<topic, entry_type, get_handle>))
// Tests that should pass for any topic that can handle multiple entries in its
// in/out buffer.
#define RUN_MULTI_ENTRY_TESTS(topic, entry_type, get_handle) \
_RUN_COMMON_TESTS(topic, entry_type, get_handle); \
RUN_TEST((multi_zero_buffer_succeeds<topic, get_handle>)); \
RUN_TEST((short_buffer_succeeds<topic, entry_type, get_handle>)); \
RUN_TEST((partially_unmapped_buffer_fails<topic, entry_type, get_handle>))
BEGIN_TEST_CASE(object_info_tests)
// ZX_INFO_HANDLE_VALID is an oddball that doesn't care about its buffer,
// so we can't use the normal topic test suites.
RUN_TEST(handle_valid_on_valid_handle_succeeds);
RUN_TEST(handle_valid_on_closed_handle_fails);
RUN_TEST((invalid_handle_fails<ZX_INFO_HANDLE_VALID, void*>));
RUN_TEST(task_stats_smoke);
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_TASK_STATS, zx_info_task_stats_t, zx_process_self);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_TASK_STATS, zx_info_task_stats_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_TASK_STATS, zx_info_task_stats_t, zx_thread_self>));
RUN_TEST(process_maps_smoke);
RUN_MULTI_ENTRY_TESTS(ZX_INFO_PROCESS_MAPS, zx_info_maps_t, get_test_process);
RUN_TEST((self_fails<ZX_INFO_PROCESS_MAPS, zx_info_maps_t>))
RUN_TEST((wrong_handle_type_fails<ZX_INFO_PROCESS_MAPS, zx_info_maps_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_PROCESS_MAPS, zx_info_maps_t, zx_thread_self>));
RUN_TEST((missing_rights_fails<ZX_INFO_PROCESS_MAPS, zx_info_maps_t, get_test_process,
ZX_RIGHT_READ>));
RUN_TEST(process_vmos_smoke);
RUN_MULTI_ENTRY_TESTS(ZX_INFO_PROCESS_VMOS, zx_info_vmo_t, get_test_process);
RUN_TEST((self_fails<ZX_INFO_PROCESS_VMOS, zx_info_vmo_t>))
RUN_TEST((wrong_handle_type_fails<ZX_INFO_PROCESS_VMOS, zx_info_vmo_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_PROCESS_VMOS, zx_info_vmo_t, zx_thread_self>));
RUN_TEST((missing_rights_fails<ZX_INFO_PROCESS_VMOS, zx_info_vmo_t, get_test_process,
ZX_RIGHT_READ>));
RUN_TEST(job_processes_smoke);
RUN_MULTI_ENTRY_TESTS(ZX_INFO_JOB_PROCESSES, zx_koid_t, get_test_job);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_JOB_PROCESSES, zx_koid_t, get_test_process>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_JOB_PROCESSES, zx_koid_t, zx_thread_self>));
RUN_TEST((missing_rights_fails<ZX_INFO_JOB_PROCESSES, zx_koid_t, get_test_job,
ZX_RIGHT_ENUMERATE>));
RUN_TEST(job_children_smoke);
RUN_MULTI_ENTRY_TESTS(ZX_INFO_JOB_CHILDREN, zx_koid_t, get_test_job);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_JOB_CHILDREN, zx_koid_t, get_test_process>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_JOB_CHILDREN, zx_koid_t, zx_thread_self>));
RUN_TEST((missing_rights_fails<ZX_INFO_JOB_CHILDREN, zx_koid_t, get_test_job,
ZX_RIGHT_ENUMERATE>));
// Basic tests for all other topics.
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_HANDLE_BASIC, zx_info_handle_basic_t, get_test_job);
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_HANDLE_BASIC, zx_info_handle_basic_t, get_test_process);
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_HANDLE_BASIC, zx_info_handle_basic_t, zx_thread_self);
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_HANDLE_BASIC, zx_info_handle_basic_t, zx_vmar_root_self);
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_PROCESS, zx_info_process_t, get_test_process);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_PROCESS, zx_info_process_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_PROCESS, zx_info_process_t, zx_thread_self>));
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_VMAR, zx_info_vmar_t, zx_vmar_root_self);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_VMAR, zx_info_vmar_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_VMAR, zx_info_vmar_t, get_test_process>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_VMAR, zx_info_vmar_t, zx_thread_self>));
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_THREAD, zx_info_thread_t, zx_thread_self);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_THREAD, zx_info_thread_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_THREAD, zx_info_thread_t, get_test_process>));
RUN_SINGLE_ENTRY_TESTS(ZX_INFO_THREAD_STATS, zx_info_thread_stats_t, zx_thread_self);
RUN_TEST((wrong_handle_type_fails<ZX_INFO_THREAD_STATS, zx_info_thread_t, get_test_job>));
RUN_TEST((wrong_handle_type_fails<ZX_INFO_THREAD_STATS, zx_info_thread_t, get_test_process>));
// ZX_INFO_PROCESS_THREADS tests.
// TODO(dbort): Use RUN_MULTI_ENTRY_TESTS instead. |short_buffer_succeeds| and
// |partially_unmapped_buffer_fails| currently fail because those tests expect
// avail > 1, but the test process only has one thread and it's not trivial to
// add more.
RUN_TEST((invalid_handle_fails<ZX_INFO_PROCESS_THREADS, zx_koid_t>));
RUN_TEST((null_avail_actual_succeeds<ZX_INFO_PROCESS_THREADS, zx_koid_t, get_test_process>));
RUN_TEST((bad_buffer_fails<ZX_INFO_PROCESS_THREADS, zx_koid_t, get_test_process>));
RUN_TEST((bad_actual_fails<ZX_INFO_PROCESS_THREADS, zx_koid_t, get_test_process>));
RUN_TEST((bad_avail_fails<ZX_INFO_PROCESS_THREADS, zx_koid_t, get_test_process>))
RUN_TEST((multi_zero_buffer_succeeds<ZX_INFO_PROCESS_THREADS, get_test_process>));
// Skip most tests for ZX_INFO_THREAD_EXCEPTION_REPORT, which is tested
// elsewhere and requires the target thread to be in a certain state.
RUN_TEST((invalid_handle_fails<ZX_INFO_THREAD_EXCEPTION_REPORT, zx_exception_report_t>));
// TODO(dbort): Test resource topics
// RUN_MULTI_ENTRY_TESTS(ZX_INFO_RESOURCE_CHILDREN, zx_rrec_t, get_root_resource);
// RUN_MULTI_ENTRY_TESTS(ZX_INFO_RESOURCE_RECORDS, zx_rrec_t, get_root_resource);
// RUN_MULTI_ENTRY_TESTS(ZX_INFO_CPU_STATS, zx_info_cpu_stats_t, get_root_resource);
// RUN_SINGLE_ENTRY_TESTS(ZX_INFO_KMEM_STATS, zx_info_kmem_stats_t, get_root_resource);
END_TEST_CASE(object_info_tests)
#ifndef BUILD_COMBINED_TESTS
int main(int argc, char** argv) {
return unittest_run_all_tests(argc, argv) ? 0 : -1;
}
#endif