blob: 3faf314466b52f40008898bbc5a22498ce9ecee4 [file] [log] [blame]
// Copyright 2016 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 <lib/fit/defer.h>
#include <lib/zx/channel.h>
#include <lib/zx/job.h>
#include <lib/zx/thread.h>
#include <lib/zx/vmar.h>
#include <zircon/threads.h>
#include <zircon/types.h>
#include <thread>
#include <zxtest/zxtest.h>
#include "utils.h"
// SYSCALL_zx_channel_call_finish is an internal system call used in the
// vDSO's implementation of zx_channel_call. It's not part of the ABI and
// so it's not exported from the vDSO. It's hard to test the kernel's
// invariants without calling this directly. So use some chicanery to
// find its address in the vDSO despite it not being public.
//
// The vdso-code.h header file is generated from the vDSO binary. It gives
// the offsets of the internal functions. So take a public vDSO function,
// subtract its offset to discover the vDSO base (could do this other ways,
// but this is the simplest), and then add the offset of the internal
// SYSCALL_zx_channel_call_finish function we want to call.
#include "vdso-code.h"
namespace channel {
namespace {
zx_status_t zx_channel_call_finish(zx_time_t deadline, const zx_channel_call_args_t* args,
uint32_t* actual_bytes, uint32_t* actual_handles) {
uintptr_t vdso_base = (uintptr_t)&zx_handle_close - VDSO_SYSCALL_zx_handle_close;
uintptr_t fnptr = vdso_base + VDSO_SYSCALL_zx_channel_call_finish;
return (*(__typeof(zx_channel_call_finish)*)fnptr)(deadline, args, actual_bytes, actual_handles);
}
TEST(ChannelInternalTest, CallFinishWithoutPreviouslyCallingCallReturnsBadState) {
char msg[8] = {
0,
};
zx_channel_call_args_t args = {
.wr_bytes = &msg,
.wr_handles = nullptr,
.rd_bytes = nullptr,
.rd_handles = nullptr,
.wr_num_bytes = sizeof(msg),
.wr_num_handles = 0,
.rd_num_bytes = 0,
.rd_num_handles = 0,
};
uint32_t act_bytes = 0xffffffff;
uint32_t act_handles = 0xffffffff;
// Call channel_call_finish without having had a channel call interrupted
ASSERT_EQ(ZX_ERR_BAD_STATE, zx_channel_call_finish(zx_deadline_after(ZX_MSEC(1000)), &args,
&act_bytes, &act_handles));
}
void WaitForThreadState(zx_handle_t thread_handle, zx_thread_state_t state) {
// Make sure the original thread is still blocked.
// It is safe to read from caller_thread_handle since this is set before
// the read happends, and we waited until the remote endpoint became readable.
zx_info_thread_t info = {};
while (info.state != state) {
uint64_t actual = 0;
uint64_t actual_2 = 0;
ASSERT_OK(
zx_object_get_info(thread_handle, ZX_INFO_THREAD, &info, sizeof(info), &actual, &actual_2));
}
return;
}
// Verify pending channel_calls are canceled when the handle is transferred.
TEST(ChannelInternalTest, TransferChannelWithPendingCall) {
constexpr uint32_t kRequestPayload = 0xc0ffee;
zx::channel local;
zx::channel remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
struct Message {
zx_txid_t id;
uint32_t payload;
};
std::atomic<const char*> caller_error = nullptr;
{
std::atomic<zx_handle_t> caller_thread_handle = ZX_HANDLE_INVALID;
AutoJoinThread caller_thread([&local, &caller_error, &caller_thread_handle] {
Message request;
request.payload = kRequestPayload;
Message reply;
caller_thread_handle.store(zx::thread::self()->get());
zx_channel_call_args_t args = {
.wr_bytes = &request,
.wr_handles = nullptr,
.rd_bytes = &reply,
.rd_handles = nullptr,
.wr_num_bytes = sizeof(Message),
.wr_num_handles = 0,
.rd_num_bytes = sizeof(Message),
.rd_num_handles = 0,
};
uint32_t actual_bytes = 0;
uint32_t actual_handles = 0;
const zx_status_t status =
local.call(0, zx::time::infinite(), &args, &actual_bytes, &actual_handles);
if (status != ZX_ERR_CANCELED) {
caller_error = "channel::call was not canceled as unexpected";
return;
}
});
ASSERT_OK(remote.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr));
// Read the message from the test thread.
Message request = {};
uint32_t actual_bytes = 0;
uint32_t actual_handles = 0;
ASSERT_OK(
remote.read(0, &request, nullptr, sizeof(Message), 0, &actual_bytes, &actual_handles));
ASSERT_EQ(actual_bytes, sizeof(Message));
ASSERT_EQ(kRequestPayload, request.payload);
// See that the original thread is still blocked.
ASSERT_NO_FATAL_FAILURE(
WaitForThreadState(caller_thread_handle.load(), ZX_THREAD_STATE_BLOCKED_CHANNEL));
{
// Transfer the local endpoint in a channel message.
zx::channel a;
zx::channel b;
ASSERT_OK(zx::channel::create(0, &a, &b));
Message transfer_msg;
zx_handle_t raw_handle = local.release();
ASSERT_OK(a.write(0, &transfer_msg, sizeof(transfer_msg), &raw_handle, 1));
raw_handle = ZX_HANDLE_INVALID;
// See that the original thread is still blocked.
ASSERT_NO_FATAL_FAILURE(
WaitForThreadState(caller_thread_handle.load(), ZX_THREAD_STATE_BLOCKED_CHANNEL));
// Reading this channel message will unblock the caller_thread.
ASSERT_OK(b.read(0, &transfer_msg, local.reset_and_get_address(), sizeof(transfer_msg), 1,
&actual_bytes, &actual_handles));
ASSERT_EQ(actual_bytes, sizeof(transfer_msg));
ASSERT_EQ(actual_handles, 1);
}
caller_thread.Join();
}
if (caller_error.load() != nullptr) {
FAIL("caller_thread encountered an error on channel::call: %s", caller_error.load());
}
}
} // namespace
} // namespace channel