[lldb-dev] C++ API: Passing information from debuggee to debugger
Duane Ellis
duane at duaneellis.com
Tue Jun 2 08:48:34 PDT 2015
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.
More information about the lldb-dev
mailing list