Author: Arnaud PICHERY (aranud@mail.dotcom.fr)
Date: September 21, 1999
The
aim was to be able to debug frozen features as well as melted features. Before,
only “supermelted” features could be debugged which means that every feature
that contained a breakpoint was supermelted and the resulting byte code sent to
the debugged application. Even melted feature had to be supermelted.
The
following table summarizes the differences between
|
Melted |
Supermelted |
Byte
code to start the feature |
BC_START |
BC_DEBUGGABLE |
Generation
of debugger hooks (BC_NEXT) |
No |
Yes |
BYTE_CONTEXT
– debug_mode |
False |
True |
The
way breakpoints were implemented was the following: by default, a debugger hook
was placed at each breakable line in the byte code (code: BC_NEXT). If there
was a breakpoint, a break was placed instead of the hook (BC_BREAK instead of
BC_NEXT). At the start of the application and at each time the application was
resumed, EiffelBench was sending the byte code of supermelted feature to the
application. As a result of this mechanism it was impossible to put a new
breakpoint in the current routine if you were already stopped in it because
EiffelBench couldn’t change the byte code you were executing.
To
improve the new debugger, I first have added the debugger hook in frozen
feature. The corresponding macro is RT_HOOK(current line number). Then I have
added in the run-time a mechanism to set and remove a breakpoint during the
execution of the application. Finally I have removed the “supermelt” notion.
Now every melted feature contains debugger hook and start with a BC_START byte
code.
Another
main improvement in the debugger is the possibility to step-into a routine, and
to step-out of the current routine. It is now possible because frozen feature
now contain debugger hooks. Debugger hooks allow testing after each line if a
breakpoint is set, or if the stack depth is too swallow, or…
Another
improvement is the possibility to disable breakpoints separately. A new button
in the toolbar has been added. By clicking on it, the user can disable all
breakpoints, and he can disable the breakpoints he desires by clicking a
breakable point, a feature or a class and droping it onto this button.
The
debugger mainly relies on two data structures, which are the execution stack
and the list of breakpoints. The execution stack is defined in the file
“eif_types.h” as below:
/* Structure used as the execution vector. This is for both the execution
* stack
(eif_stack) and the exception trace stack (eif_trace).
*/
struct ex_vect {
unsigned
char ex_type; /* Function call, pre-condition, etc... */
int ex_linenum; /* current line number (line number <=>
breakpoint slot) */
int ex_bodyid; /*
body id of the feature */
unsigned
char ex_retry; /* True if function has been retried */
unsigned
char ex_rescue; /* True if function entered its rescue clause */
unsigned
char ex_locnum; /* number of local variables in the function */
unsigned
char ex_argnum; /* number of arguments of the function */
union {
unsigned
int exu_lvl; /* Level for multi-branch
backtracking */
int
exu_sig; /* Signal number */
int
exu_errno; /* Error number reported by kernel */
struct {
char
*exua_name; /* The assertion tag */
char
*exua_where; /* The routine name where
assertion was found */
int
exua_from; /* And its origin (where it was written) */
char
*exua_oid; /* Object ID (value of Current)
*/
} exua; /*
Used by assertions */
struct {
char
*exur_jbuf; /* Execution buffer address,
null if none */
char
*exur_id; /* Object ID (value of Current) */
char
*exur_rout; /* The routine name */
int
exur_orig; /* Origin of the routine */
} exur; /*
Used by routines */
} exu;
};
During the execution of both melted and frozen features, ex_linenum and ex_body_id are updated. ex_linenum contains the current breakable line number, which means the current line number in the stop points view under EiffelBench. ex_body_id contains the real_body_id of the current feature (in fact, ex_body_id contains EiffelBench’s real_body_id - 1).
The
ex_body_id is set up at the creation of a new execution vector during the
execution of the C-routine new_exvect. Actually new_exvect is called through
the RTEAA C-macro for frozen feature and through the C-routine interp (located
in file interp.c) for melted feature. For melted feature, the real_body_id of
the feature can be found after the BC_START byte code.
In
order to add and remove a breakpoint, the application needed to store somewhere
the location of the breakpoints. The best place to store all the data the
application needed was in the d_data variable which type is struct dbinfo.
d_data is a kind of global variable defined in the file debug.c. The new
variables I have added are the following;
struct offset_list {
uint32
offset;
struct
offset_list *next;
};
/* breakpoint table. there is one entry per feature
where
* you can
find a breakpoint
*/
struct db_bpinfo {
int body_id; /*
body_id of the feature where breakpoints are located */
struct
offset_list *first_offset; /* list of all
offset where a breakpoint is */
struct
db_bpinfo *next; /* next feature that
contains breakpoint(s) */
};
struct dbinfo {
char
*db_start; /* Start of current byte code (dcall) */
int db_status; /*
Execution status (dcall) */
uint32
db_callstack_depth; /* number of
routines on the eiffel stack */
uint32
db_callstack_depth_stop; /* depth from
which we must stop (step-by-step, stepinto..) */
char
db_stepinto_mode; /* is stepinto activated
? */
char
db_discard_breakpoints; /* when set, discard all breakpoints. used
for run-no-stop, */
/* and after the end of the root creation. it
avoids the */
/* application to stop after its end when
garbage collector */
/* destroys objects */
struct
db_bpinfo *db_bpinfo; /* breakpoints table */
};
In
order to stop the application when we want, I have added a debugger hook before
each instruction (and before the final end of the feature). The debugger hook
contains the current line number. For example a frozen feature with 3 lines
will produce the following C code when frozen:
RT_HOOK(1); INSTRUCTION 1; RT_HOOK(2); INSTRUCTION 2; RT_HOOK(3); INSTRUCTION 3; RT_HOOK(4) |
do Instruction 1 Instruction 2 Instruction 3 end |
For
frozen feature, the debugger hook is the C macro RT_HOOK that calls the C
function dnext_frozen. For melted feature, the debugger hook is the BC_NEXT
byte code that calls when interpreted the C function dnext_melted. Starting
from now, BC_NEXT has an argument which is the current line number (an Eiffel
INTEGER, a C long). Dnext_frozen and dnext_melted do the same job: they check
whether a breakpoint, the stack depth stop or the stepinto flag are set or not.
In the positive case, the application stops.
Setting
a breakpoint is very simple on the run-time side. When the application is
listening to EiffelBench, EiffelBench send a message to the application with
the real_body_id of the
feature of the breakpoint, its line number within the feature and its mode (if
the application must set or remove the breakpoint). All we have to do is to
create a new cell for the feature in the bp_info structure if it does not exist, and to create a new
cell for the line number. That’s all. This is made by the dsetbreak C function.
To
implement the step-into mechanism I have used a simple flag. Actually stepping
into the next function means stopping to the next instruction executed: If we
can step into the function, the following instruction to be executed will be
the first instruction of the function where we want step into. If we can’t step
into the function (because it’s just a basic instruction), we will stop to the
following instruction of the current routine, which is in fact the next
instruction we will execute.
So,
to step into next function, we just set the step-into flag. When it is set,
application will stop at the next debugger hook encountered.
To
implement the step-out mechanism I have used the notion of stack depth. Assume
that the application is stopped in a feature. The current call stack depth is
3. The idea is to stop the application at the first following debugger hook with
a current call stack depth of 2. So we set the callstack_depth_stop variable to
2. The application will stop as soon as current_callstack_depth <=
callstack_depth_stop.
The
way the step-by-step mechanism is implemented is the same as the step-out
mechanism is. The only difference is that we set the callstack depth stop to
the current callstack depth: Assume that the application is stopped in a
feature. The current call stack depth is 3. The idea is to stop the application
at the first following debugger hook with a current call stack depth of 3. So
we set the callstack_depth_stop variable to 3. The application will stop as
soon as current_callstack_depth <= callstack_depth_stop. If the application
start to step into a function, it will increase the current stack depth and the
application will not stop inside the function.
The
Eiffel part of the new debugger is mainly located in the following four
classes: BREAKPOINT, DEBUG_INFO, APPLICATION_EXECUTION and EWB_REQUEST.
The
BREAKPOINT class deals with the handling of one breakpoint. It has only a few
attributes:
The
breakable line number is the number of the line in the stop point view under
EiffelBench where the breakpoint is set. The routine is the feature where the
breakpoint is located. We also provide the real_body_id and the body_index
which are extracted from the feature at the creation of the breakpoint and are
updated after recompilation. real_body_id is useful because we need to send it
to the application when we are setting a new breakpoint. body_index is used as
a super-key (in the SQL sense). To get a breakpoint from a list of breakpoints,
you only need its body_index and its line number; to compare to breakpoints, we
will check whether they have the same body_index and the same line number. We
can’t use the real_body_id to do this because the real_body_id may change
during a recompilation.
breakable_line_number: INTEGER;
-- line number of the breakpoint in the stoppoint view under EiffelBench
routine: E_FEATURE;
-- feature where this breakpoint is situated
real_body_id: REAL_BODY_ID;
-- real_body_id of the feature where this breakpoint is situated
body_index: BODY_INDEX;
-- real_body_id of the feature where this breakpoint is situated
The
breakpoint has also two other attributes: the bench_status and the
application_status. The bench_status reflects the current status of the
breakpoint under EiffelBench. It can have 3 different values: set, disabled,
not_set. Set means that the breakpoint is set and active, disabled means it is
set but not disabled, and not_set means that the breakpoint has been removed.
The application_status reflects the current status of the breakpoint under the
application. It can have a different value than the bench status because we
only resynchronize the two states when we are resuming or starting the
application. The application_status can have 2 different values: set or
not_set.
bench_status: INTEGER;
-- current status within EiffelBench
--
-- see the private constants at the end of the class to see the
-- different possible value taken
application_status: INTEGER;
-- last status sent to the application, this is the current
-- status of this breakpoint from the application point of view
--
-- see the private constants at the end of the class to see the
-- different possible value taken
A user can set a breakpoint, remove a breakpoint,
disable a breakpoint but also switch a breakpoint. The behavior of the switch
feature is the following:
·
if the breakpoint is
set, we remove it.
·
If the breakpoint is
not set, we set it
·
If the breakpoint is
disabled, we enable it
The
DEBUG_INFO class deals with the handling of the breakpoints of the application.
It contains an attribute called breakpoints which is an hash_table of breakpoints(HASH_TABLE[BREAKPOINT,BREAKPOINT]). This class allows to perform a wide range of
operation on breakpoints.
Remark:
to find a specified breakpoint in the hash table, we always use the same
method. We first create a ”fake” breakpoint with the same body_index and the
same line number than the breakpoint we want to get, and then we call the
hash_table feature get with the fake breakpoint as argument to retrieve the
good breakpoint.
wipe_out is
-- Empty Current.
restore is
-- reset information about breakpoints set/removed during execution
update is
-- remove breakpoint that no more useful from the hash_table
-- see BREAKPOINT/is_usefull for further comments
has_breakpoints: BOOLEAN is
-- Does the program have a breakpoint (enabled or disabled) ?
has_enabled_breakpoints: BOOLEAN is
-- Does the program have a breakpoint set?
has_disabled_breakpoints: BOOLEAN is
-- Does the program have an enabled breakpoint?
resynchronize_breakpoints is
-- Resychronize the breakpoints after a compilation.
feature_bp_list (list: LINKED_LIST [E_FEATURE]): FIXED_LIST [CELL2 [E_FEATURE, LIST [INTEGER]]] is
-- Create a list with features and breakpoints
features_with_breakpoint_set: LIST [E_FEATURE] is
-- list of all feature with a breakpoint set (enabled or disabled)
Changing all breakpoints
remove_all_breakpoints is
-- Remove all breakpoints which are currently set (enabled/disabled)
enable_all_breakpoints is
-- disable all breakpoints which are currently set and enabled
disable_all_breakpoints is
-- disable all breakpoints which are currently set and enabled
Changing all breakpoints for a feature
add_breakpoints_for_feature (feat: E_FEATURE; a_list: LIST [INTEGER]) is
-- Add all the breakpoints a_list for feature feat.
remove_breakpoints_in_feature (f: E_FEATURE) is
-- remove all breakpoints set for feature 'f'
disable_breakpoints_in_feature (f: E_FEATURE) is
-- disable all breakpoints set for feature 'f'
enable_breakpoints_in_feature (f: E_FEATURE) is
-- enable all breakpoints set for feature 'f'
Getting breakpoints status for a feature
has_breakpoint_set (f: E_FEATURE): BOOLEAN is
-- Has f a breakpoint set to stop?
breakpoints_set_for (f: E_FEATURE): LIST [INTEGER] is
-- Breakpoints set for feature f
breakpoints_disabled_for (f: E_FEATURE): LIST [INTEGER] is
-- Breakpoints set for feature f and disabled
breakpoints_enabled_for (f: E_FEATURE): LIST [INTEGER] is
-- Breakpoints set for feature f and enabled
Changing breakpoints for a class
remove_breakpoints_in_class (c: CLASS_C) is
-- remove all breakpoints set for class 'c'
disable_breakpoints_in_class (c: CLASS_C) is
-- disable all breakpoints set for feature 'f'
enable_breakpoints_in_class (c: CLASS_C) is
-- enable all breakpoints set for feature 'f'
Changing a specified breakpoint
switch_breakpoint (f: E_FEATURE; i: INTEGER) is
-- Switch the i-th breakpoint of f
remove_breakpoint (f: E_FEATURE; i: INTEGER) is
-- Switch the i-th breakpoint of f
disable_breakpoint (f: E_FEATURE; i: INTEGER) is
-- disable the i-th breakpoint of f
-- if no breakpoint already exists for 'f' at 'i', a disabled breakpoint is created
enable_breakpoint (f: E_FEATURE; i: INTEGER) is
-- enable the i-th breakpoint of f
-- if no breakpoint already exists for 'f' at 'i', a breakpoint is created
Getting the status of a specified breakpoint
is_breakpoint_set (f: E_FEATURE; i: INTEGER): BOOLEAN is
-- Is the i-th breakpoint of f set? (enabled or disabled)
is_breakpoint_enabled (f: E_FEATURE; i: INTEGER): BOOLEAN is
-- Is the i-th breakpoint of f enabled
is_breakpoint_disabled (f: E_FEATURE; i: INTEGER): BOOLEAN is
-- Is the i-th breakpoint of f disabled
The APPLICATION_EXECUTION encapsulates most of the breakpoints features of the DEBUG_INFO class. There is nothing particular to explain here.
This is the class where breakpoints are sent to the application. The feature responsible for it is send_breakpoints. It always send request of type rqst_break to the application even for sending a stepinto flag, or a new limit for the depth stack.
The prototype for sending a breakpoint is the following:
send_rqst_3 (rqst_break, real_body_id - 1,
what_to_do, line_number)
what_to_do can take 4 values which are
· break_set: to set a new breakpoint
· break_remove: to remove a breakpoint
· break_set_stack_depth: to set a new limit for the stack depth (step-out/step-by-step). Supplying the body_id is senseless, so we put zero instead. And instead of the line number, we give the new stack depth limit.
· break_set_stepinto: to activate the step-into flag. Here both body_id and line number are useless. So we put zero instead.