Saturday, August 3, 2013

Augment GDB with Convenience Variables and User-defined Commands

I have had the need over the past few months to work with GDB but without a symbol table.

Searching the web one finds others, though a small minority, that have asked how to work with GDB when there is no symbol table available.

I posted several questions to Stack Overflow regarding GDB and not having a symbol table.
SO Question 17956799
SO Question 17492119
SO Question 17956799
SO Question 17667590

People have reported problems of not being able to set breakpoints. GDB reports
     'No symbol table loaded. Use "file" command'

SO Question 4698299

Some of these questions are from newbie programmers who didn't include debug symbols in their build, but others are those, like me, trying to work with legacy programs that are no longer compatible with the GDB tool chain.

This is typical in reverse engineering of legacy code.

This blog tells about how I made GDB useful when there is no symbol table available. My experience is in the embedded environment. I have small memory sizes, slow (relatively) speed processors, and only one process. I am also working with cross-tools and target hardware.

Having used GDB before will help when reading what follows. I have added links to GDB commands mentioned in the text.

Symbol Map

First, one needs to have a symbol map.

If one doesn't have a symbol map, well, you can start creating your own by giving your own names to hex addresses where call or jsr instructions are found in a dis-assembly listing, but having a symbol map is much better.

What information does a symbol map have? Addresses with associated names.

It is much better to refer to a memory address as main, than 0x243AD. (Though over time you might start seeing main, when you see 0x243AD, but why bother?)

A symbol map, however, is not a symbol table, as far as GDB is concerned.

I have not found any documentation on how to create a symbol table file without using a linker.

One is able to create convenience variables in GDB that hold the addresses in the symbol map and give these addresses names that GDB can use. GDB Convenience Variables

From my symbol map, I have this record.

_CheckCheckSum__6EepromFs        code      000220D2    eeprom
Eeprom::CheckCheckSum(short)

I can create a GDB convenience variable.

set $Eeprom_CheckCheckSum=(unsigned int*)0x220D2

I can then say

x /40i $Eeprom_CheckCheckSum

The GDB examine (x for short) command will disassemble the next 40 instructions starting at the address $Eeprom_CheckCheckSum.
GDB examine command

JTAG or BDM Device

If one has legacy code, it probably doesn't support GDB. To use GDB, one MUST have a GDB server, either in the target hardware or on a device connected to the target hardware.

Current JTAG or BDM (Motorola CPUs only) devices provide the GDB server.

When using a JTAG device, the target of GDB is considered remote. GDB has a configuration command, target remoteGDB target remote command

Once GDB can communicate with the JTAG device, then GDB has a command that allows one to send JTAG device-specific commands, monitor.

Any command that you want to send to the JTAG device is sent via GDB by

 monitor <JTAG command>

mon is short for monitor.

Example, to reset the CPU via the Abatron BDI2000, one issues the GDB command

mon reset

Abatron BDI200 User's Manual

GDB command file

When GDB is started, it looks for the file .gdbinit. This file name can be overridden using the -x <filename> command line option. GDB Startup

This is the file where we place the target command to communicate with the JTAG device or GDB server. Placing the target remote command in .gdbinit ensures the GDB session is communicating with the target. If there's a communication problem, we will know it right away.

# Communicate with an Abatron BDI2000
target remote 192.168.0.10:2001

.gdbinit is also where we place the convenience variables we derived from the symbol map.

From my symbol map, I have this record.

_CheckCheckSum__6EepromFs        code      000220D2    eeprom
Eeprom::CheckCheckSum(short)

I create the following convenience variable and put it in the .gdbinit file.

set $Eeprom_CheckCheckSum=(unsigned int *)0x220D2


One can list the convenience variables with the GDB command

show conv


GDB User-defined Command

Along with convenience variables, GDB also allows one to define user-defined commands.

User-defined commands are way to create automated functions for repetitive tasks or complicated debugging logic.

They can also be used to create short cuts for JTAG device commands.

Below are a few of Abatron BDI2000 JTAG commands and the GDB equivalent.

# BDI2000 command 'reset'
define mreset
 mon reset
 flushregs
end
document mreset
Send the BDI2000 command 'reset'
'flushregs' is required as GDB does not
know what 'mon reset' did to the target
end

# BDI2000 command 'mrd'-read registers
define mrd
mon rd
end
document mrd
Send the BDI2000 command 'rd'. More compact than 'info reg'
end

In the document section for the mreset user-defined command is a key concept. If you use ANY monitor command that changes the registers of the target CPU, you MUST include the GDB flushregs command at the end of the command.

GDB does not know what monitor commands do to the CPU state, flushregs is the GDB command to say to GDB 'The state of the registers has changed, your cache is no longer valid.'

The user-defined commands are also placed in .gdbinit.

show user [command name]

The show user command lists the user-defined commands.
GDB show user command

So with the above user-functions and environment variables, one change enter at the GDB command prompt.

mreset
mrd
x /40i $Eeprom_CheckCheckSum

If the target hardware has hardware breakpoint support, I can also say

br $Eeprom_CheckCheckSum
continue

When needing to disassemble instructions constantly, I created a user-command dxi.

# dxi
# dxi <count>
# dxi <count> <startaddress>
define dxi
if $argc == 2
  x /$arg0i $arg1
end
if $argc == 1
  x /$arg0i $pc
end
if $argc == 0
x /20i $pc
end
end
document dni
Output $arg0 instructions from current PC
If $arg0 is not supplied, output 20 instructions
end

If one wants to capture the dis-assembly of the examine command, one can create a pair of user-commands to turn on and turn off logging. The dis-assembly listing will be recorded in a log file.

#startlog
#startlog <file>
define startlog
  if $argc == 0
    set logging off
set logging on
  end
  if $argc == 1
    set logging off
    set logging file $arg0
    set logging on
  end
end
document startlog
  Open 'gdb.txt' or $arg0 for GDB logging
end

define stoplog
set logging off
end
document stoplog
Turn off GDB logging
end

Thus without a symbol table one can create a usable debugging environment.

Hope you found this post worth reading.

The next post discusses how to augment GDB when the target processor does not have hardware breakpoint support.

No comments:

Post a Comment