280 changes: 279 additions & 1 deletion lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ Status NativeProcessProtocol::Attach(
NativeProcessLinux::NativeProcessLinux()
: NativeProcessProtocol(LLDB_INVALID_PROCESS_ID), m_arch(),
m_supports_mem_region(eLazyBoolCalculate), m_mem_region_cache(),
m_pending_notification_tid(LLDB_INVALID_THREAD_ID) {}
m_pending_notification_tid(LLDB_INVALID_THREAD_ID),
m_pt_proces_trace_id(LLDB_INVALID_UID) {}

void NativeProcessLinux::AttachToInferior(MainLoop &mainloop, lldb::pid_t pid,
Status &error) {
Expand Down Expand Up @@ -580,6 +581,7 @@ void NativeProcessLinux::MonitorCallback(lldb::pid_t pid, bool exited,
info.si_pid);

auto thread_sp = AddThread(pid);

// Resume the newly created thread.
ResumeThread(*thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER);
ThreadWasCreated(*thread_sp);
Expand Down Expand Up @@ -691,6 +693,7 @@ void NativeProcessLinux::WaitForNewThread(::pid_t tid) {

LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid);
new_thread_sp = AddThread(tid);

ResumeThread(*new_thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER);
ThreadWasCreated(*new_thread_sp);
}
Expand Down Expand Up @@ -1301,6 +1304,9 @@ Status NativeProcessLinux::Detach() {
e; // Save the error, but still attempt to detach from other threads.
}

m_processor_trace_monitor.clear();
m_pt_proces_trace_id = LLDB_INVALID_UID;

return error;
}

Expand Down Expand Up @@ -2089,6 +2095,8 @@ bool NativeProcessLinux::StopTrackingThread(lldb::tid_t thread_id) {
}
}

if (found)
StopTracingForThread(thread_id);
SignalIfAllThreadsStopped();
return found;
}
Expand All @@ -2106,6 +2114,21 @@ NativeThreadLinuxSP NativeProcessLinux::AddThread(lldb::tid_t thread_id) {

auto thread_sp = std::make_shared<NativeThreadLinux>(this, thread_id);
m_threads.push_back(thread_sp);

if (m_pt_proces_trace_id != LLDB_INVALID_UID) {
auto traceMonitor = ProcessorTraceMonitor::Create(
GetID(), thread_id, m_pt_process_trace_config, true);
if (traceMonitor) {
m_pt_traced_thread_group.insert(thread_id);
m_processor_trace_monitor.insert(
std::make_pair(thread_id, std::move(*traceMonitor)));
} else {
LLDB_LOG(log, "failed to start trace on thread {0}", thread_id);
Status error(traceMonitor.takeError());
LLDB_LOG(log, "error {0}", error);
}
}

return thread_sp;
}

Expand Down Expand Up @@ -2405,3 +2428,258 @@ Status NativeProcessLinux::PtraceWrapper(int req, lldb::pid_t pid, void *addr,

return error;
}

llvm::Expected<ProcessorTraceMonitor &>
NativeProcessLinux::LookupProcessorTraceInstance(lldb::user_id_t traceid,
lldb::tid_t thread) {
Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));
if (thread == LLDB_INVALID_THREAD_ID && traceid == m_pt_proces_trace_id) {
LLDB_LOG(log, "thread not specified: {0}", traceid);
return Status("tracing not active thread not specified").ToError();
}

for (auto& iter : m_processor_trace_monitor) {
if (traceid == iter.second->GetTraceID() &&
(thread == iter.first || thread == LLDB_INVALID_THREAD_ID))
return *(iter.second);
}

LLDB_LOG(log, "traceid not being traced: {0}", traceid);
return Status("tracing not active for this thread").ToError();
}

Status NativeProcessLinux::GetMetaData(lldb::user_id_t traceid,
lldb::tid_t thread,
llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset) {
TraceOptions trace_options;
Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));
Status error;

LLDB_LOG(log, "traceid {0}", traceid);

auto perf_monitor = LookupProcessorTraceInstance(traceid, thread);
if (!perf_monitor) {
LLDB_LOG(log, "traceid not being traced: {0}", traceid);
buffer = buffer.slice(buffer.size());
error = perf_monitor.takeError();
return error;
}
return (*perf_monitor).ReadPerfTraceData(buffer, offset);
}

Status NativeProcessLinux::GetData(lldb::user_id_t traceid, lldb::tid_t thread,
llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset) {
Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));
Status error;

LLDB_LOG(log, "traceid {0}", traceid);

