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().