Sophie

Sophie

distrib > Mandriva > 9.1 > i586 > by-pkgid > 441ff32fe4d3d955aacd4305107c0a26 > files > 17

fenris-0.07-2mdk.i586.rpm


This file consists of two interleaved pieces. First is an introduction to 
general Fenris <-> debugging shell communication protocol, and another is an
introduction to writing modules for the default debugging shell, Aegir.

The communication protocol used between Fenris running with -W option and 
debugger shells: to establish a session, the shell has to connect to the 
unix socket created earlier by Fenris under the filename passed as -W 
parameter. The communication scheme is as follows:

  Debugger sends DMSG_* messages in appropriate format described by
  struct dmsg_header in fdebug.h. Message header is followed by
  message-dependent information, as discussed later. Every message
  has to be acknowledged by Fenris by sending DMSG_REPLY with appropriate
  response, which is also message-dependent. All messages and responses are 
  either fixed size or use null-terminated strings, so while there's no 
  guarantee the data will arrive in one packet, it is possible to determine 
  packet boundaries. Responses from Fenris can be preceeded or followed by 
  (but not interrupted with) any number of DMSG_ASYNC messages that are used 
  to report normal debugger output not directly related to issuing any 
  command. The rule is that DMSG_ASYNC will carry a last message entity
  from Fenris at the time it stopped, if any appeared since last stop
  condition. If not, an empty DMSG_ASYNC (one-byte data, null-terminator
  alone) will be sent to inform the debugger that previously running code is
  now stopped. In other cases, the entity can be split into several
  DMSG_ASYNC messages.

  All messages should carry correct DMSG_MAGIC values.

In the discussion below, dword: means a 32-bit integer value, string: means
a null terminated string. Supported message types of messages are:

#define DMSG_GETMEM	2
// Purpose: get memory region
// Params: dword:startaddr, dword:endaddr
// Response: dword:length, raw binary data
// Notes: returns shorter data if cannot read

#define DMSG_GETNAME	3
// Purpose: get name associated with an address
// Params: dword:addr
// Response: string:name (empty string = no match)
// Notes: will also return "shifted" matches, such as fcnt_10+10.
// Looks up in local names and symbol tabls.

#define DMSG_GETADDR	4
// Purpose: get address associated with name
// Params: string:name
// Response: dword:addres (0 = no match)
// Note that this function looks up name in both symbol tables
// and locally assigned names, and supports +nnn.

#define DMSG_GETREGS	5
// Purpose: get registers
// Params: none
// Response: regs structure from ptrace

#define DMSG_SETREGS    6
// Purpose: set registers (ones we can actually set)
// Params: regs structure for ptrace
// Response: string:message to user

#define DMSG_GETBACK	7
// Purpose: get stack backtrace
// Params: none
// Response: string:data for user

#define DMSG_DESCADDR   8
// Purpose: return a description of an address (not only its name)
// Params: dword:addr
// Response: string:description

#define DMSG_DESCFD 	9
// Purpose: return a description of a file descriptor
// Params: dword:fd
// Response: string:description

#define DMSG_ABREAK	10
// Purpose: set address-based breakpoint
// Params: dword:addr
// Response: string:message for user

#define DMSG_SBREAK	11
// Purpose: set syscall-based breakpoint
// Params: dword:syscall
// Response: string:message for user

#define DMSG_IBREAK	12
// Purpose: set signal-based breakpoint
// Params: dword:signal
// Response: string:message for user

#define DMSG_RWATCH	13
// Purpose: set on-read watchpoint
// Params: dword:memstart, dword:memend
// Response: string:message for user

#define DMSG_WWATCH     14
// Purpose: set on-write watchpoint
// Params: dword:memstart, dword:memend
// Response: string:message for user

#define DMSG_STEP	15
// Purpose: advance by n opcodes in local code
// Params: dword:count
// Response: string:message for user

#define DMSG_TORET	16
// Purpose: advance to n-th ret in local code
// Params: dword:count
// Response: string:message for user

#define DMSG_TOLIBCALL  17
// Purpose: advance to next libcall
// Params: none
// Response: string:message for user

#define DMSG_TOSYSCALL  18
// Purpose: advance to next syscall
// Params: none
// Response: string:message for user

#define DMSG_TOLOCALCALL 19
// Purpose: advance to next local call
// Params: none
// Response: string:message for user

#define DMSG_TOLOWERNEST 20
// Purpose: advance to RET from current code
// Params: none
// Response: string:message for user

#define DMSG_TONEXT	21
// Purpose: advance to next Fenris output entity
// Params: none
// Response: string:message for user

#define DMSG_RUN        22
// Purpose: run the program
// Params: none
// Response: string:message for user


#define DMSG_DYNAMIC    35
// Purpose: skip linker / libc prolog (go to 'main')
// Params: none
// Response: string:message for user

#define DMSG_STOP	23
// Purpose: stop the running code NOW
// Params: none
// Response: string:message for user

#define DMSG_HALT	36
// Purpose: stop the running code as soon as possible
// Params: none
// Response: string:message for user