auto perf_monitor = LookupProcessorTraceInstance(traceid, thread);
if (!perf_monitor) {
LLDB_LOG(log, "traceid not being traced: {0}", traceid);
buffer = buffer.slice(buffer.size());
error = perf_monitor.takeError();
return error;
}
return (*perf_monitor).ReadPerfTraceAux(buffer, offset);
}

Status NativeProcessLinux::GetTraceConfig(lldb::user_id_t traceid,
TraceOptions &config) {
Status error;
if (config.getThreadID() == LLDB_INVALID_THREAD_ID &&
m_pt_proces_trace_id == traceid) {
if (m_pt_proces_trace_id == LLDB_INVALID_UID) {
error.SetErrorString("tracing not active for this process");
return error;
}
config = m_pt_process_trace_config;
} else {
auto perf_monitor =
LookupProcessorTraceInstance(traceid, config.getThreadID());
if (!perf_monitor) {
error = perf_monitor.takeError();
return error;
}
error = (*perf_monitor).GetTraceConfig(config);
}
return error;
}

lldb::user_id_t
NativeProcessLinux::StartTraceGroup(const TraceOptions &config,
Status &error) {

Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));
if (config.getType() != TraceType::eTraceTypeProcessorTrace)
return LLDB_INVALID_UID;

if (m_pt_proces_trace_id != LLDB_INVALID_UID) {
error.SetErrorString("tracing already active on this process");
return m_pt_proces_trace_id;
}

for (const auto &thread_sp : m_threads) {
if (auto traceInstance = ProcessorTraceMonitor::Create(
GetID(), thread_sp->GetID(), config, true)) {
m_pt_traced_thread_group.insert(thread_sp->GetID());
m_processor_trace_monitor.insert(
std::make_pair(thread_sp->GetID(), std::move(*traceInstance)));
}
}

m_pt_process_trace_config = config;
error = ProcessorTraceMonitor::GetCPUType(m_pt_process_trace_config);

// Trace on Complete process will have traceid of 0
m_pt_proces_trace_id = 0;

LLDB_LOG(log, "Process Trace ID {0}", m_pt_proces_trace_id);
return m_pt_proces_trace_id;
}

lldb::user_id_t NativeProcessLinux::StartTrace(const TraceOptions &config,
Status &error) {
if (config.getType() != TraceType::eTraceTypeProcessorTrace)
return NativeProcessProtocol::StartTrace(config, error);

Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));

lldb::tid_t threadid = config.getThreadID();

if (threadid == LLDB_INVALID_THREAD_ID)
return StartTraceGroup(config, error);

auto thread_sp = GetThreadByID(threadid);
if (!thread_sp) {
// Thread not tracked by lldb so don't trace.
error.SetErrorString("invalid thread id");
return LLDB_INVALID_UID;
}

const auto &iter = m_processor_trace_monitor.find(threadid);
if (iter != m_processor_trace_monitor.end()) {
LLDB_LOG(log, "Thread already being traced");
error.SetErrorString("tracing already active on this thread");
return LLDB_INVALID_UID;
}

auto traceMonitor =
ProcessorTraceMonitor::Create(GetID(), threadid, config, false);
if (!traceMonitor) {
error = traceMonitor.takeError();
LLDB_LOG(log, "error {0}", error);
return LLDB_INVALID_UID;
}
lldb::user_id_t ret_trace_id = (*traceMonitor)->GetTraceID();
m_processor_trace_monitor.insert(
std::make_pair(threadid, std::move(*traceMonitor)));
return ret_trace_id;
}

Status NativeProcessLinux::StopTracingForThread(lldb::tid_t thread) {
Status error;
Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));
LLDB_LOG(log, "Thread {0}", thread);

const auto& iter = m_processor_trace_monitor.find(thread);
if (iter == m_processor_trace_monitor.end()) {
error.SetErrorString("tracing not active for this thread");
return error;
}

if (iter->second->GetTraceID() == m_pt_proces_trace_id) {
// traceid maps to the whole process so we have to erase it from the
// thread group.
LLDB_LOG(log, "traceid maps to process");
m_pt_traced_thread_group.erase(thread);
}
m_processor_trace_monitor.erase(iter);

return error;
}

Status NativeProcessLinux::StopTrace(lldb::user_id_t traceid,
lldb::tid_t thread) {
Status error;

TraceOptions trace_options;
trace_options.setThreadID(thread);
error = NativeProcessLinux::GetTraceConfig(traceid, trace_options);

if (error.Fail())
return error;

switch (trace_options.getType()) {
case lldb::TraceType::eTraceTypeProcessorTrace:
if (traceid == m_pt_proces_trace_id &&
thread == LLDB_INVALID_THREAD_ID)
StopProcessorTracingOnProcess();
else
error = StopProcessorTracingOnThread(traceid, thread);
break;
default:
error.SetErrorString("trace not supported");
break;
}

return error;
}

