You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
417 lines
15 KiB
417 lines
15 KiB
/*
|
|
* \file mem_buff_demo.cpp
|
|
* \brief OpenCSD: using the library with memory buffers for data.
|
|
*
|
|
* \copyright Copyright (c) 2018, ARM Limited. All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/* Example showing techniques to drive library using only memory buffers as input data
|
|
* and image data, avoiding file i/o in main processing routines. (File I/O used to
|
|
* initially populate buffers but this can be replaced if data is generated by a client
|
|
* environment running live.)
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
|
|
#include "opencsd.h" // the library
|
|
|
|
// uncomment below to use callback function for program memory image
|
|
// #define EXAMPLE_USE_MEM_CALLBACK
|
|
|
|
/* Input trace buffer */
|
|
static uint8_t *input_trace_data = 0;
|
|
static uint32_t input_trace_data_size = 0;
|
|
|
|
/* program memory image for decode */
|
|
static uint8_t *program_image_buffer = 0; // buffer for image data.
|
|
static uint32_t program_image_size = 0; // size of program image data.
|
|
static ocsd_vaddr_t program_image_address = 0; // load address on target of program image.
|
|
|
|
/* a message logger to pass to the error logger / decoder. */
|
|
static ocsdMsgLogger logger;
|
|
|
|
/* logger callback function - print out error strings */
|
|
class logCallback : public ocsdMsgLogStrOutI
|
|
{
|
|
public:
|
|
logCallback() {};
|
|
virtual ~logCallback() {};
|
|
virtual void printOutStr(const std::string &outStr)
|
|
{
|
|
std::cout << outStr.c_str();
|
|
}
|
|
};
|
|
static logCallback logCB;
|
|
|
|
/* Decode tree is the main decoder framework - contains the frame unpacker,
|
|
packet and trace stream decoders, plus memory image references */
|
|
static DecodeTree *pDecoder = 0;
|
|
|
|
/* an error logger - Decode tree registers all components with the error logger
|
|
so that errors can be correctly attributed and printed if required
|
|
*/
|
|
static ocsdDefaultErrorLogger err_log;
|
|
|
|
/* callbacks used by the library */
|
|
#ifdef EXAMPLE_USE_MEM_CALLBACK
|
|
// program memory image callback definition
|
|
uint32_t mem_access_callback_fn(const void *p_context, const ocsd_vaddr_t address, const ocsd_mem_space_acc_t mem_space, const uint32_t reqBytes, uint8_t *byteBuffer);
|
|
#endif
|
|
|
|
// callback for the decoder output elements
|
|
class DecoderOutputProcessor : public ITrcGenElemIn
|
|
{
|
|
public:
|
|
DecoderOutputProcessor() {};
|
|
virtual ~DecoderOutputProcessor() {};
|
|
|
|
virtual ocsd_datapath_resp_t TraceElemIn(const ocsd_trc_index_t index_sop,
|
|
const uint8_t trc_chan_id,
|
|
const OcsdTraceElement &elem)
|
|
{
|
|
// must fully process or make a copy of data in here.
|
|
// element reference only valid for scope of call.
|
|
|
|
// for the example program we will stringise and print -
|
|
// but this is a client program implmentation dependent.
|
|
std::string elemStr;
|
|
std::ostringstream oss;
|
|
oss << "Idx:" << index_sop << "; ID:" << std::hex << (uint32_t)trc_chan_id << "; ";
|
|
elem.toString(elemStr);
|
|
oss << elemStr << std::endl;
|
|
logger.LogMsg(oss.str());
|
|
return OCSD_RESP_CONT;
|
|
}
|
|
};
|
|
static DecoderOutputProcessor output;
|
|
|
|
/* for test purposes we are initialising from files, but this could be generated test data as
|
|
part of a larger program and / or compiled in memory images.
|
|
|
|
We have hardcoded in one of the snapshots supplied with the library
|
|
*/
|
|
static int initDataBuffers()
|
|
{
|
|
FILE *fp;
|
|
std::string filename;
|
|
long size;
|
|
|
|
/* the file names to create the data buffers */
|
|
#ifdef _WIN32
|
|
static const char *default_base_snapshot_path = "..\\..\\..\\snapshots";
|
|
static const char *juno_snapshot = "\\juno_r1_1\\";
|
|
#else
|
|
static const char *default_base_snapshot_path = "../../../snapshots";
|
|
static const char *juno_snapshot = "/juno_r1_1/";
|
|
#endif
|
|
|
|
/* trace data and memory file dump names and values - taken from snapshot metadata */
|
|
static const char *trace_data_filename = "cstrace.bin";
|
|
static const char *memory_dump_filename = "kernel_dump.bin";
|
|
static ocsd_vaddr_t mem_dump_address = 0xFFFFFFC000081000;
|
|
|
|
/* load up the trace data */
|
|
filename = default_base_snapshot_path;
|
|
filename += (std::string)juno_snapshot;
|
|
filename += (std::string)trace_data_filename;
|
|
|
|
fp = fopen(filename.c_str(), "rb");
|
|
if (!fp)
|
|
return OCSD_ERR_FILE_ERROR;
|
|
fseek(fp, 0, SEEK_END);
|
|
size = ftell(fp);
|
|
input_trace_data_size = (uint32_t)size;
|
|
input_trace_data = new (std::nothrow) uint8_t[input_trace_data_size];
|
|
if (!input_trace_data) {
|
|
fclose(fp);
|
|
return OCSD_ERR_MEM;
|
|
}
|
|
rewind(fp);
|
|
fread(input_trace_data, 1, input_trace_data_size, fp);
|
|
fclose(fp);
|
|
|
|
/* load up a memory image */
|
|
filename = default_base_snapshot_path;
|
|
filename += (std::string)juno_snapshot;
|
|
filename += (std::string)memory_dump_filename;
|
|
|
|
fp = fopen(filename.c_str(), "rb");
|
|
if (!fp)
|
|
return OCSD_ERR_FILE_ERROR;
|
|
fseek(fp, 0, SEEK_END);
|
|
size = ftell(fp);
|
|
program_image_size = (uint32_t)size;
|
|
program_image_buffer = new (std::nothrow) uint8_t[program_image_size];
|
|
if (!program_image_buffer) {
|
|
fclose(fp);
|
|
return OCSD_ERR_MEM;
|
|
}
|
|
rewind(fp);
|
|
fread(program_image_buffer, 1, program_image_size, fp);
|
|
fclose(fp);
|
|
program_image_address = mem_dump_address;
|
|
return OCSD_OK;
|
|
}
|
|
|
|
static ocsd_err_t createETMv4StreamDecoder()
|
|
{
|
|
ocsd_etmv4_cfg trace_config;
|
|
ocsd_err_t err = OCSD_OK;
|
|
EtmV4Config *pCfg = 0;
|
|
|
|
/*
|
|
* populate the ETMv4 configuration structure with
|
|
* hard coded values from snapshot .ini files.
|
|
*/
|
|
|
|
trace_config.arch_ver = ARCH_V8;
|
|
trace_config.core_prof = profile_CortexA;
|
|
|
|
trace_config.reg_configr = 0x000000C1;
|
|
trace_config.reg_traceidr = 0x00000010; /* this is the trace ID -> 0x10, change this to analyse other streams in snapshot.*/
|
|
trace_config.reg_idr0 = 0x28000EA1;
|
|
trace_config.reg_idr1 = 0x4100F403;
|
|
trace_config.reg_idr2 = 0x00000488;
|
|
trace_config.reg_idr8 = 0x0;
|
|
trace_config.reg_idr9 = 0x0;
|
|
trace_config.reg_idr10 = 0x0;
|
|
trace_config.reg_idr11 = 0x0;
|
|
trace_config.reg_idr12 = 0x0;
|
|
trace_config.reg_idr13 = 0x0;
|
|
|
|
pCfg = new (std::nothrow) EtmV4Config(&trace_config);
|
|
if (!pCfg)
|
|
return OCSD_ERR_MEM;
|
|
|
|
err = pDecoder->createDecoder(OCSD_BUILTIN_DCD_ETMV4I, /* etm v4 decoder */
|
|
OCSD_CREATE_FLG_FULL_DECODER, /* full trace decode */
|
|
pCfg);
|
|
delete pCfg;
|
|
return err;
|
|
}
|
|
|
|
/* Create the decode tree and add the error logger, stream decoder, memory image data to it.
|
|
Also register the output callback that processes the decoded trace packets. */
|
|
static ocsd_err_t initialiseDecoder()
|
|
{
|
|
ocsd_err_t ret = OCSD_OK;
|
|
|
|
/* use the creation function to get the type of decoder we want
|
|
either OCSD_TRC_SRC_SINGLE : single trace source - not frame formatted
|
|
OCSD_TRC_SRC_FRAME_FORMATTED :multi source - CoreSight trace frame
|
|
and set the config flags for operation
|
|
OCSD_DFRMTR_FRAME_MEM_ALIGN: input data mem aligned -> no syncs
|
|
|
|
For this test we create a decode that can unpack frames and is not expecting sync packets.
|
|
*/
|
|
pDecoder = DecodeTree::CreateDecodeTree(OCSD_TRC_SRC_FRAME_FORMATTED, OCSD_DFRMTR_FRAME_MEM_ALIGN);
|
|
if (!pDecoder)
|
|
return OCSD_ERR_MEM;
|
|
|
|
/* set up decoder logging - the message logger for output, and the error logger for the library */
|
|
logger.setLogOpts(ocsdMsgLogger::OUT_STR_CB); /* no IO from the logger, just a string callback. */
|
|
logger.setStrOutFn(&logCB); /* set the callback - in this example it will go to stdio but this is up to the implementor. */
|
|
|
|
// for debugging - stdio and file
|
|
// logger.setLogOpts(ocsdMsgLogger::OUT_FILE | ocsdMsgLogger::OUT_STDOUT);
|
|
|
|
err_log.initErrorLogger(OCSD_ERR_SEV_INFO);
|
|
err_log.setOutputLogger(&logger); /* pass the output logger to the error logger. */
|
|
|
|
pDecoder->setAlternateErrorLogger(&err_log); /* pass the error logger to the decoder, do not use the library version. */
|
|
|
|
/* now set up the elements that the decoder needs */
|
|
|
|
/* we will decode one of the streams in this example
|
|
create a Full decode ETMv4 stream decoder */
|
|
ret = createETMv4StreamDecoder();
|
|
if (ret != OCSD_OK)
|
|
return ret;
|
|
|
|
/* as this has full decode we must supply a memory image. */
|
|
|
|
ret = pDecoder->createMemAccMapper(); // the mapper is needed to add code images to.
|
|
if (ret != OCSD_OK)
|
|
return ret;
|
|
|
|
#ifdef EXAMPLE_USE_MEM_CALLBACK
|
|
// in this example we have a single buffer so we demonstrate how to use a callback.
|
|
// we are passing the buffer pointer as context as we only have one buffer, but this
|
|
// could be a structure that is a list of memory image buffers. Context is entirely
|
|
// client defined.
|
|
// Always use OCSD_MEM_SPACE_ANY unless there is a reason to restrict the image to a specific
|
|
// memory space.
|
|
pDecoder->addCallbackMemAcc(program_image_address, program_image_address + program_image_size-1,
|
|
OCSD_MEM_SPACE_ANY,mem_access_callback_fn, program_image_buffer);
|
|
#else
|
|
// or we can use the built in memory buffer interface - split our one buffer into two to
|
|
// demonstrate the addition of multiple regions
|
|
ocsd_vaddr_t block1_st, block2_st;
|
|
uint32_t block1_sz, block2_sz;
|
|
uint8_t *p_block1, *p_block2;
|
|
|
|
// break our single buffer into 2 buffers for demo purposes
|
|
block1_sz = program_image_size / 2;
|
|
block1_sz &= ~0x3; // align
|
|
block2_sz = program_image_size - block1_sz;
|
|
block1_st = program_image_address; // loaded program memory start address of program
|
|
block2_st = program_image_address + block1_sz;
|
|
p_block1 = program_image_buffer;
|
|
p_block2 = program_image_buffer + block1_sz;
|
|
|
|
/* how to add 2 "separate" buffers to the decoder */
|
|
// Always use OCSD_MEM_SPACE_ANY unless there is a reason to restrict the image to a specific
|
|
// memory space.
|
|
ret = pDecoder->addBufferMemAcc(block1_st, OCSD_MEM_SPACE_ANY, p_block1, block1_sz);
|
|
if (ret != OCSD_OK)
|
|
return ret;
|
|
|
|
ret = pDecoder->addBufferMemAcc(block2_st, OCSD_MEM_SPACE_ANY, p_block2, block2_sz);
|
|
if (ret != OCSD_OK)
|
|
return ret;
|
|
#endif
|
|
|
|
/* finally we need to provide an output callback to recieve the decoded information */
|
|
pDecoder->setGenTraceElemOutI(&output);
|
|
return ret;
|
|
}
|
|
|
|
/* get rid of the objects we created */
|
|
static void destroyDecoder()
|
|
{
|
|
delete pDecoder;
|
|
delete [] input_trace_data;
|
|
delete [] program_image_buffer;
|
|
}
|
|
|
|
#ifdef EXAMPLE_USE_MEM_CALLBACK
|
|
/* if we set up to use a callback to access memory image then this is what will be called. */
|
|
/* In this case the client must do all the work in determining if the requested address is in the
|
|
memory area. */
|
|
uint32_t mem_access_callback_fn(const void *p_context, const ocsd_vaddr_t address,
|
|
const ocsd_mem_space_acc_t mem_space, const uint32_t reqBytes, uint8_t *byteBuffer)
|
|
{
|
|
ocsd_vaddr_t buf_end_address = program_image_address + program_image_size - 1;
|
|
uint32_t read_bytes = reqBytes;
|
|
|
|
/* context should be our memory image buffer - if not return 0 bytes read */
|
|
if (p_context != program_image_buffer)
|
|
return 0;
|
|
|
|
/* not concerned with memory spaces - assume all global */
|
|
if ((address < program_image_address) || (address > buf_end_address))
|
|
return 0; // requested address not in our buffer.
|
|
|
|
// if requested bytes from address more than we have, only read to end of buffer
|
|
if ((address + reqBytes - 1) > buf_end_address)
|
|
read_bytes = (uint32_t)(buf_end_address - (address - 1));
|
|
|
|
// copy the requested data.
|
|
memcpy(byteBuffer, program_image_buffer + (address - program_image_address), read_bytes);
|
|
|
|
return read_bytes;
|
|
}
|
|
#endif
|
|
|
|
/* use the decoder to process the global trace data buffer */
|
|
static ocsd_datapath_resp_t processTraceData(uint32_t *bytes_done)
|
|
{
|
|
/* process in blocks of fixed size. */
|
|
#define DATA_CHUNK_SIZE 2048
|
|
|
|
ocsd_datapath_resp_t resp = OCSD_RESP_CONT;
|
|
uint32_t block_size, buff_offset, bytes_to_do = input_trace_data_size, bytes_processed;
|
|
ocsd_trc_index_t index = 0;
|
|
|
|
/* process the data in chunks, until either all done or
|
|
* error occurs.
|
|
*/
|
|
while ((resp == OCSD_RESP_CONT) && (bytes_to_do))
|
|
{
|
|
/* size up a block of input data */
|
|
block_size = (bytes_to_do >= DATA_CHUNK_SIZE) ? DATA_CHUNK_SIZE : bytes_to_do;
|
|
buff_offset = input_trace_data_size - bytes_to_do;
|
|
|
|
/* push it through the decoder */
|
|
resp = pDecoder->TraceDataIn(OCSD_OP_DATA, index, block_size,
|
|
input_trace_data + buff_offset, &bytes_processed);
|
|
|
|
/* adjust counter per bytes processed */
|
|
bytes_to_do -= bytes_processed;
|
|
index += bytes_processed;
|
|
}
|
|
|
|
/* if all done then signal end of trace - flushes out any remaining data */
|
|
if (!bytes_to_do)
|
|
resp = pDecoder->TraceDataIn(OCSD_OP_EOT, 0, 0, 0, 0);
|
|
|
|
/* return amount processed */
|
|
*bytes_done = input_trace_data_size - bytes_to_do;
|
|
return resp;
|
|
}
|
|
|
|
/* main routine - init input data, decode, finish ... */
|
|
int main(int argc, char* argv[])
|
|
{
|
|
int ret = OCSD_OK;
|
|
ocsd_datapath_resp_t retd;
|
|
char msg[256];
|
|
uint32_t bytes_done;
|
|
|
|
/* initialise all the data needed for decode */
|
|
if ((ret = initDataBuffers()) != OCSD_OK)
|
|
{
|
|
logger.LogMsg("Failed to create trace data buffers\n");
|
|
return ret;
|
|
}
|
|
/* initialise a decoder object */
|
|
if ((ret = initialiseDecoder()) == OCSD_OK)
|
|
{
|
|
retd = processTraceData(&bytes_done);
|
|
if (!OCSD_DATA_RESP_IS_CONT(retd))
|
|
{
|
|
ret = OCSD_ERR_DATA_DECODE_FATAL;
|
|
logger.LogMsg("Processing failed with data error\n");
|
|
}
|
|
|
|
/* get rid of the decoder and print a brief result. */
|
|
destroyDecoder();
|
|
sprintf(msg, "Processed %u bytes out of %u\n", bytes_done, input_trace_data_size);
|
|
logger.LogMsg(msg);
|
|
}
|
|
else
|
|
logger.LogMsg("Failed to create decoder for trace processing\n");
|
|
return ret;
|
|
}
|