diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000000000..374e16efcdf13 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,36 @@ +name: Build + +on: [pull_request, workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install deps + run: | + sudo apt-get -y update + sudo apt-get install -y git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build autoconf libtool protobuf-c-compiler libprotobuf-c-dev + - name: Install piqi + run: | + curl -OL https://raw.github.com/alavrik/piqi-binary/master/Linux-x86_64/piqi + chmod +x piqi + sudo mv piqi /usr/local/bin + piqi --version + - name: Checkout qemu + uses: actions/checkout@v4 + with: + repository: BinaryAnalysisPlatform/qemu + path: qemu + submodules: true + - name: Build for Targets + run: | + cd qemu + mkdir build + cd build + ../configure --enable-plugins --target-list=sparc-linux-user,sparc64-linux-user + ninja diff --git a/.github/workflows/lockdown.yml b/.github/workflows/lockdown.yml deleted file mode 100644 index d5e1265cffb39..0000000000000 --- a/.github/workflows/lockdown.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Configuration for Repo Lockdown - https://github.com/dessant/repo-lockdown - -name: 'Repo Lockdown' - -on: - pull_request_target: - types: opened - -permissions: - pull-requests: write - -jobs: - action: - runs-on: ubuntu-latest - steps: - - uses: dessant/repo-lockdown@v2 - with: - pr-comment: | - Thank you for your interest in the QEMU project. - - This repository is a read-only mirror of the project's repostories hosted - on https://gitlab.com/qemu-project/qemu.git. - The project does not process merge requests filed on GitHub. - - QEMU welcomes contributions of code (either fixing bugs or adding new - functionality). However, we get a lot of patches, and so we have some - guidelines about contributing on the project website: - https://www.qemu.org/contribute/ - lock-pr: true - close-pr: true diff --git a/.gitmodules b/.gitmodules index 73cae4cd4da00..439b531cfbdda 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,6 @@ [submodule "tests/lcitool/libvirt-ci"] path = tests/lcitool/libvirt-ci url = https://gitlab.com/libvirt/libvirt-ci.git +[submodule "contrib/plugins/bap-tracing/bap-frames"] + path = contrib/plugins/bap-tracing/bap-frames + url = git@github.com:BinaryAnalysisPlatform/bap-frames.git diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..c48b0cff261dd --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# BAP emulation trace generator + +This QEMU fork implements the TCG plugin to generate execution traces in the +[bap-frame](https://github.com/BinaryAnalysisPlatform/bap-frames) format. + +This plugin does not yet support all targets. +If not listed below it is untested. + +Known to work: + +- Sparc +- Hexagon +- PPC + +Needs fixes: + +- ARM (cannot get current mode of VCPU if target can switch between ARM/Thumb). + +Previous traces were generated with a patched QEMU. +You can find these in tracewrap-* branches. + +## Dependencies + +1. Install [piqi](https://piqi.org/downloads/) so you have the `piqi` binary in `PATH`. +2. Install the developer package of `protobuf-c`. E.g. `protobuf-c-devel` (Fedora), `libprotobuf-c-dev` (Debian). +3. QEMU dependencies (see [QEMU docs](https://www.qemu.org/docs/master/devel/build-environment.html)). + +## Building + +```bash +mkdir build +cd build +# See `../configure --help` for a list of targets. +../configure --enable-plugins --target-list= +make +``` + +## Tracing a binary + +The plugin takes two required arguments: + +`bin_path`: The path to the binary emulated. Due to a [QEMU bug](https://gitlab.com/qemu-project/qemu/-/issues/3014) this cannot be inferred. +`out`: The output file to save the trace into. +`endianness`: The architecture endanness. + +```bash +./qemu-sparc64 -plugin file=buil/contrib/plugins/bap-tracing/libbap_tracing.so,bin_path=,out=,endianness=[b/l] -d plugin +ls +``` + +You can also use the helper shell script: + +```bash +./gen-trace.sh ./build/ sparc64 b +``` + +> [!NOTE] +> The trace plugin currently only generates standard frames. +> This is due to the limitations of the QEMU plugin API. +> +> If the traced binary exits due to an exception it can only indirectly be observed. +> It will produce a standard frame without any logged post register state. +> Any completed memory read/write might still be logged. +> +> If you suspect this, execute the binary with the `execlog` plugin (see `gen-trace.sh` or `gen-execlog.sh`) +> to check of the execution stops earlier than expected. + + +## Trace format + +The generated trace consists of three parts: the header, +a table of contents (TOC) holding the frame entries, and an index into the TOC. + +Each frame entry starts with the size of the frame, followed by the actual frame data. +A fixed number of frame entries are considered one _entry_ in the TOC. + +The TOC index is stored at the end. + +For specifics about the frame contents, please refer +to the [definitions](https://github.com/BinaryAnalysisPlatform/bap-frames/tree/master/piqi) in +the BAP-frames repository. + +**Format** + +| Offset | Type | Field | Trace section | +|--------|------|-------|------| +| 0x0 | uint64_t | magic number (7456879624156307493LL) | Header begin | +| 0x8 | uint64_t | trace version number | | +| 0x10 | uint64_t | frame_architecture | | +| 0x18 | uint64_t | frame_machine, 0 for unspecified. | | +| 0x20 | uint64_t | n = total number of frames in trace. | | +| 0x28 | uint64_t | T = offset to TOC index. | | +| 0x30 | uint64_t | sizeof(frame_0) | TOC begin | +| 0x38 | meta_frame | frame_0 | | +| 0x40 | uint64_t | sizeof(frame_1) | | +| 0x48 | type(frame_1) | frame_1 | | +| ... | ... | ... | | +| T-0x10 | uint64_t | sizeof(frame_n-1) | | +| T-0x8 | type(frame_n-1) | frame_n-1 | | +| T+0 | uint64_t | m = number of frames per TOC entry | TOC index begin | +| T+0x8 | uint64_t | offset toc_entry(0) | | +| T+0x10 | uint64_t | offset toc_entry(1) | | +| ... | ... | ... | | +| T+0x8+(0x8*ceil(n/m)) | uint64_t | offset toc_entry(ceil(n/m)) | | diff --git a/configs/targets/sparc-linux-user.mak b/configs/targets/sparc-linux-user.mak index 4ff4b7287d28f..18cc1ce29d71f 100644 --- a/configs/targets/sparc-linux-user.mak +++ b/configs/targets/sparc-linux-user.mak @@ -2,4 +2,5 @@ TARGET_ARCH=sparc TARGET_SYSTBL_ABI=common,32 TARGET_SYSTBL=syscall.tbl TARGET_BIG_ENDIAN=y +TARGET_XML_FILES= gdb-xml/sparc32-core.xml gdb-xml/sparc32-cp0.xml gdb-xml/sparc32-cpu.xml gdb-xml/sparc32-fpu.xml TARGET_LONG_BITS=32 diff --git a/configs/targets/sparc-softmmu.mak b/configs/targets/sparc-softmmu.mak index 78c2e25bd13ec..0a14c1839c22a 100644 --- a/configs/targets/sparc-softmmu.mak +++ b/configs/targets/sparc-softmmu.mak @@ -1,4 +1,5 @@ TARGET_ARCH=sparc TARGET_BIG_ENDIAN=y TARGET_SUPPORTS_MTTCG=y +TARGET_XML_FILES= gdb-xml/sparc32-core.xml gdb-xml/sparc32-cp0.xml gdb-xml/sparc32-cpu.xml gdb-xml/sparc32-fpu.xml TARGET_LONG_BITS=32 diff --git a/configs/targets/sparc32plus-linux-user.mak b/configs/targets/sparc32plus-linux-user.mak index 7a16934fd17bd..029fa69cb0288 100644 --- a/configs/targets/sparc32plus-linux-user.mak +++ b/configs/targets/sparc32plus-linux-user.mak @@ -5,4 +5,5 @@ TARGET_ABI_DIR=sparc TARGET_SYSTBL_ABI=common,32 TARGET_SYSTBL=syscall.tbl TARGET_BIG_ENDIAN=y +TARGET_XML_FILES= gdb-xml/sparc64-core.xml gdb-xml/sparc64-cp0.xml gdb-xml/sparc64-cpu.xml gdb-xml/sparc64-fpu.xml TARGET_LONG_BITS=64 diff --git a/configs/targets/sparc64-linux-user.mak b/configs/targets/sparc64-linux-user.mak index 64ea04e3e2ee4..67a11f3c8adab 100644 --- a/configs/targets/sparc64-linux-user.mak +++ b/configs/targets/sparc64-linux-user.mak @@ -4,4 +4,5 @@ TARGET_ABI_DIR=sparc TARGET_SYSTBL_ABI=common,64 TARGET_SYSTBL=syscall.tbl TARGET_BIG_ENDIAN=y +TARGET_XML_FILES= gdb-xml/sparc64-core.xml gdb-xml/sparc64-cp0.xml gdb-xml/sparc64-cpu.xml gdb-xml/sparc64-fpu.xml TARGET_LONG_BITS=64 diff --git a/configs/targets/sparc64-softmmu.mak b/configs/targets/sparc64-softmmu.mak index f7bab97a00289..6929774e553e8 100644 --- a/configs/targets/sparc64-softmmu.mak +++ b/configs/targets/sparc64-softmmu.mak @@ -2,4 +2,5 @@ TARGET_ARCH=sparc64 TARGET_BASE_ARCH=sparc TARGET_BIG_ENDIAN=y TARGET_SUPPORTS_MTTCG=y +TARGET_XML_FILES= gdb-xml/sparc64-core.xml gdb-xml/sparc64-cp0.xml gdb-xml/sparc64-cpu.xml gdb-xml/sparc64-fpu.xml TARGET_LONG_BITS=64 diff --git a/contrib/plugins/bap-tracing/bap-frames b/contrib/plugins/bap-tracing/bap-frames new file mode 160000 index 0000000000000..71368108ee63d --- /dev/null +++ b/contrib/plugins/bap-tracing/bap-frames @@ -0,0 +1 @@ +Subproject commit 71368108ee63d3bc557657e81fb58d7a6235b920 diff --git a/contrib/plugins/bap-tracing/fix_proto_src.py b/contrib/plugins/bap-tracing/fix_proto_src.py new file mode 100755 index 0000000000000..15efa5fc68938 --- /dev/null +++ b/contrib/plugins/bap-tracing/fix_proto_src.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +""" +This just does: +sed -i 's/->base/->__base/g' +sed -i 's/ProtobufCMessage base;/ProtobufCMessage __base;/g' +""" + +import sys +import re + +if len(sys.argv) != 6 or sys.argv[3] != "-o": + print("usage: fix_proto_src.py frame.piqi.pb-c.c frame.piqi.pb-c.h -o frame.piqi.pb-c-fixed.c frame.piqi.pb-c-fixed.h") + exit(1) + +for (in_file, out_file) in zip(sys.argv[1:3], sys.argv[4:6]): + with open(in_file, "r") as i: + contents = i.read() + contents = contents.replace("->base", "->__base") + contents = contents.replace("ProtobufCMessage base;", "ProtobufCMessage __base;") + contents = re.sub(r'".*frame.piqi.pb-c.h"', '"frame.piqi.pb-c-patched.h"', contents) + with open(out_file, "w") as o: + o.write(contents) diff --git a/contrib/plugins/bap-tracing/frame_buffer.c b/contrib/plugins/bap-tracing/frame_buffer.c new file mode 100644 index 0000000000000..bbfa0c313534d --- /dev/null +++ b/contrib/plugins/bap-tracing/frame_buffer.c @@ -0,0 +1,296 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: GPL-2.0-only + +#include "frame_buffer.h" +#include "trace_meta.h" + +static Frame *frame_new_std(uint64_t addr, int vcpu_id, const char *mode_id, + uint8_t *bytes, size_t bytes_len) { + Frame *frame = g_new(Frame, 1); + frame__init(frame); + + StdFrame *sframe = g_new(StdFrame, 1); + std_frame__init(sframe); + frame->std_frame = sframe; + + sframe->address = addr; + if (mode_id) { + sframe->mode = g_strdup(mode_id); + } + sframe->thread_id = vcpu_id; + sframe->rawbytes.len = bytes_len; + sframe->rawbytes.data = g_malloc(bytes_len); + memcpy(sframe->rawbytes.data, bytes, bytes_len); + + OperandValueList *ol_in = g_new(OperandValueList, 1); + operand_value_list__init(ol_in); + ol_in->n_elem = 0; + sframe->operand_pre_list = ol_in; + + OperandValueList *ol_out = g_new(OperandValueList, 1); + operand_value_list__init(ol_out); + ol_out->n_elem = 0; + sframe->operand_post_list = ol_out; + return frame; +} + +static inline void free_operand(OperandInfo *oi) { + OperandInfoSpecific *ois = oi->operand_info_specific; + + // Free reg-operand + RegOperand *ro = ois->reg_operand; + if (ro && ro->name) + g_free(ro->name); + g_free(ro); + + // Free mem-operand + MemOperand *mo = ois->mem_operand; + g_free(mo); + g_free(oi->value.data); + g_free(oi->taint_info); + g_free(ois); + g_free(oi->operand_usage); + g_free(oi); +} + +static void frame_free(Frame *frame) { + if (!frame) { + return; + } + StdFrame *sframe = frame->std_frame; + for (size_t i = 0; i < sframe->operand_pre_list->n_elem; i++) { + free_operand(sframe->operand_pre_list->elem[i]); + } + g_free(sframe->operand_pre_list->elem); + g_free(sframe->operand_pre_list); + + for (size_t i = 0; i < sframe->operand_post_list->n_elem; i++) { + free_operand(sframe->operand_post_list->elem[i]); + } + g_free(sframe->operand_post_list->elem); + g_free(sframe->operand_post_list); + + g_free(sframe->rawbytes.data); + g_free(sframe); + g_free(frame); +} + +static bool std_frame_add_operand(StdFrame *std_frame, OperandInfo *oi) { + OperandValueList *ol; + if (oi->operand_usage->written) { + ol = std_frame->operand_post_list; + } else { + ol = std_frame->operand_pre_list; + } + + oi->taint_info = g_new(TaintInfo, 1); + taint_info__init(oi->taint_info); + oi->taint_info->no_taint = 1; + oi->taint_info->has_no_taint = 1; + + ol->n_elem += 1; + ol->elem = g_renew(OperandInfo *, ol->elem, ol->n_elem); + ol->elem[ol->n_elem - 1] = oi; + return true; +} + +FrameBuffer *frame_buffer_new(void) { + FrameBuffer *fb = g_malloc0(sizeof(FrameBuffer)); + return fb; +} + +bool frame_buffer_is_full(const FrameBuffer *buf) { + return buf->idx >= frames_per_toc_entry; +} + +void frame_buffer_close_frame(FrameBuffer *buf) { + char *str = frame_buffer_as_str(buf); + // qemu_plugin_outs("Close frame: "); + // qemu_plugin_outs(str); + // qemu_plugin_outs("\n\n"); + g_free(str); + buf->idx++; +} + +#define FRAME_STR_SIZE 8192 + +#define APPEND(...) \ + snprintf(str + off, max - off, __VA_ARGS__); \ + off = strlen(str); + +char *frame_buffer_as_str(const FrameBuffer *buf) { + char *str = g_malloc0(FRAME_STR_SIZE); + const Frame *frame = buf->fbuf[buf->idx]; + if (!frame) { + snprintf(str, FRAME_STR_SIZE, ""); + return str; + } + size_t max = FRAME_STR_SIZE - 1; + snprintf(str, max, "{ pre: [ "); + size_t off = strlen(str); + + StdFrame *sframe = frame->std_frame; + for (size_t i = 0; i < sframe->operand_pre_list->n_elem; i++) { + OperandInfo *oi = sframe->operand_pre_list->elem[i]; + if (oi->operand_info_specific->reg_operand) { + APPEND("r:%s=", oi->operand_info_specific->reg_operand->name); + } else { + APPEND("m:0x%016lx=", oi->operand_info_specific->mem_operand->address); + } + + for (size_t k = 0; k < oi->value.len; ++k) { + APPEND("%02x", oi->value.data[k]); + } + APPEND(", "); + } + APPEND(" ], post: [ "); + for (size_t i = 0; i < sframe->operand_post_list->n_elem; i++) { + OperandInfo *oi = sframe->operand_post_list->elem[i]; + if (oi->operand_info_specific->reg_operand) { + APPEND("r:%s=", oi->operand_info_specific->reg_operand->name); + } else { + APPEND("m:0x%016lx=", oi->operand_info_specific->mem_operand->address); + } + + for (size_t k = 0; k < oi->value.len; ++k) { + APPEND("%02x", oi->value.data[k]); + } + APPEND(", "); + } + + APPEND("]}"); + return str; +} + +bool frame_buffer_is_empty(const FrameBuffer *buf) { + return buf->fbuf[buf->idx] == NULL; +} + +void frame_buffer_clean(FrameBuffer *buf) { + memset(buf->fbuf, 0, sizeof(buf->fbuf)); + buf->idx = 0; +} + +bool frame_buffer_write_frame_to_file(FrameBuffer *buf, WLOCKED FILE *file, + size_t i) { + if (i > buf->idx) { + return false; + } + Frame *frame = buf->fbuf[i]; + size_t msg_size = frame__get_packed_size(frame); + uint8_t *packed_buffer = g_alloca(msg_size); + uint64_t packed_size = frame__pack(frame, packed_buffer); + WRITE(packed_size); + WRITE_BUF(packed_buffer, packed_size); + frame_free(frame); + return true; +} + +/// @brief Dumps the file buffer as TOC entry into the file. +uint64_t frame_buffer_flush_to_file(FrameBuffer *buf, WLOCKED FILE *file) { + uint64_t n = 0; + for (size_t i = 0; i < buf->idx; ++i) { + frame_buffer_write_frame_to_file(buf, file, i); + n++; + } + frame_buffer_clean(buf); + return n; +} + +bool frame_buffer_new_frame_std(FrameBuffer *buf, unsigned int thread_id, + uint64_t vaddr, const char *mode, + uint8_t *bytes, size_t bytes_len) { + if (frame_buffer_is_full(buf)) { + return false; + } + Frame *frame = frame_new_std(vaddr, thread_id, mode, bytes, bytes_len); + if (!frame) { + return false; + } + buf->fbuf[buf->idx] = frame; + return true; +} + +static bool append_op_info(FrameBuffer *buf, OperandInfo *oi) { + Frame *frame = buf->fbuf[buf->idx]; + if (!frame || !frame->std_frame) { + qemu_plugin_outs( + "Attempt to append operand info to a uninitialzied frame."); + return false; + } + return std_frame_add_operand(frame->std_frame, oi); +} + +bool frame_buffer_append_reg_info(FrameBuffer *buf, const char *name, + const GByteArray *content, size_t reg_size, + OperandAccess acc) { + OperandInfo *oi = frame_init_reg_operand_info( + name, content->data + content->len - reg_size, reg_size, acc); + g_assert(oi); + return append_op_info(buf, oi); +} + +OperandInfo *frame_init_reg_operand_info(const char *name, const uint8_t *value, + size_t value_size, + OperandAccess access) { + RegOperand *ro = g_new(RegOperand, 1); + reg_operand__init(ro); + ro->name = strdup(name); + + OperandInfoSpecific *ois = g_new(OperandInfoSpecific, 1); + operand_info_specific__init(ois); + ois->reg_operand = ro; + + OperandUsage *ou = g_new(OperandUsage, 1); + operand_usage__init(ou); + ou->read = access & OperandRead; + ou->written = access & OperandWritten; + OperandInfo *oi = g_new(OperandInfo, 1); + operand_info__init(oi); + oi->bit_length = value_size * 8; + oi->operand_info_specific = ois; + oi->operand_usage = ou; + oi->value.len = value_size; + oi->value.data = g_malloc(oi->value.len); + memcpy(oi->value.data, value, value_size); + + return oi; +} + +static OperandInfo *frame_init_mem_operand_info(uint64_t vaddr, + const uint8_t *mval, + size_t mval_bits, + bool is_store) { + MemOperand *ro = g_new(MemOperand, 1); + mem_operand__init(ro); + ro->address = vaddr; + + OperandInfoSpecific *ois = g_new(OperandInfoSpecific, 1); + operand_info_specific__init(ois); + ois->mem_operand = ro; + + size_t byte_width = mval_bits / 8; + OperandUsage *ou = g_new(OperandUsage, 1); + operand_usage__init(ou); + ou->read = !is_store; + ou->written = is_store; + OperandInfo *oi = g_new(OperandInfo, 1); + operand_info__init(oi); + oi->bit_length = mval_bits; + oi->operand_info_specific = ois; + oi->operand_usage = ou; + oi->value.len = byte_width; + oi->value.data = g_malloc(oi->value.len); + memcpy(oi->value.data, mval, oi->value.len); + + return oi; +} + +bool frame_buffer_append_mem_info(FrameBuffer *fbuf, uint64_t vaddr, + const uint8_t *mval, size_t mval_bits, + bool is_store) { + OperandInfo *oi = + frame_init_mem_operand_info(vaddr, mval, mval_bits, is_store); + g_assert(oi); + return append_op_info(fbuf, oi); +} diff --git a/contrib/plugins/bap-tracing/frame_buffer.h b/contrib/plugins/bap-tracing/frame_buffer.h new file mode 100644 index 0000000000000..f6126ce00dc26 --- /dev/null +++ b/contrib/plugins/bap-tracing/frame_buffer.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef BAP_FRAME_BUFFER_H +#define BAP_FRAME_BUFFER_H + +#include +#include +#include + +#include "frame.piqi.pb-c-patched.h" +#include "trace_consts.h" +#include "trace_meta.h" + +typedef enum { + OperandRead = 1, + OperandWritten = 2, +} OperandAccess; + +typedef struct { + Frame *fbuf[FRAMES_PER_TOC_ENTRY_]; ///< The frames buffered. + size_t idx; ///< Points to currently open frame. +} FrameBuffer; + +/** + * \brief Initializes a frame buffer with space for \p size frames. + * Returns the buffer or NULL in case of failure. + */ +FrameBuffer *frame_buffer_new(void); + +uint64_t frame_buffer_flush_to_file(FrameBuffer *buf, WLOCKED FILE *file); +void frame_buffer_clean(FrameBuffer *buf); +bool frame_buffer_write_frame_to_file(FrameBuffer *buf, WLOCKED FILE *file, size_t i); +bool frame_buffer_is_full(const FrameBuffer *buf); +bool frame_buffer_is_empty(const FrameBuffer *buf); +void frame_buffer_close_frame(FrameBuffer *buf); +char *frame_buffer_as_str(const FrameBuffer *buf); + +bool frame_buffer_new_frame_std(FrameBuffer *buf, unsigned int thread_id, + uint64_t vaddr, const char *mode_id, + uint8_t *bytes, size_t bytes_len); + +bool frame_buffer_append_mem_info(FrameBuffer *fbuf, uint64_t vaddr, + const uint8_t *mval, size_t mval_bits, + bool is_store); + +/** + * \brief Appends the given operand info to the open frame. + */ +bool frame_buffer_append_reg_info(FrameBuffer *buf, const char *name, + const GByteArray *content, size_t reg_size, + OperandAccess acc); + +OperandInfo *frame_init_reg_operand_info(const char *name, const uint8_t *value, + size_t value_size, + OperandAccess access); + +#endif diff --git a/contrib/plugins/bap-tracing/meson.build b/contrib/plugins/bap-tracing/meson.build new file mode 100644 index 0000000000000..08ddca168141b --- /dev/null +++ b/contrib/plugins/bap-tracing/meson.build @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2025 Rot127 +# SPDX-License-Identifier: GPL-2.0-only + +piqi = find_program('piqi') +protoc_c = find_program('protoc-c') + +# piqi -> protobuf file. +piqi_src = custom_target('piqi', + input: 'bap-frames/piqi/frame.piqi', + output: 'frame.piqi.proto', + command: [piqi, 'to-proto', '@INPUT@', '-o', '@OUTPUT@']) + +frame_arch_h = fs.copyfile('bap-frames/libtrace/src/frame_arch.h', + 'frame_arch.h') + +# protobuf file -> C code +frame_proto_src_raw = custom_target('proto', + input: piqi_src, + output: ['frame.piqi.pb-c.c', 'frame.piqi.pb-c.h'], + command: [protoc_c, '--c_out=.', '@INPUT@'], + depends: piqi_src) + +# Patch protobuf header: base -> __base. +# Necessary for the C build. +# See fix_proto_src.py +frame_proto_src = custom_target( + input: frame_proto_src_raw, + output: ['frame.piqi.pb-c-patched.c', 'frame.piqi.pb-c-patched.h'], + command: [files('fix_proto_src.py'), '@INPUT@', '-o', '@OUTPUT@'], + depends: frame_proto_src_raw +) + +libprotobuf = dependency('libprotobuf-c') +frame_protobuf = static_library('protobuf', [frame_proto_src], pic: true) +dep_libprotobuf = declare_dependency( + sources : [frame_proto_src, frame_arch_h], + link_with : [frame_protobuf], + dependencies: [libprotobuf] +) + +bap_tracing_src = files( + 'frame_buffer.c', + 'tracing.c', + 'trace_meta.c', +) + +if host_os == 'windows' + plugin_modules += shared_module('bap_tracing', bap_tracing_src, + include_directories: '../../../include/qemu', + link_depends: [win32_qemu_plugin_api_lib], + link_args: win32_qemu_plugin_api_link_flags, + dependencies: [glib, dep_libprotobuf]) +else + plugin_modules += shared_module('bap_tracing', bap_tracing_src, + include_directories: '../../../include/qemu', + dependencies: [glib, dep_libprotobuf]) +endif diff --git a/contrib/plugins/bap-tracing/trace_consts.h b/contrib/plugins/bap-tracing/trace_consts.h new file mode 100644 index 0000000000000..892d7cf885794 --- /dev/null +++ b/contrib/plugins/bap-tracing/trace_consts.h @@ -0,0 +1,30 @@ +#ifndef BAP_TRACE_CONSTS_H +#define BAP_TRACE_CONSTS_H + +#include "qemu-version.h" +#include + +// Trace header constants + +static const uint64_t magic_number = 7456879624156307493LL; + +static const uint64_t offset_magic_number = 0LL; +static const uint64_t offset_trace_version = 8LL; +static const uint64_t offset_target_arch = 16LL; +static const uint64_t offset_target_machine = 24LL; +static const uint64_t offset_total_num_frames = 32LL; +static const uint64_t offset_toc_index_offset = 40LL; +static const uint64_t offset_toc_start = 48LL; +static const uint64_t offset_first_frame = 48LL; + +static const uint64_t trace_version = 3LL; + +#define TRACER_NAME "qemu" +#define TRACER_VERSION "plugin " QEMU_FULL_VERSION + +#define FRAMES_PER_TOC_ENTRY_ 64LL +static const uint64_t frames_per_toc_entry = FRAMES_PER_TOC_ENTRY_; + +// Arch specific + +#endif diff --git a/contrib/plugins/bap-tracing/trace_meta.c b/contrib/plugins/bap-tracing/trace_meta.c new file mode 100644 index 0000000000000..4f15265435bee --- /dev/null +++ b/contrib/plugins/bap-tracing/trace_meta.c @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "frame.piqi.pb-c-patched.h" +#include "trace_consts.h" +#include "trace_meta.h" + +#define MD5LEN 16 + +static void compute_target_md5(const char *binary_path, + guchar target_md5[MD5LEN]) { + const GChecksumType md5 = G_CHECKSUM_MD5; + + GChecksum *cs = g_checksum_new(md5); + FILE *target = fopen(binary_path, "r"); + guchar buf[BUFSIZ]; + gsize expected_length = MD5LEN; + + if (!cs) + qemu_plugin_outs("failed to create a checksum"); + if (!target) + qemu_plugin_outs("failed to open target binary"); + if (g_checksum_type_get_length(md5) != expected_length) + abort(); + + while (!feof(target)) { + size_t len = fread(buf, 1, BUFSIZ, target); + if (ferror(target)) + qemu_plugin_outs("failed to read target binary"); + g_checksum_update(cs, buf, len); + } + + g_checksum_get_digest(cs, target_md5, &expected_length); + fclose(target); +} + +static void init_tracer(Tracer *tracer, char **argv, int argc) { + tracer__init(tracer); + tracer->name = g_strdup(TRACER_NAME); + tracer->n_args = argc; + tracer->args = argv; + tracer->n_envp = 0; + tracer->envp = NULL; + tracer->version = g_strdup(TRACER_VERSION); +} + +static void init_target(Target *target, const char *bin_path, char **argv, + int argc) { + target__init(target); + + if (bin_path) { + guchar *target_md5 = g_malloc0(MD5LEN); + compute_target_md5(bin_path, target_md5); + target->path = g_strdup(bin_path); + target->md5sum.len = MD5LEN; + target->md5sum.data = target_md5; + } + target->n_args = argc; + target->args = argv; + target->n_envp = 0; + target->envp = NULL; +} + +#ifdef G_OS_UNIX +static bool unix_fill_fstats(Fstats *fstats, const char *path) { + struct stat stats; + if (stat(path, &stats) < 0) { + qemu_plugin_outs("failed to obtain file stats"); + return false; + } + + fstats->size = stats.st_size; + fstats->atime = stats.st_atime; + fstats->mtime = stats.st_mtime; + fstats->ctime = stats.st_ctime; + return true; +} +#endif + +static bool init_fstats(Fstats *fstats, const char *binary_path) { + fstats__init(fstats); +#ifdef G_OS_UNIX + return unix_fill_fstats(fstats, binary_path); +#endif + return true; +} + +char *get_argv_val(char **argv, int argc, const char *key) { + for (size_t i = 0; i < argc; ++i) { + if (!strncmp(argv[i], key, strlen(key))) { + const char *val = argv[i] + strlen(key); + if (val[0] != '=') { + qemu_plugin_outs("Invalid argument value for "); + qemu_plugin_outs(key); + qemu_plugin_outs("\n"); + qemu_plugin_outs("Should be 'key=val'\n"); + return NULL; + } + val++; + const char *end = strchr(val, ','); + while (end && *(end - 1) == '\\') { + // Allow escaped commas. + end = strchr(val, ','); + } + size_t len = !end ? strlen(val) : end - val; + char *argument = g_malloc0(len + 1); + memcpy(argument, val, len); + return argument; + } + } + return NULL; +} + +void file_exists_exit(const char *file) { + FILE *test = fopen(file, "r"); + if (!test) { + qemu_plugin_outs("Failed to open binary file: "); + qemu_plugin_outs(file); + qemu_plugin_outs("\n"); + exit(1); + } + fclose(test); +} + +void write_meta(WLOCKED FILE *file, char **plugin_argv, size_t plugin_argc) { + char *arg_bin_path = get_argv_val(plugin_argv, plugin_argc, "bin_path"); + // Note: Usually we should get the binary path from + // qemu_plugin_path_to_binary(). But it doesn't seem to work due to dependency + // issues. See: https://gitlab.com/qemu-project/qemu/-/issues/3014 + const char *bin_path = NULL; // qemu_plugin_path_to_binary(); + if (!bin_path && !arg_bin_path) { + qemu_plugin_outs("\nFailed to retrieve the binary path\n"); + qemu_plugin_outs("This is required.\n"); + qemu_plugin_outs("You can pass it as plugin argument " + "'bin_path='.\n\n"); + exit(1); + } else { + file_exists_exit(arg_bin_path); + } + if (bin_path && arg_bin_path) { + qemu_plugin_outs( + "'bin_path' argument found, but the binary path is known to the module.\n\ + Argument 'bin_path' is ignored.\n"); + } + + MetaFrame meta = {0}; + Tracer tracer = {0}; + Target target = {0}; + Fstats fstats = {0}; + + meta_frame__init(&meta); + init_tracer(&tracer, plugin_argv, plugin_argc); + init_target(&target, bin_path ? bin_path : arg_bin_path, plugin_argv, + plugin_argc); + init_fstats(&fstats, bin_path ? bin_path : arg_bin_path); + + meta.tracer = &tracer; + meta.target = ⌖ + meta.fstats = &fstats; + meta.time = time(NULL); + char *user = g_strdup(g_get_real_name()); + meta.user = user; + + char *host = g_strdup(g_get_host_name()); + meta.host = host; + + size_t msg_size = meta_frame__get_packed_size(&meta); + uint8_t *packed_buffer = g_malloc0(msg_size); + uint64_t packed_size = meta_frame__pack(&meta, packed_buffer); + g_assert(msg_size == packed_size); + WRITE(packed_size); + + // I don't know why, but ASAN crashes at this line if the WRITE_BUF macro + // is used. Although it should be the exact same code. + if (fwrite((packed_buffer), 1, (packed_size), file) != packed_size) { + err(1, "fwrite failed"); + } + + g_free(packed_buffer); + g_free(tracer.name); + g_free(tracer.version); + g_free(target.path); + g_free(target.md5sum.data); + + g_free(user); + g_free(host); + g_free(arg_bin_path); +} + +/// Copies src to dst. dst will always be in little endian byte order. +void memcpy_le(uint8_t *dst, const uint8_t *src, size_t len, bool big_endian) { + if (!big_endian) { + memcpy(dst, src, len); + return; + } + for (size_t k = 0; k < len; ++k) { + dst[k] = src[len - 1 - k]; + } +} + +void swap_to_le(uint8_t *buf, size_t len, bool big_endian) { + if (!big_endian || len == 1) { + return; + } + for (size_t k = 0; k < len / 2; ++k) { + uint8_t tmp = buf[k]; + buf[k] = buf[len - 1 - k]; + buf[len - 1 - k] = tmp; + } +} diff --git a/contrib/plugins/bap-tracing/trace_meta.h b/contrib/plugins/bap-tracing/trace_meta.h new file mode 100644 index 0000000000000..03eb57ce3d85c --- /dev/null +++ b/contrib/plugins/bap-tracing/trace_meta.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef BAP_TRACE_META_H +#define BAP_TRACE_META_H + +#include +#include +#include +#include + +/** + * \brief Empty macros indicate the argument, variable etc. + * must be locked for writing. + */ +#define WLOCKED + +#define WRITE(x) \ + do { \ + if (fwrite(&(x), sizeof(x), 1, file) != 1) \ + err(1, "fwrite failed"); \ + } while (0) + +#define WRITE_BUF(x, n) \ + do { \ + if (fwrite((x), 1, (n), file) != n) \ + err(1, "fwrite failed"); \ + } while (0) + +#define SEEK(off) \ + do { \ + if (fseek(file, (off), SEEK_SET) < 0) \ + err(1, "stream not seekable"); \ + } while (0) + +void write_meta(WLOCKED FILE *file, char **plugin_argv, size_t plugin_argc); +char *get_argv_val(char **argv, int argc, const char *key); +void file_exists_exit(const char *file); +void memcpy_le(uint8_t *dst, const uint8_t *src, size_t len, bool big_endian); +void swap_to_le(uint8_t *buf, size_t len, bool big_endian); + +#endif diff --git a/contrib/plugins/bap-tracing/tracing.c b/contrib/plugins/bap-tracing/tracing.c new file mode 100644 index 0000000000000..fbb7161f0e5ce --- /dev/null +++ b/contrib/plugins/bap-tracing/tracing.c @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "compiler.h" +#include "frame_arch.h" +#include "frame_buffer.h" +#include "qemu-plugin.h" +#include "trace_consts.h" +#include "trace_meta.h" +#include "tracing.h" + +static TraceState state = {0}; + +static void mval_to_buf(qemu_plugin_mem_value *val, uint8_t *buf) { + size_t mem_val_size = 0; + switch (val->type) { + case QEMU_PLUGIN_MEM_VALUE_U8: + buf[0] = val->data.u8; + mem_val_size = 1; + break; + case QEMU_PLUGIN_MEM_VALUE_U16: + buf[0] = (uint8_t)val->data.u16; + buf[1] = (uint8_t)(val->data.u16 >> 8); + mem_val_size = 2; + break; + case QEMU_PLUGIN_MEM_VALUE_U32: + buf[0] = (uint8_t)val->data.u32; + buf[1] = (uint8_t)(val->data.u32 >> 8); + buf[2] = (uint8_t)(val->data.u32 >> 16); + buf[3] = (uint8_t)(val->data.u32 >> 24); + mem_val_size = 4; + break; + case QEMU_PLUGIN_MEM_VALUE_U64: + for (size_t i = 0; i < 8; ++i) { + buf[i] = (uint8_t)(val->data.u64 >> (i * 8)); + } + mem_val_size = 8; + break; + case QEMU_PLUGIN_MEM_VALUE_U128: + for (size_t i = 0; i < 8; ++i) { + buf[i] = (uint8_t)(val->data.u128.low >> (i * 8)); + } + for (size_t i = 0; i < 8; ++i) { + buf[i + 8] = (uint8_t)(val->data.u128.high >> (i * 8)); + } + mem_val_size = 16; + break; + default: + g_assert(false); + } + swap_to_le(buf, mem_val_size, state.is_big_endian); +} + +static size_t mval_type_to_int(enum qemu_plugin_mem_value_type type) { + switch (type) { + case QEMU_PLUGIN_MEM_VALUE_U8: + return 8; + case QEMU_PLUGIN_MEM_VALUE_U16: + return 16; + case QEMU_PLUGIN_MEM_VALUE_U32: + return 32; + case QEMU_PLUGIN_MEM_VALUE_U64: + return 64; + case QEMU_PLUGIN_MEM_VALUE_U128: + return 128; + default: + g_assert(false); + } + return 0; +} + +static void add_mem_op(VCPU *vcpu, unsigned int vcpu_index, FrameBuffer *fbuf, + uint64_t vaddr, qemu_plugin_mem_value *mval, + bool is_store) { + size_t mval_bits = mval_type_to_int(mval->type); + uint8_t *buf = g_malloc(mval_bits / 8); + mval_to_buf(mval, buf); + if (!frame_buffer_append_mem_info(fbuf, vaddr, buf, mval_bits, is_store)) { + qemu_plugin_outs("Failed to append memory info\n"); + } + return; +} + +static void log_insn_mem_access(unsigned int vcpu_index, + qemu_plugin_meminfo_t info, uint64_t vaddr, + void *userdata) { + g_rw_lock_reader_lock(&state.vcpus_array_lock); + g_rw_lock_reader_lock(&state.frame_buffer_lock); + + VCPU *vcpu = g_ptr_array_index(state.vcpus, vcpu_index); + g_assert(vcpu); + FrameBuffer *fbuf = g_ptr_array_index(state.frame_buffer, vcpu_index); + + bool is_store = qemu_plugin_mem_is_store(info); + qemu_plugin_mem_value mval = qemu_plugin_mem_get_value(info); + + add_mem_op(vcpu, vcpu_index, fbuf, vaddr, &mval, is_store); + + g_rw_lock_writer_unlock(&state.frame_buffer_lock); + g_rw_lock_writer_unlock(&state.vcpus_array_lock); +} + +static void add_post_reg_state(VCPU *vcpu, unsigned int vcpu_index, + GArray *current_regs, FrameBuffer *fbuf) { + + GByteArray *rdata = g_byte_array_new(); + for (size_t i = 0; i < current_regs->len; ++i) { + Register *prev_reg = vcpu->registers->pdata[i]; + + qemu_plugin_reg_descriptor *reg = + &g_array_index(current_regs, qemu_plugin_reg_descriptor, i); + int s = qemu_plugin_read_register(reg->handle, rdata); + assert(s == prev_reg->content->len); + swap_to_le(rdata->data, s, state.is_big_endian); + if (!memcmp(rdata->data, prev_reg->content->data, s)) { + // No change + // Flush byte array + g_byte_array_set_size(rdata, 0); + continue; + } + + if (!frame_buffer_append_reg_info(fbuf, reg->name, rdata, s, + OperandWritten)) { + qemu_plugin_outs("Failed to append opinfo.\n"); + return; + } + // Flush byte array + g_byte_array_set_size(rdata, 0); + } +} + +static void add_pre_reg_state(VCPU *vcpu, unsigned int vcpu_index, + GArray *current_regs, FrameBuffer *fbuf) { + GByteArray *rdata = g_byte_array_new(); + for (size_t i = 0; i < current_regs->len; ++i) { + qemu_plugin_reg_descriptor *reg = + &g_array_index(current_regs, qemu_plugin_reg_descriptor, i); + size_t s = qemu_plugin_read_register(reg->handle, rdata); + Register *prev_reg = g_ptr_array_index(vcpu->registers, i); + g_assert(!g_ascii_strcasecmp(prev_reg->name, reg->name) && + prev_reg->handle == reg->handle); + memcpy_le(prev_reg->content->data, rdata->data, prev_reg->content->len, + state.is_big_endian); + frame_buffer_append_reg_info(fbuf, reg->name, prev_reg->content, s, + OperandRead); + // Flush byte array + g_byte_array_set_size(rdata, 0); + } +} + +static GPtrArray *registers_init(void) { + GArray *reg_list = qemu_plugin_get_registers(); + + if (reg_list->len == 0) { + g_array_free(reg_list, false); + return NULL; + } + GPtrArray *registers = g_ptr_array_new(); + for (size_t r = 0; r < reg_list->len; r++) { + qemu_plugin_reg_descriptor *rd = + &g_array_index(reg_list, qemu_plugin_reg_descriptor, r); + Register *reg = init_vcpu_register(rd); + g_ptr_array_add(registers, reg); + } + + return registers->len ? g_steal_pointer(®isters) : NULL; +} + +static void flush_and_write_toc_entry(FrameBuffer *fbuf) { + g_rw_lock_writer_lock(&state.file_lock); + g_rw_lock_writer_lock(&state.toc_entries_offsets_lock); + g_rw_lock_writer_lock(&state.total_num_frames_lock); + + state.total_num_frames += frame_buffer_flush_to_file(fbuf, state.file); + uint64_t next_toc_entry = ftell(state.file); + g_array_append_val(state.toc_entries_offsets, next_toc_entry); + + g_rw_lock_writer_unlock(&state.total_num_frames_lock); + g_rw_lock_writer_unlock(&state.toc_entries_offsets_lock); + g_rw_lock_writer_unlock(&state.file_lock); +} + +static void flush_all_frame_bufs(void) { + g_rw_lock_writer_lock(&state.file_lock); + g_rw_lock_writer_lock(&state.toc_entries_offsets_lock); + g_rw_lock_writer_lock(&state.total_num_frames_lock); + g_rw_lock_writer_lock(&state.frame_buffer_lock); + + FILE *file = state.file; + + // Dump the rest of the frames but be mindeful about the + // maximum number of frames per TOC entry. + + size_t total_to_write = 0; + for (size_t i = 0; i < state.vcpus->len; ++i) { + // Add post operands to last instructions. + FrameBuffer *fbuf = g_ptr_array_index(state.frame_buffer, i); + VCPU *vcpu = g_ptr_array_index(state.vcpus, i); + g_assert(vcpu); + GArray *current_regs = qemu_plugin_get_registers(); + g_assert(current_regs->len == vcpu->registers->len); + add_post_reg_state(vcpu, i, current_regs, fbuf); + frame_buffer_close_frame(fbuf); + + total_to_write += fbuf->idx; + } + + size_t entry_count = 0; + for (size_t i = 0; i < state.vcpus->len && total_to_write > 0; ++i) { + if (entry_count == frames_per_toc_entry) { + entry_count = 0; + uint64_t next_toc_entry = ftell(state.file); + g_array_append_val(state.toc_entries_offsets, next_toc_entry); + } + + FrameBuffer *fbuf = g_ptr_array_index(state.frame_buffer, i); + for (size_t k = 0; k < fbuf->idx; ++k) { + frame_buffer_write_frame_to_file(fbuf, file, k); + entry_count++; + state.total_num_frames++; + total_to_write--; + } + } + + g_rw_lock_writer_unlock(&state.frame_buffer_lock); + g_rw_lock_writer_unlock(&state.total_num_frames_lock); + g_rw_lock_writer_unlock(&state.toc_entries_offsets_lock); + g_rw_lock_writer_unlock(&state.file_lock); +} + +static void log_insn_reg_access(unsigned int vcpu_index, void *udata) { + g_rw_lock_reader_lock(&state.vcpus_array_lock); + g_rw_lock_writer_lock(&state.frame_buffer_lock); + + FrameBuffer *fbuf = g_ptr_array_index(state.frame_buffer, vcpu_index); + VCPU *vcpu = g_ptr_array_index(state.vcpus, vcpu_index); + g_assert(vcpu); + GArray *current_regs = qemu_plugin_get_registers(); + g_assert(current_regs->len == vcpu->registers->len); + + if (!frame_buffer_is_empty(fbuf)) { + add_post_reg_state(vcpu, vcpu_index, current_regs, fbuf); + frame_buffer_close_frame(fbuf); + } + + if (frame_buffer_is_full(fbuf)) { + flush_and_write_toc_entry(fbuf); + } + + // Open new one. + Instruction *insn = udata; + g_rw_lock_reader_lock(&state.vcpu_mode_lock); + if (!frame_buffer_new_frame_std( + fbuf, vcpu_index, insn->vaddr, + g_ptr_array_index(state.vcpu_modes, vcpu_index), insn->bytes, + insn->size)) { + err(1, "Failed to add new frame.\n"); + } + g_rw_lock_reader_unlock(&state.vcpu_mode_lock); + + add_pre_reg_state(vcpu, vcpu_index, current_regs, fbuf); + + g_rw_lock_writer_unlock(&state.frame_buffer_lock); + g_rw_lock_reader_unlock(&state.vcpus_array_lock); +} + +Register *init_vcpu_register(qemu_plugin_reg_descriptor *desc) { + Register *reg = g_new0(Register, 1); + g_autofree gchar *lower = g_utf8_strdown(desc->name, -1); + + reg->handle = desc->handle; + reg->name = g_intern_string(lower); + reg->content = g_byte_array_new(); + + /* read the initial value */ + int r = qemu_plugin_read_register(reg->handle, reg->content); + g_assert(r > 0); + return reg; +} + +static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) { + g_rw_lock_writer_lock(&state.vcpus_array_lock); + g_rw_lock_writer_lock(&state.frame_buffer_lock); + g_rw_lock_writer_lock(&state.vcpu_mode_lock); + + VCPU *vcpu = g_malloc0(sizeof(VCPU)); + vcpu->registers = registers_init(); + g_assert(vcpu->registers); + g_ptr_array_insert(state.vcpus, vcpu_index, vcpu); + + FrameBuffer *vcpu_frame_buffer = frame_buffer_new(); + g_ptr_array_insert(state.frame_buffer, vcpu_index, vcpu_frame_buffer); + + uint64_t frame_arch = 0; + uint64_t frame_mach = 0; + if (!get_frame_arch_mach(state.target_name, &frame_arch, &frame_mach)) { + qemu_plugin_outs("Failed to get arch/mach.\n"); + } + const char *mode = FRAME_MODE_NONE; + if (frame_arch == frame_arch_powerpc && frame_mach == frame_mach_ppc64) { + mode = FRAME_MODE_PPC64; + } else if (frame_arch == frame_arch_powerpc && frame_mach == frame_mach_ppc) { + mode = FRAME_MODE_PPC32; + } + // TODO: handle ARM + g_ptr_array_insert(state.vcpu_modes, vcpu_index, + mode ? g_strdup(mode) : NULL); + + g_rw_lock_writer_unlock(&state.vcpu_mode_lock); + g_rw_lock_writer_unlock(&state.frame_buffer_lock); + g_rw_lock_writer_unlock(&state.vcpus_array_lock); +} + +Instruction *init_insn(struct qemu_plugin_insn *tb_insn) { + Instruction *insn = g_malloc0(sizeof(Instruction)); + qemu_plugin_insn_data(tb_insn, &insn->bytes, sizeof(insn->bytes)); + insn->size = qemu_plugin_insn_size(tb_insn); + insn->vaddr = qemu_plugin_insn_vaddr(tb_insn); + return insn; +} + +static void cb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { + // Add a callback for each instruction in every translated block. + struct qemu_plugin_insn *tb_insn; + size_t n_insns = qemu_plugin_tb_n_insns(tb); + for (size_t i = 0; i < n_insns; i++) { + tb_insn = qemu_plugin_tb_get_insn(tb, i); + Instruction *insn_data = init_insn(tb_insn); + qemu_plugin_register_vcpu_insn_exec_cb(tb_insn, log_insn_reg_access, + QEMU_PLUGIN_CB_R_REGS, insn_data); + qemu_plugin_register_vcpu_mem_cb(tb_insn, log_insn_mem_access, + QEMU_PLUGIN_CB_NO_REGS, QEMU_PLUGIN_MEM_RW, + NULL); + } +} + +static void plugin_exit(qemu_plugin_id_t id, void *udata) { + flush_all_frame_bufs(); + + g_rw_lock_writer_lock(&state.file_lock); + g_rw_lock_reader_lock(&state.toc_entries_offsets_lock); + g_rw_lock_reader_lock(&state.total_num_frames_lock); + + FILE *file = state.file; + + // Update fields in the header + uint64_t toc_index_offset = ftell(file); + SEEK(offset_toc_index_offset); + WRITE(toc_index_offset); + SEEK(offset_total_num_frames); + WRITE(state.total_num_frames); + + // Write the TOC index + SEEK(toc_index_offset); + WRITE(frames_per_toc_entry); + size_t add = state.total_num_frames % frames_per_toc_entry != 0 ? 1 : 0; + size_t entries = ((state.total_num_frames) / frames_per_toc_entry) + add; + + for (size_t i = 0; i < entries; ++i) { + uint64_t toc_entry_off = + g_array_index(state.toc_entries_offsets, uint64_t, i); + WRITE(toc_entry_off); + } + fclose(file); + + g_rw_lock_reader_unlock(&state.total_num_frames_lock); + g_rw_lock_reader_unlock(&state.toc_entries_offsets_lock); + g_rw_lock_writer_unlock(&state.file_lock); + qemu_plugin_outs("Finished trace\n"); +} + +static bool write_header(FILE *file, const char *target_name) { + uint64_t frame_arch = 0; + uint64_t frame_mach = 0; + if (!get_frame_arch_mach(target_name, &frame_arch, &frame_mach)) { + qemu_plugin_outs("Failed to get arch/mach.\n"); + return false; + } + uint64_t total_num_frames = 0ULL; + uint64_t toc_index_offset = 0ULL; + WRITE(magic_number); + WRITE(trace_version); + WRITE(frame_arch); + WRITE(frame_mach); + WRITE(total_num_frames); // Gets updated later + WRITE(toc_index_offset); // Gets updated later + return true; +} + +QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, + const qemu_info_t *info, int argc, + char **argv) { + qemu_plugin_outs("Target name: "); + qemu_plugin_outs(info->target_name); + qemu_plugin_outs("\n"); + char *output = get_argv_val(argv, argc, "out"); + if (!output) { + qemu_plugin_outs("'out' argument is missing.\n"); + qemu_plugin_outs("This is required.\n"); + qemu_plugin_outs("Pass it with 'out='.\n\n"); + exit(1); + } + char *endianness = get_argv_val(argv, argc, "endianness"); + if (!endianness || (strcmp(endianness, "b") && strcmp(endianness, "l"))) { + qemu_plugin_outs("'endianness' argument is missing or is not 'b' or 'l'.\n"); + qemu_plugin_outs("This is required until QEMU plugins get a richer API.\n"); + qemu_plugin_outs("Pass it with 'endianness=[b/l]'.\n\n"); + exit(1); + } + state.is_big_endian = endianness[0] == 'b'; + + state.target_name = g_strdup(info->target_name); + state.frame_buffer = g_ptr_array_new(); + state.toc_entries_offsets = g_array_new(false, true, sizeof(uint64_t)); + state.vcpus = g_ptr_array_new(); + state.vcpu_modes = g_ptr_array_new(); + state.file = fopen(output, "wb"); + if (!(state.frame_buffer || state.vcpus || state.file || + !state.toc_entries_offsets)) { + return 1; + } + g_free(output); + if (!write_header(state.file, info->target_name)) { + qemu_plugin_outs("Failed to write header.\n"); + return 1; + } + write_meta(state.file, argv, argc); + + g_array_append_val(state.toc_entries_offsets, offset_toc_start); + + qemu_plugin_register_vcpu_init_cb(id, vcpu_init); + qemu_plugin_register_vcpu_tb_trans_cb(id, cb_trans); + qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); + + return 0; +} diff --git a/contrib/plugins/bap-tracing/tracing.h b/contrib/plugins/bap-tracing/tracing.h new file mode 100644 index 0000000000000..131ac73a27b2b --- /dev/null +++ b/contrib/plugins/bap-tracing/tracing.h @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2025 Rot127 +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef BAP_TRACING_H +#define BAP_TRACING_H + +#include +#include +#include + +#include "frame.piqi.pb-c-patched.h" +#include "frame_arch.h" +#include "frame_buffer.h" +#include "trace_consts.h" + +struct arch_enum_entry { + const char *name; + enum frame_architecture arch; + size_t machine; +}; + +static struct arch_enum_entry arch_map[] = { + {.name = "unknown", .arch = frame_arch_unknown, .machine = 0}, + {.name = "obscure", .arch = frame_arch_obscure, .machine = 0}, + {.name = "m68k", .arch = frame_arch_m68k, .machine = 0}, + {.name = "vax", .arch = frame_arch_vax, .machine = 0}, + {.name = "i960", .arch = frame_arch_i960, .machine = 0}, + {.name = "or32", .arch = frame_arch_or32, .machine = 0}, + {.name = "sparc", .arch = frame_arch_sparc, .machine = frame_mach_sparc_v8plusa}, + {.name = "sparc64", .arch = frame_arch_sparc, .machine = frame_mach_sparc_v9b}, + {.name = "spu", .arch = frame_arch_spu, .machine = 0}, + {.name = "mips", .arch = frame_arch_mips, .machine = 0}, + {.name = "i386", .arch = frame_arch_i386, .machine = 0}, + {.name = "l1om", .arch = frame_arch_l1om, .machine = 0}, + {.name = "we32k", .arch = frame_arch_we32k, .machine = 0}, + {.name = "tahoe", .arch = frame_arch_tahoe, .machine = 0}, + {.name = "i860", .arch = frame_arch_i860, .machine = 0}, + {.name = "i370", .arch = frame_arch_i370, .machine = 0}, + {.name = "romp", .arch = frame_arch_romp, .machine = 0}, + {.name = "convex", .arch = frame_arch_convex, .machine = 0}, + {.name = "m88k", .arch = frame_arch_m88k, .machine = 0}, + {.name = "m98k", .arch = frame_arch_m98k, .machine = 0}, + {.name = "pyramid", .arch = frame_arch_pyramid, .machine = 0}, + {.name = "h8300", .arch = frame_arch_h8300, .machine = 0}, + {.name = "pdp11", .arch = frame_arch_pdp11, .machine = 0}, + {.name = "plugin", .arch = frame_arch_plugin, .machine = 0}, + {.name = "ppc", .arch = frame_arch_powerpc, .machine = frame_mach_ppc}, + {.name = "ppc64", .arch = frame_arch_powerpc, .machine = frame_mach_ppc64}, + {.name = "rs6000", .arch = frame_arch_rs6000, .machine = 0}, + {.name = "hppa", .arch = frame_arch_hppa, .machine = 0}, + {.name = "d10v", .arch = frame_arch_d10v, .machine = 0}, + {.name = "d30v", .arch = frame_arch_d30v, .machine = 0}, + {.name = "dlx", .arch = frame_arch_dlx, .machine = 0}, + {.name = "m68hc11", .arch = frame_arch_m68hc11, .machine = 0}, + {.name = "m68hc12", .arch = frame_arch_m68hc12, .machine = 0}, + {.name = "z8k", .arch = frame_arch_z8k, .machine = 0}, + {.name = "h8500", .arch = frame_arch_h8500, .machine = 0}, + {.name = "sh", .arch = frame_arch_sh, .machine = 0}, + {.name = "alpha", .arch = frame_arch_alpha, .machine = 0}, + {.name = "arm", .arch = frame_arch_arm, .machine = 0}, + {.name = "ns32k", .arch = frame_arch_ns32k, .machine = 0}, + {.name = "w65", .arch = frame_arch_w65, .machine = 0}, + {.name = "tic30", .arch = frame_arch_tic30, .machine = 0}, + {.name = "tic4x", .arch = frame_arch_tic4x, .machine = 0}, + {.name = "tic54x", .arch = frame_arch_tic54x, .machine = 0}, + {.name = "tic6x", .arch = frame_arch_tic6x, .machine = 0}, + {.name = "tic80", .arch = frame_arch_tic80, .machine = 0}, + {.name = "v850", .arch = frame_arch_v850, .machine = 0}, + {.name = "arc", .arch = frame_arch_arc, .machine = 0}, + {.name = "m32c", .arch = frame_arch_m32c, .machine = 0}, + {.name = "m32r", .arch = frame_arch_m32r, .machine = 0}, + {.name = "mn10200", .arch = frame_arch_mn10200, .machine = 0}, + {.name = "mn10300", .arch = frame_arch_mn10300, .machine = 0}, + {.name = "fr30", .arch = frame_arch_fr30, .machine = 0}, + {.name = "frv", .arch = frame_arch_frv, .machine = 0}, + {.name = "moxie", .arch = frame_arch_moxie, .machine = 0}, + {.name = "mcore", .arch = frame_arch_mcore, .machine = 0}, + {.name = "mep", .arch = frame_arch_mep, .machine = 0}, + {.name = "ia64", .arch = frame_arch_ia64, .machine = 0}, + {.name = "ip2k", .arch = frame_arch_ip2k, .machine = 0}, + {.name = "iq2000", .arch = frame_arch_iq2000, .machine = 0}, + {.name = "mt", .arch = frame_arch_mt, .machine = 0}, + {.name = "pj", .arch = frame_arch_pj, .machine = 0}, + {.name = "avr", .arch = frame_arch_avr, .machine = 0}, + {.name = "bfin", .arch = frame_arch_bfin, .machine = 0}, + {.name = "cr16", .arch = frame_arch_cr16, .machine = 0}, + {.name = "cr16c", .arch = frame_arch_cr16c, .machine = 0}, + {.name = "crx", .arch = frame_arch_crx, .machine = 0}, + {.name = "cris", .arch = frame_arch_cris, .machine = 0}, + {.name = "rx", .arch = frame_arch_rx, .machine = 0}, + {.name = "s390", .arch = frame_arch_s390, .machine = 0}, + {.name = "score", .arch = frame_arch_score, .machine = 0}, + {.name = "openrisc", .arch = frame_arch_openrisc, .machine = 0}, + {.name = "mmix", .arch = frame_arch_mmix, .machine = 0}, + {.name = "xstormy16", .arch = frame_arch_xstormy16, .machine = 0}, + {.name = "msp430", .arch = frame_arch_msp430, .machine = 0}, + {.name = "xc16x", .arch = frame_arch_xc16x, .machine = 0}, + {.name = "xtensa", .arch = frame_arch_xtensa, .machine = 0}, + {.name = "z80", .arch = frame_arch_z80, .machine = 0}, + {.name = "lm32", .arch = frame_arch_lm32, .machine = 0}, + {.name = "microblaze", .arch = frame_arch_microblaze, .machine = 0}, + {.name = "6502", .arch = frame_arch_6502, .machine = 0}, + {.name = "aarch64", .arch = frame_arch_aarch64, .machine = 0}, + {.name = "8051", .arch = frame_arch_8051, .machine = 0}, + {.name = "sm83", .arch = frame_arch_sm83, .machine = 0}, + {.name = "hexagon", .arch = frame_arch_hexagon, .machine = 0}, + {.name = NULL, .arch = frame_arch_last, .machine = 0}, +}; + +static inline bool get_frame_arch_mach(const char *target_name, uint64_t *arch, + uint64_t *mach) { + *mach = 0; + *arch = frame_arch_last; + const char *aname = arch_map[0].name; + for (size_t i = 0; arch_map[i].name; ++i) { + aname = arch_map[i].name; + if (!strcmp(aname, target_name)) { + *arch = arch_map[i].arch; + *mach = arch_map[i].machine; + break; + } + } + if (*arch == frame_arch_last) { + qemu_plugin_outs("Could not find frame_arch/mach value for target name: "); + qemu_plugin_outs(target_name); + qemu_plugin_outs("\nConsider adding it.\n"); + } + return *arch != frame_arch_last; +} + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/** + * \brief VLIW architecture have instructions longer than 4 or 8bytes. + */ +#define MAX_INSTRUCTION_SIZE 64 + +typedef struct { + uint8_t bytes[MAX_INSTRUCTION_SIZE]; ///< Instruction bytes. + size_t size; ///< Len of instruction in bytes. + uint64_t vaddr; +} Instruction; + +typedef struct { + struct qemu_plugin_register *handle; ///< Passed to qemu API. + GByteArray *content; + const char *name; +} Register; + +typedef struct { + GPtrArray /**/ *registers; +} VCPU; + +typedef struct { + GRWLock vcpus_array_lock; + GPtrArray /**/ *vcpus; + + GRWLock frame_buffer_lock; + GPtrArray /**/ *frame_buffer; ///< Indexed by vcpu id + + GRWLock toc_entries_offsets_lock; + GArray /**/ *toc_entries_offsets; + + GRWLock total_num_frames_lock; + uint64_t total_num_frames; + + GRWLock file_lock; + FILE *file; + + GRWLock vcpu_mode_lock; + GPtrArray /**/ *vcpu_modes; ///< Indexed by vcpu id. + + const char *target_name; + bool is_big_endian; +} TraceState; + +VCPU *vcpu_new(void); +Register *init_vcpu_register(qemu_plugin_reg_descriptor *desc); +Instruction *init_insn(struct qemu_plugin_insn *insn); + +#endif diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index fa8a426c8b594..3001f1d8f0e35 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -7,6 +7,8 @@ endif t = [] if get_option('plugins') + subdir('bap-tracing') + foreach i : contrib_plugins if host_os == 'windows' t += shared_module(i, files(i + '.c') + 'win32_linker.c', diff --git a/gdb-xml/sparc32-core.xml b/gdb-xml/sparc32-core.xml new file mode 100644 index 0000000000000..61964a79d121d --- /dev/null +++ b/gdb-xml/sparc32-core.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc32-cp0.xml b/gdb-xml/sparc32-cp0.xml new file mode 100644 index 0000000000000..a7f6e64de1b27 --- /dev/null +++ b/gdb-xml/sparc32-cp0.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc32-cpu.xml b/gdb-xml/sparc32-cpu.xml new file mode 100644 index 0000000000000..b28c533a4fbcc --- /dev/null +++ b/gdb-xml/sparc32-cpu.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc32-fpu.xml b/gdb-xml/sparc32-fpu.xml new file mode 100644 index 0000000000000..289c1d2b99aab --- /dev/null +++ b/gdb-xml/sparc32-fpu.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-core.xml b/gdb-xml/sparc64-core.xml new file mode 100644 index 0000000000000..375b9bb0cc6a7 --- /dev/null +++ b/gdb-xml/sparc64-core.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-cp0.xml b/gdb-xml/sparc64-cp0.xml new file mode 100644 index 0000000000000..cef58f312b6f8 --- /dev/null +++ b/gdb-xml/sparc64-cp0.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-cpu.xml b/gdb-xml/sparc64-cpu.xml new file mode 100644 index 0000000000000..b8a66d911aa36 --- /dev/null +++ b/gdb-xml/sparc64-cpu.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/sparc64-fpu.xml b/gdb-xml/sparc64-fpu.xml new file mode 100644 index 0000000000000..cef935ebd6b17 --- /dev/null +++ b/gdb-xml/sparc64-fpu.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gen-execlog.sh b/gen-execlog.sh new file mode 100755 index 0000000000000..e65cc50f8a936 --- /dev/null +++ b/gen-execlog.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +if [ "$#" -lt 2 ]; then + echo "$0 [args...]" + echo " is appended to qemu-" + exit 1 +fi + +BUILDIR=$1 +ARCH=$2 +BIN=$3 + +$BUILDIR/qemu-$ARCH -plugin file="$BUILDIR/contrib/plugins/libexeclog.so",reg=* -d plugin "$BIN" + diff --git a/gen-trace.sh b/gen-trace.sh new file mode 100755 index 0000000000000..ccb007fb7e12a --- /dev/null +++ b/gen-trace.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +if [ "$#" -lt 4 ]; then + echo "$0 [args...]" + echo " is appended to qemu-" + exit 1 +fi + +BUILDIR=$1 +ARCH=$2 +EN=$3 +BIN=$4 +BNAME=$(basename $BIN) + +$BUILDIR/qemu-$ARCH -plugin file="$BUILDIR/contrib/plugins/bap-tracing/libbap_tracing.so,bin_path=$BIN",out="$BNAME.trace",endianness="$EN" -d plugin "$BIN" ""${@:5}"" +ls -lh "$BNAME.trace" +# $BUILDIR/qemu-$ARCH -plugin file="$BUILDIR/contrib/plugins/libexeclog.so" -d plugin "$BIN" diff --git a/target/sparc/cpu.c b/target/sparc/cpu.c index 571612011730b..be2ecb989f56b 100644 --- a/target/sparc/cpu.c +++ b/target/sparc/cpu.c @@ -1047,8 +1047,10 @@ static void sparc_cpu_class_init(ObjectClass *oc, void *data) cc->disas_set_info = cpu_sparc_disas_set_info; #if defined(TARGET_SPARC64) && !defined(TARGET_ABI32) + cc->gdb_core_xml_file = "sparc64-core.xml"; cc->gdb_num_core_regs = 86; #else + cc->gdb_core_xml_file = "sparc32-core.xml"; cc->gdb_num_core_regs = 72; #endif cc->tcg_ops = &sparc_tcg_ops;