void NativeProcessLinux::StopProcessorTracingOnProcess() {
for (auto thread_id_iter : m_pt_traced_thread_group)
m_processor_trace_monitor.erase(thread_id_iter);
m_pt_traced_thread_group.clear();
m_pt_proces_trace_id = LLDB_INVALID_UID;
}

Status NativeProcessLinux::StopProcessorTracingOnThread(lldb::user_id_t traceid,
lldb::tid_t thread) {
Status error;
Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE));

if (thread == LLDB_INVALID_THREAD_ID) {
for (auto& iter : m_processor_trace_monitor) {
if (iter.second->GetTraceID() == traceid) {
// Stopping a trace instance for an individual thread
// hence there will only be one traceid that can match.
m_processor_trace_monitor.erase(iter.first);
return error;
}
LLDB_LOG(log, "Trace ID {0}", iter.second->GetTraceID());
}

LLDB_LOG(log, "Invalid TraceID");
error.SetErrorString("invalid trace id");
return error;
}

// thread is specified so we can use find function on the map.
const auto& iter = m_processor_trace_monitor.find(thread);
if (iter == m_processor_trace_monitor.end()) {
// thread not found in our map.
LLDB_LOG(log, "thread not being traced");
error.SetErrorString("tracing not active for this thread");
return error;
}
if (iter->second->GetTraceID() != traceid) {
// traceid did not match so it has to be invalid.
LLDB_LOG(log, "Invalid TraceID");
error.SetErrorString("invalid trace id");
return error;
}

LLDB_LOG(log, "UID - {0} , Thread -{1}", traceid, thread);

if (traceid == m_pt_proces_trace_id) {
// traceid maps to the whole process so we have to erase it from the
// thread group.
LLDB_LOG(log, "traceid maps to process");
m_pt_traced_thread_group.erase(thread);
}
m_processor_trace_monitor.erase(iter);

return error;
}
54 changes: 54 additions & 0 deletions lldb/source/Plugins/Process/Linux/NativeProcessLinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "lldb/lldb-types.h"

#include "NativeThreadLinux.h"
#include "ProcessorTrace.h"
#include "lldb/Host/common/NativeProcessProtocol.h"

namespace lldb_private {
Expand Down Expand Up @@ -105,6 +106,22 @@ class NativeProcessLinux : public NativeProcessProtocol {
return getProcFile(GetID(), "auxv");
}

lldb::user_id_t StartTrace(const TraceOptions &config,
Status &error) override;

Status StopTrace(lldb::user_id_t traceid,
lldb::tid_t thread) override;

Status GetData(lldb::user_id_t traceid, lldb::tid_t thread,
llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset = 0) override;

Status GetMetaData(lldb::user_id_t traceid, lldb::tid_t thread,
llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset = 0) override;

Status GetTraceConfig(lldb::user_id_t traceid, TraceOptions &config) override;

// ---------------------------------------------------------------------
// Interface used by NativeRegisterContext-derived classes.
// ---------------------------------------------------------------------
Expand Down Expand Up @@ -228,6 +245,43 @@ class NativeProcessLinux : public NativeProcessProtocol {
void SigchldHandler();

Status PopulateMemoryRegionCache();

lldb::user_id_t StartTraceGroup(const TraceOptions &config,
Status &error);

// This function is intended to be used to stop tracing
// on a thread that exited.
Status StopTracingForThread(lldb::tid_t thread);

// The below function as the name suggests, looks up a ProcessorTrace
// instance from the m_processor_trace_monitor map. In the case of
// process tracing where the traceid passed would map to the complete
// process, it is mandatory to provide a threadid to obtain a trace
// instance (since ProcessorTrace is tied to a thread). In the other
// scenario that an individual thread is being traced, just the traceid
// is sufficient to obtain the actual ProcessorTrace instance.
llvm::Expected<ProcessorTraceMonitor &>
LookupProcessorTraceInstance(lldb::user_id_t traceid, lldb::tid_t thread);

// Stops tracing on individual threads being traced. Not intended
// to be used to stop tracing on complete process.
Status StopProcessorTracingOnThread(lldb::user_id_t traceid,
lldb::tid_t thread);

// Intended to stop tracing on complete process.
// Should not be used for stopping trace on
// individual threads.
void StopProcessorTracingOnProcess();

llvm::DenseMap<lldb::tid_t, ProcessorTraceMonitorUP>
m_processor_trace_monitor;

// Set for tracking threads being traced under
// same process user id.
llvm::DenseSet<lldb::tid_t> m_pt_traced_thread_group;

lldb::user_id_t m_pt_proces_trace_id;
TraceOptions m_pt_process_trace_config;
};

} // namespace process_linux
Expand Down
400 changes: 400 additions & 0 deletions lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp

Large diffs are not rendered by default.

141 changes: 141 additions & 0 deletions lldb/source/Plugins/Process/Linux/ProcessorTrace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//===-- ProcessorTrace.h -------------------------------------- -*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_ProcessorTrace_H_
#define liblldb_ProcessorTrace_H_

#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"

#include "lldb/Core/TraceOptions.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-types.h"

#include <linux/perf_event.h>
#include <sys/mman.h>

namespace lldb_private {

namespace process_linux {

// ---------------------------------------------------------------------
// This class keeps track of one tracing instance of
// Intel(R) Processor Trace on Linux OS. There is a map keeping track
// of different tracing instances on each thread, which enables trace
// gathering on a per thread level.
//
// The tracing instance is linked with a trace id. The trace id acts like
// a key to the tracing instance and trace manipulations could be
// performed using the trace id.
//
// The traace id could map to trace instances for a group of threads
// (spanning to all the threads in the process) or a single thread.
// The kernel interface for us is the perf_event_open.
// ---------------------------------------------------------------------

class ProcessorTraceMonitor;
typedef std::unique_ptr<ProcessorTraceMonitor> ProcessorTraceMonitorUP;

class ProcessorTraceMonitor {

class munmap_delete {
size_t m_length;

public:
munmap_delete(size_t length) : m_length(length) {}
void operator()(void *ptr) {
if (m_length)
munmap(ptr, m_length);
}
};

class file_close {

public:
file_close() = default;
void operator()(int *ptr) {
if (ptr == nullptr)
return;
if (*ptr == -1)
return;
close(*ptr);
std::default_delete<int>()(ptr);
}
};

std::unique_ptr<perf_event_mmap_page, munmap_delete> m_mmap_meta;
std::unique_ptr<uint8_t, munmap_delete> m_mmap_aux;
std::unique_ptr<int, file_close> m_fd;

// perf_event_mmap_page *m_mmap_base;
lldb::user_id_t m_traceid;
lldb::tid_t m_thread_id;

// Counter to track trace instances.
static lldb::user_id_t m_trace_num;

void SetTraceID(lldb::user_id_t traceid) { m_traceid = traceid; }

Status StartTrace(lldb::pid_t pid, lldb::tid_t tid,
const TraceOptions &config);

llvm::MutableArrayRef<uint8_t> GetAuxBuffer();
llvm::MutableArrayRef<uint8_t> GetDataBuffer();

ProcessorTraceMonitor()
: m_mmap_meta(nullptr, munmap_delete(0)),
m_mmap_aux(nullptr, munmap_delete(0)), m_fd(nullptr, file_close()),
m_traceid(LLDB_INVALID_UID), m_thread_id(LLDB_INVALID_THREAD_ID){};

void SetThreadID(lldb::tid_t tid) { m_thread_id = tid; }

public:
static Status GetCPUType(TraceOptions &config);

static llvm::Expected<ProcessorTraceMonitorUP>
Create(lldb::pid_t pid, lldb::tid_t tid, const TraceOptions &config,
bool useProcessSettings);

Status ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset = 0);

Status ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset = 0);

~ProcessorTraceMonitor() = default;

lldb::tid_t GetThreadID() const { return m_thread_id; }

lldb::user_id_t GetTraceID() const { return m_traceid; }

Status GetTraceConfig(TraceOptions &config) const;

// ---------------------------------------------------------------------
/// Read data from a cyclic buffer
///
/// @param[in] [out] buf
/// Destination buffer, the buffer will be truncated to written size.
///
/// @param[in] src
/// Source buffer which must be a cyclic buffer.
///
/// @param[in] src_cyc_index
/// The index pointer (start of the valid data in the cyclic
/// buffer).
///
/// @param[in] offset
/// The offset to begin reading the data in the cyclic buffer.
// ---------------------------------------------------------------------
static void ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
llvm::MutableArrayRef<uint8_t> src,
size_t src_cyc_index, size_t offset);
};
} // namespace process_linux
} // namespace lldb_private
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -1250,9 +1250,9 @@ GDBRemoteCommunicationServerLLGS::Handle_jTraceRead(

lldb::user_id_t uid = LLDB_INVALID_UID;

size_t byte_count = std::numeric_limits<size_t>::max();
uint64_t byte_count = std::numeric_limits<uint64_t>::max();
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
size_t offset = std::numeric_limits<size_t>::max();
uint64_t offset = std::numeric_limits<uint64_t>::max();

auto json_object = StructuredData::ParseJSON(packet.Peek());

Expand Down Expand Up @@ -1286,8 +1286,8 @@ GDBRemoteCommunicationServerLLGS::Handle_jTraceRead(
if (error.Fail())
return SendErrorResponse(error.GetError());

for (size_t i = 0; i < buf.size(); ++i)
response.PutHex8(buf[i]);
for (auto i : buf)
response.PutHex8(i);

StreamGDBRemote escaped_response;
escaped_response.PutEscapedBytes(response.GetData(), response.GetSize());
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/Process/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
add_subdirectory(gdb-remote)
add_subdirectory(Linux)
add_subdirectory(minidump)
8 changes: 8 additions & 0 deletions lldb/unittests/Process/Linux/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
include_directories(${LLDB_SOURCE_DIR}/source/Plugins/Process/Linux)

add_lldb_unittest(ProcessorTraceTest
ProcessorTraceTest.cpp

LINK_LIBS
lldbPluginProcessLinux
)
152 changes: 152 additions & 0 deletions lldb/unittests/Process/Linux/ProcessorTraceTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//===-- ProcessorTraceMonitorTest.cpp ------------------------- -*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"

#include "ProcessorTrace.h"
#include "llvm/ADT/ArrayRef.h"
// C Includes

// C++ Includes

using namespace lldb_private;
using namespace process_linux;

size_t ReadCylicBufferWrapper(void *buf, size_t buf_size, void *cyc_buf,
size_t cyc_buf_size, size_t cyc_start,
size_t offset) {
llvm::MutableArrayRef<uint8_t> dst(reinterpret_cast<uint8_t *>(buf),
buf_size);
llvm::MutableArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(cyc_buf),
cyc_buf_size);
ProcessorTraceMonitor::ReadCyclicBuffer(dst, src, cyc_start, offset);
return dst.size();
}

TEST(CyclicBuffer, EdgeCases) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};

