From patchwork Mon Mar 11 15:34:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alex_Benn=C3=A9e?= X-Patchwork-Id: 779416 Delivered-To: patch@linaro.org Received: by 2002:a5d:604e:0:b0:33e:7753:30bd with SMTP id j14csp1591812wrt; Mon, 11 Mar 2024 08:35:21 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXMwIUsDXqE0QeiyEvPdQfnaqxWrLZs29olyhB/d3u8CDuIJSigiv4LCgnc1X/L4+DmV3b9iOS4nYOC0+QySvyN X-Google-Smtp-Source: AGHT+IGtCu67e8RAzdPpFhcGmqomqo7pLu7qOPC/75AFhBRfdBXvB3MvfafIMle9PEdIoXkbRfDR X-Received: by 2002:a0c:f14f:0:b0:690:c3ed:b15d with SMTP id y15-20020a0cf14f000000b00690c3edb15dmr6217027qvl.22.1710171320966; Mon, 11 Mar 2024 08:35:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1710171320; cv=none; d=google.com; s=arc-20160816; b=WRP4QGmpBwnCGqMLDVWcHCf8VfaOikJc71iSk/kAHwt7PTpz7cgA9iylWNXPxsjdnG u2/h4fQkk8bZrzUa3XxwXfEPDb0F5oP3xMgvEd7E6bouHeaos9tUKn6kmC2u4vnEQYtc 9Rzs2SMHtywf4UPx54BVkrarVehj9jWFJXJlRgoBC2VpZBmTiVcEasnhINc4PObz2ln6 Rb0C4oIxxU39ikZbfFP9pC/m82DrzZ8i046i7WRHnCL8DF7TgaMr4JFZMxcr1SwC2yrF o5zpgaNcz8ncQenl+uRAVVSJkMgWAfZh9e6GnVzFVPKiFORsWcgr522EnDeUyj5eRrIC 3qBw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:content-transfer-encoding :mime-version:message-id:date:subject:cc:to:from:dkim-signature; bh=CBncdoSZmTUoP5BilG527cTjiARGsoRXwBhFtFEzxqY=; fh=BKHMJL7d1CxyE7AgH++zKeukKyYaLxDgQIQCAlb8jv8=; b=dB73UQhyAME2MFkiNADfqcG5bNvm/dTLbiYoXCZRY7SU+KKYqu23Lbq0sEs75ufA2i LlsVp70lDPc5eSlgACFnnq2GDiGnZ02uLoWcKS3CUmj8ejafMSQmA6KbuF8xGdL/UTko UiPI98KBH+kY2XtK3EeVc8qCNNpXH8z18S3Zc3VMs1O9kGijesvIF+D11l0g0rO9LTgK fBSWfa2mimFO8K1fFBpBPlzCsyjYZ6F744KWdskExxD8Fju7e2UhkkWYDUjAH6G10v1I faN4NUvbSrD+zwwfa7hbK4+IIY8kvTtEDcYbSbj7lVE5nMaVUbCHrgOQtrMZExzEV2E4 6C8Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=tcoo2OUn; spf=pass (google.com: domain of qemu-devel-bounces+patch=linaro.org@nongnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom="qemu-devel-bounces+patch=linaro.org@nongnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from lists.gnu.org (lists.gnu.org. [209.51.188.17]) by mx.google.com with ESMTPS id 5-20020a0562140d6500b0068f5cbb3f36si5753613qvs.298.2024.03.11.08.35.20 for (version=TLS1_2 cipher=ECDHE-ECDSA-CHACHA20-POLY1305 bits=256/256); Mon, 11 Mar 2024 08:35:20 -0700 (PDT) Received-SPF: pass (google.com: domain of qemu-devel-bounces+patch=linaro.org@nongnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=tcoo2OUn; spf=pass (google.com: domain of qemu-devel-bounces+patch=linaro.org@nongnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom="qemu-devel-bounces+patch=linaro.org@nongnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rjhfo-0005gP-Ma; Mon, 11 Mar 2024 11:34:44 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rjhfm-0005ex-Ox for qemu-devel@nongnu.org; Mon, 11 Mar 2024 11:34:42 -0400 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rjhfk-0006EO-G3 for qemu-devel@nongnu.org; Mon, 11 Mar 2024 11:34:42 -0400 Received: by mail-wm1-x32f.google.com with SMTP id 5b1f17b1804b1-413183f5e11so17575855e9.1 for ; Mon, 11 Mar 2024 08:34:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1710171279; x=1710776079; darn=nongnu.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=CBncdoSZmTUoP5BilG527cTjiARGsoRXwBhFtFEzxqY=; b=tcoo2OUnpRmQRk4IzWMBrxL05nq4aR0cMWdn3rSVkxRoC2qs1LJ3nHYOnDGrC6+UPn khY1azxY4nlLWoFAsBNWI3DAcrKeqeKuod45vafneex4OaZ6r4qmgKmJWt//+X3nO1YB TMbem/RjWZJ4RzZeGlHRpZsn4/a+kPCd5q1wiMQnSuTjHDXa7Pck8dFz9JG2krZMNRXS 8RIr8UafF0zK82HYdN5dJKcvtWVqi/6Ji3/QAk5chox/GWF8U35+Dxx8+zlE1X/KL4MK 9HFN7dPf84+zCcAQyRenqDgCpKkZ4DXnnwVFmZ7UO/PaAQbcKxJkDfRVsPDxkyLz6Tct YSyg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1710171279; x=1710776079; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=CBncdoSZmTUoP5BilG527cTjiARGsoRXwBhFtFEzxqY=; b=N4TD00BB8vmKXiTIOjDR56XmDqoRaQ2X/iduEGwx/1ygEhQKX8nuurJWjhRgY4h2mf Mi/ltfSVVQgHTqDx6vPFSBgH+oq+HIXSA1mhb3sP8qpO0tO38ArKKCrF78JiIGjvdAL5 KSVWfqOYj/FVLf6YHm2yV2nyjAr9zi6gTc0ACmV02nfCOoe4E4iEz2mvZF4vZP1BqMv7 cd3iCzCqtkRXxCNHKX4ZViL+18ppK4Mct3+wkKZWC/2K8sjDzxk2W2MMjddzV/wYC/ee pp/H9cJQn8t+h8TIqd3vmTQGJrdY13oQYwPuFLCVfi3cTFwmHqeHO9LSTKOZShB36Yju uzoQ== X-Gm-Message-State: AOJu0Yx9hNjoqdbGQbMtq0YvCxWmwCZ0CKOp2bG0s5/XYWLQziiz6eMd Yu+8CthWYgr6LLZESDUp8SzXHJ2VjRwRAyo8MssCXBwm6BKg7SZBq3yaNunzlTtJp9gAicMw+kC x X-Received: by 2002:a05:600c:1991:b0:413:2704:a1c1 with SMTP id t17-20020a05600c199100b004132704a1c1mr4046582wmq.11.1710171278702; Mon, 11 Mar 2024 08:34:38 -0700 (PDT) Received: from draig.lan ([85.9.250.243]) by smtp.gmail.com with ESMTPSA id c10-20020a05600c0a4a00b004132f8c2ac1sm1157989wmq.14.2024.03.11.08.34.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Mar 2024 08:34:38 -0700 (PDT) Received: from draig.lan (localhost [IPv6:::1]) by draig.lan (Postfix) with ESMTP id 0B0775F88D; Mon, 11 Mar 2024 15:34:38 +0000 (GMT) From: =?utf-8?q?Alex_Benn=C3=A9e?= To: qemu-devel@nongnu.org Cc: =?utf-8?q?Alex_Benn=C3=A9e?= , Gustavo Romero , Pierrick Bouvier , Alexandre Iooss , Mahmoud Mandour Subject: [RFC PATCH] contrib/plugins: control flow plugin (WIP!) Date: Mon, 11 Mar 2024 15:34:32 +0000 Message-Id: <20240311153432.1395190-1-alex.bennee@linaro.org> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 Received-SPF: pass client-ip=2a00:1450:4864:20::32f; envelope-from=alex.bennee@linaro.org; helo=mail-wm1-x32f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patch=linaro.org@nongnu.org Sender: qemu-devel-bounces+patch=linaro.org@nongnu.org This is a simple control flow tracking plugin that uses the latest inline and conditional operations to detect and track control flow changes. It is currently an exercise at seeing how useful the changes are. Signed-off-by: Alex Bennée Based-on: 20240229055359.972151-1-pierrick.bouvier@linaro.org Cc: Gustavo Romero Cc: Pierrick Bouvier --- This is a work in progress. It looks like I've found a bug in the processing of udata (see fprintf) because I see: vcpu_tb_trans: 0x41717c vcpu_tb_branched_exec: 0x5620a598e8a0 vcpu_tb_trans: 0x417194 vcpu_tb_trans: 0x409af0 vcpu_tb_branched_exec: 0x5620a598e8a0 vcpu_tb_trans: 0x409afc vcpu_tb_trans: 0x423920 vcpu_tb_branched_exec: 0x5620a598e8a0 collected 1429 destination nodes in the hash table addr: 0x4046a4 b.hs #0x4046c8 branches 1 to 0xa598e8a0 (0) addr: 0x4019c0 bl #0x400944 branches 12 to 0xa598e8a0 (11) addr: 0x445da8 b.eq #0x445df8 so it looks like udata is always junk. --- contrib/plugins/cflow.c | 344 +++++++++++++++++++++++++++++++++++++++ contrib/plugins/Makefile | 1 + 2 files changed, 345 insertions(+) create mode 100644 contrib/plugins/cflow.c diff --git a/contrib/plugins/cflow.c b/contrib/plugins/cflow.c new file mode 100644 index 0000000000..f3ad6fd20f --- /dev/null +++ b/contrib/plugins/cflow.c @@ -0,0 +1,344 @@ +/* + * Control Flow plugin + * + * This plugin will track changes to control flow and detect where + * instructions fault. + * + * Copyright (c) 2024 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include +#include +#include +#include +#include +#include + +#include + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +/* Temp hack, works for Aarch64 */ +#define INSN_WIDTH 4 + +typedef enum { + SORT_HOTDEST, /* hottest branch */ + SORT_EARLY, /* most early exits */ + SORT_POPDEST, /* most destinations */ +} ReportType; + +ReportType report = SORT_HOTDEST; +int topn = 10; + +typedef struct { + uint64_t daddr; + uint64_t dcount; +} DestData; + +/* A node is an address where we can go to multiple places */ +typedef struct { + GMutex lock; + /* address of the branch point */ + uint64_t addr; + /* array of DestData */ + GArray *dests; + /* early exit count */ + uint64_t early_exit; + /* jump destination count */ + uint64_t dest_count; + /* instruction data */ + char *insn_disas; + /* times translated as last in block? */ + int last_count; + /* times translated in the middle of block? */ + int mid_count; +} NodeData; + +/* We use this to track the current execution state */ +typedef struct { + /* address of start of block */ + uint64_t block_start; + /* address of end of block */ + uint64_t block_end; + /* address of last executed PC */ + uint64_t last_pc; +} VCPUScoreBoard; + +static GMutex node_lock; +static GHashTable *nodes; +struct qemu_plugin_scoreboard *state; + +/* SORT_HOTDEST */ +static gint hottest(gconstpointer a, gconstpointer b) +{ + NodeData *na = (NodeData *) a; + NodeData *nb = (NodeData *) b; + + return na->dest_count > nb->dest_count ? -1 : + na->dest_count == nb->dest_count ? 0 : 1; +} + +static gint early(gconstpointer a, gconstpointer b) +{ + NodeData *na = (NodeData *) a; + NodeData *nb = (NodeData *) b; + + return na->early_exit > nb->early_exit ? -1 : + na->early_exit == nb->early_exit ? 0 : 1; +} + +static gint popular(gconstpointer a, gconstpointer b) +{ + NodeData *na = (NodeData *) a; + NodeData *nb = (NodeData *) b; + + return na->dests->len > nb->dests->len ? -1 : + na->dests->len == nb->dests->len ? 0 : 1; +} + +static void plugin_exit(qemu_plugin_id_t id, void *p) +{ + g_autoptr(GString) result = g_string_new("collected "); + GList *data; + GCompareFunc sort = &hottest; + int n = 0; + + g_mutex_lock(&node_lock); + g_string_append_printf(result, "%d destination nodes in the hash table\n", + g_hash_table_size(nodes)); + + data = g_hash_table_get_values(nodes); + + switch (report) { + case SORT_HOTDEST: + sort = &hottest; + break; + case SORT_EARLY: + sort = &early; + break; + case SORT_POPDEST: + sort = &popular; + break; + } + + data = g_list_sort(data, sort); + + for (GList *l = data; + l != NULL && n < topn; + l = l->next, n++) { + NodeData *n = l->data; + g_string_append_printf(result, " addr: 0x%"PRIx64 " %s\n", + n->addr, n->insn_disas); + if (n->early_exit) { + g_string_append_printf(result, " early exits %"PRId64"\n", + n->early_exit); + } + g_string_append_printf(result, " branches %"PRId64"\n", + n->dest_count); + for (int j = 0; j < n->dests->len; j++ ) { + DestData *dd = &g_array_index(n->dests, DestData, j); + g_string_append_printf(result, " to 0x%"PRIx64" (%"PRId64")\n", + dd->daddr, dd->dcount); + } + } + + qemu_plugin_outs(result->str); + + g_mutex_unlock(&node_lock); +} + +static void plugin_init(void) +{ + g_mutex_init(&node_lock); + nodes = g_hash_table_new(NULL, g_direct_equal); + state = qemu_plugin_scoreboard_new(sizeof(VCPUScoreBoard)); +} + +static NodeData *create_node(uint64_t addr) +{ + NodeData *node = g_new0(NodeData, 1); + g_mutex_init(&node->lock); + node->addr = addr; + node->dests = g_array_new(true, true, sizeof(DestData)); + return node; +} + +static NodeData *fetch_node(uint64_t addr, bool create_if_not_found) +{ + NodeData *node = NULL; + + g_mutex_lock(&node_lock); + node = (NodeData *) g_hash_table_lookup(nodes, (gconstpointer) addr); + if (!node && create_if_not_found) { + node = create_node(addr); + g_hash_table_insert(nodes, (gpointer) addr, (gpointer) node); + } + g_mutex_unlock(&node_lock); + return node; +} + +/* Called when we detect an early exit from a block */ +static void vcpu_tb_early_exit_exec(unsigned int cpu_index, void *udata) +{ + qemu_plugin_u64 last_pc_entry = qemu_plugin_scoreboard_u64_in_struct(state, VCPUScoreBoard, last_pc); + uint64_t last_pc = qemu_plugin_u64_get(last_pc_entry, cpu_index); + NodeData *node = fetch_node(last_pc, true); + g_mutex_lock(&node->lock); + node->early_exit++; + if (!node->mid_count) { + /* count now as we've only just allocated */ + node->mid_count++; + } + g_mutex_unlock(&node->lock); +} + +/* Called when we detect a non-linear execution */ +static void vcpu_tb_branched_exec(unsigned int cpu_index, void *udata) +{ + qemu_plugin_u64 last_pc_entry = qemu_plugin_scoreboard_u64_in_struct(state, VCPUScoreBoard, last_pc); + uint64_t last_pc = qemu_plugin_u64_get(last_pc_entry, cpu_index); + + /* return early for address 0 */ + if (!last_pc) { + return; + } + + uint64_t current_pc = GPOINTER_TO_UINT(udata); + NodeData *node = fetch_node(last_pc, true); + DestData *data = NULL; + GArray *dests; + + /* BUG? */ + fprintf(stderr, "%s: %p\n", __func__, udata); + + g_mutex_lock(&node->lock); + dests = node->dests; + for (int i = 0; i < dests->len; i++) { + if (g_array_index(dests, DestData, i).daddr == current_pc) { + data = &g_array_index(dests, DestData, i); + } + } + + /* we've never seen this before, allocate a new entry */ + if (!data) { + DestData new_entry = { .daddr = current_pc }; + g_array_append_val(dests, new_entry); + data = &g_array_index(dests, DestData, dests->len); + } + + data->dcount++; + node->dest_count++; + + g_mutex_unlock(&node->lock); +} + +/* + * At the start of each block we need to resolve two things: + * + * - is last_pc == block_end, if not we had an early exit + * - is start of block last_pc + insn width, if not we jumped + * + * Once those are dealt with we can instrument the rest of the + * instructions for their execution. + * + */ +static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) +{ + uint64_t pc = qemu_plugin_tb_vaddr(tb); + size_t insns = qemu_plugin_tb_n_insns(tb); + + /* score board declarations */ + qemu_plugin_u64 start_block = qemu_plugin_scoreboard_u64_in_struct(state, VCPUScoreBoard, block_start); + qemu_plugin_u64 end_block = qemu_plugin_scoreboard_u64_in_struct(state, VCPUScoreBoard, block_end); + qemu_plugin_u64 last_pc = qemu_plugin_scoreboard_u64_in_struct(state, VCPUScoreBoard, last_pc); + + /* + * check for last_pc != block_end + */ + /* qemu_plugin_register_vcpu_tb_exec_cond_cb( */ + /* tb, vcpu_tb_early_exit_exec, QEMU_PLUGIN_CB_NO_REGS, */ + /* QEMU_PLUGIN_COND_NEQ, last_pc, /\* block_end *\/, GUINT_TO_POINTER(pc)); */ + + /* + * check for pc == last_pc + insn_width + */ + uint64_t pc_minus = pc - INSN_WIDTH; + gpointer udata = GUINT_TO_POINTER(pc); + /* BUG? udata getting corrupted */ + fprintf(stderr, "%s: %p\n", __func__, udata); + qemu_plugin_register_vcpu_tb_exec_cond_cb( + tb, vcpu_tb_branched_exec, QEMU_PLUGIN_CB_NO_REGS, + QEMU_PLUGIN_COND_NE, last_pc, pc_minus, udata); + + /* + * Now we can set start/end for this block so the next block can + * check where we are at. + */ + qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu(tb, + QEMU_PLUGIN_INLINE_STORE_U64, + start_block, pc); + qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu(tb, + QEMU_PLUGIN_INLINE_STORE_U64, + end_block, pc + (INSN_WIDTH * insns)); + + for (int idx = 0; idx < qemu_plugin_tb_n_insns(tb); ++idx) { + struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, idx); + uint64_t ipc = qemu_plugin_insn_vaddr(insn); + bool last_insn = idx == (insns - 1); + /* + * If this is a potential branch point check if we could grab + * the disassembly for it. If it is the last instruction + * always create an entry. + */ + NodeData *node = fetch_node(ipc, last_insn); + if (node) { + g_mutex_lock(&node->lock); + if (!node->insn_disas) { + node->insn_disas = qemu_plugin_insn_disas(insn); + } + if (last_insn) { + node->last_count++; + } else { + node->mid_count++; + } + g_mutex_unlock(&node->lock); + } + + /* Store the PC of what we are about to execute */ + qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu(insn, + QEMU_PLUGIN_INLINE_STORE_U64, + last_pc, ipc); + } +} + +QEMU_PLUGIN_EXPORT +int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, + int argc, char **argv) +{ + for (int i = 0; i < argc; i++) { + char *opt = argv[i]; + g_auto(GStrv) tokens = g_strsplit(opt, "=", 2); + if (g_strcmp0(tokens[0], "sort") == 0) { + if (g_strcmp0(tokens[1], "hottest") == 0) { + report = SORT_HOTDEST; + } else if (g_strcmp0(tokens[1], "early") == 0) { + report = SORT_EARLY; + } else if (g_strcmp0(tokens[1], "popular") == 0) { + report = SORT_POPDEST; + } else { + fprintf(stderr, "failed to parse: %s\n", tokens[1]); + return -1; + } + } else { + fprintf(stderr, "option parsing failed: %s\n", opt); + return -1; + } + } + + plugin_init(); + + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); + qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); + return 0; +} diff --git a/contrib/plugins/Makefile b/contrib/plugins/Makefile index 0b64d2c1e3..78dc7407a5 100644 --- a/contrib/plugins/Makefile +++ b/contrib/plugins/Makefile @@ -27,6 +27,7 @@ endif NAMES += hwprofile NAMES += cache NAMES += drcov +NAMES += cflow ifeq ($(CONFIG_WIN32),y) SO_SUFFIX := .dll