| /* Emulation of eBPF helpers. |
| Copyright (C) 2020-2021 Free Software Foundation, Inc. |
| |
| This file is part of GDB, the GNU debugger. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| /* BPF programs rely on the existence of several helper functions, |
| which are provided by the kernel. This simulator provides an |
| implementation of the helpers, which can be customized by the |
| user. */ |
| |
| /* This must come before any other includes. */ |
| #include "defs.h" |
| |
| #define WANT_CPU_BPFBF |
| #define WANT_CPU bpfbf |
| |
| #include "sim-main.h" |
| #include "cgen-mem.h" |
| #include "cgen-ops.h" |
| #include "cpu.h" |
| |
| #include "bpf-helpers.h" |
| |
| /* bpf_trace_printk is a printk-like facility for debugging. |
| |
| In the kernel, it appends a line to the Linux's tracing debugging |
| interface. |
| |
| In this simulator, it uses the simulator's tracing interface |
| instead. |
| |
| The format tags recognized by this helper are: |
| %d, %i, %u, %x, %ld, %li, %lu, %lx, %lld, %lli, %llu, %llx, |
| %p, %s |
| |
| A maximum of three tags are supported. |
| |
| This helper returns the number of bytes written, or a negative |
| value in case of failure. */ |
| |
| int |
| bpf_trace_printk (SIM_CPU *current_cpu) |
| { |
| va_list ap; |
| SIM_DESC sd = CPU_STATE (current_cpu); |
| |
| DI fmt_address; |
| uint32_t size, tags_processed; |
| size_t i, bytes_written = 0; |
| |
| /* The first argument is the format string, which is passed as a |
| pointer in %r1. */ |
| fmt_address = GET_H_GPR (1); |
| |
| /* The second argument is the length of the format string, as an |
| unsigned 32-bit number in %r2. */ |
| size = GET_H_GPR (2); |
| |
| /* Read the format string from the memory pointed by %r2, printing |
| out the stuff as we go. There is a maximum of three format tags |
| supported, which are read from %r3, %r4 and %r5 respectively. */ |
| for (i = 0, tags_processed = 0; i < size;) |
| { |
| UDI value; |
| QI c = GETMEMUQI (current_cpu, CPU_PC_GET (current_cpu), |
| fmt_address + i); |
| |
| switch (c) |
| { |
| case '%': |
| /* Check we are not exceeding the limit of three format |
| tags. */ |
| if (tags_processed > 2) |
| return -1; /* XXX look for kernel error code. */ |
| |
| /* Depending on the kind of tag, extract the value from the |
| proper argument. */ |
| if (i++ >= size) |
| return -1; /* XXX look for kernel error code. */ |
| |
| value = GET_H_GPR (3 + tags_processed); |
| |
| switch ((GETMEMUQI (current_cpu, CPU_PC_GET (current_cpu), |
| fmt_address + i))) |
| { |
| case 'd': |
| trace_printf (sd, current_cpu, "%d", (int) value); |
| break; |
| case 'i': |
| trace_printf (sd, current_cpu, "%i", (int) value); |
| break; |
| case 'u': |
| trace_printf (sd, current_cpu, "%u", (unsigned int) value); |
| break; |
| case 'x': |
| trace_printf (sd, current_cpu, "%x", (unsigned int) value); |
| break; |
| case 'l': |
| { |
| if (i++ >= size) |
| return -1; |
| switch (GETMEMUQI (current_cpu, CPU_PC_GET (current_cpu), |
| fmt_address + i)) |
| { |
| case 'd': |
| trace_printf (sd, current_cpu, "%ld", (long) value); |
| break; |
| case 'i': |
| trace_printf (sd, current_cpu, "%li", (long) value); |
| break; |
| case 'u': |
| trace_printf (sd, current_cpu, "%lu", (unsigned long) value); |
| break; |
| case 'x': |
| trace_printf (sd, current_cpu, "%lx", (unsigned long) value); |
| break; |
| case 'l': |
| { |
| if (i++ >= size) |
| return -1; |
| switch (GETMEMUQI (current_cpu, CPU_PC_GET (current_cpu), |
| fmt_address + i)) { |
| case 'd': |
| trace_printf (sd, current_cpu, "%lld", (long long) value); |
| break; |
| case 'i': |
| trace_printf (sd, current_cpu, "%lli", (long long) value); |
| break; |
| case 'u': |
| trace_printf (sd, current_cpu, "%llu", (unsigned long long) value); |
| break; |
| case 'x': |
| trace_printf (sd, current_cpu, "%llx", (unsigned long long) value); |
| break; |
| default: |
| assert (0); |
| break; |
| } |
| break; |
| } |
| default: |
| assert (0); |
| break; |
| } |
| break; |
| } |
| default: |
| /* XXX completeme */ |
| assert (0); |
| break; |
| } |
| |
| tags_processed++; |
| i++; |
| break; |
| case '\0': |
| i = size; |
| break; |
| default: |
| trace_printf (sd, current_cpu, "%c", c); |
| bytes_written++; |
| i++; |
| break; |
| } |
| } |
| |
| return bytes_written; |
| } |