// We will always leave the last bytes untouched
// so that string comparisions work.
char bigger_buffer[10] = {};
char equal_size_buffer[7] = {};
char smaller_buffer[4] = {};

// empty buffer to read into
bytes_read = ReadCylicBufferWrapper(smaller_buffer, 0, cyclic_buffer,
sizeof(cyclic_buffer), 3, 0);
ASSERT_EQ(0, bytes_read);

// empty cyclic buffer
bytes_read = ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, 0, 3, 0);
ASSERT_EQ(0, bytes_read);

// bigger offset
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 6);
ASSERT_EQ(0, bytes_read);

// wrong offset
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 7);
ASSERT_EQ(0, bytes_read);

// wrong start
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 7);
ASSERT_EQ(0, bytes_read);
}

TEST(CyclicBuffer, EqualSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};

char cyclic[] = "cyclic";
for (int i = 0; i < sizeof(cyclic); i++) {
// We will always leave the last bytes untouched
// so that string comparisions work.
char equal_size_buffer[7] = {};
bytes_read =
ReadCylicBufferWrapper(equal_size_buffer, sizeof(cyclic_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, i);
ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read);
ASSERT_STREQ(equal_size_buffer, (cyclic + i));
}
}

TEST(CyclicBuffer, SmallerSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};

// We will always leave the last bytes untouched
// so that string comparisions work.
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 0);
ASSERT_EQ(3, bytes_read);
ASSERT_STREQ(smaller_buffer, "cyc");

bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 1);
ASSERT_EQ(3, bytes_read);
ASSERT_STREQ(smaller_buffer, "ycl");

bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 2);
ASSERT_EQ(3, bytes_read);
ASSERT_STREQ(smaller_buffer, "cli");

bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 3);
ASSERT_EQ(3, bytes_read);
ASSERT_STREQ(smaller_buffer, "lic");

{
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 4);
ASSERT_EQ(2, bytes_read);
ASSERT_STREQ(smaller_buffer, "ic");
}
{
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 5);
ASSERT_EQ(1, bytes_read);
ASSERT_STREQ(smaller_buffer, "c");
}
}

TEST(CyclicBuffer, BiggerSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};

char cyclic[] = "cyclic";
for (int i = 0; i < sizeof(cyclic); i++) {
// We will always leave the last bytes untouched
// so that string comparisions work.
char bigger_buffer[10] = {};
bytes_read =
ReadCylicBufferWrapper(bigger_buffer, (sizeof(bigger_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, i);
ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read);
ASSERT_STREQ(bigger_buffer, (cyclic + i));
}
}