qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] gdbstub.c: add support for info proc mappings


From: Dominik Czarnota
Subject: Re: [PATCH] gdbstub.c: add support for info proc mappings
Date: Wed, 2 Mar 2022 14:38:13 +0100

ping

The patch can also be seen on patchew:
https://patchew.org/QEMU/20220221030910.3203063-1-dominik.b.czarnota@gmail.com/
or on lore.kernel.org:
https://lore.kernel.org/qemu-devel/20220221030910.3203063-1-dominik.b.czarnota@gmail.com/



On Mon, 21 Feb 2022 at 04:09, Disconnect3d <dominik.b.czarnota@gmail.com> wrote:
>
> This commit adds support for `info proc mappings` and a few other commands 
> into
> the QEMU user-mode emulation gdbstub.
>
> For that, support for the following GDB remote protocol commands has been 
> added:
> * vFile:setfs: pid
> * vFile:open: filename, flags, mode
> * vFile:pread: fd, count, offset
> * vFile:close: fd
> * qXfer:exec-file:read:annex:offset,length
>
> Additionally, a `REMOTE_DEBUG_PRINT` macro has been added for printing remote 
> debug logs.
> To enable it, set the `ENABLE_REMOTE_DEBUG` definition to 1.
>
> All this can be tested with the following steps (commands from Ubuntu 20.04):
> 1. Compiling an example program, e.g. for ARM:
>     echo 'int main() {puts("Hello world");}' | arm-linux-gnueabihf-gcc -xc -
> 2. Running qemu-arm with gdbstub:
>     qemu-arm -g 1234 -L /usr/arm-linux-gnueabihf/ ./a.out
> 3. Connecting to the gdbstub with GDB:
>     gdb-multiarch -ex 'target remote localhost:1234'
> 4. Executing `info proc mappings` in GDB.
>
> The opening of files is done on behalf of the inferior by reusing the 
> do_openat syscall.
> Note that the current solution is still imperfect: while it allows us to 
> fetch procfs
> files like /proc/$pid/maps in the same way as the inferior is seeing them, 
> there are
> two downsides to it. First of all, it is indeed performed on behalf of the 
> inferior.
> Second of all, there are some files that the GDB tries to request like 
> /lib/libc.so.6,
> but they are usually not available as they do not exist in those paths and 
> may need to
> be served from the prefix provided in the -L flag to the qemu-user binary. I 
> may try
> to add a support for this in another patch and maybe refactor the solution to 
> not use
> the do_openat function directly.
>
> Before this commit, one would get (except of amd64, but not i386 targets):
> ```
> (gdb) info proc mappings
> process 1
> warning: unable to open /proc file '/proc/1/maps'
> ```
>
> And after this commit, we should get something like:
> ```
> (gdb) info proc mappings
> process 3167519
> Mapped address spaces:
>
>         Start Addr   End Addr       Size     Offset objfile
>         0x3f7d0000 0x3f7d1000     0x1000        0x0
>         0x3f7d1000 0x3f7ed000    0x1c000        0x0 
> /usr/arm-linux-gnueabihf/lib/ld-2.33.so
>         0x3f7ed000 0x3f7fd000    0x10000        0x0
>         0x3f7fd000 0x3f7ff000     0x2000    0x1c000 
> /usr/arm-linux-gnueabihf/lib/ld-2.33.so
>         0x3f7ff000 0x3f800000     0x1000        0x0
>         0x3f800000 0x40000000   0x800000        0x0 [stack]
>         0x40000000 0x40001000     0x1000        0x0 /home/dc/src/qemu/a.out
>         0x40001000 0x40010000     0xf000        0x0
>         0x40010000 0x40012000     0x2000        0x0 /home/dc/src/qemu/a.out
> ```
>
> However, on amd64 targets we would get and still get the following on the GDB 
> side
> (even after this commit):
> ```
> (gdb) info proc mappings
> Not supported on this target.
> ```
>
> The x64 behavior is related to the fact that the GDB client does not 
> initialize
> some of its remote handlers properly when the gdbstub does not send an 
> "orig_rax"
> register in the target.xml file that describes the target. This happens in 
> GDB in the
> amd64_linux_init_abi function in the amd64-linux-tdep.c file [0]. The GDB 
> tries to find
> the "org.gnu.gdb.i386.linux" feature and the "orig_rax" register in it and if 
> it is not
> present, then it does not proceed with the `amd64_linux_init_abi_common 
> (info, gdbarch, 2);` call
> which initializes whatever is needed so that GDB fetches `info proc mappings` 
> properly.
>
> I tried to fix this but just adding the orig_rax registry into the target.xml 
> did not work
> (there was some mismatch between the expected and sent register values; I 
> guess the QEMU stub
> would need to know how to send this register's value). On the other hand, 
> this could also be
> fixed on the GDB side. I will discuss this with GDB maintainers or/and 
> propose a patch to GDB
> related to this.
>
> [0] 
> https://github.com/bminor/binutils-gdb/blob/dc5483c989f29fc9c7934965071ae1bb80cff902/gdb/amd64-linux-tdep.c#L1863-L1873
>
> Signed-off-by: Dominik 'Disconnect3d' Czarnota <dominik.b.czarnota@gmail.com>
> ---
>  gdbstub.c            | 272 +++++++++++++++++++++++++++++++++++++++++++
>  linux-user/qemu.h    |   2 +
>  linux-user/syscall.c |   2 +-
>  3 files changed, 275 insertions(+), 1 deletion(-)
>
> diff --git a/gdbstub.c b/gdbstub.c
> index 3c14c6a038..69cf8bbb0c 100644
> --- a/gdbstub.c
> +++ b/gdbstub.c
> @@ -34,6 +34,10 @@
>  #include "exec/gdbstub.h"
>  #ifdef CONFIG_USER_ONLY
>  #include "qemu.h"
> +#ifdef CONFIG_LINUX
> +#include "linux-user/qemu.h"
> +#include "linux-user/loader.h"
> +#endif
>  #else
>  #include "monitor/monitor.h"
>  #include "chardev/char.h"
> @@ -62,6 +66,21 @@
>  static int phy_memory_mode;
>  #endif
>
> +/*
> + *  Set to 1 to enable remote protocol debugging output. This output is 
> similar
> + *  to the one produced by the gdbserver's --remote-debug flag with some
> + *  additions. Anyway, the main debug prints are:
> + * - getpkt ("...") which refers to received data (or, send by the GDB 
> client)
> + * - putpkt ("...") which refers to sent data
> + */
> +#define ENABLE_REMOTE_DEBUG 0
> +
> +#if ENABLE_REMOTE_DEBUG
> +#define REMOTE_DEBUG_PRINT printf
> +#else
> +#define REMOTE_DEBUG_PRINT(...)
> +#endif
> +
>  static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr,
>                                           uint8_t *buf, int len, bool 
> is_write)
>  {
> @@ -554,6 +573,7 @@ static int gdb_continue_partial(char *newstates)
>
>  static void put_buffer(const uint8_t *buf, int len)
>  {
> +    REMOTE_DEBUG_PRINT("putpkt (\"%.*s\");\n", len, buf);
>  #ifdef CONFIG_USER_ONLY
>      int ret;
>
> @@ -1982,6 +2002,157 @@ static void handle_v_kill(GArray *params, void 
> *user_ctx)
>      exit(0);
>  }
>
> +#ifdef CONFIG_USER_ONLY
> +/*
> + * Handles the `vFile:setfs: pid` command
> + *
> + * Example call: vFile:setfs:0
> + *
> + * --- From the GDB remote protocol documentation ---
> + * Select the filesystem on which vFile operations with filename arguments
> + * will operate. This is required for GDB to be able to access files on
> + * remote targets where the remote stub does not share a common filesystem 
> with
> + * the inferior(s). If pid is nonzero, select the filesystem as seen by 
> process
> + * pid. If pid is zero, select the filesystem as seen by the remote stub.
> + * Return 0 on success, or -1 if an error occurs. If vFile:setfs: indicates
> + * success, the selected filesystem remains selected until the next 
> successful
> + * vFile:setfs: operation.
> +*/
> +static void handle_v_setfs(GArray *params, void *user_ctx)
> +{
> +    /*
> +     * We do not support different filesystem view for different pids
> +     * Return that all is OK, so that GDB can proceed
> +     */
> +    put_packet("F0");
> +}
> +
> +/*
> + * Handle the `vFile:open: filename, flags, mode` command
> + *
> + * We try to serve the filesystem here from the inferior point of view
> +
> + * Example call: vFile:open:6a7573742070726f62696e67,0,1c0
> + * (tries to open "just probing" with flags=0 mode=448)
> + *
> + * --- From the GDB remote protocol documentation ---
> + * Open a file at filename and return a file descriptor for it, or return
> + * -1 if an error occurs. The filename is a string, flags is an integer
> + * indicating a mask of open flags (see Open Flags), and mode is an integer
> + * indicating a mask of mode bits to use if the file is created
> + * (see mode_t Values). See open, for details of the open flags and mode
> + * values.
> + */
> +static void handle_v_file_open(GArray *params, void *user_ctx)
> +{
> +    uint64_t flags = get_param(params, 1)->val_ull;
> +    uint64_t mode = get_param(params, 2)->val_ull;
> +    const char *hex_filename = get_param(params, 0)->data;
> +
> +    /* Decode the filename & append a null byte so we can use it later on */
> +    hextomem(gdbserver_state.mem_buf, hex_filename, strlen(hex_filename));
> +    const char *null_byte = "\0";
> +    g_byte_array_append(gdbserver_state.mem_buf, (const guint8 *)null_byte, 
> 1);
> +
> +    const char *filename = (const char *)gdbserver_state.mem_buf->data;
> +
> +    REMOTE_DEBUG_PRINT("vFile:open: filename=\"%s\" flags=%ld mode=%ld\n",
> +                       filename, flags, mode);
> +
> +    /*
> +     * On Linux we call the do_openat syscall on behalf of the inferior as it
> +     * handles special filepaths properly like the /proc/$pid files, which 
> are
> +     * fetched by GDB for certain info (such as `info proc mappings`).
> +     */
> +#ifdef CONFIG_LINUX
> +    int fd = do_openat(gdbserver_state.g_cpu->env_ptr,
> +                       /* dirfd */ 0, filename, flags, mode);
> +    REMOTE_DEBUG_PRINT("do_openat = %d\n", fd);
> +#else
> +    int fd = open(filename, flags, mode);
> +    REMOTE_DEBUG_PRINT("open = %d\n", fd);
> +#endif
> +
> +    g_string_printf(gdbserver_state.str_buf, "F%d", fd);
> +    if (fd < 0) {
> +        /* Append ENOENT result.
> +         * TODO/FIXME: Can we retrieve errno from do_openat/open and return 
> it here?
> +         */
> +        g_string_append(gdbserver_state.str_buf, ",2");
> +    }
> +    put_strbuf();
> +}
> +
> +/*
> + * Handles the `vFile:pread: fd, count, offset` command
> + *
> + * Example call: vFile:pread:7,47ff,0
> + *
> + * --- From the GDB remote protocol documentation ---
> + * Read data from the open file corresponding to fd.
> + * Up to count bytes will be read from the file, starting at offset relative 
> to
> + * the start of the file. The target may read fewer bytes; common reasons
> + * include packet size limits and an end-of-file condition. The number of 
> bytes
> + * read is returned. Zero should only be returned for a successful read at 
> the
> + * end of the file, or if count was zero.
> + *
> + * The data read should be returned as a binary attachment on success. If 
> zero
> + * bytes were read, the response should include an empty binary attachment
> + * (i.e. a trailing semicolon). The return value is the number of target 
> bytes
> + * read; the binary attachment may be longer if some characters were escaped.
> + */
> +static void handle_v_file_pread(GArray *params, void *user_ctx)
> +{
> +    int fd = get_param(params, 0)->val_ul;
> +    uint64_t count = get_param(params, 1)->val_ull;
> +    uint64_t offset = get_param(params, 2)->val_ull;
> +
> +    g_autoptr(GString) file_content = g_string_new(NULL);
> +
> +    REMOTE_DEBUG_PRINT("vFile:read: fd=%d, count=%lu, offset=%lu\n",
> +                       fd, count, offset);
> +
> +    while (count > 0) {
> +        char buf[1024] = {0};
> +        ssize_t n = pread(fd, buf, sizeof(buf), offset);
> +        if (n <= 0) {
> +            break;
> +        }
> +        g_string_append_len(file_content, buf, n);
> +        count -= n;
> +        offset += n;
> +    }
> +    g_string_printf(gdbserver_state.str_buf, "F%lx;", file_content->len);
> +    /* Encode special chars */
> +    memtox(gdbserver_state.str_buf, file_content->str, file_content->len);
> +    put_packet_binary(gdbserver_state.str_buf->str,
> +                      gdbserver_state.str_buf->len, true);
> +}
> +
> +/*
> + * Handles the `vFile:close: fd` command
> + *
> + * Example call: vFile:close:7
> + *
> + * --- From the GDB remote protocol documentation ---
> + * Close the open file corresponding to fd and return 0, or -1 if an error 
> occurs.
> + */
> +static void handle_v_file_close(GArray *params, void *user_ctx)
> +{
> +    int fd = get_param(params, 0)->val_ul;
> +    int res = close(fd);
> +    if (res == 0) {
> +        put_packet("F00");
> +    } else {
> +        /* This may happen only with a bugged GDB client or a bugged 
> inferior */
> +        REMOTE_DEBUG_PRINT("Warning: the vFile:close(fd=%d) operation 
> returned %d\n",
> +                           fd, res);
> +        g_string_printf(gdbserver_state.str_buf, "F%d,%d", res, errno);
> +        put_strbuf();
> +    }
> +}
> +#endif /* CONFIG_USER_ONLY */
> +
>  static const GdbCmdParseEntry gdb_v_commands_table[] = {
>      /* Order is important if has same prefix */
>      {
> @@ -2001,6 +2172,32 @@ static const GdbCmdParseEntry gdb_v_commands_table[] = 
> {
>          .cmd_startswith = 1,
>          .schema = "l0"
>      },
> +    #ifdef CONFIG_USER_ONLY
> +    {
> +        .handler = handle_v_setfs,
> +        .cmd = "File:setfs:",
> +        .cmd_startswith = 1,
> +        .schema = "l0"
> +    },
> +    {
> +        .handler = handle_v_file_open,
> +        .cmd = "File:open:",
> +        .cmd_startswith = 1,
> +        .schema = "s,L,L0"
> +    },
> +    {
> +        .handler = handle_v_file_pread,
> +        .cmd = "File:pread:",
> +        .cmd_startswith = 1,
> +        .schema = "l,L,L0"
> +    },
> +    {
> +        .handler = handle_v_file_close,
> +        .cmd = "File:close:",
> +        .cmd_startswith = 1,
> +        .schema = "l0"
> +    },
> +    #endif
>      {
>          .handler = handle_v_kill,
>          .cmd = "Kill;",
> @@ -2197,6 +2394,8 @@ static void handle_query_supported(GArray *params, void 
> *user_ctx)
>      if (gdbserver_state.c_cpu->opaque) {
>          g_string_append(gdbserver_state.str_buf, ";qXfer:auxv:read+");
>      }
> +
> +    g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+");
>  #endif
>
>      if (params->len &&
> @@ -2305,6 +2504,63 @@ static void handle_query_xfer_auxv(GArray *params, 
> void *user_ctx)
>      put_packet_binary(gdbserver_state.str_buf->str,
>                        gdbserver_state.str_buf->len, true);
>  }
> +
> +/*
> + * Handle the `qXfer:exec-file:read:annex:offset,length` command
> + *
> + * Example call: qXfer:exec-file:read:241022:0,ffb
> + *
> + * --- From the GDB remote protocol documentation ---
> + * Return the full absolute name of the file that was executed to create a 
> process
> + * running on the remote system. The annex specifies the numeric process ID 
> of the
> + * process to query, encoded as a hexadecimal number. If the annex part is 
> empty the
> + * remote stub should return the filename corresponding to the currently 
> executing
> + * process.
> + *
> + * This packet is not probed by default; the remote stub must request it, by 
> supplying
> + * an appropriate ‘qSupported’ response (see qSupported).
> + */
> +static void handle_query_xfer_exec_file(GArray *params, void *user_ctx)
> +{
> +    uint32_t pid = get_param(params, 0)->val_ul;
> +    uint32_t offset = get_param(params, 1)->val_ul;
> +    uint32_t length = get_param(params, 2)->val_ul;
> +
> +    GDBProcess *process = gdb_get_process(pid);
> +    if (!process) {
> +        put_packet("E01");
> +        return;
> +    }
> +
> +    CPUState *cpu = get_first_cpu_in_process(process);
> +    if (!cpu) {
> +        put_packet("E02");
> +        return;
> +    }
> +
> +    TaskState *ts = cpu->opaque;
> +    /* Those should be there but lets sanity check them */
> +    if (!ts || !ts->bprm || !ts->bprm->filename) {
> +        put_packet("E03");
> +        return;
> +    }
> +
> +    /*
> +     * This filename is an absolute path even when QEMU user-mode emulation 
> is called
> +     * with a symlink path so we do not have to resolve it with readlink(2)
> +     */
> +    const char *filename = ts->bprm->filename;
> +
> +    /* It does not make sense to return anything after the filename */
> +    if (offset > strlen(filename)) {
> +        put_packet("E04");
> +        return;
> +    }
> +
> +    g_string_printf(gdbserver_state.str_buf, "l%.*s", length, filename + 
> offset);
> +    put_strbuf();
> +    return;
> +}
>  #endif
>
>  static void handle_query_attached(GArray *params, void *user_ctx)
> @@ -2419,6 +2675,12 @@ static const GdbCmdParseEntry gdb_gen_query_table[] = {
>          .cmd_startswith = 1,
>          .schema = "l,l0"
>      },
> +    {
> +        .handler = handle_query_xfer_exec_file,
> +        .cmd = "Xfer:exec-file:read:",
> +        .cmd_startswith = 1,
> +        .schema = "l:l,l0"
> +    },
>  #endif
>      {
>          .handler = handle_query_attached,
> @@ -2516,6 +2778,7 @@ static int gdb_handle_packet(const char *line_buf)
>      const GdbCmdParseEntry *cmd_parser = NULL;
>
>      trace_gdbstub_io_command(line_buf);
> +    REMOTE_DEBUG_PRINT("getpkt (\"%s\");\n", line_buf);
>
>      switch (line_buf[0]) {
>      case '!':
> @@ -3133,6 +3396,15 @@ static void create_default_process(GDBState *s)
>      GDBProcess *process;
>      int max_pid = 0;
>
> +#if defined(CONFIG_USER_ONLY)
> +    /*
> +     * In QEMU user-mode emulation we want to return the real PID of the 
> proces
> +     * as this allows us to return proper view of /proc/$pid files as seen by
> +     * the inferior
> +     */
> +    max_pid = getpid() - 1;
> +#endif
> +
>      if (gdbserver_state.process_num) {
>          max_pid = s->processes[s->process_num - 1].pid;
>      }
> diff --git a/linux-user/qemu.h b/linux-user/qemu.h
> index 98dfbf2096..44a71b9740 100644
> --- a/linux-user/qemu.h
> +++ b/linux-user/qemu.h
> @@ -182,6 +182,8 @@ static inline bool access_ok(CPUState *cpu, int type,
>      return access_ok_untagged(type, cpu_untagged_addr(cpu, addr), size);
>  }
>
> +int do_openat(void *cpu_env, int dirfd, const char *pathname, int flags, 
> mode_t mode);
> +
>  /* NOTE __get_user and __put_user use host pointers and don't check access.
>     These are usually used to access struct data members once the struct has
>     been locked - usually with lock_user_struct.  */
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index b9b18a7eaf..bfc271b568 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -8233,7 +8233,7 @@ static int open_hardware(void *cpu_env, int fd)
>  }
>  #endif
>
> -static int do_openat(void *cpu_env, int dirfd, const char *pathname, int 
> flags, mode_t mode)
> +int do_openat(void *cpu_env, int dirfd, const char *pathname, int flags, 
> mode_t mode)
>  {
>      struct fake_open {
>          const char *filename;
> --
> 2.30.2
>



reply via email to

[Prev in Thread] Current Thread [Next in Thread]