venerdì 21 marzo 2014

GDB and Geany IDE: Remote Cross Debugging

Embedded Linux System Debugging with Geany


Remote debugging is when GDB runs on one machine and the program being debugged runs on another. This is useful in embedded systems, and often in this case it's called remote cross-debugging because the target CPU is differerent from the host used for developing code.
gdbserver can be used to remotely debug the program without needing to change it in any way.

The Geany IDE debugger plugin does not support remote debugging natively. Some graphical debugger such as Insight or the Eclipse IDE support it, but not Geany. And I like Geany very much, while I don't like Eclipse: so I arranged a patch/workaround to do it (based on plugin version 0.21).


Compiling the patch

Download geany plugins sources [http://plugins.geany.org/downloads.html] and install geany and gtk devel packages for your distribution. You'll need also Geany devel package installed. Compiling debugger plugin needs also vte-devel package installed.
  # cd geany-plugins-0.21.1
  # ./configure --prefix=/usr --mandir=/usr/share/man/
  # cd debugger/src
  # make

Compiled plugin is in the debugger/src/.libs/ directory (debugger.so). You will need to copy it in /usr/lib/geany (or where your distribution places the geany plugins), or install it with:
  make install

Geany can use an addictional path for plugins search (see Geany options), and if you want to use it you will have to configure the sources with:
  # ./configure --prefix=/your/path/to/plugins --mandir=/usr/share/man/

Here below the patch for dbm_gdb.c file:

  --- geany-plugins-0.21.1/debugger/src/dbm_gdb.c.original
  +++ geany-plugins-0.21.1/debugger/src/dbm_gdb.c
  @@ -96,6 +96,9 @@
   
   /* flag, showing that on debugger stop we have to call a callback */
   gboolean requested_interrupt = FALSE;
  +
  +/* flag: remote debugging session*/
  +gboolean remote_session = FALSE;
   
   /* autos list */
   static GList *autos = NULL;
  @@ -598,6 +601,9 @@
    */
   gboolean load(char* file, char* commandline, GList* env, GList *witer)
   {
  +char *ip;
  +char *port;
  +
    /* loading file */
    GString *command = g_string_new("");
    g_string_printf(command, "-file-exec-and-symbols %s", file);
  @@ -610,11 +616,38 @@
     return FALSE;
    }
   
  - /* set arguments */
  - command = g_string_new("");
  - g_string_printf(command, "-exec-arguments %s", commandline);
  - exec_sync_command(command->str, TRUE, NULL);
  - g_string_free(command, TRUE);
  + /* is it a remote target? */
  + /* (remote sessions handle commandline on debugserver side) */
  + if (commandline[0] == '@') {
  +  ip = commandline + 1;
  +  port = strchr(ip, ':');
  +  if (port != NULL) {
  +   *port = '\0';
  +   port++;
  +  }
  +  else {
  +   port = (char *)"3278";
  +  }
  +  if (ip == '\0') {
  +   ip = (char *)"127.0.0.1";
  +  }
  +  if (port == '\0') {
  +   port = (char *)"3278";
  +  }
  +  command = g_string_new("");
  +  g_string_printf(command, "target remote %s:%s", ip, port);
  +  exec_sync_command(command->str, TRUE, NULL);
  +  g_string_free(command, TRUE);
  +  remote_session = TRUE;
  + }
  + /* set arguments in non-remote sessions */
  + else {
  +  remote_session = FALSE;
  +  command = g_string_new("");
  +  g_string_printf(command, "-exec-arguments %s", commandline);
  +  exec_sync_command(command->str, TRUE, NULL);
  +  g_string_free(command, TRUE);
  + }
    
    /* set locale */
    command = g_string_new("");
  @@ -678,7 +711,14 @@
    }
    free_start_messages();
   
  - exec_async_command("-exec-run &");
  + if (remote_session) {
  +  exec_async_command("break main");
  +  sleep(2);
  +  exec_async_command("continue");
  + }
  + else {
  +  exec_async_command("-exec-run &");
  + }
   }
   
   /*
  @@ -686,6 +726,9 @@
    */
   void restart(char* terminal_device)
   {
  + if (remote_session) {
  +  dbg_cbs->report_error(_("Restart command not available in remote sessions"));
  + }
    dbg_cbs->clear_messages();
    exec_async_command("-exec-run &");
   }


Remote debugging

After compiling and installing this patched plugin, enable Debugger plugin in Geany. Remote debugging can be used inserting in the debugger plugin tab "Command Line" field the following format string:
  @remoteipaddr:port

for example:
  @192.168.2.51:4567

If you don't specify remoteipaddr, the default used is 127.0.0.1; if you don't specify port, the default used is 3278.

On the remote side, you'll have to start gdbserver:
  # gdbserver localhost:4567 yourprogram [yourprogram options]

Command options must be specified on the gdbserver side, when debugging remotely.


Cross debugging

The remote device can be a target CPU different from the host. For example it could be an embedded ARM Linux board. In this case you have to use a shell script to start geany with the correct path for the right gdb executable (x86 gdb executable can not debug other target CPUs than x86); let's suppose your ARM gdb executable is gdb-arm-linux (you can download it from emdebian: gdb-arm-linux-gnueabi_7.2-1_i386.deb) and you installed it in your ~/bin/arm-linux-emdebian/usr/bin/ directory:
  • create a symlink gdb to gdb-arm-linux:
      # cd ~/bin/arm-linux-emdebian/usr/bin/
    
      # ln -s gdb-arm-linux gdb
    
  • then create a shell script like this and use it for starting Geany:
      #!/bin/sh
      PATH=/home/yourhome/bin/arm-linux-emdebian/usr/bin/:$PATH
      geany $1
    
    
    

Notes

A few notes on remote debugging use:
  • don't use "Restart" command in remote debugging session: it can't be used
  • a breakpoint is automatically placed at main start (break main) so program stops there after first run, but it's not shown in Geany
  • place manually yourself a breakpoint in the place where program stops initially: it will prevents other breakpoints garbage later
This does not want to be a definitive solution: it's a dirty patch/workaround for my needs.