Eugene>> I need the deguggee to stop and pass some information to the debugger 
- if the debugger is attached, that is.

This sounds like “ARM SEMI-HOSTING” but as an application under Linux,

There is a simple way to do this, I did this in a previous system it worked 
very well.

You need some function in the target, it needs to be a single instance, not 
multiple instances.

Step 1:   Create some function 

        int   DEBUGGER_CALL(  int command, intptr_t param1, intptr_t param2, … )

        You want to model this like a SYSCALL()

        Return value -1 means error
        Return value (0 or positive) means success

        Parameter 0 - is the command or request.
        Parameter 1 - is an “intptr_t” so it can handle anything
        Parmeter N is the last parameter (you need N=5 for most everything)

        Alternative: Create a “struct sys_call” and pass the structure pointer

SUPER IMPORTANT:
        DO NOT do unions
        DO NOT use VARDAC functions
        Make the API very fixed - explicitly fixed very simple
        Otherwise it does not port across targets very well.
        
These things make it hard
                Host /TARGET ENDIAN changes
                Host / TARGET - bit field ENDIAN
                Host/ TARGET structure packing changes
                Host/TARGET enum size scaling 
                Host/TARGET variable sizes  (i.e.:  INT = 32 or 64bits?)

        I faced the above problems between our 24bit cpu, SunOS and Linux 
        These things where painful hence my suggestion to you   

        Thus you might do something like:

        struct  debugger_syscall {
                // The request
                int   request_id;
                // Debugger sets this value
                int  result_code;
                intptr_t param[ 5 ];
        }

Step 2:

        The default implementation of this function is simply:  “return -1”
        This would indicate DEBUGGER NOT PRESENT or ERROR

        For example it might just do this:

        void DEBUGGER_CALL( struct foo *p )
        {
                p->result_code = -1;
        }

        When you compile this function, you might want to implement it in 
assembly language
        Reason: If you write this in assembly, you can control the exact opcode 
sequence

        Otherwise different compiler options (optimizations, etc) cause problems
        Besides, this is a super tiny function about 4 instructions in assembler
        It’s not like you need to write a huge amount of code in assembly.

        Don’t forget about optimizing linkers! These can also cause problems.

        HINT:   Make your code something like this, put extra NOP 
        (See below for reason)

                label:
                        load   R0, -1 ;  return value is minus 1
                        return
                        // these are here for use by the debugger
                        NOP
                        NOP
                        NOP
                        NOP
                        NOP
                        return

Step 3:
        The debugger sets a breakpoint on this function.
        
        The “breakpoint action function” (aka: your script)
        Can now examine the parameters (or structure pointer)
        And perform the requested action

In my case we had two builds of the application: 

        Build(HOSTED) ran on linux (and SunOS, pre-solaris)
        Build(TARGET) - ran on our custom CPU target

When the application was under debug/test, we had 2 choices:

Option 1:   Let it run as normal, the default return value was always -1

Option 2:  Under LINUX/SunOS  Use LD_LIBRARY_PATH to insert a replacement 
function that could perform additional steps.

  This is of course a huge security risk, but it worked great

  We put this local to the directory where the test was being executed.
  Thus, each ‘test directory’ could have a different  “debugger hook function'

When the application was run on the target (bare metal application)

Option 3)  No debugger present, the default library function just returned -1 
(error)
    We shipped product this way - in masked ROM chips

Option 4)  With the debugger present,

     we set a breakpoint at that function and setup a script to perform some 
action on that breakpoint
     In our case, we inspected the opcodes at that location
     If they where incorrect the test session was rejected as failure

     If they where correct, the debugger modified the OPCODES 
        This is why we put the extra NOPs in the function
        We had extra room to do what we required

    On program load - the debugger can look for your “magic symbol name”
        If the magic symbol is present in the symbol table
        The debugger can “attach” and automate much of this process

Worked really well - same code ran on Sun SPARC,  LINUX, and our custom 24bit 
CPU target under GDB

In our case, on the debugger side, we had some very standard code that 
extracted the parameter structure

At the bottom of that standard code you have a dispatch table that mapped the 
request ID to a handler in the debugger script

Worked great

If you implement the above in a standard way, it would be universal to all LLDB 
instances

Each target wanting to support this would only need to create the *very* tiny 5 
to 10 opcode long  “DEBUGGER_CALL” function

I would also suggest a series of standard function numbers (i.e.: the ARM 
semi-hosting ones are a good start)

-Duane.



_______________________________________________
lldb-dev mailing list
lldb-dev@cs.uiuc.edu
http://lists.cs.uiuc.edu/mailman/listinfo/lldb-dev

Reply via email to