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 remote. GDB target remote command
When using a JTAG device, the target of GDB is considered remote. GDB has a configuration command, target remote. GDB 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>
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
# 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
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
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.
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