| /* | |
| * \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 <new> | |
| #include "opencsd.h" // the library | |
| /* 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; | |
| static bool use_callback = false; | |
| static bool use_logfile = false; | |
| static bool no_print = false; | |
| /* 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 | |
| static const char *usr_snapshot_path = 0; | |
| #define MAX_TRACE_FILE_PATH_LEN 512 | |
| /* logger callback class - print out error strings. | |
| * Example of custom log printer callback | |
| */ | |
| class logCallback : public ocsdMsgLogStrOutI | |
| { | |
| public: | |
| logCallback() | |
| { | |
| out_console = true; | |
| out_file = false; | |
| } | |
| virtual ~logCallback() | |
| { | |
| closeOutFile(); | |
| } | |
| virtual void printOutStr(const std::string& outStr) | |
| { | |
| if (out_console) | |
| std::cout << outStr.c_str(); | |
| printOutFile(outStr); | |
| } | |
| void setOutConsole(const bool console_output) | |
| { | |
| out_console = console_output; | |
| } | |
| void setOutFile(const std::string &filename) | |
| { | |
| openOutFile(filename); | |
| } | |
| private: | |
| void openOutFile(const std::string &filename) | |
| { | |
| m_out_file.open(filename.c_str(), std::fstream::out | std::fstream::app); | |
| if (m_out_file.is_open()) | |
| out_file = true; | |
| } | |
| void printOutFile(const std::string& str) | |
| { | |
| if (out_file) | |
| { | |
| m_out_file << str; | |
| } | |
| } | |
| void closeOutFile() | |
| { | |
| if (out_file) { | |
| // close output file | |
| m_out_file.close(); | |
| } | |
| } | |
| bool out_file; | |
| bool out_console; | |
| std::fstream m_out_file; | |
| }; | |
| 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 */ | |
| // 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); | |
| // 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; | |
| size_t bytes_read; | |
| /* 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 = (usr_snapshot_path ? usr_snapshot_path : 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); | |
| bytes_read = fread(input_trace_data, 1, input_trace_data_size, fp); | |
| fclose(fp); | |
| if (bytes_read < (size_t)input_trace_data_size) | |
| return OCSD_ERR_FILE_ERROR; | |
| /* load up a memory image */ | |
| filename = (usr_snapshot_path ? usr_snapshot_path : 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); | |
| bytes_read = fread(program_image_buffer, 1, program_image_size, fp); | |
| fclose(fp); | |
| if (bytes_read < (size_t)program_image_size) | |
| return OCSD_ERR_FILE_ERROR; | |
| 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; | |
| if (use_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; | |
| } | |
| /* 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; | |
| } | |
| /* 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; | |
| } | |
| /* 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; | |
| } | |
| void printhelp() | |
| { | |
| printf("mem_buff_demo memory accessor test program\n"); | |
| printf("==========================================\n\n"); | |
| printf("options:-\n"); | |
| printf(" -callback : test callback mem acc functionality\n"); | |
| printf(" -logfile : output test result to logfile\n"); | |
| printf(" -noprint : no output to console\n"); | |
| printf(" -ss_path <path> : set path to snapshot directory\n"); | |
| printf("\n\n========================================\n\n"); | |
| } | |
| int process_cmd_line_opts(int argc, char* argv[]) | |
| { | |
| int options_to_process = argc - 1; | |
| int opt_idx = 1; | |
| std::string opt; | |
| while(options_to_process) | |
| { | |
| opt = argv[opt_idx]; | |
| if (opt == "-callback") | |
| use_callback = true; | |
| else if (opt == "-logfile") | |
| use_logfile = true; | |
| else if (opt == "-noprint") | |
| no_print = true; | |
| else if (opt == "-ss_path") | |
| { | |
| opt_idx++; | |
| options_to_process--; | |
| if ((opt_idx >= argc) || (strlen(argv[opt_idx]) == 0)) | |
| { | |
| printf("-ss_path: Missing path parameter or zero length\n"); | |
| return -1; | |
| } | |
| else | |
| { | |
| if (strlen(argv[opt_idx]) > (MAX_TRACE_FILE_PATH_LEN - 32)) | |
| { | |
| printf("-ss_path: path too long\n"); | |
| return -1; | |
| } | |
| usr_snapshot_path = argv[opt_idx]; | |
| } | |
| } | |
| else | |
| { | |
| printhelp(); | |
| return -1; | |
| } | |
| opt_idx++; | |
| options_to_process--; | |
| } | |
| return 0; | |
| } | |
| void log_cmd_line_opts(int argc, char* argv[]) | |
| { | |
| std::ostringstream oss; | |
| oss << "Test Command Line:-\n"; | |
| oss << argv[0] << " "; | |
| for (int i = 1; i < argc; i++) | |
| { | |
| oss << argv[i] << " "; | |
| } | |
| oss << "\n\n"; | |
| logCB.printOutStr(oss.str()); | |
| } | |
| /* 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; | |
| std::ostringstream oss; | |
| if (process_cmd_line_opts(argc, argv) != 0) | |
| return -1; | |
| if (use_logfile) | |
| logCB.setOutFile(use_callback ? "mem_buff_demo_cb.ppl" : "mem_buff_demo.ppl"); | |
| if (no_print) | |
| logCB.setOutConsole(false); | |
| oss << "MemBuffDemo\n--------------\n\n"; | |
| oss << "** Library Version : " << ocsdVersion::vers_str() << "\n\n"; | |
| logCB.printOutStr(oss.str()); | |
| log_cmd_line_opts(argc, argv); | |
| /* 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; | |
| } |