#define DMSG_FPRINT	24
// Purpose: fingerprint an address
// Params: dword:addr
// Response: string:standard fprint response for user

#define DMSG_SETMEM	25
// Purpose: set memory region
// Params: dword:startaddr, dword:length, raw data
// Response: string:message for user

#define DMSG_LISTBREAK	26
// Purpose: list breakpoints
// Params: none
// Response: string:list for user

#define DMSG_DEL	27
// Purpose: delete a breakpoint
// Params: dword:number
// Response: string:message for user

#define DMSG_GETMAP	29
// Purpose: get memory map
// Params: none
// Response: string:description of memory reigons for user

#define DMSG_FDMAP	30
// Purpose: get fd map
// Params: none
// Response: string:description for user

#define DMSG_SIGNALS	31
// Purpose: get list of signal handlers
// Params: none
// Response: string:description for user

#define DMSG_KILL	32
// Purpose: kill the session
// Params: none
// Response: string:goodbye!

#define DMSG_FOO	33
// Purpose: ping / program status check
// Params: none
// Response: empty message.

#define DMSG_FNLIST     34
// Purpose: list known functions
// Params: none
// Response: string:list for user

#define DMSG_DYNAMIC   35
// Purpose: continue to dynamic code
// Params: none
// Response: string:list for user


Keep in mind that you ALWAYS have to send null-terminator with your
string. NEVER send strlen(your_string) bytes - always make it
strlen(your_string)+1. If you want to send an empty string, send single
'\0'. A typical symptom of not sending the terminator is Aegir or
Fenris hanging forever (or, more precisely, until killed).

Programming Aegir is relatively simple. All modules are essentially .so
files that have aegir_module_init function inside. This function takes
no parameters and returns no value, and should register at least one
command, so that the module can be invoked by users. This is done by calling
function register_command(char* commd,void* handler,char* help). "commd"
parameter should be set to the name of the command you want to bind.
Non-ambigious abbreviations are automatically handled by Aegir, so try to
give meaningful names instead of simple ones. "Handler" has to point to
a function that accepts char* parameter and returns no value, and will
be called every time this command is issued. Eventual parameters provided
with this command by user are passed to this function using this char*
parameter. If "handler" is NULL, this function will be listed in "help"
but reported as not implemented - you probably don't want to do it ;-) Third
parameter to register_command() function is "help". This string should
keep a brief (shorter than +/- 50 characters) description of how this
command works. If this function accepts any parameters, you can describe
the syntax by using ":" inside this string - everything before ":" will
be considered command syntax. So, if commd="foobar" and help="do nothing",
this will be displayed as:

  foobar  	    - do nothing

But if you use commd="bleh" and help="foobar x [ yyy ]: do nothing x times 
[ displaying string yyy every time ]", it will result in:

  foobar x [ yyy ]  - do nothing x times [ displaying string yyy every time ]

If "help" is set to NULL, this command will be available but not listed in
help section. This is useful if you want to define aliases for a command,
but do not want them to be listed and make help section less readable.

Once your module is loaded and commands binded, your command handlers have
to communicate with Fenris to send appropriate messages - that is, unless
all you want them to do is to say "Hello world"... This (communicating,
not saying "hello world") can be done with send_message(int message_type,
char* data,char* store) function, which returns a response acquired from 
Fenris (response data as char*). This function takes care of asynchronous 
messages and queues them, so you don't have to worry. Last parameter, "store",
should be set to 0, unless you want the function to store received data
somewhere outside a static buffer returned by default. Once you leave your 
handler, the last entity will be displayed, if any received, unless you call 
destroy_async() function - which is not very useful, but perhaps there
are situations when you want to inhibit Fenris output and just present
your own results.

You can easily write your own breakpoint that, say, stops when two different 
regions of memory have exactly the same value, and third region is set to 42, 
but only right after open(). How to do it? You have to constantly issue 
DMSG_TOSYSCALL, DMSG_STEP or other preferred continuation message,
then probably call function wait_for_stopped(void) - which waits until the 
traced application is stopped. The purpose of calling wait_for_stopped() is 
that Aegir is asynchronous - that is, traced process can be running and you 
still can read registers and such - and in this case, you want to examine
the state only when a step is finished or breakpoint is reached. Well,
as a matter of fact, it was so troublesome that I changed the default,
but you can still go back to async operations by adding '%' before the
parameter to Aegir ;-)

When the program is stopped at the location you wanted, fetch interesting 
memory region using DMSG_GETMEM, and do the comparsion or evaluate any
other fancy condition. If you got it, simply write a message to the console
using debug() and return from your handler leaving the program in stopped
state. If not, try again.

One more thing - definitions of Aegir API is in aegir-mod.h. All other
stuff for talking to Fenris is in fdebug.h. Since fdebug.h is included
from aegir-mod.h, for modules, you need the first #include only.

If you want to implement a module for nc-aegir, it is pretty much the
same. There is only one difference, you have to include curses.h
and put the following code:

extern WINDOW* Waegir;
#define debug(x...) wprintw(Waegir,x)

All reporting to the user has to be done by calling debug(). Its semantics
is roughly the same as for printf().