* record-btrace
@ 2013-01-28 16:14 Metzger, Markus T
2013-01-29 9:53 ` record-btrace Jan Kratochvil
0 siblings, 1 reply; 10+ messages in thread
From: Metzger, Markus T @ 2013-01-28 16:14 UTC (permalink / raw)
To: Jan Kratochvil; +Cc: markus.t.metzger, gdb-patches
Hi Jan,
I was interrupted by some other task. Now I'm switching back to btrace and to your request for merging it with record. Let me try to recap what you're asking for and sketch a preliminary plan for you to comment on.
1. add a new command "record-btrace" that pushes a new record_btrace_target_ops onto the target stack. By default, this will enable branch tracing for all existing and all new threads. Arguments may later be added to only enable branch tracing selectively for some threads.
This record_btrace_target_ops will be fairly empty in the first step. We don't support reverse execution, yet. The only effect should really be to prevent record and btrace from co-existing.
The existing brace target-ops and also the btrace RSP packets will need to remain since I still need to configure branch tracing on the target. Without them (i.e. the functionality hard-coded in record_btrace_target_ops), I don't see how I could handle the remote case. Record_btrace_open will call target_btrace_enable to do the actual work. It will fail if one of the btrace enable calls fails.
The target is global. The data (i.e. configuration, history), however, will still be per-thread. I don't know how target record handles this. I was thinking of keeping the btrace internal structure.
If we're going to extend this in the future, say, by adding an LBR-based branch trace, this would be another record_btrace_lbr_target_ops that may share some of the functions with the BTS-based record-btrace target.
You mentioned that we should not pop that target when the process terminates to implement auto enabling for new processes. I'm not quite clear how that will work. I always thought the target stack is newly setup for each process. I wonder how we can leave the topmost entry always on?
2. implement the standard "record" commands for this new record-btrace target, i.e. "info record", "set/show record", "record goto", "record save", "record restore", "record stop", "record delete".
The "set/show record" sub-commands won't make much sense for record-btrace. On the other hand, we would need some other sub-commands to configure branch tracing. Would I dynamically add and remove those (sub-)commands depending on which record target is currently active? I have not seen functions to remove commands. Or would I need to put sub-command handling into a single "set record" and "show record" command (i.e. do not use sub-commands)?
The "info record" command makes sense for all record variants, but it will show different information for each record target. Would I add a new target-ops function for it? Or would I add a new struct record_ops to collect those functions?
The "record save/restore" commands won't be available. Needs more target-ops or record-ops functions.
The "record goto" and "record delete" commands will need to work on btrace data structures, instead. Needs more target-ops or record-ops functions.
I would essentially move those commands into a new file and implement some record abstraction for them to work on.
3. add a new "record disas" sub-command to print the disassembly of the last instructions and iterate through the instruction history similar to the list command. This will be disabled for target record and implemented for target record-btrace.
I would add another target_ops or record_ops function to do the actual work that is called from the "record disas" command.
In a later step, this could be implemented for target record, as well.
4. add a new "record list" sub-command similar to "record disas" but listing the source lines that have been executed.
5. extend "record stop" to take parameters that allow selective disabling of branch tracing. Not sure if and how this will work with the existing target record.
6. Implement reverse execution for record_btrace_target_ops:
To_resume/to_wait: iterate through instruction history
To_insert_breakpoint: maintain breakpoint list - don't forward to target
To_fetch_registers: only support the instruction pointer register
To_store_registers: not allowed
To_xfer_partial: not allowed
To_~_watchpoint: not allowed
When I don't allow reading memory in target record, nothing works, anymore, i.e. almost all gdb commands fail.
When I don't allow fetching registers in target record, there seems to be no impact - I guess the regcache is up-to-date so we don't really need to ask the target.
When I do a single reverse stepping command, however, it fails and gdb believes that the target is executing - nothing works, anymore.
The first experiment is not very encouraging. Do you think this can be done, at all?
I may need to allow reading memory for read-only dynamic sections so that at least "disas" works. Does gdb maintain a dynamic section map? Could gdb be taught to read this memory from a file, instead?
I may also need to replace the unwinder even though I'm not adding unwind support, yet - just to prevent it from failing. Are unwinders global or can a target add/remove unwinders? If unwinders can't be added/removed dynamically, I would add an unwinder to target_ops that will, if present, precede all other unwinders. Would that be OK?
I would like to simulate the above two (i.e. allow access to read-only memory and add a do-nothing unwinder) to see what issues we might be running into with a target record-btrace. Would you help me with that?
Reverse- and subsequent forward- stepping command should target the current thread only as long as the thread is inside the recorded execution history. I hope that's what I get when I overwrite to_resume and to_wait and only consider the current thread.
When they reach the end of the trace, they turn into normal stepping commands, i.e. I will forward to_resume and to_wait to the target beneath. This should implicitly continue other threads depending on the execution mode. When the target stops next time, the tracing positions for all stopped threads will be reset - I won't be able to find the old position in the new version of the trace. This behavior might be somewhat surprising if you switched threads while traversing the history.
Thanks,
Markus.
Intel GmbH
Dornacher Strasse 1
85622 Feldkirchen/Muenchen, Deutschland
Sitz der Gesellschaft: Feldkirchen bei Muenchen
Geschaeftsfuehrer: Christian Lamprechter, Hannes Schwaderer, Douglas Lusk
Registergericht: Muenchen HRB 47456
Ust.-IdNr./VAT Registration No.: DE129385895
Citibank Frankfurt a.M. (BLZ 502 109 00) 600119052
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: record-btrace
2013-01-28 16:14 record-btrace Metzger, Markus T
@ 2013-01-29 9:53 ` Jan Kratochvil
2013-01-29 11:05 ` record-btrace Metzger, Markus T
0 siblings, 1 reply; 10+ messages in thread
From: Jan Kratochvil @ 2013-01-29 9:53 UTC (permalink / raw)
To: Metzger, Markus T; +Cc: markus.t.metzger, gdb-patches
Hi Markus,
thanks for a nice plan overview.
On Mon, 28 Jan 2013 17:13:59 +0100, Metzger, Markus T wrote:
> 1. add a new command "record-btrace" that pushes a new
"record btrace", there are already subcommands of "record".
It would be unconvenient to have some subcommands as "record-ABC" and some as
"record DEF" wrt tab-completion.
On Thu, 20 Dec 2012 08:17:26 +0100, Jan Kratochvil wrote:
# Currently one uses just "record" to start the full-recording. I was expecting
# it could print an error and one would use in newer versions of GDB
# "record btrace" vs. "record full" or something like that.
Expecting in this mail the current record backend would be called "full",
I do not have a better name now. "slow" does not seem to great. :-)
> record_btrace_target_ops onto the target stack. By default, this will enable
> branch tracing for all existing and all new threads. Arguments may later be
> added to only enable branch tracing selectively for some threads.
OK.
> The existing brace target-ops and also the btrace RSP packets will need to
> remain since I still need to configure branch tracing on the target. Without
> them (i.e. the functionality hard-coded in record_btrace_target_ops),
> I don't see how I could handle the remote case. Record_btrace_open will call
> target_btrace_enable to do the actual work. It will fail if one of the
> btrace enable calls fails.
OK; therefore two btrace struct target_ops for linux-nat.c vs. remote.c and one
btrace struct target_ops for record btrace (vs. full).
> The target is global. The data (i.e. configuration, history), however, will
> still be per-thread. I don't know how target record handles this.
record.c has global variables which is sure wrong.
> I was thinking of keeping the btrace internal structure.
Yes; FYI struct thread_info could have REGISTRY_FIELDS for dynamic data with
functions like register_thread_info_data_with_cleanup. But currently it does
not have REGISTRY_FIELDS so the current struct thread_info->btrace is OK.
> If we're going to extend this in the future, say, by adding an LBR-based
> branch trace, this would be another record_btrace_lbr_target_ops that may
> share some of the functions with the BTS-based record-btrace target.
Probably, unaware now how much the LBR backend will differ.
> You mentioned that we should not pop that target when the process terminates
> to implement auto enabling for new processes. I'm not quite clear how that
> will work. I always thought the target stack is newly setup for each
> process. I wonder how we can leave the topmost entry always on?
It is rather the opposite. Targets hook on to_mourn_inferior and call
unpush_target on themselves.
> 2. implement the standard "record" commands for this new record-btrace
> target, i.e. "info record", "set/show record", "record goto", "record save",
> "record restore", "record stop", "record delete".
>
> The "set/show record" sub-commands won't make much sense for record-btrace.
> On the other hand, we would need some other sub-commands to configure branch
> tracing. Would I dynamically add and remove those (sub-)commands depending
> on which record target is currently active? I have not seen functions to
> remove commands. Or would I need to put sub-command handling into a single
> "set record" and "show record" command (i.e. do not use sub-commands)?
For example current "set record insn-number-max" should be moved to
"set record full insn-number-max" and current "set record insn-number-max" made
an alias to it with deprecate_cmd.
You are right dynamically adding/removing commands is not used in GDB. They
are added as long as the module is compiled in during GDB configure and
commands are never removed.
It would be good to protect these commands
like "set record full insn-number-max" and the btrace ones so that if other
record target is currently pushed on stack they print a warning that the new
value is ignored with the currently active record backend.
> The "info record" command makes sense for all record variants, but it will
> show different information for each record target. Would I add a new
> target-ops function for it? Or would I add a new struct record_ops to
> collect those functions?
As there will be already pushed/unpushed target_ops for the backend I find
easier to make new target_ops methods for such dispatching.
This abstract part of record.c should be moved to a separate file.
Or rather the "full" backend to be moved from record.c to record-full.c and
record.c to remain only a thin wrapper for some of these record parts common
across the btrace+full record backends.
"target record" could be also renamed to "target record-full" (and "target
record" kept as deprecated_cmd alias).
> The "record save/restore" commands won't be available. Needs more target-ops
> or record-ops functions.
Yes, so they should also use targets_ops method and the btrace backend will
print an error for them.
> The "record goto" and "record delete" commands will need to work on btrace
> data structures, instead. Needs more target-ops or record-ops functions.
Yes, new target_ops methods seem OK to me.
> I would essentially move those commands into a new file and implement some
> record abstraction for them to work on.
I see you probably describe what I have described above.
> 3. add a new "record disas"
"record disassemble" (the shortening is done automatically by GDB commands
parser).
> sub-command to print the disassembly of the last
> instructions and iterate through the instruction history similar to the list
> command. This will be disabled for target record and implemented for target
> record-btrace.
"disabled" = printing an error.
> I would add another target_ops or record_ops function to do the actual work
> that is called from the "record disas" command.
>
> In a later step, this could be implemented for target record, as well.
Yes.
> 4. add a new "record list" sub-command similar to "record disas" but listing
> the source lines that have been executed.
Extending the functionality also for the record-full target can be thought
afterwards.
> 5. extend "record stop" to take parameters that allow selective disabling of
> branch tracing. Not sure if and how this will work with the existing target
> record.
Existing record-full does not have any selection so it would print an error on
such attempt.
> 6. Implement reverse execution for record_btrace_target_ops:
>
> To_resume/to_wait: iterate through instruction history
> To_insert_breakpoint: maintain breakpoint list - don't forward to target
> To_fetch_registers: only support the instruction pointer register
> To_store_registers: not allowed
> To_xfer_partial: not allowed
> To_~_watchpoint: not allowed
>
> When I don't allow reading memory in target record, nothing works, anymore,
> i.e. almost all gdb commands fail.
> When I don't allow fetching registers in target record, there seems to be no
> impact - I guess the regcache is up-to-date so we don't really need to ask
> the target.
> When I do a single reverse stepping command, however, it fails and gdb
> believes that the target is executing - nothing works, anymore.
>
> The first experiment is not very encouraging. Do you think this can be done,
> at all?
>
> I may need to allow reading memory for read-only dynamic sections so that at
> least "disas" works. Does gdb maintain a dynamic section map? Could gdb be
> taught to read this memory from a file, instead?
I would not be too strict with accessing the inferior memory. I understand
that the memory content may be different when GDB is in the btrace history but
most of the memory including read/write variables a user may want to read will
be the same.
It could rather just print a CLI (the default command-line interface) warning
if one accesses a read/write memory during command execution.
Forbidding any memory access may be correct but it may be pain for the users.
> I may also need to replace the unwinder even though I'm not adding unwind
> support, yet - just to prevent it from failing.
I agree, that would be too confusing.
> Are unwinders global or can
> a target add/remove unwinders? If unwinders can't be added/removed
> dynamically, I would add an unwinder to target_ops that will, if present,
> precede all other unwinders. Would that be OK?
You can use frame_unwind_prepend_unwinder to place it as the first unwinder.
You do not need to remove it, struct frame_unwind->frame_sniffer_ftype sniffer
can just always return 0 when the record-btrace backend is currently not
pushed on the target stack. Therefore next unwinder will be queried in such
case.
> I would like to simulate the above two (i.e. allow access to read-only
> memory and add a do-nothing unwinder) to see what issues we might be running
> into with a target record-btrace. Would you help me with that?
Yes, still I hope it is more clear now.
> Reverse- and subsequent forward- stepping command should target the current
> thread only as long as the thread is inside the recorded execution history.
> I hope that's what I get when I overwrite to_resume and to_wait and only
> consider the current thread.
BTW this behavior normally depends on "set scheduler-locking off/step/on".
to_resume's 3rd parameter STEP will be 0 if one does "continue".
<offtopic a bit, just for illustration>
FSF GDB has default "set scheduler-locking off" which I (and IIRC users) find
confusing so for example Fedora GDB has default "set scheduler-locking step".
I believe FSF GDB should also default to "set scheduler-locking step" but it
causes some other issues which is why it is not so.
</offtopic a bit>
I do not see how to implement scheduler-locking-aware implementation of the
btrace backend.
So I agree it is OK it would behave as if "set scheduler-locking on" was set.
> When they reach the end of the trace, they turn into normal stepping
> commands, i.e. I will forward to_resume and to_wait to the target beneath.
> This should implicitly continue other threads depending on the execution
> mode. When the target stops next time, the tracing positions for all stopped
> threads will be reset - I won't be able to find the old position in the new
> version of the trace. This behavior might be somewhat surprising if you
> switched threads while traversing the history.
I would rather print an error in such case - that one should switch to the
thread with position in history and roll it back to the current position
before the whole process can be resumed by the target beneath.
But I really do not mind how this special case is handled, IMO it won't happen
in any common case.
Thanks,
Jan
^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: record-btrace
2013-01-29 9:53 ` record-btrace Jan Kratochvil
@ 2013-01-29 11:05 ` Metzger, Markus T
2013-01-29 15:36 ` record-btrace Jan Kratochvil
0 siblings, 1 reply; 10+ messages in thread
From: Metzger, Markus T @ 2013-01-29 11:05 UTC (permalink / raw)
To: Jan Kratochvil; +Cc: markus.t.metzger, gdb-patches
> -----Original Message-----
> From: Jan Kratochvil [mailto:jan.kratochvil@redhat.com]
> Sent: Tuesday, January 29, 2013 10:53 AM
Hi Jan,
Thanks for the quick reply!
> On Mon, 28 Jan 2013 17:13:59 +0100, Metzger, Markus T wrote:
> > 1. add a new command "record-btrace" that pushes a new
>
> "record btrace", there are already subcommands of "record".
>
> It would be unconvenient to have some subcommands as "record-ABC" and some as
> "record DEF" wrt tab-completion.
OK. So the targets will be called "record-full" and "record-btrace" and can be used directly with the "target" command; the "record" command will use sub-commands. We thus have:
"target record-full"
"target record-btrace"
"target record" aliases "target record-full"
"record full"
"record btrace"
" record" aliases "record full"
If we now want to add LBR-based branch tracing, we could either change record-btrace to record-btrace-bts and add record-btrace-lbr or make "bts" and "lbr" configuration options of record-btrace. The former leads to very long names and commands; the latter requires "set/show record" commands to be usable even if no or the wrong record target is currently active.
> > The existing brace target-ops and also the btrace RSP packets will need to
> > remain since I still need to configure branch tracing on the target. Without
> > them (i.e. the functionality hard-coded in record_btrace_target_ops),
> > I don't see how I could handle the remote case. Record_btrace_open will call
> > target_btrace_enable to do the actual work. It will fail if one of the
> > btrace enable calls fails.
>
> OK; therefore two btrace struct target_ops for linux-nat.c vs. remote.c and one
> btrace struct target_ops for record btrace (vs. full).
If you're OK to keep the btrace target_ops methods, we shouldn't need separate target_ops structs for native and remote. The btrace methods will be supplied by the native and remote targets. The record-btrace target will be on top and use the btrace methods from the target beneath.
> > I may need to allow reading memory for read-only dynamic sections so that at
> > least "disas" works. Does gdb maintain a dynamic section map? Could gdb be
> > taught to read this memory from a file, instead?
>
> I would not be too strict with accessing the inferior memory. I understand
> that the memory content may be different when GDB is in the btrace history but
> most of the memory including read/write variables a user may want to read will
> be the same.
>
> It could rather just print a CLI (the default command-line interface) warning
> if one accesses a read/write memory during command execution.
>
> Forbidding any memory access may be correct but it may be pain for the users.
I would find this very confusing. Stack and heap might not be available any more - or might contain different objects.
I can do the CLI warning as first step. Will this warning also be available via MI? Do you know whether gdb is reading memory for some internal logic?
> > When they reach the end of the trace, they turn into normal stepping
> > commands, i.e. I will forward to_resume and to_wait to the target beneath.
> > This should implicitly continue other threads depending on the execution
> > mode. When the target stops next time, the tracing positions for all stopped
> > threads will be reset - I won't be able to find the old position in the new
> > version of the trace. This behavior might be somewhat surprising if you
> > switched threads while traversing the history.
>
> I would rather print an error in such case - that one should switch to the
> thread with position in history and roll it back to the current position
> before the whole process can be resumed by the target beneath.
That can easily become quite annoying if you have several threads. We could query the user if he wants to continue even though some threads are somewhere in the history. How would this work for MI?
Is scheduler-locking implemented by the step commands or by the target's to_resume and to_wait functions?
I have only considered a single inferior, so far.
Regards,
Markus.
Intel GmbH
Dornacher Strasse 1
85622 Feldkirchen/Muenchen, Deutschland
Sitz der Gesellschaft: Feldkirchen bei Muenchen
Geschaeftsfuehrer: Christian Lamprechter, Hannes Schwaderer, Douglas Lusk
Registergericht: Muenchen HRB 47456
Ust.-IdNr./VAT Registration No.: DE129385895
Citibank Frankfurt a.M. (BLZ 502 109 00) 600119052
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: record-btrace
2013-01-29 11:05 ` record-btrace Metzger, Markus T
@ 2013-01-29 15:36 ` Jan Kratochvil
2013-01-31 15:38 ` record-btrace Metzger, Markus T
0 siblings, 1 reply; 10+ messages in thread
From: Jan Kratochvil @ 2013-01-29 15:36 UTC (permalink / raw)
To: Metzger, Markus T; +Cc: markus.t.metzger, gdb-patches
On Tue, 29 Jan 2013 12:03:40 +0100, Metzger, Markus T wrote:
> "target record-full"
> "target record-btrace"
> "target record" aliases "target record-full"
> "record full"
> "record btrace"
> " record" aliases "record full"
Maybe "record" and "target record" could already print an error about the
command being deprecated; but that is a subtle difference, I do not mind.
> If we now want to add LBR-based branch tracing, we could either change
> record-btrace to record-btrace-bts and add record-btrace-lbr or make "bts"
> and "lbr" configuration options of record-btrace.
If we had LBR I believe it should be always enabled by default - when
available - as it has AFAIK no measurable performance disadvantage. So the
way of enabling it explicitly should not matter much.
> > OK; therefore two btrace struct target_ops for linux-nat.c vs. remote.c and one
> > btrace struct target_ops for record btrace (vs. full).
>
> If you're OK to keep the btrace target_ops methods, we shouldn't need separate target_ops structs for native and remote. The btrace methods will be supplied by the native and remote targets. The record-btrace target will be on top and use the btrace methods from the target beneath.
[ OT: Could you wrap the lines to 80 columns? It is the gdb-patches mailing
list style. ]
So there will be:
amd64-linux-nat.c:
current:
t->to_enable_btrace = amd64_linux_enable_btrace;
new one:
t->to_record_list = btrace_record_list;
remote.c:
current:
remote_ops.to_enable_btrace = remote_enable_btrace;
new one:
t->to_record_list = btrace_record_list;
record-full.c:
new one, to be implemented in the future:
t->to_record_list = record_full_list;
[detail]
Initialization of the common fields like to_record_list could be moved to some
common initialization function like i386_use_watchpoints().
> > I would not be too strict with accessing the inferior memory. I understand
> > that the memory content may be different when GDB is in the btrace history but
> > most of the memory including read/write variables a user may want to read will
> > be the same.
> >
> > It could rather just print a CLI (the default command-line interface) warning
> > if one accesses a read/write memory during command execution.
> >
> > Forbidding any memory access may be correct but it may be pain for the users.
>
> I would find this very confusing. Stack and heap might not be available any
> more - or might contain different objects.
The problem is if you are in history and you want to print a variable - should
you "continue" back to the current state, read the variable and move back?
> I can do the CLI warning as first step. Will this warning also be available
> via MI?
It will be printed as general GDB output record but at least Eclipse CDT users
won't notice it. It gets displayed only after selecting Debug window -> "gdb"
and then it is in the displayed window "Console" as a "warning: ..." text.
> Do you know whether gdb is reading memory for some internal logic?
You can see it by "set debug target 1". GDB still inserts/removes various
internal breakpoints ("maintenance info breakpoints")
due to "set breakpoint always-inserted" but that should be suppressed by the
record.c to_insert_breakpoint wrapper.
> That can easily become quite annoying if you have several threads. We could
> query the user if he wants to continue even though some threads are
> somewhere in the history.
Yes, that seems as the best choice.
> How would this work for MI?
Such questions get default-answered (nquery->n, yquery->y) when in MI mode.
> Is scheduler-locking implemented by the step commands or by the target's
> to_resume and to_wait functions?
target's to_resume PTID parameter specifies whether all processes or all
threads of the specified process or just one specified thread get resumed.
> I have only considered a single inferior, so far.
That should generally work even with multiple inferiors, each thread of each
inferior is has globally unique PTID so as long as you track the threads
separately and use ptid_match() for the given PTID comparison it works.
Thanks,
Jan
^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: record-btrace
2013-01-29 15:36 ` record-btrace Jan Kratochvil
@ 2013-01-31 15:38 ` Metzger, Markus T
2013-02-01 14:18 ` record-btrace Jan Kratochvil
0 siblings, 1 reply; 10+ messages in thread
From: Metzger, Markus T @ 2013-01-31 15:38 UTC (permalink / raw)
To: Jan Kratochvil; +Cc: markus.t.metzger, gdb-patches
> -----Original Message-----
> From: Jan Kratochvil [mailto:jan.kratochvil@redhat.com]
> Sent: Tuesday, January 29, 2013 4:36 PM
> > "target record-full"
> > "target record-btrace"
> > "target record" aliases "target record-full"
> > "record full"
> > "record btrace"
> > " record" aliases "record full"
>
> Maybe "record" and "target record" could already print an error about the
> command being deprecated; but that is a subtle difference, I do not mind.
I made "record" default to "target record-full", without a deprecation notice.
The problem I have with "target record" is that the sub-commands are added in
add_target, one per added target. Would it be OK to export targetlist so I can add
an alias for target "record-full"? Or is it OK to just drop target "record"?
If we dropped target record, we would break backwards compatibility. There's a
single test in gdb.mi that would need to be adjusted, but I would also expect that
GUIs would be broken.
On a similar matter, I renamed the existing "set/show record" subcommands by
prepending "full-", i.e. "insn-number-max" becomes "full-insn-number-max".
This does not break any tests but would affect scripts and MI. If we wouldn't
do that, on the other hand, it would look quite odd when we started adding
btrace-related configuration options.
> > If we now want to add LBR-based branch tracing, we could either change
> > record-btrace to record-btrace-bts and add record-btrace-lbr or make "bts"
> > and "lbr" configuration options of record-btrace.
>
> If we had LBR I believe it should be always enabled by default - when
> available - as it has AFAIK no measurable performance disadvantage. So the
> way of enabling it explicitly should not matter much.
LBR does not work together with BTS. We can either have one or the other.
> [detail]
> Initialization of the common fields like to_record_list could be moved to some
> common initialization function like i386_use_watchpoints().
For record-btrace and record-full, to_record_list will be quite different. For all
other targets, it will be NULL.
We could share to_disconnect, to_detach, to_kill, and to_mourn_inferior. They
are just forwarding the request after unpushing the record target.
I made the "record stop" command generic. It searches for a record target
beneath the current and unpushes the first it finds. I could do something
similar for the above.
There's a record_changed notifier that is called in to_open and in the stop
command. Why is it not called in to_close instead of in the stop command?
> > > I would not be too strict with accessing the inferior memory. I understand
> > > that the memory content may be different when GDB is in the btrace history but
> > > most of the memory including read/write variables a user may want to read will
> > > be the same.
> > >
> > > It could rather just print a CLI (the default command-line interface) warning
> > > if one accesses a read/write memory during command execution.
> > >
> > > Forbidding any memory access may be correct but it may be pain for the users.
> >
> > I would find this very confusing. Stack and heap might not be available any
> > more - or might contain different objects.
>
> The problem is if you are in history and you want to print a variable - should
> you "continue" back to the current state, read the variable and move back?
As long as you know what you are doing, it's convenient to be able to read
variables that you know have not changed. You need to be very careful, though,
to consider also stack changes when you reverse-step into a different function.
Things get worse if the compiler generates location lists.
> > I can do the CLI warning as first step. Will this warning also be available
> > via MI?
>
> It will be printed as general GDB output record but at least Eclipse CDT users
> won't notice it. It gets displayed only after selecting Debug window -> "gdb"
> and then it is in the displayed window "Console" as a "warning: ..." text.
We should maybe make this configurable so GUIs will not show wrong data.
Thanks,
Markus.
Intel GmbH
Dornacher Strasse 1
85622 Feldkirchen/Muenchen, Deutschland
Sitz der Gesellschaft: Feldkirchen bei Muenchen
Geschaeftsfuehrer: Christian Lamprechter, Hannes Schwaderer, Douglas Lusk
Registergericht: Muenchen HRB 47456
Ust.-IdNr./VAT Registration No.: DE129385895
Citibank Frankfurt a.M. (BLZ 502 109 00) 600119052
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: record-btrace
2013-01-31 15:38 ` record-btrace Metzger, Markus T
@ 2013-02-01 14:18 ` Jan Kratochvil
2013-02-04 14:31 ` record-btrace Metzger, Markus T
0 siblings, 1 reply; 10+ messages in thread
From: Jan Kratochvil @ 2013-02-01 14:18 UTC (permalink / raw)
To: Metzger, Markus T; +Cc: markus.t.metzger, gdb-patches
On Thu, 31 Jan 2013 16:38:43 +0100, Metzger, Markus T wrote:
> The problem I have with "target record" is that the sub-commands are added in
> add_target, one per added target. Would it be OK to export targetlist so I can add
> an alias for target "record-full"? Or is it OK to just drop target "record"?
OK, let's export targetlist, with some comment it should not be normally used.
> If we dropped target record, we would break backwards compatibility.
With eclipse-cdt-8.1.0-1.fc17.x86_64 I see it uses command "record" and not
"target record".
> There's a single test in gdb.mi that would need to be adjusted, but I would
> also expect that GUIs would be broken.
OK, both "record" and "target record" can be kept compatible, I do not mind,
you are right it may cause front ends compatibility issue.
> On a similar matter, I renamed the existing "set/show record" subcommands by
> prepending "full-", i.e. "insn-number-max" becomes "full-insn-number-max".
Could it be a prefix command? "set record full insn-number-max" etc.
To match "record full" (vs. "record btrace").
> This does not break any tests but would affect scripts and MI.
There should be aliases with deprecate_cmd during such changes, deprecate_cmd
also ensures it does not get <tab>-completed anymore.
> For record-btrace and record-full, to_record_list will be quite different. For all
> other targets, it will be NULL.
>
> We could share to_disconnect, to_detach, to_kill, and to_mourn_inferior. They
> are just forwarding the request after unpushing the record target.
>
> I made the "record stop" command generic. It searches for a record target
> beneath the current and unpushes the first it finds. I could do something
> similar for the above.
It looks OK to me, it would be better to see the code.
> There's a record_changed notifier that is called in to_open and in the stop
> command. Why is it not called in to_close instead of in the stop command?
to_close is a bit dangerous that the target is no longer on stack, as
documented in unpush_target. So maybe record_changed could want to do
something with the record traget yet.
But with the currently only listener MI it should work even from to_close.
> As long as you know what you are doing, it's convenient to be able to read
> variables that you know have not changed. You need to be very careful, though,
> to consider also stack changes when you reverse-step into a different function.
> Things get worse if the compiler generates location lists.
As there should be the custom unwinder blocking unwind the current frame won't
be found there to access local variables, they should print an error.
> > > I can do the CLI warning as first step. Will this warning also be available
> > > via MI?
> >
> > It will be printed as general GDB output record but at least Eclipse CDT users
> > won't notice it. It gets displayed only after selecting Debug window -> "gdb"
> > and then it is in the displayed window "Console" as a "warning: ..." text.
>
> We should maybe make this configurable so GUIs will not show wrong data.
I believe it is up to GUI to handle it somehow specially. The GUI knows it is
currently operating in history, Eclipse already has special support for
"record".
Thanks,
Jan
^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: record-btrace
2013-02-01 14:18 ` record-btrace Jan Kratochvil
@ 2013-02-04 14:31 ` Metzger, Markus T
2013-02-05 19:45 ` record-btrace Jan Kratochvil
0 siblings, 1 reply; 10+ messages in thread
From: Metzger, Markus T @ 2013-02-04 14:31 UTC (permalink / raw)
To: Jan Kratochvil; +Cc: markus.t.metzger, gdb-patches
[-- Attachment #1: Type: text/plain, Size: 2788 bytes --]
> -----Original Message-----
> From: Jan Kratochvil [mailto:jan.kratochvil@redhat.com]
> Sent: Friday, February 01, 2013 3:18 PM
> > The problem I have with "target record" is that the sub-commands are added in
> > add_target, one per added target. Would it be OK to export targetlist so I can add
> > an alias for target "record-full"? Or is it OK to just drop target "record"?
>
> OK, let's export targetlist, with some comment it should not be normally used.
I added a new function add_deprecated_target_alias to do this. Patch attached.
Please let me know if you'd rather want it inline in a separate email.
> > On a similar matter, I renamed the existing "set/show record" subcommands by
> > prepending "full-", i.e. "insn-number-max" becomes "full-insn-number-max".
>
> Could it be a prefix command? "set record full insn-number-max" etc.
>
> To match "record full" (vs. "record btrace").
Done.
> There should be aliases with deprecate_cmd during such changes, deprecate_cmd
> also ensures it does not get <tab>-completed anymore.
When I use add_alias_cmd, I do not get the deprecated warning. Am I doing something
wrong (patch attached)?
> > For record-btrace and record-full, to_record_list will be quite different. For all
> > other targets, it will be NULL.
> >
> > We could share to_disconnect, to_detach, to_kill, and to_mourn_inferior. They
> > are just forwarding the request after unpushing the record target.
> >
> > I made the "record stop" command generic. It searches for a record target
> > beneath the current and unpushes the first it finds. I could do something
> > similar for the above.
>
> It looks OK to me, it would be better to see the code.
Please find the patch attached. Please let me know if you want the patch inline
in a separate email, instead.
> > There's a record_changed notifier that is called in to_open and in the stop
> > command. Why is it not called in to_close instead of in the stop command?
>
> to_close is a bit dangerous that the target is no longer on stack, as
> documented in unpush_target. So maybe record_changed could want to do
> something with the record traget yet.
>
> But with the currently only listener MI it should work even from to_close.
We're not sending a notification every time we unpush the record target, e.g.
on mourn_inferior or detach.
I'm preserving the current behavior.
Regards,
Markus.
Intel GmbH
Dornacher Strasse 1
85622 Feldkirchen/Muenchen, Deutschland
Sitz der Gesellschaft: Feldkirchen bei Muenchen
Geschaeftsfuehrer: Christian Lamprechter, Hannes Schwaderer, Douglas Lusk
Registergericht: Muenchen HRB 47456
Ust.-IdNr./VAT Registration No.: DE129385895
Citibank Frankfurt a.M. (BLZ 502 109 00) 600119052
[-- Attachment #2: add_deprecated_target_alias.patch --]
[-- Type: application/octet-stream, Size: 1245 bytes --]
diff --git a/gdb/target.c b/gdb/target.c
index 10f69dc..25f4629 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -434,6 +434,20 @@ information on the arguments for a particular protocol, type\n\
add_cmd (t->to_shortname, no_class, t->to_open, t->to_doc, &targetlist);
}
+/* See target.h. */
+
+void
+add_deprecated_target_alias (struct target_ops *t, char *alias)
+{
+ struct cmd_list_element *c;
+ char *alt;
+
+ /* If we use add_alias_cmd, here, we do not get the deprecated warning. */
+ c = add_cmd (alias, no_class, t->to_open, t->to_doc, &targetlist);
+ alt = xstrprintf ("target %s", t->to_shortname);
+ deprecate_cmd (c, alt);
+}
+
/* Stub functions */
void
diff --git a/gdb/target.h b/gdb/target.h
index c543118..1d73336 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -1779,6 +1779,11 @@ int target_verify_memory (const gdb_byte *data,
extern void add_target (struct target_ops *);
+/* Adds a command ALIAS for target T and marks it deprecated. This is useful
+ for maintaining backwards compatibility when renaming targets. */
+
+extern void add_deprecated_target_alias (struct target_ops *t, char *alias);
+
extern void push_target (struct target_ops *);
extern int unpush_target (struct target_ops *);
[-- Attachment #3: rename_record.patch --]
[-- Type: application/octet-stream, Size: 194246 bytes --]
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 336e8d0..ba284ee 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -752,7 +752,7 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \
valarith.c valops.c valprint.c value.c varobj.c common/vec.c \
xml-tdesc.c xml-support.c \
inferior.c gdb_usleep.c \
- record.c gcore.c \
+ record.c record-full.c gcore.c \
jit.c \
xml-syscall.c \
annotate.c common/signals.c copying.c dfp.c gdb.c inf-child.c \
@@ -828,6 +828,7 @@ dicos-tdep.h filesystem.h gcore.h gdb_wchar.h hppabsd-tdep.h \
i386-darwin-tdep.h i386-nat.h linux-record.h moxie-tdep.h \
osdata.h procfs.h python/py-event.h python/py-events.h python/py-stopevent.h \
python/python-internal.h python/python.h ravenscar-thread.h record.h \
+record-full.h \
solib-darwin.h solib-ia64-hpux.h solib-spu.h windows-nat.h xcoffread.h \
gnulib/import/extra/snippet/arg-nonnull.h gnulib/import/extra/snippet/c++defs.h \
gnulib/import/extra/snippet/warn-on-use.h \
@@ -925,7 +926,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
prologue-value.o memory-map.o memrange.o \
xml-support.o xml-syscall.o xml-utils.o \
target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
- inferior.o osdata.o gdb_usleep.o record.o gcore.o \
+ inferior.o osdata.o gdb_usleep.o record.o record-full.o gcore.o \
gdb_vecs.o jit.o progspace.o skip.o probe.o \
common-utils.o buffer.o ptid.o gdb-dlfcn.o common-agent.o \
format.o registry.o btrace.o
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index e262c19..4f383db 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -48,7 +48,7 @@
/* The syscall's XML filename for i386. */
#define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml"
-#include "record.h"
+#include "record-full.h"
#include "linux-record.h"
/* Supported register note sections. */
diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
index 15a1247..f96fc81 100644
--- a/gdb/i386-linux-tdep.c
+++ b/gdb/i386-linux-tdep.c
@@ -44,7 +44,7 @@
/* The syscall's XML filename for i386. */
#define XML_SYSCALL_FILENAME_I386 "syscalls/i386-linux.xml"
-#include "record.h"
+#include "record-full.h"
#include "linux-record.h"
#include <stdint.h>
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index df077bc..01ecb99 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -51,7 +51,7 @@
#include "i387-tdep.h"
#include "i386-xstate.h"
-#include "record.h"
+#include "record-full.h"
#include <stdint.h>
#include "features/i386/i386.c"
diff --git a/gdb/infrun.c b/gdb/infrun.c
index f4ff5bb..3f50493 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -48,7 +48,7 @@
#include "gdb_assert.h"
#include "mi/mi-common.h"
#include "event-top.h"
-#include "record.h"
+#include "record-full.h"
#include "inline-frame.h"
#include "jit.h"
#include "tracepoint.h"
diff --git a/gdb/linux-record.c b/gdb/linux-record.c
index 5a9ec99..71d0acf 100644
--- a/gdb/linux-record.c
+++ b/gdb/linux-record.c
@@ -21,7 +21,7 @@
#include "target.h"
#include "gdbtypes.h"
#include "regcache.h"
-#include "record.h"
+#include "record-full.h"
#include "linux-record.h"
/* These macros are the values of the first argument of system call
diff --git a/gdb/record-full.c b/gdb/record-full.c
new file mode 100644
index 0000000..3b49296
--- /dev/null
+++ b/gdb/record-full.c
@@ -0,0 +1,3036 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+ Copyright (C) 2008-2013 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "gdbthread.h"
+#include "event-top.h"
+#include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
+#include "record-full.h"
+#include "elf-bfd.h"
+#include "gcore.h"
+#include "event-loop.h"
+#include "inf-loop.h"
+#include "gdb_bfd.h"
+#include "observer.h"
+
+#include <signal.h>
+
+/* This module implements "target record-full", also known as "process
+ record and replay". This target sits on top of a "normal" target
+ (a target that "has execution"), and provides a record and replay
+ functionality, including reverse debugging.
+
+ Target record has two modes: recording, and replaying.
+
+ In record mode, we intercept the to_resume and to_wait methods.
+ Whenever gdb resumes the target, we run the target in single step
+ mode, and we build up an execution log in which, for each executed
+ instruction, we record all changes in memory and register state.
+ This is invisible to the user, to whom it just looks like an
+ ordinary debugging session (except for performance degredation).
+
+ In replay mode, instead of actually letting the inferior run as a
+ process, we simulate its execution by playing back the recorded
+ execution log. For each instruction in the log, we simulate the
+ instruction's side effects by duplicating the changes that it would
+ have made on memory and registers. */
+
+#define DEFAULT_RECORD_INSN_MAX_NUM 200000
+
+#define RECORD_IS_REPLAY \
+ (record_list->next || execution_direction == EXEC_REVERSE)
+
+#define RECORD_FILE_MAGIC netorder32(0x20091016)
+
+/* These are the core structs of the process record functionality.
+
+ A record_entry is a record of the value change of a register
+ ("record_reg") or a part of memory ("record_mem"). And each
+ instruction must have a struct record_entry ("record_end") that
+ indicates that this is the last struct record_entry of this
+ instruction.
+
+ Each struct record_entry is linked to "record_list" by "prev" and
+ "next" pointers. */
+
+struct record_mem_entry
+{
+ CORE_ADDR addr;
+ int len;
+ /* Set this flag if target memory for this entry
+ can no longer be accessed. */
+ int mem_entry_not_accessible;
+ union
+ {
+ gdb_byte *ptr;
+ gdb_byte buf[sizeof (gdb_byte *)];
+ } u;
+};
+
+struct record_reg_entry
+{
+ unsigned short num;
+ unsigned short len;
+ union
+ {
+ gdb_byte *ptr;
+ gdb_byte buf[2 * sizeof (gdb_byte *)];
+ } u;
+};
+
+struct record_end_entry
+{
+ enum gdb_signal sigval;
+ ULONGEST insn_num;
+};
+
+enum record_type
+{
+ record_end = 0,
+ record_reg,
+ record_mem
+};
+
+/* This is the data structure that makes up the execution log.
+
+ The execution log consists of a single linked list of entries
+ of type "struct record_entry". It is doubly linked so that it
+ can be traversed in either direction.
+
+ The start of the list is anchored by a struct called
+ "record_first". The pointer "record_list" either points to the
+ last entry that was added to the list (in record mode), or to the
+ next entry in the list that will be executed (in replay mode).
+
+ Each list element (struct record_entry), in addition to next and
+ prev pointers, consists of a union of three entry types: mem, reg,
+ and end. A field called "type" determines which entry type is
+ represented by a given list element.
+
+ Each instruction that is added to the execution log is represented
+ by a variable number of list elements ('entries'). The instruction
+ will have one "reg" entry for each register that is changed by
+ executing the instruction (including the PC in every case). It
+ will also have one "mem" entry for each memory change. Finally,
+ each instruction will have an "end" entry that separates it from
+ the changes associated with the next instruction. */
+
+struct record_entry
+{
+ struct record_entry *prev;
+ struct record_entry *next;
+ enum record_type type;
+ union
+ {
+ /* reg */
+ struct record_reg_entry reg;
+ /* mem */
+ struct record_mem_entry mem;
+ /* end */
+ struct record_end_entry end;
+ } u;
+};
+
+/* If true, query if PREC cannot record memory
+ change of next instruction. */
+int record_memory_query = 0;
+
+struct record_core_buf_entry
+{
+ struct record_core_buf_entry *prev;
+ struct target_section *p;
+ bfd_byte *buf;
+};
+
+/* Record buf with core target. */
+static gdb_byte *record_core_regbuf = NULL;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
+/* The following variables are used for managing the linked list that
+ represents the execution log.
+
+ record_first is the anchor that holds down the beginning of the list.
+
+ record_list serves two functions:
+ 1) In record mode, it anchors the end of the list.
+ 2) In replay mode, it traverses the list and points to
+ the next instruction that must be emulated.
+
+ record_arch_list_head and record_arch_list_tail are used to manage
+ a separate list, which is used to build up the change elements of
+ the currently executing instruction during record mode. When this
+ instruction has been completely annotated in the "arch list", it
+ will be appended to the main execution log. */
+
+static struct record_entry record_first;
+static struct record_entry *record_list = &record_first;
+static struct record_entry *record_arch_list_head = NULL;
+static struct record_entry *record_arch_list_tail = NULL;
+
+/* 1 ask user. 0 auto delete the last struct record_entry. */
+static int record_stop_at_limit = 1;
+/* Maximum allowed number of insns in execution log. */
+static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
+/* Actual count of insns presently in execution log. */
+static int record_insn_num = 0;
+/* Count of insns logged so far (may be larger
+ than count of insns presently in execution log). */
+static ULONGEST record_insn_count;
+
+/* The target_ops of process record. */
+static struct target_ops record_ops;
+static struct target_ops record_core_ops;
+
+/* Command lists for "set/show record full". */
+static struct cmd_list_element *set_record_full_cmdlist;
+static struct cmd_list_element *show_record_full_cmdlist;
+
+/* The beneath function pointers. */
+static struct target_ops *record_beneath_to_resume_ops;
+static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
+ enum gdb_signal);
+static struct target_ops *record_beneath_to_wait_ops;
+static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
+ struct target_waitstatus *,
+ int);
+static struct target_ops *record_beneath_to_store_registers_ops;
+static void (*record_beneath_to_store_registers) (struct target_ops *,
+ struct regcache *,
+ int regno);
+static struct target_ops *record_beneath_to_xfer_partial_ops;
+static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops,
+ enum target_object object,
+ const char *annex,
+ gdb_byte *readbuf,
+ const gdb_byte *writebuf,
+ ULONGEST offset,
+ LONGEST len);
+static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *,
+ struct bp_target_info *);
+static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
+ struct bp_target_info *);
+static int (*record_beneath_to_stopped_by_watchpoint) (void);
+static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
+ CORE_ADDR *);
+static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *);
+
+/* Alloc and free functions for record_reg, record_mem, and record_end
+ entries. */
+
+/* Alloc a record_reg record entry. */
+
+static inline struct record_entry *
+record_reg_alloc (struct regcache *regcache, int regnum)
+{
+ struct record_entry *rec;
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+ rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+ rec->type = record_reg;
+ rec->u.reg.num = regnum;
+ rec->u.reg.len = register_size (gdbarch, regnum);
+ if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+ rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
+
+ return rec;
+}
+
+/* Free a record_reg record entry. */
+
+static inline void
+record_reg_release (struct record_entry *rec)
+{
+ gdb_assert (rec->type == record_reg);
+ if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+ xfree (rec->u.reg.u.ptr);
+ xfree (rec);
+}
+
+/* Alloc a record_mem record entry. */
+
+static inline struct record_entry *
+record_mem_alloc (CORE_ADDR addr, int len)
+{
+ struct record_entry *rec;
+
+ rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+ rec->type = record_mem;
+ rec->u.mem.addr = addr;
+ rec->u.mem.len = len;
+ if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+ rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
+
+ return rec;
+}
+
+/* Free a record_mem record entry. */
+
+static inline void
+record_mem_release (struct record_entry *rec)
+{
+ gdb_assert (rec->type == record_mem);
+ if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+ xfree (rec->u.mem.u.ptr);
+ xfree (rec);
+}
+
+/* Alloc a record_end record entry. */
+
+static inline struct record_entry *
+record_end_alloc (void)
+{
+ struct record_entry *rec;
+
+ rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
+ rec->type = record_end;
+
+ return rec;
+}
+
+/* Free a record_end record entry. */
+
+static inline void
+record_end_release (struct record_entry *rec)
+{
+ xfree (rec);
+}
+
+/* Free one record entry, any type.
+ Return entry->type, in case caller wants to know. */
+
+static inline enum record_type
+record_entry_release (struct record_entry *rec)
+{
+ enum record_type type = rec->type;
+
+ switch (type) {
+ case record_reg:
+ record_reg_release (rec);
+ break;
+ case record_mem:
+ record_mem_release (rec);
+ break;
+ case record_end:
+ record_end_release (rec);
+ break;
+ }
+ return type;
+}
+
+/* Free all record entries in list pointed to by REC. */
+
+static void
+record_list_release (struct record_entry *rec)
+{
+ if (!rec)
+ return;
+
+ while (rec->next)
+ rec = rec->next;
+
+ while (rec->prev)
+ {
+ rec = rec->prev;
+ record_entry_release (rec->next);
+ }
+
+ if (rec == &record_first)
+ {
+ record_insn_num = 0;
+ record_first.next = NULL;
+ }
+ else
+ record_entry_release (rec);
+}
+
+/* Free all record entries forward of the given list position. */
+
+static void
+record_list_release_following (struct record_entry *rec)
+{
+ struct record_entry *tmp = rec->next;
+
+ rec->next = NULL;
+ while (tmp)
+ {
+ rec = tmp->next;
+ if (record_entry_release (tmp) == record_end)
+ {
+ record_insn_num--;
+ record_insn_count--;
+ }
+ tmp = rec;
+ }
+}
+
+/* Delete the first instruction from the beginning of the log, to make
+ room for adding a new instruction at the end of the log.
+
+ Note -- this function does not modify record_insn_num. */
+
+static void
+record_list_release_first (void)
+{
+ struct record_entry *tmp;
+
+ if (!record_first.next)
+ return;
+
+ /* Loop until a record_end. */
+ while (1)
+ {
+ /* Cut record_first.next out of the linked list. */
+ tmp = record_first.next;
+ record_first.next = tmp->next;
+ tmp->next->prev = &record_first;
+
+ /* tmp is now isolated, and can be deleted. */
+ if (record_entry_release (tmp) == record_end)
+ break; /* End loop at first record_end. */
+
+ if (!record_first.next)
+ {
+ gdb_assert (record_insn_num == 1);
+ break; /* End loop when list is empty. */
+ }
+ }
+}
+
+/* Add a struct record_entry to record_arch_list. */
+
+static void
+record_arch_list_add (struct record_entry *rec)
+{
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_arch_list_add %s.\n",
+ host_address_to_string (rec));
+
+ if (record_arch_list_tail)
+ {
+ record_arch_list_tail->next = rec;
+ rec->prev = record_arch_list_tail;
+ record_arch_list_tail = rec;
+ }
+ else
+ {
+ record_arch_list_head = rec;
+ record_arch_list_tail = rec;
+ }
+}
+
+/* Return the value storage location of a record entry. */
+static inline gdb_byte *
+record_get_loc (struct record_entry *rec)
+{
+ switch (rec->type) {
+ case record_mem:
+ if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
+ return rec->u.mem.u.ptr;
+ else
+ return rec->u.mem.u.buf;
+ case record_reg:
+ if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
+ return rec->u.reg.u.ptr;
+ else
+ return rec->u.reg.u.buf;
+ case record_end:
+ default:
+ gdb_assert_not_reached ("unexpected record_entry type");
+ return NULL;
+ }
+}
+
+/* Record the value of a register NUM to record_arch_list. */
+
+int
+record_arch_list_add_reg (struct regcache *regcache, int regnum)
+{
+ struct record_entry *rec;
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add register num = %d to "
+ "record list.\n",
+ regnum);
+
+ rec = record_reg_alloc (regcache, regnum);
+
+ regcache_raw_read (regcache, regnum, record_get_loc (rec));
+
+ record_arch_list_add (rec);
+
+ return 0;
+}
+
+int
+record_read_memory (struct gdbarch *gdbarch,
+ CORE_ADDR memaddr, gdb_byte *myaddr,
+ ssize_t len)
+{
+ int ret = target_read_memory (memaddr, myaddr, len);
+
+ if (ret && record_debug)
+ printf_unfiltered (_("Process record: error reading memory "
+ "at addr %s len = %ld.\n"),
+ paddress (gdbarch, memaddr), (long) len);
+ return ret;
+}
+
+/* Record the value of a region of memory whose address is ADDR and
+ length is LEN to record_arch_list. */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+ struct record_entry *rec;
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add mem addr = %s len = %d to "
+ "record list.\n",
+ paddress (target_gdbarch (), addr), len);
+
+ if (!addr) /* FIXME: Why? Some arch must permit it... */
+ return 0;
+
+ rec = record_mem_alloc (addr, len);
+
+ if (record_read_memory (target_gdbarch (), addr, record_get_loc (rec), len))
+ {
+ record_mem_release (rec);
+ return -1;
+ }
+
+ record_arch_list_add (rec);
+
+ return 0;
+}
+
+/* Add a record_end type struct record_entry to record_arch_list. */
+
+int
+record_arch_list_add_end (void)
+{
+ struct record_entry *rec;
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add end to arch list.\n");
+
+ rec = record_end_alloc ();
+ rec->u.end.sigval = GDB_SIGNAL_0;
+ rec->u.end.insn_num = ++record_insn_count;
+
+ record_arch_list_add (rec);
+
+ return 0;
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+ if (record_insn_max_num)
+ {
+ gdb_assert (record_insn_num <= record_insn_max_num);
+ if (record_insn_num == record_insn_max_num)
+ {
+ /* Ask user what to do. */
+ if (record_stop_at_limit)
+ {
+ int q;
+
+ if (set_terminal)
+ target_terminal_ours ();
+ q = yquery (_("Do you want to auto delete previous execution "
+ "log entries when record/replay buffer becomes "
+ "full (record full-stop-at-limit)?"));
+ if (set_terminal)
+ target_terminal_inferior ();
+ if (q)
+ record_stop_at_limit = 0;
+ else
+ error (_("Process record: stopped by user."));
+ }
+ }
+ }
+}
+
+static void
+record_arch_list_cleanups (void *ignore)
+{
+ record_list_release (record_arch_list_tail);
+}
+
+/* Before inferior step (when GDB record the running message, inferior
+ only can step), GDB will call this function to record the values to
+ record_list. This function will call gdbarch_process_record to
+ record the running message of inferior and set them to
+ record_arch_list, and add it to record_list. */
+
+static int
+record_message (struct regcache *regcache, enum gdb_signal signal)
+{
+ int ret;
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+
+ /* Check record_insn_num. */
+ record_check_insn_num (1);
+
+ /* If gdb sends a signal value to target_resume,
+ save it in the 'end' field of the previous instruction.
+
+ Maybe process record should record what really happened,
+ rather than what gdb pretends has happened.
+
+ So if Linux delivered the signal to the child process during
+ the record mode, we will record it and deliver it again in
+ the replay mode.
+
+ If user says "ignore this signal" during the record mode, then
+ it will be ignored again during the replay mode (no matter if
+ the user says something different, like "deliver this signal"
+ during the replay mode).
+
+ User should understand that nothing he does during the replay
+ mode will change the behavior of the child. If he tries,
+ then that is a user error.
+
+ But we should still deliver the signal to gdb during the replay,
+ if we delivered it during the recording. Therefore we should
+ record the signal during record_wait, not record_resume. */
+ if (record_list != &record_first) /* FIXME better way to check */
+ {
+ gdb_assert (record_list->type == record_end);
+ record_list->u.end.sigval = signal;
+ }
+
+ if (signal == GDB_SIGNAL_0
+ || !gdbarch_process_record_signal_p (gdbarch))
+ ret = gdbarch_process_record (gdbarch,
+ regcache,
+ regcache_read_pc (regcache));
+ else
+ ret = gdbarch_process_record_signal (gdbarch,
+ regcache,
+ signal);
+
+ if (ret > 0)
+ error (_("Process record: inferior program stopped."));
+ if (ret < 0)
+ error (_("Process record: failed to record execution log."));
+
+ discard_cleanups (old_cleanups);
+
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ record_list_release_first ();
+ else
+ record_insn_num++;
+
+ return 1;
+}
+
+struct record_message_args {
+ struct regcache *regcache;
+ enum gdb_signal signal;
+};
+
+static int
+record_message_wrapper (void *args)
+{
+ struct record_message_args *record_args = args;
+
+ return record_message (record_args->regcache, record_args->signal);
+}
+
+static int
+record_message_wrapper_safe (struct regcache *regcache,
+ enum gdb_signal signal)
+{
+ struct record_message_args args;
+
+ args.regcache = regcache;
+ args.signal = signal;
+
+ return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL);
+}
+
+/* Set to 1 if record_store_registers and record_xfer_partial
+ doesn't need record. */
+
+static int record_gdb_operation_disable = 0;
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+ struct cleanup *old_cleanups = NULL;
+
+ old_cleanups =
+ make_cleanup_restore_integer (&record_gdb_operation_disable);
+ record_gdb_operation_disable = 1;
+
+ return old_cleanups;
+}
+
+/* Flag set to TRUE for target_stopped_by_watchpoint. */
+static int record_hw_watchpoint = 0;
+
+/* Execute one instruction from the record log. Each instruction in
+ the log will be represented by an arbitrary sequence of register
+ entries and memory entries, followed by an 'end' entry. */
+
+static inline void
+record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
+ struct record_entry *entry)
+{
+ switch (entry->type)
+ {
+ case record_reg: /* reg */
+ {
+ gdb_byte reg[MAX_REGISTER_SIZE];
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_reg %s to "
+ "inferior num = %d.\n",
+ host_address_to_string (entry),
+ entry->u.reg.num);
+
+ regcache_cooked_read (regcache, entry->u.reg.num, reg);
+ regcache_cooked_write (regcache, entry->u.reg.num,
+ record_get_loc (entry));
+ memcpy (record_get_loc (entry), reg, entry->u.reg.len);
+ }
+ break;
+
+ case record_mem: /* mem */
+ {
+ /* Nothing to do if the entry is flagged not_accessible. */
+ if (!entry->u.mem.mem_entry_not_accessible)
+ {
+ gdb_byte *mem = alloca (entry->u.mem.len);
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_mem %s to "
+ "inferior addr = %s len = %d.\n",
+ host_address_to_string (entry),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+
+ if (record_read_memory (gdbarch,
+ entry->u.mem.addr, mem, entry->u.mem.len))
+ entry->u.mem.mem_entry_not_accessible = 1;
+ else
+ {
+ if (target_write_memory (entry->u.mem.addr,
+ record_get_loc (entry),
+ entry->u.mem.len))
+ {
+ entry->u.mem.mem_entry_not_accessible = 1;
+ if (record_debug)
+ warning (_("Process record: error writing memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ {
+ memcpy (record_get_loc (entry), mem, entry->u.mem.len);
+
+ /* We've changed memory --- check if a hardware
+ watchpoint should trap. Note that this
+ presently assumes the target beneath supports
+ continuable watchpoints. On non-continuable
+ watchpoints target, we'll want to check this
+ _before_ actually doing the memory change, and
+ not doing the change at all if the watchpoint
+ traps. */
+ if (hardware_watchpoint_inserted_in_range
+ (get_regcache_aspace (regcache),
+ entry->u.mem.addr, entry->u.mem.len))
+ record_hw_watchpoint = 1;
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+static struct target_ops *tmp_to_resume_ops;
+static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
+ enum gdb_signal);
+static struct target_ops *tmp_to_wait_ops;
+static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
+ struct target_waitstatus *,
+ int);
+static struct target_ops *tmp_to_store_registers_ops;
+static void (*tmp_to_store_registers) (struct target_ops *,
+ struct regcache *,
+ int regno);
+static struct target_ops *tmp_to_xfer_partial_ops;
+static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
+ enum target_object object,
+ const char *annex,
+ gdb_byte *readbuf,
+ const gdb_byte *writebuf,
+ ULONGEST offset,
+ LONGEST len);
+static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
+ struct bp_target_info *);
+static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
+ struct bp_target_info *);
+static int (*tmp_to_stopped_by_watchpoint) (void);
+static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *);
+
+static void record_save (char *, int);
+static void record_restore (void);
+
+/* Asynchronous signal handle registered as event loop source for when
+ we have pending events ready to be passed to the core. */
+
+static struct async_event_handler *record_async_inferior_event_token;
+
+static void
+record_async_inferior_event_handler (gdb_client_data data)
+{
+ inferior_event_handler (INF_REG_EVENT, NULL);
+}
+
+/* Open the process record target. */
+
+static void
+record_core_open_1 (char *name, int from_tty)
+{
+ struct regcache *regcache = get_current_regcache ();
+ int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ /* Get record_core_regbuf. */
+ target_fetch_registers (regcache, -1);
+ record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+ for (i = 0; i < regnum; i ++)
+ regcache_raw_collect (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+ /* Get record_core_start and record_core_end. */
+ if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+ {
+ xfree (record_core_regbuf);
+ record_core_regbuf = NULL;
+ error (_("\"%s\": Can't find sections: %s"),
+ bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+ }
+
+ push_target (&record_core_ops);
+ record_restore ();
+}
+
+/* "to_open" target method for 'live' processes. */
+
+static void
+record_open_1 (char *name, int from_tty)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
+ /* check exec */
+ if (!target_has_execution)
+ error (_("Process record: the program is not being run."));
+ if (non_stop)
+ error (_("Process record target can't debug inferior in non-stop mode "
+ "(non-stop)."));
+
+ if (!gdbarch_process_record_p (target_gdbarch ()))
+ error (_("Process record: the current architecture doesn't support "
+ "record function."));
+
+ if (!tmp_to_resume)
+ error (_("Could not find 'to_resume' method on the target stack."));
+ if (!tmp_to_wait)
+ error (_("Could not find 'to_wait' method on the target stack."));
+ if (!tmp_to_store_registers)
+ error (_("Could not find 'to_store_registers' "
+ "method on the target stack."));
+ if (!tmp_to_insert_breakpoint)
+ error (_("Could not find 'to_insert_breakpoint' "
+ "method on the target stack."));
+ if (!tmp_to_remove_breakpoint)
+ error (_("Could not find 'to_remove_breakpoint' "
+ "method on the target stack."));
+ if (!tmp_to_stopped_by_watchpoint)
+ error (_("Could not find 'to_stopped_by_watchpoint' "
+ "method on the target stack."));
+ if (!tmp_to_stopped_data_address)
+ error (_("Could not find 'to_stopped_data_address' "
+ "method on the target stack."));
+
+ push_target (&record_ops);
+}
+
+static void record_init_record_breakpoints (void);
+
+/* "to_open" target method. Open the process record target. */
+
+static void
+record_open (char *name, int from_tty)
+{
+ struct target_ops *t;
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+
+ /* Check if record target is already running. */
+ if (current_target.to_stratum == record_stratum)
+ error (_("Process record target already running. Use \"record stop\" to "
+ "stop record target first."));
+
+ /* Reset the tmp beneath pointers. */
+ tmp_to_resume_ops = NULL;
+ tmp_to_resume = NULL;
+ tmp_to_wait_ops = NULL;
+ tmp_to_wait = NULL;
+ tmp_to_store_registers_ops = NULL;
+ tmp_to_store_registers = NULL;
+ tmp_to_xfer_partial_ops = NULL;
+ tmp_to_xfer_partial = NULL;
+ tmp_to_insert_breakpoint = NULL;
+ tmp_to_remove_breakpoint = NULL;
+ tmp_to_stopped_by_watchpoint = NULL;
+ tmp_to_stopped_data_address = NULL;
+ tmp_to_async = NULL;
+
+ /* Set the beneath function pointers. */
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ {
+ if (!tmp_to_resume)
+ {
+ tmp_to_resume = t->to_resume;
+ tmp_to_resume_ops = t;
+ }
+ if (!tmp_to_wait)
+ {
+ tmp_to_wait = t->to_wait;
+ tmp_to_wait_ops = t;
+ }
+ if (!tmp_to_store_registers)
+ {
+ tmp_to_store_registers = t->to_store_registers;
+ tmp_to_store_registers_ops = t;
+ }
+ if (!tmp_to_xfer_partial)
+ {
+ tmp_to_xfer_partial = t->to_xfer_partial;
+ tmp_to_xfer_partial_ops = t;
+ }
+ if (!tmp_to_insert_breakpoint)
+ tmp_to_insert_breakpoint = t->to_insert_breakpoint;
+ if (!tmp_to_remove_breakpoint)
+ tmp_to_remove_breakpoint = t->to_remove_breakpoint;
+ if (!tmp_to_stopped_by_watchpoint)
+ tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint;
+ if (!tmp_to_stopped_data_address)
+ tmp_to_stopped_data_address = t->to_stopped_data_address;
+ if (!tmp_to_async)
+ tmp_to_async = t->to_async;
+ }
+ if (!tmp_to_xfer_partial)
+ error (_("Could not find 'to_xfer_partial' method on the target stack."));
+
+ /* Reset */
+ record_insn_num = 0;
+ record_insn_count = 0;
+ record_list = &record_first;
+ record_list->next = NULL;
+
+ /* Set the tmp beneath pointers to beneath pointers. */
+ record_beneath_to_resume_ops = tmp_to_resume_ops;
+ record_beneath_to_resume = tmp_to_resume;
+ record_beneath_to_wait_ops = tmp_to_wait_ops;
+ record_beneath_to_wait = tmp_to_wait;
+ record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
+ record_beneath_to_store_registers = tmp_to_store_registers;
+ record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
+ record_beneath_to_xfer_partial = tmp_to_xfer_partial;
+ record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
+ record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
+ record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint;
+ record_beneath_to_stopped_data_address = tmp_to_stopped_data_address;
+ record_beneath_to_async = tmp_to_async;
+
+ if (core_bfd)
+ record_core_open_1 (name, from_tty);
+ else
+ record_open_1 (name, from_tty);
+
+ /* Register extra event sources in the event loop. */
+ record_async_inferior_event_token
+ = create_async_event_handler (record_async_inferior_event_handler,
+ NULL);
+
+ record_init_record_breakpoints ();
+
+ observer_notify_record_changed (current_inferior (), 1);
+}
+
+/* "to_close" target method. Close the process record target. */
+
+static void
+record_close (int quitting)
+{
+ struct record_core_buf_entry *entry;
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+
+ record_list_release (record_list);
+
+ /* Release record_core_regbuf. */
+ if (record_core_regbuf)
+ {
+ xfree (record_core_regbuf);
+ record_core_regbuf = NULL;
+ }
+
+ /* Release record_core_buf_list. */
+ if (record_core_buf_list)
+ {
+ for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+ {
+ xfree (record_core_buf_list);
+ record_core_buf_list = entry;
+ }
+ record_core_buf_list = NULL;
+ }
+
+ if (record_async_inferior_event_token)
+ delete_async_event_handler (&record_async_inferior_event_token);
+}
+
+static int record_resume_step = 0;
+
+/* True if we've been resumed, and so each record_wait call should
+ advance execution. If this is false, record_wait will return a
+ TARGET_WAITKIND_IGNORE. */
+static int record_resumed = 0;
+
+/* The execution direction of the last resume we got. This is
+ necessary for async mode. Vis (order is not strictly accurate):
+
+ 1. user has the global execution direction set to forward
+ 2. user does a reverse-step command
+ 3. record_resume is called with global execution direction
+ temporarily switched to reverse
+ 4. GDB's execution direction is reverted back to forward
+ 5. target record notifies event loop there's an event to handle
+ 6. infrun asks the target which direction was it going, and switches
+ the global execution direction accordingly (to reverse)
+ 7. infrun polls an event out of the record target, and handles it
+ 8. GDB goes back to the event loop, and goto #4.
+*/
+static enum exec_direction_kind record_execution_dir = EXEC_FORWARD;
+
+/* "to_resume" target method. Resume the process record target. */
+
+static void
+record_resume (struct target_ops *ops, ptid_t ptid, int step,
+ enum gdb_signal signal)
+{
+ record_resume_step = step;
+ record_resumed = 1;
+ record_execution_dir = execution_direction;
+
+ if (!RECORD_IS_REPLAY)
+ {
+ struct gdbarch *gdbarch = target_thread_architecture (ptid);
+
+ record_message (get_current_regcache (), signal);
+
+ if (!step)
+ {
+ /* This is not hard single step. */
+ if (!gdbarch_software_single_step_p (gdbarch))
+ {
+ /* This is a normal continue. */
+ step = 1;
+ }
+ else
+ {
+ /* This arch support soft sigle step. */
+ if (single_step_breakpoints_inserted ())
+ {
+ /* This is a soft single step. */
+ record_resume_step = 1;
+ }
+ else
+ {
+ /* This is a continue.
+ Try to insert a soft single step breakpoint. */
+ if (!gdbarch_software_single_step (gdbarch,
+ get_current_frame ()))
+ {
+ /* This system don't want use soft single step.
+ Use hard sigle step. */
+ step = 1;
+ }
+ }
+ }
+ }
+
+ /* Make sure the target beneath reports all signals. */
+ target_pass_signals (0, NULL);
+
+ record_beneath_to_resume (record_beneath_to_resume_ops,
+ ptid, step, signal);
+ }
+
+ /* We are about to start executing the inferior (or simulate it),
+ let's register it with the event loop. */
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+ /* Notify the event loop there's an event to wait for. We do
+ most of the work in record_wait. */
+ mark_async_event_handler (record_async_inferior_event_token);
+ }
+}
+
+static int record_get_sig = 0;
+
+/* SIGINT signal handler, registered by "to_wait" method. */
+
+static void
+record_sig_handler (int signo)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+
+ /* It will break the running inferior in replay mode. */
+ record_resume_step = 1;
+
+ /* It will let record_wait set inferior status to get the signal
+ SIGINT. */
+ record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+ if (execution_direction == EXEC_REVERSE)
+ {
+ if (record_list->next)
+ record_list = record_list->next;
+ }
+ else
+ record_list = record_list->prev;
+}
+
+/* "to_wait" target method for process record target.
+
+ In record mode, the target is always run in singlestep mode
+ (even when gdb says to continue). The to_wait method intercepts
+ the stop events and determines which ones are to be passed on to
+ gdb. Most stop events are just singlestep events that gdb is not
+ to know about, so the to_wait method just records them and keeps
+ singlestepping.
+
+ In replay mode, this function emulates the recorded execution log,
+ one instruction at a time (forward or backward), and determines
+ where to stop. */
+
+static ptid_t
+record_wait_1 (struct target_ops *ops,
+ ptid_t ptid, struct target_waitstatus *status,
+ int options)
+{
+ struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_wait "
+ "record_resume_step = %d, record_resumed = %d, direction=%s\n",
+ record_resume_step, record_resumed,
+ record_execution_dir == EXEC_FORWARD ? "forward" : "reverse");
+
+ if (!record_resumed)
+ {
+ gdb_assert ((options & TARGET_WNOHANG) != 0);
+
+ /* No interesting event. */
+ status->kind = TARGET_WAITKIND_IGNORE;
+ return minus_one_ptid;
+ }
+
+ record_get_sig = 0;
+ signal (SIGINT, record_sig_handler);
+
+ if (!RECORD_IS_REPLAY && ops != &record_core_ops)
+ {
+ if (record_resume_step)
+ {
+ /* This is a single step. */
+ return record_beneath_to_wait (record_beneath_to_wait_ops,
+ ptid, status, options);
+ }
+ else
+ {
+ /* This is not a single step. */
+ ptid_t ret;
+ CORE_ADDR tmp_pc;
+ struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid);
+
+ while (1)
+ {
+ ret = record_beneath_to_wait (record_beneath_to_wait_ops,
+ ptid, status, options);
+ if (status->kind == TARGET_WAITKIND_IGNORE)
+ {
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_wait "
+ "target beneath not done yet\n");
+ return ret;
+ }
+
+ if (single_step_breakpoints_inserted ())
+ remove_single_step_breakpoints ();
+
+ if (record_resume_step)
+ return ret;
+
+ /* Is this a SIGTRAP? */
+ if (status->kind == TARGET_WAITKIND_STOPPED
+ && status->value.sig == GDB_SIGNAL_TRAP)
+ {
+ struct regcache *regcache;
+ struct address_space *aspace;
+
+ /* Yes -- this is likely our single-step finishing,
+ but check if there's any reason the core would be
+ interested in the event. */
+
+ registers_changed ();
+ regcache = get_current_regcache ();
+ tmp_pc = regcache_read_pc (regcache);
+ aspace = get_regcache_aspace (regcache);
+
+ if (target_stopped_by_watchpoint ())
+ {
+ /* Always interested in watchpoints. */
+ }
+ else if (breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ /* There is a breakpoint here. Let the core
+ handle it. */
+ if (software_breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ struct gdbarch *gdbarch
+ = get_regcache_arch (regcache);
+ CORE_ADDR decr_pc_after_break
+ = gdbarch_decr_pc_after_break (gdbarch);
+ if (decr_pc_after_break)
+ regcache_write_pc (regcache,
+ tmp_pc + decr_pc_after_break);
+ }
+ }
+ else
+ {
+ /* This is a single-step trap. Record the
+ insn and issue another step.
+ FIXME: this part can be a random SIGTRAP too.
+ But GDB cannot handle it. */
+ int step = 1;
+
+ if (!record_message_wrapper_safe (regcache,
+ GDB_SIGNAL_0))
+ {
+ status->kind = TARGET_WAITKIND_STOPPED;
+ status->value.sig = GDB_SIGNAL_0;
+ break;
+ }
+
+ if (gdbarch_software_single_step_p (gdbarch))
+ {
+ /* Try to insert the software single step breakpoint.
+ If insert success, set step to 0. */
+ set_executing (inferior_ptid, 0);
+ reinit_frame_cache ();
+ if (gdbarch_software_single_step (gdbarch,
+ get_current_frame ()))
+ step = 0;
+ set_executing (inferior_ptid, 1);
+ }
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_wait "
+ "issuing one more step in the target beneath\n");
+ record_beneath_to_resume (record_beneath_to_resume_ops,
+ ptid, step,
+ GDB_SIGNAL_0);
+ continue;
+ }
+ }
+
+ /* The inferior is broken by a breakpoint or a signal. */
+ break;
+ }
+
+ return ret;
+ }
+ }
+ else
+ {
+ struct regcache *regcache = get_current_regcache ();
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct address_space *aspace = get_regcache_aspace (regcache);
+ int continue_flag = 1;
+ int first_record_end = 1;
+ struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+ CORE_ADDR tmp_pc;
+
+ record_hw_watchpoint = 0;
+ status->kind = TARGET_WAITKIND_STOPPED;
+
+ /* Check breakpoint when forward execute. */
+ if (execution_direction == EXEC_FORWARD)
+ {
+ tmp_pc = regcache_read_pc (regcache);
+ if (breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: break at %s.\n",
+ paddress (gdbarch, tmp_pc));
+
+ if (decr_pc_after_break
+ && !record_resume_step
+ && software_breakpoint_inserted_here_p (aspace, tmp_pc))
+ regcache_write_pc (regcache,
+ tmp_pc + decr_pc_after_break);
+ goto replay_out;
+ }
+ }
+
+ /* If GDB is in terminal_inferior mode, it will not get the signal.
+ And in GDB replay mode, GDB doesn't need to be in terminal_inferior
+ mode, because inferior will not executed.
+ Then set it to terminal_ours to make GDB get the signal. */
+ target_terminal_ours ();
+
+ /* In EXEC_FORWARD mode, record_list points to the tail of prev
+ instruction. */
+ if (execution_direction == EXEC_FORWARD && record_list->next)
+ record_list = record_list->next;
+
+ /* Loop over the record_list, looking for the next place to
+ stop. */
+ do
+ {
+ /* Check for beginning and end of log. */
+ if (execution_direction == EXEC_REVERSE
+ && record_list == &record_first)
+ {
+ /* Hit beginning of record log in reverse. */
+ status->kind = TARGET_WAITKIND_NO_HISTORY;
+ break;
+ }
+ if (execution_direction != EXEC_REVERSE && !record_list->next)
+ {
+ /* Hit end of record log going forward. */
+ status->kind = TARGET_WAITKIND_NO_HISTORY;
+ break;
+ }
+
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->type == record_end)
+ {
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_end %s to "
+ "inferior.\n",
+ host_address_to_string (record_list));
+
+ if (first_record_end && execution_direction == EXEC_REVERSE)
+ {
+ /* When reverse excute, the first record_end is the part of
+ current instruction. */
+ first_record_end = 0;
+ }
+ else
+ {
+ /* In EXEC_REVERSE mode, this is the record_end of prev
+ instruction.
+ In EXEC_FORWARD mode, this is the record_end of current
+ instruction. */
+ /* step */
+ if (record_resume_step)
+ {
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: step.\n");
+ continue_flag = 0;
+ }
+
+ /* check breakpoint */
+ tmp_pc = regcache_read_pc (regcache);
+ if (breakpoint_inserted_here_p (aspace, tmp_pc))
+ {
+ int decr_pc_after_break
+ = gdbarch_decr_pc_after_break (gdbarch);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: break "
+ "at %s.\n",
+ paddress (gdbarch, tmp_pc));
+ if (decr_pc_after_break
+ && execution_direction == EXEC_FORWARD
+ && !record_resume_step
+ && software_breakpoint_inserted_here_p (aspace,
+ tmp_pc))
+ regcache_write_pc (regcache,
+ tmp_pc + decr_pc_after_break);
+ continue_flag = 0;
+ }
+
+ if (record_hw_watchpoint)
+ {
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: hit hw "
+ "watchpoint.\n");
+ continue_flag = 0;
+ }
+ /* Check target signal */
+ if (record_list->u.end.sigval != GDB_SIGNAL_0)
+ /* FIXME: better way to check */
+ continue_flag = 0;
+ }
+ }
+
+ if (continue_flag)
+ {
+ if (execution_direction == EXEC_REVERSE)
+ {
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+ else
+ {
+ if (record_list->next)
+ record_list = record_list->next;
+ }
+ }
+ }
+ while (continue_flag);
+
+replay_out:
+ if (record_get_sig)
+ status->value.sig = GDB_SIGNAL_INT;
+ else if (record_list->u.end.sigval != GDB_SIGNAL_0)
+ /* FIXME: better way to check */
+ status->value.sig = record_list->u.end.sigval;
+ else
+ status->value.sig = GDB_SIGNAL_TRAP;
+
+ discard_cleanups (old_cleanups);
+ }
+
+ signal (SIGINT, handle_sigint);
+
+ do_cleanups (set_cleanups);
+ return inferior_ptid;
+}
+
+static ptid_t
+record_wait (struct target_ops *ops,
+ ptid_t ptid, struct target_waitstatus *status,
+ int options)
+{
+ ptid_t return_ptid;
+
+ return_ptid = record_wait_1 (ops, ptid, status, options);
+ if (status->kind != TARGET_WAITKIND_IGNORE)
+ {
+ /* We're reporting a stop. Make sure any spurious
+ target_wait(WNOHANG) doesn't advance the target until the
+ core wants us resumed again. */
+ record_resumed = 0;
+ }
+ return return_ptid;
+}
+
+static int
+record_stopped_by_watchpoint (void)
+{
+ if (RECORD_IS_REPLAY)
+ return record_hw_watchpoint;
+ else
+ return record_beneath_to_stopped_by_watchpoint ();
+}
+
+static int
+record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
+{
+ if (RECORD_IS_REPLAY)
+ return 0;
+ else
+ return record_beneath_to_stopped_data_address (ops, addr_p);
+}
+
+/* "to_disconnect" method for process record target. */
+
+static void
+record_disconnect (struct target_ops *target, char *args, int from_tty)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n");
+
+ unpush_target (&record_ops);
+ target_disconnect (args, from_tty);
+}
+
+/* "to_detach" method for process record target. */
+
+static void
+record_detach (struct target_ops *ops, char *args, int from_tty)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n");
+
+ unpush_target (&record_ops);
+ target_detach (args, from_tty);
+}
+
+/* "to_mourn_inferior" method for process record target. */
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: "
+ "record_mourn_inferior\n");
+
+ unpush_target (&record_ops);
+ target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process. */
+
+static void
+record_kill (struct target_ops *ops)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+
+ unpush_target (&record_ops);
+ target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction. */
+
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+ /* Check record_insn_num. */
+ record_check_insn_num (0);
+
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+
+ if (regnum < 0)
+ {
+ int i;
+
+ for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+ {
+ if (record_arch_list_add_reg (regcache, i))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ }
+ }
+ else
+ {
+ if (record_arch_list_add_reg (regcache, regnum))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ }
+ if (record_arch_list_add_end ())
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ record_list_release_first ();
+ else
+ record_insn_num++;
+}
+
+/* "to_store_registers" method for process record target. */
+
+static void
+record_store_registers (struct target_ops *ops, struct regcache *regcache,
+ int regno)
+{
+ if (!record_gdb_operation_disable)
+ {
+ if (RECORD_IS_REPLAY)
+ {
+ int n;
+
+ /* Let user choose if he wants to write register or not. */
+ if (regno < 0)
+ n =
+ query (_("Because GDB is in replay mode, changing the "
+ "value of a register will make the execution "
+ "log unusable from this point onward. "
+ "Change all registers?"));
+ else
+ n =
+ query (_("Because GDB is in replay mode, changing the value "
+ "of a register will make the execution log unusable "
+ "from this point onward. Change register %s?"),
+ gdbarch_register_name (get_regcache_arch (regcache),
+ regno));
+
+ if (!n)
+ {
+ /* Invalidate the value of regcache that was set in function
+ "regcache_raw_write". */
+ if (regno < 0)
+ {
+ int i;
+
+ for (i = 0;
+ i < gdbarch_num_regs (get_regcache_arch (regcache));
+ i++)
+ regcache_invalidate (regcache, i);
+ }
+ else
+ regcache_invalidate (regcache, regno);
+
+ error (_("Process record canceled the operation."));
+ }
+
+ /* Destroy the record from here forward. */
+ record_list_release_following (record_list);
+ }
+
+ record_registers_change (regcache, regno);
+ }
+ record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
+ regcache, regno);
+}
+
+/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY.
+ In replay mode, we cannot write memory unles we are willing to
+ invalidate the record/replay log from this point forward. */
+
+static LONGEST
+record_xfer_partial (struct target_ops *ops, enum target_object object,
+ const char *annex, gdb_byte *readbuf,
+ const gdb_byte *writebuf, ULONGEST offset, LONGEST len)
+{
+ if (!record_gdb_operation_disable
+ && (object == TARGET_OBJECT_MEMORY
+ || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+ {
+ if (RECORD_IS_REPLAY)
+ {
+ /* Let user choose if he wants to write memory or not. */
+ if (!query (_("Because GDB is in replay mode, writing to memory "
+ "will make the execution log unusable from this "
+ "point onward. Write memory at address %s?"),
+ paddress (target_gdbarch (), offset)))
+ error (_("Process record canceled the operation."));
+
+ /* Destroy the record from here forward. */
+ record_list_release_following (record_list);
+ }
+
+ /* Check record_insn_num */
+ record_check_insn_num (0);
+
+ /* Record registers change to list as an instruction. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ if (record_arch_list_add_mem (offset, len))
+ {
+ record_list_release (record_arch_list_tail);
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: failed to record "
+ "execution log.");
+ return -1;
+ }
+ if (record_arch_list_add_end ())
+ {
+ record_list_release (record_arch_list_tail);
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: failed to record "
+ "execution log.");
+ return -1;
+ }
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ record_list_release_first ();
+ else
+ record_insn_num++;
+ }
+
+ return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+}
+
+/* This structure represents a breakpoint inserted while the record
+ target is active. We use this to know when to install/remove
+ breakpoints in/from the target beneath. For example, a breakpoint
+ may be inserted while recording, but removed when not replaying nor
+ recording. In that case, the breakpoint had not been inserted on
+ the target beneath, so we should not try to remove it there. */
+
+struct record_breakpoint
+{
+ /* The address and address space the breakpoint was set at. */
+ struct address_space *address_space;
+ CORE_ADDR addr;
+
+ /* True when the breakpoint has been also installed in the target
+ beneath. This will be false for breakpoints set during replay or
+ when recording. */
+ int in_target_beneath;
+};
+
+typedef struct record_breakpoint *record_breakpoint_p;
+DEF_VEC_P(record_breakpoint_p);
+
+/* The list of breakpoints inserted while the record target is
+ active. */
+VEC(record_breakpoint_p) *record_breakpoints = NULL;
+
+static void
+record_sync_record_breakpoints (struct bp_location *loc, void *data)
+{
+ if (loc->loc_type != bp_loc_software_breakpoint)
+ return;
+
+ if (loc->inserted)
+ {
+ struct record_breakpoint *bp = XNEW (struct record_breakpoint);
+
+ bp->addr = loc->target_info.placed_address;
+ bp->address_space = loc->target_info.placed_address_space;
+
+ bp->in_target_beneath = 1;
+
+ VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
+ }
+}
+
+/* Sync existing breakpoints to record_breakpoints. */
+
+static void
+record_init_record_breakpoints (void)
+{
+ VEC_free (record_breakpoint_p, record_breakpoints);
+
+ iterate_over_bp_locations (record_sync_record_breakpoints);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY. We will not actually
+ insert or remove breakpoints in the real target when replaying, nor
+ when recording. */
+
+static int
+record_insert_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ struct record_breakpoint *bp;
+ int in_target_beneath = 0;
+
+ if (!RECORD_IS_REPLAY)
+ {
+ /* When recording, we currently always single-step, so we don't
+ really need to install regular breakpoints in the inferior.
+ However, we do have to insert software single-step
+ breakpoints, in case the target can't hardware step. To keep
+ things single, we always insert. */
+ struct cleanup *old_cleanups;
+ int ret;
+
+ old_cleanups = record_gdb_operation_disable_set ();
+ ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
+ do_cleanups (old_cleanups);
+
+ if (ret != 0)
+ return ret;
+
+ in_target_beneath = 1;
+ }
+
+ bp = XNEW (struct record_breakpoint);
+ bp->addr = bp_tgt->placed_address;
+ bp->address_space = bp_tgt->placed_address_space;
+ bp->in_target_beneath = in_target_beneath;
+ VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
+ return 0;
+}
+
+/* "to_remove_breakpoint" method for process record target. */
+
+static int
+record_remove_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ struct record_breakpoint *bp;
+ int ix;
+
+ for (ix = 0;
+ VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp);
+ ++ix)
+ {
+ if (bp->addr == bp_tgt->placed_address
+ && bp->address_space == bp_tgt->placed_address_space)
+ {
+ if (bp->in_target_beneath)
+ {
+ struct cleanup *old_cleanups;
+ int ret;
+
+ old_cleanups = record_gdb_operation_disable_set ();
+ ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
+ do_cleanups (old_cleanups);
+
+ if (ret != 0)
+ return ret;
+ }
+
+ VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix);
+ return 0;
+ }
+ }
+
+ gdb_assert_not_reached ("removing unknown breakpoint");
+}
+
+/* "to_can_execute_reverse" method for process record target. */
+
+static int
+record_can_execute_reverse (void)
+{
+ return 1;
+}
+
+/* "to_get_bookmark" method for process record and prec over core. */
+
+static gdb_byte *
+record_get_bookmark (char *args, int from_tty)
+{
+ gdb_byte *ret = NULL;
+
+ /* Return stringified form of instruction count. */
+ if (record_list && record_list->type == record_end)
+ ret = xstrdup (pulongest (record_list->u.end.insn_num));
+
+ if (record_debug)
+ {
+ if (ret)
+ fprintf_unfiltered (gdb_stdlog,
+ "record_get_bookmark returns %s\n", ret);
+ else
+ fprintf_unfiltered (gdb_stdlog,
+ "record_get_bookmark returns NULL\n");
+ }
+ return ret;
+}
+
+/* "to_goto_bookmark" method for process record and prec over core. */
+
+static void
+record_goto_bookmark (gdb_byte *bookmark, int from_tty)
+{
+ struct cleanup *cleanup;
+ char *cmd;
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ "record_goto_bookmark receives %s\n", bookmark);
+
+ if (bookmark[0] == '\'' || bookmark[0] == '\"')
+ {
+ if (bookmark[strlen (bookmark) - 1] != bookmark[0])
+ error (_("Unbalanced quotes: %s"), bookmark);
+
+ /* Strip trailing quote. */
+ bookmark[strlen (bookmark) - 1] = '\0';
+ /* Strip leading quote. */
+ bookmark++;
+ /* Pass along to "record goto". */
+ }
+
+ cmd = xstrprintf ("record goto %s", bookmark);
+ cleanup = make_cleanup (xfree, cmd);
+
+ execute_command (cmd, from_tty);
+
+ do_cleanups (cleanup);
+}
+
+static void
+record_async (void (*callback) (enum inferior_event_type event_type,
+ void *context), void *context)
+{
+ /* If we're on top of a line target (e.g., linux-nat, remote), then
+ set it to async mode as well. Will be NULL if we're sitting on
+ top of the core target, for "record restore". */
+ if (record_beneath_to_async != NULL)
+ record_beneath_to_async (callback, context);
+}
+
+static int
+record_can_async_p (void)
+{
+ /* We only enable async when the user specifically asks for it. */
+ return target_async_permitted;
+}
+
+static int
+record_is_async_p (void)
+{
+ /* We only enable async when the user specifically asks for it. */
+ return target_async_permitted;
+}
+
+static enum exec_direction_kind
+record_execution_direction (void)
+{
+ return record_execution_dir;
+}
+
+/* The "to_info_record" method. */
+
+static void record_info (char *args, int from_tty)
+{
+ struct record_entry *p;
+
+ if (current_target.to_stratum == record_stratum)
+ {
+ if (RECORD_IS_REPLAY)
+ printf_filtered (_("Replay mode:\n"));
+ else
+ printf_filtered (_("Record mode:\n"));
+
+ /* Find entry for first actual instruction in the log. */
+ for (p = record_first.next;
+ p != NULL && p->type != record_end;
+ p = p->next)
+ ;
+
+ /* Do we have a log at all? */
+ if (p != NULL && p->type == record_end)
+ {
+ /* Display instruction number for first instruction in the log. */
+ printf_filtered (_("Lowest recorded instruction number is %s.\n"),
+ pulongest (p->u.end.insn_num));
+
+ /* If in replay mode, display where we are in the log. */
+ if (RECORD_IS_REPLAY)
+ printf_filtered (_("Current instruction number is %s.\n"),
+ pulongest (record_list->u.end.insn_num));
+
+ /* Display instruction number for last instruction in the log. */
+ printf_filtered (_("Highest recorded instruction number is %s.\n"),
+ pulongest (record_insn_count));
+
+ /* Display log count. */
+ printf_filtered (_("Log contains %d instructions.\n"),
+ record_insn_num);
+ }
+ else
+ {
+ printf_filtered (_("No instructions have been logged.\n"));
+ }
+ }
+ else
+ {
+ printf_filtered (_("target record is not active.\n"));
+ }
+
+ /* Display max log size. */
+ printf_filtered (_("Max logged instructions is %d.\n"),
+ record_insn_max_num);
+}
+
+/* record_goto_insn -- rewind the record log (forward or backward,
+ depending on DIR) to the given entry, changing the program state
+ correspondingly. */
+
+static void
+record_goto_insn (struct record_entry *entry,
+ enum exec_direction_kind dir)
+{
+ struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
+ struct regcache *regcache = get_current_regcache ();
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+ /* Assume everything is valid: we will hit the entry,
+ and we will not hit the end of the recording. */
+
+ if (dir == EXEC_FORWARD)
+ record_list = record_list->next;
+
+ do
+ {
+ record_exec_insn (regcache, gdbarch, record_list);
+ if (dir == EXEC_REVERSE)
+ record_list = record_list->prev;
+ else
+ record_list = record_list->next;
+ } while (record_list != entry);
+ do_cleanups (set_cleanups);
+}
+
+/* The "to_goto_record" method. */
+
+static void record_goto (char *arg, int from_tty)
+{
+ struct record_entry *p = NULL;
+ ULONGEST target_insn = 0;
+
+ if (arg == NULL || *arg == '\0')
+ error (_("Command requires an argument (insn number to go to)."));
+
+ if (strncmp (arg, "start", strlen ("start")) == 0
+ || strncmp (arg, "begin", strlen ("begin")) == 0)
+ {
+ /* Special case. Find first insn. */
+ for (p = &record_first; p != NULL; p = p->next)
+ if (p->type == record_end)
+ break;
+ if (p)
+ target_insn = p->u.end.insn_num;
+ }
+ else if (strncmp (arg, "end", strlen ("end")) == 0)
+ {
+ /* Special case. Find last insn. */
+ for (p = record_list; p->next != NULL; p = p->next)
+ ;
+ for (; p!= NULL; p = p->prev)
+ if (p->type == record_end)
+ break;
+ if (p)
+ target_insn = p->u.end.insn_num;
+ }
+ else
+ {
+ /* General case. Find designated insn. */
+ target_insn = parse_and_eval_long (arg);
+
+ for (p = &record_first; p != NULL; p = p->next)
+ if (p->type == record_end && p->u.end.insn_num == target_insn)
+ break;
+ }
+
+ if (p == NULL)
+ error (_("Target insn '%s' not found."), arg);
+ else if (p == record_list)
+ error (_("Already at insn '%s'."), arg);
+ else if (p->u.end.insn_num > record_list->u.end.insn_num)
+ {
+ printf_filtered (_("Go forward to insn number %s\n"),
+ pulongest (target_insn));
+ record_goto_insn (p, EXEC_FORWARD);
+ }
+ else
+ {
+ printf_filtered (_("Go backward to insn number %s\n"),
+ pulongest (target_insn));
+ record_goto_insn (p, EXEC_REVERSE);
+ }
+ registers_changed ();
+ reinit_frame_cache ();
+ print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+/* The "to_delete_record" method of target record-full. */
+
+static void
+record_delete (char *args, int from_tty)
+{
+ if (RECORD_IS_REPLAY)
+ {
+ if (!from_tty || query (_("Delete the log from this point forward "
+ "and begin to record the running message "
+ "at current PC?")))
+ record_list_release_following (record_list);
+ }
+ else
+ printf_unfiltered (_("Already at end of record list.\n"));
+}
+
+static void
+init_record_ops (void)
+{
+ record_ops.to_shortname = "record-full";
+ record_ops.to_longname = "Process record and replay target";
+ record_ops.to_doc =
+ "Log program while executing and replay execution from log.";
+ record_ops.to_open = record_open;
+ record_ops.to_close = record_close;
+ record_ops.to_resume = record_resume;
+ record_ops.to_wait = record_wait;
+ record_ops.to_disconnect = record_disconnect;
+ record_ops.to_detach = record_detach;
+ record_ops.to_mourn_inferior = record_mourn_inferior;
+ record_ops.to_kill = record_kill;
+ record_ops.to_create_inferior = find_default_create_inferior;
+ record_ops.to_store_registers = record_store_registers;
+ record_ops.to_xfer_partial = record_xfer_partial;
+ record_ops.to_insert_breakpoint = record_insert_breakpoint;
+ record_ops.to_remove_breakpoint = record_remove_breakpoint;
+ record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
+ record_ops.to_stopped_data_address = record_stopped_data_address;
+ record_ops.to_can_execute_reverse = record_can_execute_reverse;
+ record_ops.to_stratum = record_stratum;
+ /* Add bookmark target methods. */
+ record_ops.to_get_bookmark = record_get_bookmark;
+ record_ops.to_goto_bookmark = record_goto_bookmark;
+ record_ops.to_async = record_async;
+ record_ops.to_can_async_p = record_can_async_p;
+ record_ops.to_is_async_p = record_is_async_p;
+ record_ops.to_execution_direction = record_execution_direction;
+ record_ops.to_info_record = record_info;
+ record_ops.to_save_record = record_save;
+ record_ops.to_delete_record = record_delete;
+ record_ops.to_goto_record = record_goto;
+ record_ops.to_magic = OPS_MAGIC;
+}
+
+/* "to_resume" method for prec over corefile. */
+
+static void
+record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
+ enum gdb_signal signal)
+{
+ record_resume_step = step;
+ record_resumed = 1;
+ record_execution_dir = execution_direction;
+
+ /* We are about to start executing the inferior (or simulate it),
+ let's register it with the event loop. */
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+
+ /* Notify the event loop there's an event to wait for. */
+ mark_async_event_handler (record_async_inferior_event_token);
+ }
+}
+
+/* "to_kill" method for prec over corefile. */
+
+static void
+record_core_kill (struct target_ops *ops)
+{
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
+
+ unpush_target (&record_core_ops);
+}
+
+/* "to_fetch_registers" method for prec over corefile. */
+
+static void
+record_core_fetch_registers (struct target_ops *ops,
+ struct regcache *regcache,
+ int regno)
+{
+ if (regno < 0)
+ {
+ int num = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ for (i = 0; i < num; i ++)
+ regcache_raw_supply (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+ }
+ else
+ regcache_raw_supply (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+}
+
+/* "to_prepare_to_store" method for prec over corefile. */
+
+static void
+record_core_prepare_to_store (struct regcache *regcache)
+{
+}
+
+/* "to_store_registers" method for prec over corefile. */
+
+static void
+record_core_store_registers (struct target_ops *ops,
+ struct regcache *regcache,
+ int regno)
+{
+ if (record_gdb_operation_disable)
+ regcache_raw_collect (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+ else
+ error (_("You can't do that without a process to debug."));
+}
+
+/* "to_xfer_partial" method for prec over corefile. */
+
+static LONGEST
+record_core_xfer_partial (struct target_ops *ops, enum target_object object,
+ const char *annex, gdb_byte *readbuf,
+ const gdb_byte *writebuf, ULONGEST offset,
+ LONGEST len)
+{
+ if (object == TARGET_OBJECT_MEMORY)
+ {
+ if (record_gdb_operation_disable || !writebuf)
+ {
+ struct target_section *p;
+
+ for (p = record_core_start; p < record_core_end; p++)
+ {
+ if (offset >= p->addr)
+ {
+ struct record_core_buf_entry *entry;
+ ULONGEST sec_offset;
+
+ if (offset >= p->endaddr)
+ continue;
+
+ if (offset + len > p->endaddr)
+ len = p->endaddr - offset;
+
+ sec_offset = offset - p->addr;
+
+ /* Read readbuf or write writebuf p, offset, len. */
+ /* Check flags. */
+ if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+ || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+ {
+ if (readbuf)
+ memset (readbuf, 0, len);
+ return len;
+ }
+ /* Get record_core_buf_entry. */
+ for (entry = record_core_buf_list; entry;
+ entry = entry->prev)
+ if (entry->p == p)
+ break;
+ if (writebuf)
+ {
+ if (!entry)
+ {
+ /* Add a new entry. */
+ entry = (struct record_core_buf_entry *)
+ xmalloc (sizeof (struct record_core_buf_entry));
+ entry->p = p;
+ if (!bfd_malloc_and_get_section (p->bfd,
+ p->the_bfd_section,
+ &entry->buf))
+ {
+ xfree (entry);
+ return 0;
+ }
+ entry->prev = record_core_buf_list;
+ record_core_buf_list = entry;
+ }
+
+ memcpy (entry->buf + sec_offset, writebuf,
+ (size_t) len);
+ }
+ else
+ {
+ if (!entry)
+ return record_beneath_to_xfer_partial
+ (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+
+ memcpy (readbuf, entry->buf + sec_offset,
+ (size_t) len);
+ }
+
+ return len;
+ }
+ }
+
+ return -1;
+ }
+ else
+ error (_("You can't do that without a process to debug."));
+ }
+
+ return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+}
+
+/* "to_insert_breakpoint" method for prec over corefile. */
+
+static int
+record_core_insert_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ return 0;
+}
+
+/* "to_remove_breakpoint" method for prec over corefile. */
+
+static int
+record_core_remove_breakpoint (struct gdbarch *gdbarch,
+ struct bp_target_info *bp_tgt)
+{
+ return 0;
+}
+
+/* "to_has_execution" method for prec over corefile. */
+
+static int
+record_core_has_execution (struct target_ops *ops, ptid_t the_ptid)
+{
+ return 1;
+}
+
+static void
+init_record_core_ops (void)
+{
+ record_core_ops.to_shortname = "record-core";
+ record_core_ops.to_longname = "Process record and replay target";
+ record_core_ops.to_doc =
+ "Log program while executing and replay execution from log.";
+ record_core_ops.to_open = record_open;
+ record_core_ops.to_close = record_close;
+ record_core_ops.to_resume = record_core_resume;
+ record_core_ops.to_wait = record_wait;
+ record_core_ops.to_kill = record_core_kill;
+ record_core_ops.to_fetch_registers = record_core_fetch_registers;
+ record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
+ record_core_ops.to_store_registers = record_core_store_registers;
+ record_core_ops.to_xfer_partial = record_core_xfer_partial;
+ record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
+ record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
+ record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
+ record_core_ops.to_stopped_data_address = record_stopped_data_address;
+ record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
+ record_core_ops.to_has_execution = record_core_has_execution;
+ record_core_ops.to_stratum = record_stratum;
+ /* Add bookmark target methods. */
+ record_core_ops.to_get_bookmark = record_get_bookmark;
+ record_core_ops.to_goto_bookmark = record_goto_bookmark;
+ record_core_ops.to_async = record_async;
+ record_core_ops.to_can_async_p = record_can_async_p;
+ record_core_ops.to_is_async_p = record_is_async_p;
+ record_core_ops.to_execution_direction = record_execution_direction;
+ record_core_ops.to_info_record = record_info;
+ record_core_ops.to_save_record = record_save;
+ record_core_ops.to_delete_record = record_delete;
+ record_core_ops.to_goto_record = record_goto;
+ record_core_ops.to_magic = OPS_MAGIC;
+}
+
+/* Set upper limit of record log size. */
+
+static void
+set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c)
+{
+ if (record_insn_num > record_insn_max_num && record_insn_max_num)
+ {
+ /* Count down record_insn_num while releasing records from list. */
+ while (record_insn_num > record_insn_max_num)
+ {
+ record_list_release_first ();
+ record_insn_num--;
+ }
+ }
+}
+
+/* Record log save-file format
+ Version 1 (never released)
+
+ Header:
+ 4 bytes: magic number htonl(0x20090829).
+ NOTE: be sure to change whenever this file format changes!
+
+ Records:
+ record_end:
+ 1 byte: record type (record_end, see enum record_type).
+ record_reg:
+ 1 byte: record type (record_reg, see enum record_type).
+ 8 bytes: register id (network byte order).
+ MAX_REGISTER_SIZE bytes: register value.
+ record_mem:
+ 1 byte: record type (record_mem, see enum record_type).
+ 8 bytes: memory length (network byte order).
+ 8 bytes: memory address (network byte order).
+ n bytes: memory value (n == memory length).
+
+ Version 2
+ 4 bytes: magic number netorder32(0x20091016).
+ NOTE: be sure to change whenever this file format changes!
+
+ Records:
+ record_end:
+ 1 byte: record type (record_end, see enum record_type).
+ 4 bytes: signal
+ 4 bytes: instruction count
+ record_reg:
+ 1 byte: record type (record_reg, see enum record_type).
+ 4 bytes: register id (network byte order).
+ n bytes: register value (n == actual register size).
+ (eg. 4 bytes for x86 general registers).
+ record_mem:
+ 1 byte: record type (record_mem, see enum record_type).
+ 4 bytes: memory length (network byte order).
+ 8 bytes: memory address (network byte order).
+ n bytes: memory value (n == memory length).
+
+*/
+
+/* bfdcore_read -- read bytes from a core file section. */
+
+static inline void
+bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+ int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
+
+ if (ret)
+ *offset += len;
+ else
+ error (_("Failed to read %d bytes from core file %s ('%s')."),
+ len, bfd_get_filename (obfd),
+ bfd_errmsg (bfd_get_error ()));
+}
+
+static inline uint64_t
+netorder64 (uint64_t input)
+{
+ uint64_t ret;
+
+ store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
+ BFD_ENDIAN_BIG, input);
+ return ret;
+}
+
+static inline uint32_t
+netorder32 (uint32_t input)
+{
+ uint32_t ret;
+
+ store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
+ BFD_ENDIAN_BIG, input);
+ return ret;
+}
+
+static inline uint16_t
+netorder16 (uint16_t input)
+{
+ uint16_t ret;
+
+ store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
+ BFD_ENDIAN_BIG, input);
+ return ret;
+}
+
+/* Restore the execution log from a core_bfd file. */
+static void
+record_restore (void)
+{
+ uint32_t magic;
+ struct cleanup *old_cleanups;
+ struct record_entry *rec;
+ asection *osec;
+ uint32_t osec_size;
+ int bfd_offset = 0;
+ struct regcache *regcache;
+
+ /* We restore the execution log from the open core bfd,
+ if there is one. */
+ if (core_bfd == NULL)
+ return;
+
+ /* "record_restore" can only be called when record list is empty. */
+ gdb_assert (record_first.next == NULL);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n");
+
+ /* Now need to find our special note section. */
+ osec = bfd_get_section_by_name (core_bfd, "null0");
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n",
+ osec ? "succeeded" : "failed");
+ if (osec == NULL)
+ return;
+ osec_size = bfd_section_size (core_bfd, osec);
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec));
+
+ /* Check the magic code. */
+ bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset);
+ if (magic != RECORD_FILE_MAGIC)
+ error (_("Version mis-match or file format error in core file %s."),
+ bfd_get_filename (core_bfd));
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading 4-byte magic cookie "
+ "RECORD_FILE_MAGIC (0x%s)\n",
+ phex_nz (netorder32 (magic), 4));
+
+ /* Restore the entries in recfd into record_arch_list_head and
+ record_arch_list_tail. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ record_insn_num = 0;
+ old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
+ regcache = get_current_regcache ();
+
+ while (1)
+ {
+ uint8_t rectype;
+ uint32_t regnum, len, signal, count;
+ uint64_t addr;
+
+ /* We are finished when offset reaches osec_size. */
+ if (bfd_offset >= osec_size)
+ break;
+ bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset);
+
+ switch (rectype)
+ {
+ case record_reg: /* reg */
+ /* Get register number to regnum. */
+ bfdcore_read (core_bfd, osec, ®num,
+ sizeof (regnum), &bfd_offset);
+ regnum = netorder32 (regnum);
+
+ rec = record_reg_alloc (regcache, regnum);
+
+ /* Get val. */
+ bfdcore_read (core_bfd, osec, record_get_loc (rec),
+ rec->u.reg.len, &bfd_offset);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading register %d (1 "
+ "plus %lu plus %d bytes)\n",
+ rec->u.reg.num,
+ (unsigned long) sizeof (regnum),
+ rec->u.reg.len);
+ break;
+
+ case record_mem: /* mem */
+ /* Get len. */
+ bfdcore_read (core_bfd, osec, &len,
+ sizeof (len), &bfd_offset);
+ len = netorder32 (len);
+
+ /* Get addr. */
+ bfdcore_read (core_bfd, osec, &addr,
+ sizeof (addr), &bfd_offset);
+ addr = netorder64 (addr);
+
+ rec = record_mem_alloc (addr, len);
+
+ /* Get val. */
+ bfdcore_read (core_bfd, osec, record_get_loc (rec),
+ rec->u.mem.len, &bfd_offset);
+
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading memory %s (1 plus "
+ "%lu plus %lu plus %d bytes)\n",
+ paddress (get_current_arch (),
+ rec->u.mem.addr),
+ (unsigned long) sizeof (addr),
+ (unsigned long) sizeof (len),
+ rec->u.mem.len);
+ break;
+
+ case record_end: /* end */
+ rec = record_end_alloc ();
+ record_insn_num ++;
+
+ /* Get signal value. */
+ bfdcore_read (core_bfd, osec, &signal,
+ sizeof (signal), &bfd_offset);
+ signal = netorder32 (signal);
+ rec->u.end.sigval = signal;
+
+ /* Get insn count. */
+ bfdcore_read (core_bfd, osec, &count,
+ sizeof (count), &bfd_offset);
+ count = netorder32 (count);
+ rec->u.end.insn_num = count;
+ record_insn_count = count + 1;
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Reading record_end (1 + "
+ "%lu + %lu bytes), offset == %s\n",
+ (unsigned long) sizeof (signal),
+ (unsigned long) sizeof (count),
+ paddress (get_current_arch (),
+ bfd_offset));
+ break;
+
+ default:
+ error (_("Bad entry type in core file %s."),
+ bfd_get_filename (core_bfd));
+ break;
+ }
+
+ /* Add rec to record arch list. */
+ record_arch_list_add (rec);
+ }
+
+ discard_cleanups (old_cleanups);
+
+ /* Add record_arch_list_head to the end of record list. */
+ record_first.next = record_arch_list_head;
+ record_arch_list_head->prev = &record_first;
+ record_arch_list_tail->next = NULL;
+ record_list = &record_first;
+
+ /* Update record_insn_max_num. */
+ if (record_insn_num > record_insn_max_num)
+ {
+ record_insn_max_num = record_insn_num;
+ warning (_("Auto increase record/replay buffer limit to %d."),
+ record_insn_max_num);
+ }
+
+ /* Succeeded. */
+ printf_filtered (_("Restored records from core file %s.\n"),
+ bfd_get_filename (core_bfd));
+
+ print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
+/* Restore the execution log from a file. We use a modified elf
+ corefile format, with an extra section for our data. */
+
+static void
+cmd_record_restore (char *args, int from_tty)
+{
+ core_file_command (args, from_tty);
+ record_open (args, from_tty);
+}
+
+/* Alias for "target record". */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+ if (args != NULL && *args != 0)
+ error (_("Invalid argument."));
+
+ execute_command ("target record-full", from_tty);
+}
+
+/* bfdcore_write -- write bytes into a core file section. */
+
+static inline void
+bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
+{
+ int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
+
+ if (ret)
+ *offset += len;
+ else
+ error (_("Failed to write %d bytes to core file %s ('%s')."),
+ len, bfd_get_filename (obfd),
+ bfd_errmsg (bfd_get_error ()));
+}
+
+static void
+record_save_cleanups (void *data)
+{
+ bfd *obfd = data;
+ char *pathname = xstrdup (bfd_get_filename (obfd));
+
+ gdb_bfd_unref (obfd);
+ unlink (pathname);
+ xfree (pathname);
+}
+
+/* Save the execution log to a file. We use a modified elf corefile
+ format, with an extra section for our data. */
+
+static void
+record_save (char *recfilename, int from_tty)
+{
+ struct record_entry *cur_record_list;
+ uint32_t magic;
+ struct regcache *regcache;
+ struct gdbarch *gdbarch;
+ struct cleanup *old_cleanups;
+ struct cleanup *set_cleanups;
+ bfd *obfd;
+ int save_size = 0;
+ asection *osec = NULL;
+ int bfd_offset = 0;
+
+ /* Open the save file. */
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n",
+ recfilename);
+
+ /* Open the output file. */
+ obfd = create_gcore_bfd (recfilename);
+ old_cleanups = make_cleanup (record_save_cleanups, obfd);
+
+ /* Save the current record entry to "cur_record_list". */
+ cur_record_list = record_list;
+
+ /* Get the values of regcache and gdbarch. */
+ regcache = get_current_regcache ();
+ gdbarch = get_regcache_arch (regcache);
+
+ /* Disable the GDB operation record. */
+ set_cleanups = record_gdb_operation_disable_set ();
+
+ /* Reverse execute to the begin of record list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == &record_first)
+ break;
+
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ /* Compute the size needed for the extra bfd section. */
+ save_size = 4; /* magic cookie */
+ for (record_list = record_first.next; record_list;
+ record_list = record_list->next)
+ switch (record_list->type)
+ {
+ case record_end:
+ save_size += 1 + 4 + 4;
+ break;
+ case record_reg:
+ save_size += 1 + 4 + record_list->u.reg.len;
+ break;
+ case record_mem:
+ save_size += 1 + 4 + 8 + record_list->u.mem.len;
+ break;
+ }
+
+ /* Make the new bfd section. */
+ osec = bfd_make_section_anyway_with_flags (obfd, "precord",
+ SEC_HAS_CONTENTS
+ | SEC_READONLY);
+ if (osec == NULL)
+ error (_("Failed to create 'precord' section for corefile %s: %s"),
+ recfilename,
+ bfd_errmsg (bfd_get_error ()));
+ bfd_set_section_size (obfd, osec, save_size);
+ bfd_set_section_vma (obfd, osec, 0);
+ bfd_set_section_alignment (obfd, osec, 0);
+ bfd_section_lma (obfd, osec) = 0;
+
+ /* Save corefile state. */
+ write_gcore_file (obfd);
+
+ /* Write out the record log. */
+ /* Write the magic code. */
+ magic = RECORD_FILE_MAGIC;
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing 4-byte magic cookie "
+ "RECORD_FILE_MAGIC (0x%s)\n",
+ phex_nz (magic, 4));
+ bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset);
+
+ /* Save the entries to recfd and forward execute to the end of
+ record list. */
+ record_list = &record_first;
+ while (1)
+ {
+ /* Save entry. */
+ if (record_list != &record_first)
+ {
+ uint8_t type;
+ uint32_t regnum, len, signal, count;
+ uint64_t addr;
+
+ type = record_list->type;
+ bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset);
+
+ switch (record_list->type)
+ {
+ case record_reg: /* reg */
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing register %d (1 "
+ "plus %lu plus %d bytes)\n",
+ record_list->u.reg.num,
+ (unsigned long) sizeof (regnum),
+ record_list->u.reg.len);
+
+ /* Write regnum. */
+ regnum = netorder32 (record_list->u.reg.num);
+ bfdcore_write (obfd, osec, ®num,
+ sizeof (regnum), &bfd_offset);
+
+ /* Write regval. */
+ bfdcore_write (obfd, osec, record_get_loc (record_list),
+ record_list->u.reg.len, &bfd_offset);
+ break;
+
+ case record_mem: /* mem */
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing memory %s (1 plus "
+ "%lu plus %lu plus %d bytes)\n",
+ paddress (gdbarch,
+ record_list->u.mem.addr),
+ (unsigned long) sizeof (addr),
+ (unsigned long) sizeof (len),
+ record_list->u.mem.len);
+
+ /* Write memlen. */
+ len = netorder32 (record_list->u.mem.len);
+ bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset);
+
+ /* Write memaddr. */
+ addr = netorder64 (record_list->u.mem.addr);
+ bfdcore_write (obfd, osec, &addr,
+ sizeof (addr), &bfd_offset);
+
+ /* Write memval. */
+ bfdcore_write (obfd, osec, record_get_loc (record_list),
+ record_list->u.mem.len, &bfd_offset);
+ break;
+
+ case record_end:
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog,
+ " Writing record_end (1 + "
+ "%lu + %lu bytes)\n",
+ (unsigned long) sizeof (signal),
+ (unsigned long) sizeof (count));
+ /* Write signal value. */
+ signal = netorder32 (record_list->u.end.sigval);
+ bfdcore_write (obfd, osec, &signal,
+ sizeof (signal), &bfd_offset);
+
+ /* Write insn count. */
+ count = netorder32 (record_list->u.end.insn_num);
+ bfdcore_write (obfd, osec, &count,
+ sizeof (count), &bfd_offset);
+ break;
+ }
+ }
+
+ /* Execute entry. */
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->next)
+ record_list = record_list->next;
+ else
+ break;
+ }
+
+ /* Reverse execute to cur_record_list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == cur_record_list)
+ break;
+
+ record_exec_insn (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ do_cleanups (set_cleanups);
+ gdb_bfd_unref (obfd);
+ discard_cleanups (old_cleanups);
+
+ /* Succeeded. */
+ printf_filtered (_("Saved core file %s with execution log.\n"),
+ recfilename);
+}
+
+/* The "set record full" command. */
+
+static void
+set_record_full_command (char *args, int from_tty)
+{
+ printf_unfiltered (_("\"set record full\" must be followed "
+ "by an apporpriate subcommand.\n"));
+ help_list (set_record_full_cmdlist, "set record full ", all_commands,
+ gdb_stdout);
+}
+
+/* The "show record full" command. */
+
+static void
+show_record_full_command (char *args, int from_tty)
+{
+ cmd_show_list (show_record_full_cmdlist, from_tty, "");
+}
+
+/* Provide a prototype to silence -Wmissing-prototypes. */
+extern initialize_file_ftype _initialize_record_full;
+
+void
+_initialize_record_full (void)
+{
+ struct cmd_list_element *c;
+
+ /* Init record_first. */
+ record_first.prev = NULL;
+ record_first.next = NULL;
+ record_first.type = record_end;
+
+ init_record_ops ();
+ add_target (&record_ops);
+ add_deprecated_target_alias (&record_ops, "record");
+ init_record_core_ops ();
+ add_target (&record_core_ops);
+
+ add_cmd ("full", class_obscure, cmd_record_start,
+ _("Start full execution recording."),
+ &record_cmdlist);
+
+ c = add_cmd ("restore", class_obscure, cmd_record_restore,
+ _("Restore the execution log from a file.\n\
+Argument is filename. File must be created with 'record save'."),
+ &record_cmdlist);
+ set_cmd_completer (c, filename_completer);
+
+ add_prefix_cmd ("full", class_support, set_record_full_command,
+ _("Set record options"), &set_record_full_cmdlist,
+ "set record full ", 0, &set_record_cmdlist);
+
+ add_prefix_cmd ("full", class_support, show_record_full_command,
+ _("Show record options"), &show_record_full_cmdlist,
+ "show record full ", 0, &show_record_cmdlist);
+
+ /* Record instructions number limit command. */
+ add_setshow_boolean_cmd ("stop-at-limit", no_class,
+ &record_stop_at_limit, _("\
+Set whether record/replay stops when record/replay buffer becomes full."), _("\
+Show whether record/replay stops when record/replay buffer becomes full."),
+ _("Default is ON.\n\
+When ON, if the record/replay buffer becomes full, ask user what to do.\n\
+When OFF, if the record/replay buffer becomes full,\n\
+delete the oldest recorded instruction to make room for each new one."),
+ NULL, NULL,
+ &set_record_full_cmdlist, &show_record_full_cmdlist);
+
+ c = add_alias_cmd ("stop-at-limit",
+ "full stop-at-limit", no_class, 1,
+ &set_record_cmdlist);
+ deprecate_cmd (c, "set record full stop-at-limit");
+
+ c = add_alias_cmd ("stop-at-limit",
+ "full stop-at-limit", no_class, 1,
+ &show_record_cmdlist);
+ deprecate_cmd (c, "show record full stop-at-limit");
+
+ add_setshow_uinteger_cmd ("insn-number-max", no_class,
+ &record_insn_max_num,
+ _("Set record/replay buffer limit."),
+ _("Show record/replay buffer limit."), _("\
+Set the maximum number of instructions to be stored in the\n\
+record/replay buffer. Zero means unlimited. Default is 200000."),
+ set_record_insn_max_num,
+ NULL, &set_record_full_cmdlist,
+ &show_record_full_cmdlist);
+
+ c = add_alias_cmd ("insn-number-max",
+ "full insn-number-max", no_class, 1,
+ &set_record_cmdlist);
+ deprecate_cmd (c, "set record full insn-number-max");
+
+ c = add_alias_cmd ("insn-number-max",
+ "full insn-number-max", no_class, 1,
+ &show_record_cmdlist);
+ deprecate_cmd (c, "show record full insn-number-max");
+
+ add_setshow_boolean_cmd ("memory-query", no_class,
+ &record_memory_query, _("\
+Set whether query if PREC cannot record memory change of next instruction."),
+ _("\
+Show whether query if PREC cannot record memory change of next instruction."),
+ _("\
+Default is OFF.\n\
+When ON, query if PREC cannot record memory change of next instruction."),
+ NULL, NULL,
+ &set_record_full_cmdlist, &show_record_full_cmdlist);
+
+ c = add_alias_cmd ("memory-query",
+ "full memory-query", no_class, 1,
+ &set_record_cmdlist);
+ deprecate_cmd (c, "set record full memory-query");
+
+ c = add_alias_cmd ("memory-query",
+ "full memory-query", no_class, 1,
+ &show_record_cmdlist);
+ deprecate_cmd (c, "show record full memory-query");
+}
diff --git a/gdb/record-full.h b/gdb/record-full.h
new file mode 100644
index 0000000..08508de
--- /dev/null
+++ b/gdb/record-full.h
@@ -0,0 +1,38 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+ Copyright (C) 2008-2013 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _RECORD_FULL_H_
+#define _RECORD_FULL_H_
+
+#include "record.h"
+
+extern int record_memory_query;
+
+extern int record_arch_list_add_reg (struct regcache *regcache, int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (void);
+extern struct cleanup *record_gdb_operation_disable_set (void);
+
+/* Wrapper for target_read_memory that prints a debug message if
+ reading memory fails. */
+extern int record_read_memory (struct gdbarch *gdbarch,
+ CORE_ADDR memaddr, gdb_byte *myaddr,
+ ssize_t len);
+
+#endif /* _RECORD_FULL_H_ */
diff --git a/gdb/record.c b/gdb/record.c
index 1a68738..cfa2637 100644
--- a/gdb/record.c
+++ b/gdb/record.c
@@ -18,2198 +18,29 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "defs.h"
+#include "record.h"
#include "gdbcmd.h"
-#include "regcache.h"
-#include "gdbthread.h"
-#include "event-top.h"
-#include "exceptions.h"
#include "completer.h"
-#include "arch-utils.h"
-#include "gdbcore.h"
-#include "exec.h"
-#include "record.h"
-#include "elf-bfd.h"
-#include "gcore.h"
-#include "event-loop.h"
-#include "inf-loop.h"
-#include "gdb_bfd.h"
#include "observer.h"
-
-#include <signal.h>
-
-/* This module implements "target record", also known as "process
- record and replay". This target sits on top of a "normal" target
- (a target that "has execution"), and provides a record and replay
- functionality, including reverse debugging.
-
- Target record has two modes: recording, and replaying.
-
- In record mode, we intercept the to_resume and to_wait methods.
- Whenever gdb resumes the target, we run the target in single step
- mode, and we build up an execution log in which, for each executed
- instruction, we record all changes in memory and register state.
- This is invisible to the user, to whom it just looks like an
- ordinary debugging session (except for performance degredation).
-
- In replay mode, instead of actually letting the inferior run as a
- process, we simulate its execution by playing back the recorded
- execution log. For each instruction in the log, we simulate the
- instruction's side effects by duplicating the changes that it would
- have made on memory and registers. */
-
-#define DEFAULT_RECORD_INSN_MAX_NUM 200000
-
-#define RECORD_IS_REPLAY \
- (record_list->next || execution_direction == EXEC_REVERSE)
-
-#define RECORD_FILE_MAGIC netorder32(0x20091016)
-
-/* These are the core structs of the process record functionality.
-
- A record_entry is a record of the value change of a register
- ("record_reg") or a part of memory ("record_mem"). And each
- instruction must have a struct record_entry ("record_end") that
- indicates that this is the last struct record_entry of this
- instruction.
-
- Each struct record_entry is linked to "record_list" by "prev" and
- "next" pointers. */
-
-struct record_mem_entry
-{
- CORE_ADDR addr;
- int len;
- /* Set this flag if target memory for this entry
- can no longer be accessed. */
- int mem_entry_not_accessible;
- union
- {
- gdb_byte *ptr;
- gdb_byte buf[sizeof (gdb_byte *)];
- } u;
-};
-
-struct record_reg_entry
-{
- unsigned short num;
- unsigned short len;
- union
- {
- gdb_byte *ptr;
- gdb_byte buf[2 * sizeof (gdb_byte *)];
- } u;
-};
-
-struct record_end_entry
-{
- enum gdb_signal sigval;
- ULONGEST insn_num;
-};
-
-enum record_type
-{
- record_end = 0,
- record_reg,
- record_mem
-};
-
-/* This is the data structure that makes up the execution log.
-
- The execution log consists of a single linked list of entries
- of type "struct record_entry". It is doubly linked so that it
- can be traversed in either direction.
-
- The start of the list is anchored by a struct called
- "record_first". The pointer "record_list" either points to the
- last entry that was added to the list (in record mode), or to the
- next entry in the list that will be executed (in replay mode).
-
- Each list element (struct record_entry), in addition to next and
- prev pointers, consists of a union of three entry types: mem, reg,
- and end. A field called "type" determines which entry type is
- represented by a given list element.
-
- Each instruction that is added to the execution log is represented
- by a variable number of list elements ('entries'). The instruction
- will have one "reg" entry for each register that is changed by
- executing the instruction (including the PC in every case). It
- will also have one "mem" entry for each memory change. Finally,
- each instruction will have an "end" entry that separates it from
- the changes associated with the next instruction. */
-
-struct record_entry
-{
- struct record_entry *prev;
- struct record_entry *next;
- enum record_type type;
- union
- {
- /* reg */
- struct record_reg_entry reg;
- /* mem */
- struct record_mem_entry mem;
- /* end */
- struct record_end_entry end;
- } u;
-};
+#include "inferior.h"
/* This is the debug switch for process record. */
unsigned int record_debug = 0;
-/* If true, query if PREC cannot record memory
- change of next instruction. */
-int record_memory_query = 0;
-
-struct record_core_buf_entry
-{
- struct record_core_buf_entry *prev;
- struct target_section *p;
- bfd_byte *buf;
-};
-
-/* Record buf with core target. */
-static gdb_byte *record_core_regbuf = NULL;
-static struct target_section *record_core_start;
-static struct target_section *record_core_end;
-static struct record_core_buf_entry *record_core_buf_list = NULL;
-
-/* The following variables are used for managing the linked list that
- represents the execution log.
-
- record_first is the anchor that holds down the beginning of the list.
-
- record_list serves two functions:
- 1) In record mode, it anchors the end of the list.
- 2) In replay mode, it traverses the list and points to
- the next instruction that must be emulated.
-
- record_arch_list_head and record_arch_list_tail are used to manage
- a separate list, which is used to build up the change elements of
- the currently executing instruction during record mode. When this
- instruction has been completely annotated in the "arch list", it
- will be appended to the main execution log. */
-
-static struct record_entry record_first;
-static struct record_entry *record_list = &record_first;
-static struct record_entry *record_arch_list_head = NULL;
-static struct record_entry *record_arch_list_tail = NULL;
-
-/* 1 ask user. 0 auto delete the last struct record_entry. */
-static int record_stop_at_limit = 1;
-/* Maximum allowed number of insns in execution log. */
-static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
-/* Actual count of insns presently in execution log. */
-static int record_insn_num = 0;
-/* Count of insns logged so far (may be larger
- than count of insns presently in execution log). */
-static ULONGEST record_insn_count;
-
-/* The target_ops of process record. */
-static struct target_ops record_ops;
-static struct target_ops record_core_ops;
-
-/* The beneath function pointers. */
-static struct target_ops *record_beneath_to_resume_ops;
-static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
- enum gdb_signal);
-static struct target_ops *record_beneath_to_wait_ops;
-static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
- struct target_waitstatus *,
- int);
-static struct target_ops *record_beneath_to_store_registers_ops;
-static void (*record_beneath_to_store_registers) (struct target_ops *,
- struct regcache *,
- int regno);
-static struct target_ops *record_beneath_to_xfer_partial_ops;
-static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops,
- enum target_object object,
- const char *annex,
- gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset,
- LONGEST len);
-static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *,
- struct bp_target_info *);
-static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
- struct bp_target_info *);
-static int (*record_beneath_to_stopped_by_watchpoint) (void);
-static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
- CORE_ADDR *);
-static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *);
-
-/* Alloc and free functions for record_reg, record_mem, and record_end
- entries. */
-
-/* Alloc a record_reg record entry. */
-
-static inline struct record_entry *
-record_reg_alloc (struct regcache *regcache, int regnum)
-{
- struct record_entry *rec;
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
-
- rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
- rec->type = record_reg;
- rec->u.reg.num = regnum;
- rec->u.reg.len = register_size (gdbarch, regnum);
- if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
- rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
-
- return rec;
-}
-
-/* Free a record_reg record entry. */
-
-static inline void
-record_reg_release (struct record_entry *rec)
-{
- gdb_assert (rec->type == record_reg);
- if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
- xfree (rec->u.reg.u.ptr);
- xfree (rec);
-}
-
-/* Alloc a record_mem record entry. */
-
-static inline struct record_entry *
-record_mem_alloc (CORE_ADDR addr, int len)
-{
- struct record_entry *rec;
-
- rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
- rec->type = record_mem;
- rec->u.mem.addr = addr;
- rec->u.mem.len = len;
- if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
- rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
-
- return rec;
-}
-
-/* Free a record_mem record entry. */
-
-static inline void
-record_mem_release (struct record_entry *rec)
-{
- gdb_assert (rec->type == record_mem);
- if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
- xfree (rec->u.mem.u.ptr);
- xfree (rec);
-}
-
-/* Alloc a record_end record entry. */
-
-static inline struct record_entry *
-record_end_alloc (void)
-{
- struct record_entry *rec;
-
- rec = (struct record_entry *) xcalloc (1, sizeof (struct record_entry));
- rec->type = record_end;
-
- return rec;
-}
-
-/* Free a record_end record entry. */
-
-static inline void
-record_end_release (struct record_entry *rec)
-{
- xfree (rec);
-}
-
-/* Free one record entry, any type.
- Return entry->type, in case caller wants to know. */
-
-static inline enum record_type
-record_entry_release (struct record_entry *rec)
-{
- enum record_type type = rec->type;
-
- switch (type) {
- case record_reg:
- record_reg_release (rec);
- break;
- case record_mem:
- record_mem_release (rec);
- break;
- case record_end:
- record_end_release (rec);
- break;
- }
- return type;
-}
+struct cmd_list_element *record_cmdlist = NULL;
+struct cmd_list_element *set_record_cmdlist = NULL;
+struct cmd_list_element *show_record_cmdlist = NULL;
+struct cmd_list_element *info_record_cmdlist = NULL;
-/* Free all record entries in list pointed to by REC. */
+/* Check that recording is active. Throw an error, if it isn't. */
static void
-record_list_release (struct record_entry *rec)
+require_record (void)
{
- if (!rec)
+ if (RECORD_IS_USED)
return;
- while (rec->next)
- rec = rec->next;
-
- while (rec->prev)
- {
- rec = rec->prev;
- record_entry_release (rec->next);
- }
-
- if (rec == &record_first)
- {
- record_insn_num = 0;
- record_first.next = NULL;
- }
- else
- record_entry_release (rec);
-}
-
-/* Free all record entries forward of the given list position. */
-
-static void
-record_list_release_following (struct record_entry *rec)
-{
- struct record_entry *tmp = rec->next;
-
- rec->next = NULL;
- while (tmp)
- {
- rec = tmp->next;
- if (record_entry_release (tmp) == record_end)
- {
- record_insn_num--;
- record_insn_count--;
- }
- tmp = rec;
- }
-}
-
-/* Delete the first instruction from the beginning of the log, to make
- room for adding a new instruction at the end of the log.
-
- Note -- this function does not modify record_insn_num. */
-
-static void
-record_list_release_first (void)
-{
- struct record_entry *tmp;
-
- if (!record_first.next)
- return;
-
- /* Loop until a record_end. */
- while (1)
- {
- /* Cut record_first.next out of the linked list. */
- tmp = record_first.next;
- record_first.next = tmp->next;
- tmp->next->prev = &record_first;
-
- /* tmp is now isolated, and can be deleted. */
- if (record_entry_release (tmp) == record_end)
- break; /* End loop at first record_end. */
-
- if (!record_first.next)
- {
- gdb_assert (record_insn_num == 1);
- break; /* End loop when list is empty. */
- }
- }
-}
-
-/* Add a struct record_entry to record_arch_list. */
-
-static void
-record_arch_list_add (struct record_entry *rec)
-{
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_arch_list_add %s.\n",
- host_address_to_string (rec));
-
- if (record_arch_list_tail)
- {
- record_arch_list_tail->next = rec;
- rec->prev = record_arch_list_tail;
- record_arch_list_tail = rec;
- }
- else
- {
- record_arch_list_head = rec;
- record_arch_list_tail = rec;
- }
-}
-
-/* Return the value storage location of a record entry. */
-static inline gdb_byte *
-record_get_loc (struct record_entry *rec)
-{
- switch (rec->type) {
- case record_mem:
- if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
- return rec->u.mem.u.ptr;
- else
- return rec->u.mem.u.buf;
- case record_reg:
- if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
- return rec->u.reg.u.ptr;
- else
- return rec->u.reg.u.buf;
- case record_end:
- default:
- gdb_assert_not_reached ("unexpected record_entry type");
- return NULL;
- }
-}
-
-/* Record the value of a register NUM to record_arch_list. */
-
-int
-record_arch_list_add_reg (struct regcache *regcache, int regnum)
-{
- struct record_entry *rec;
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: add register num = %d to "
- "record list.\n",
- regnum);
-
- rec = record_reg_alloc (regcache, regnum);
-
- regcache_raw_read (regcache, regnum, record_get_loc (rec));
-
- record_arch_list_add (rec);
-
- return 0;
-}
-
-int
-record_read_memory (struct gdbarch *gdbarch,
- CORE_ADDR memaddr, gdb_byte *myaddr,
- ssize_t len)
-{
- int ret = target_read_memory (memaddr, myaddr, len);
-
- if (ret && record_debug)
- printf_unfiltered (_("Process record: error reading memory "
- "at addr %s len = %ld.\n"),
- paddress (gdbarch, memaddr), (long) len);
- return ret;
-}
-
-/* Record the value of a region of memory whose address is ADDR and
- length is LEN to record_arch_list. */
-
-int
-record_arch_list_add_mem (CORE_ADDR addr, int len)
-{
- struct record_entry *rec;
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: add mem addr = %s len = %d to "
- "record list.\n",
- paddress (target_gdbarch (), addr), len);
-
- if (!addr) /* FIXME: Why? Some arch must permit it... */
- return 0;
-
- rec = record_mem_alloc (addr, len);
-
- if (record_read_memory (target_gdbarch (), addr, record_get_loc (rec), len))
- {
- record_mem_release (rec);
- return -1;
- }
-
- record_arch_list_add (rec);
-
- return 0;
-}
-
-/* Add a record_end type struct record_entry to record_arch_list. */
-
-int
-record_arch_list_add_end (void)
-{
- struct record_entry *rec;
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: add end to arch list.\n");
-
- rec = record_end_alloc ();
- rec->u.end.sigval = GDB_SIGNAL_0;
- rec->u.end.insn_num = ++record_insn_count;
-
- record_arch_list_add (rec);
-
- return 0;
-}
-
-static void
-record_check_insn_num (int set_terminal)
-{
- if (record_insn_max_num)
- {
- gdb_assert (record_insn_num <= record_insn_max_num);
- if (record_insn_num == record_insn_max_num)
- {
- /* Ask user what to do. */
- if (record_stop_at_limit)
- {
- int q;
-
- if (set_terminal)
- target_terminal_ours ();
- q = yquery (_("Do you want to auto delete previous execution "
- "log entries when record/replay buffer becomes "
- "full (record stop-at-limit)?"));
- if (set_terminal)
- target_terminal_inferior ();
- if (q)
- record_stop_at_limit = 0;
- else
- error (_("Process record: stopped by user."));
- }
- }
- }
-}
-
-static void
-record_arch_list_cleanups (void *ignore)
-{
- record_list_release (record_arch_list_tail);
-}
-
-/* Before inferior step (when GDB record the running message, inferior
- only can step), GDB will call this function to record the values to
- record_list. This function will call gdbarch_process_record to
- record the running message of inferior and set them to
- record_arch_list, and add it to record_list. */
-
-static int
-record_message (struct regcache *regcache, enum gdb_signal signal)
-{
- int ret;
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
- struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
-
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
-
- /* Check record_insn_num. */
- record_check_insn_num (1);
-
- /* If gdb sends a signal value to target_resume,
- save it in the 'end' field of the previous instruction.
-
- Maybe process record should record what really happened,
- rather than what gdb pretends has happened.
-
- So if Linux delivered the signal to the child process during
- the record mode, we will record it and deliver it again in
- the replay mode.
-
- If user says "ignore this signal" during the record mode, then
- it will be ignored again during the replay mode (no matter if
- the user says something different, like "deliver this signal"
- during the replay mode).
-
- User should understand that nothing he does during the replay
- mode will change the behavior of the child. If he tries,
- then that is a user error.
-
- But we should still deliver the signal to gdb during the replay,
- if we delivered it during the recording. Therefore we should
- record the signal during record_wait, not record_resume. */
- if (record_list != &record_first) /* FIXME better way to check */
- {
- gdb_assert (record_list->type == record_end);
- record_list->u.end.sigval = signal;
- }
-
- if (signal == GDB_SIGNAL_0
- || !gdbarch_process_record_signal_p (gdbarch))
- ret = gdbarch_process_record (gdbarch,
- regcache,
- regcache_read_pc (regcache));
- else
- ret = gdbarch_process_record_signal (gdbarch,
- regcache,
- signal);
-
- if (ret > 0)
- error (_("Process record: inferior program stopped."));
- if (ret < 0)
- error (_("Process record: failed to record execution log."));
-
- discard_cleanups (old_cleanups);
-
- record_list->next = record_arch_list_head;
- record_arch_list_head->prev = record_list;
- record_list = record_arch_list_tail;
-
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
-
- return 1;
-}
-
-struct record_message_args {
- struct regcache *regcache;
- enum gdb_signal signal;
-};
-
-static int
-record_message_wrapper (void *args)
-{
- struct record_message_args *record_args = args;
-
- return record_message (record_args->regcache, record_args->signal);
-}
-
-static int
-record_message_wrapper_safe (struct regcache *regcache,
- enum gdb_signal signal)
-{
- struct record_message_args args;
-
- args.regcache = regcache;
- args.signal = signal;
-
- return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL);
-}
-
-/* Set to 1 if record_store_registers and record_xfer_partial
- doesn't need record. */
-
-static int record_gdb_operation_disable = 0;
-
-struct cleanup *
-record_gdb_operation_disable_set (void)
-{
- struct cleanup *old_cleanups = NULL;
-
- old_cleanups =
- make_cleanup_restore_integer (&record_gdb_operation_disable);
- record_gdb_operation_disable = 1;
-
- return old_cleanups;
-}
-
-/* Flag set to TRUE for target_stopped_by_watchpoint. */
-static int record_hw_watchpoint = 0;
-
-/* Execute one instruction from the record log. Each instruction in
- the log will be represented by an arbitrary sequence of register
- entries and memory entries, followed by an 'end' entry. */
-
-static inline void
-record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
- struct record_entry *entry)
-{
- switch (entry->type)
- {
- case record_reg: /* reg */
- {
- gdb_byte reg[MAX_REGISTER_SIZE];
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_reg %s to "
- "inferior num = %d.\n",
- host_address_to_string (entry),
- entry->u.reg.num);
-
- regcache_cooked_read (regcache, entry->u.reg.num, reg);
- regcache_cooked_write (regcache, entry->u.reg.num,
- record_get_loc (entry));
- memcpy (record_get_loc (entry), reg, entry->u.reg.len);
- }
- break;
-
- case record_mem: /* mem */
- {
- /* Nothing to do if the entry is flagged not_accessible. */
- if (!entry->u.mem.mem_entry_not_accessible)
- {
- gdb_byte *mem = alloca (entry->u.mem.len);
-
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_mem %s to "
- "inferior addr = %s len = %d.\n",
- host_address_to_string (entry),
- paddress (gdbarch, entry->u.mem.addr),
- entry->u.mem.len);
-
- if (record_read_memory (gdbarch,
- entry->u.mem.addr, mem, entry->u.mem.len))
- entry->u.mem.mem_entry_not_accessible = 1;
- else
- {
- if (target_write_memory (entry->u.mem.addr,
- record_get_loc (entry),
- entry->u.mem.len))
- {
- entry->u.mem.mem_entry_not_accessible = 1;
- if (record_debug)
- warning (_("Process record: error writing memory at "
- "addr = %s len = %d."),
- paddress (gdbarch, entry->u.mem.addr),
- entry->u.mem.len);
- }
- else
- {
- memcpy (record_get_loc (entry), mem, entry->u.mem.len);
-
- /* We've changed memory --- check if a hardware
- watchpoint should trap. Note that this
- presently assumes the target beneath supports
- continuable watchpoints. On non-continuable
- watchpoints target, we'll want to check this
- _before_ actually doing the memory change, and
- not doing the change at all if the watchpoint
- traps. */
- if (hardware_watchpoint_inserted_in_range
- (get_regcache_aspace (regcache),
- entry->u.mem.addr, entry->u.mem.len))
- record_hw_watchpoint = 1;
- }
- }
- }
- }
- break;
- }
-}
-
-static struct target_ops *tmp_to_resume_ops;
-static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
- enum gdb_signal);
-static struct target_ops *tmp_to_wait_ops;
-static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
- struct target_waitstatus *,
- int);
-static struct target_ops *tmp_to_store_registers_ops;
-static void (*tmp_to_store_registers) (struct target_ops *,
- struct regcache *,
- int regno);
-static struct target_ops *tmp_to_xfer_partial_ops;
-static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
- enum target_object object,
- const char *annex,
- gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset,
- LONGEST len);
-static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
- struct bp_target_info *);
-static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
- struct bp_target_info *);
-static int (*tmp_to_stopped_by_watchpoint) (void);
-static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
-static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
-static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *);
-
-static void record_restore (void);
-
-/* Asynchronous signal handle registered as event loop source for when
- we have pending events ready to be passed to the core. */
-
-static struct async_event_handler *record_async_inferior_event_token;
-
-static void
-record_async_inferior_event_handler (gdb_client_data data)
-{
- inferior_event_handler (INF_REG_EVENT, NULL);
-}
-
-/* Open the process record target. */
-
-static void
-record_core_open_1 (char *name, int from_tty)
-{
- struct regcache *regcache = get_current_regcache ();
- int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
- int i;
-
- /* Get record_core_regbuf. */
- target_fetch_registers (regcache, -1);
- record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
- for (i = 0; i < regnum; i ++)
- regcache_raw_collect (regcache, i,
- record_core_regbuf + MAX_REGISTER_SIZE * i);
-
- /* Get record_core_start and record_core_end. */
- if (build_section_table (core_bfd, &record_core_start, &record_core_end))
- {
- xfree (record_core_regbuf);
- record_core_regbuf = NULL;
- error (_("\"%s\": Can't find sections: %s"),
- bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
- }
-
- push_target (&record_core_ops);
- record_restore ();
-}
-
-/* "to_open" target method for 'live' processes. */
-
-static void
-record_open_1 (char *name, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
-
- /* check exec */
- if (!target_has_execution)
- error (_("Process record: the program is not being run."));
- if (non_stop)
- error (_("Process record target can't debug inferior in non-stop mode "
- "(non-stop)."));
-
- if (!gdbarch_process_record_p (target_gdbarch ()))
- error (_("Process record: the current architecture doesn't support "
- "record function."));
-
- if (!tmp_to_resume)
- error (_("Could not find 'to_resume' method on the target stack."));
- if (!tmp_to_wait)
- error (_("Could not find 'to_wait' method on the target stack."));
- if (!tmp_to_store_registers)
- error (_("Could not find 'to_store_registers' "
- "method on the target stack."));
- if (!tmp_to_insert_breakpoint)
- error (_("Could not find 'to_insert_breakpoint' "
- "method on the target stack."));
- if (!tmp_to_remove_breakpoint)
- error (_("Could not find 'to_remove_breakpoint' "
- "method on the target stack."));
- if (!tmp_to_stopped_by_watchpoint)
- error (_("Could not find 'to_stopped_by_watchpoint' "
- "method on the target stack."));
- if (!tmp_to_stopped_data_address)
- error (_("Could not find 'to_stopped_data_address' "
- "method on the target stack."));
-
- push_target (&record_ops);
-}
-
-static void record_init_record_breakpoints (void);
-
-/* "to_open" target method. Open the process record target. */
-
-static void
-record_open (char *name, int from_tty)
-{
- struct target_ops *t;
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
-
- /* Check if record target is already running. */
- if (current_target.to_stratum == record_stratum)
- error (_("Process record target already running. Use \"record stop\" to "
- "stop record target first."));
-
- /* Reset the tmp beneath pointers. */
- tmp_to_resume_ops = NULL;
- tmp_to_resume = NULL;
- tmp_to_wait_ops = NULL;
- tmp_to_wait = NULL;
- tmp_to_store_registers_ops = NULL;
- tmp_to_store_registers = NULL;
- tmp_to_xfer_partial_ops = NULL;
- tmp_to_xfer_partial = NULL;
- tmp_to_insert_breakpoint = NULL;
- tmp_to_remove_breakpoint = NULL;
- tmp_to_stopped_by_watchpoint = NULL;
- tmp_to_stopped_data_address = NULL;
- tmp_to_async = NULL;
-
- /* Set the beneath function pointers. */
- for (t = current_target.beneath; t != NULL; t = t->beneath)
- {
- if (!tmp_to_resume)
- {
- tmp_to_resume = t->to_resume;
- tmp_to_resume_ops = t;
- }
- if (!tmp_to_wait)
- {
- tmp_to_wait = t->to_wait;
- tmp_to_wait_ops = t;
- }
- if (!tmp_to_store_registers)
- {
- tmp_to_store_registers = t->to_store_registers;
- tmp_to_store_registers_ops = t;
- }
- if (!tmp_to_xfer_partial)
- {
- tmp_to_xfer_partial = t->to_xfer_partial;
- tmp_to_xfer_partial_ops = t;
- }
- if (!tmp_to_insert_breakpoint)
- tmp_to_insert_breakpoint = t->to_insert_breakpoint;
- if (!tmp_to_remove_breakpoint)
- tmp_to_remove_breakpoint = t->to_remove_breakpoint;
- if (!tmp_to_stopped_by_watchpoint)
- tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint;
- if (!tmp_to_stopped_data_address)
- tmp_to_stopped_data_address = t->to_stopped_data_address;
- if (!tmp_to_async)
- tmp_to_async = t->to_async;
- }
- if (!tmp_to_xfer_partial)
- error (_("Could not find 'to_xfer_partial' method on the target stack."));
-
- /* Reset */
- record_insn_num = 0;
- record_insn_count = 0;
- record_list = &record_first;
- record_list->next = NULL;
-
- /* Set the tmp beneath pointers to beneath pointers. */
- record_beneath_to_resume_ops = tmp_to_resume_ops;
- record_beneath_to_resume = tmp_to_resume;
- record_beneath_to_wait_ops = tmp_to_wait_ops;
- record_beneath_to_wait = tmp_to_wait;
- record_beneath_to_store_registers_ops = tmp_to_store_registers_ops;
- record_beneath_to_store_registers = tmp_to_store_registers;
- record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops;
- record_beneath_to_xfer_partial = tmp_to_xfer_partial;
- record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint;
- record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
- record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint;
- record_beneath_to_stopped_data_address = tmp_to_stopped_data_address;
- record_beneath_to_async = tmp_to_async;
-
- if (core_bfd)
- record_core_open_1 (name, from_tty);
- else
- record_open_1 (name, from_tty);
-
- /* Register extra event sources in the event loop. */
- record_async_inferior_event_token
- = create_async_event_handler (record_async_inferior_event_handler,
- NULL);
-
- record_init_record_breakpoints ();
-
- observer_notify_record_changed (current_inferior (), 1);
-}
-
-/* "to_close" target method. Close the process record target. */
-
-static void
-record_close (int quitting)
-{
- struct record_core_buf_entry *entry;
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
-
- record_list_release (record_list);
-
- /* Release record_core_regbuf. */
- if (record_core_regbuf)
- {
- xfree (record_core_regbuf);
- record_core_regbuf = NULL;
- }
-
- /* Release record_core_buf_list. */
- if (record_core_buf_list)
- {
- for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
- {
- xfree (record_core_buf_list);
- record_core_buf_list = entry;
- }
- record_core_buf_list = NULL;
- }
-
- if (record_async_inferior_event_token)
- delete_async_event_handler (&record_async_inferior_event_token);
-}
-
-static int record_resume_step = 0;
-
-/* True if we've been resumed, and so each record_wait call should
- advance execution. If this is false, record_wait will return a
- TARGET_WAITKIND_IGNORE. */
-static int record_resumed = 0;
-
-/* The execution direction of the last resume we got. This is
- necessary for async mode. Vis (order is not strictly accurate):
-
- 1. user has the global execution direction set to forward
- 2. user does a reverse-step command
- 3. record_resume is called with global execution direction
- temporarily switched to reverse
- 4. GDB's execution direction is reverted back to forward
- 5. target record notifies event loop there's an event to handle
- 6. infrun asks the target which direction was it going, and switches
- the global execution direction accordingly (to reverse)
- 7. infrun polls an event out of the record target, and handles it
- 8. GDB goes back to the event loop, and goto #4.
-*/
-static enum exec_direction_kind record_execution_dir = EXEC_FORWARD;
-
-/* "to_resume" target method. Resume the process record target. */
-
-static void
-record_resume (struct target_ops *ops, ptid_t ptid, int step,
- enum gdb_signal signal)
-{
- record_resume_step = step;
- record_resumed = 1;
- record_execution_dir = execution_direction;
-
- if (!RECORD_IS_REPLAY)
- {
- struct gdbarch *gdbarch = target_thread_architecture (ptid);
-
- record_message (get_current_regcache (), signal);
-
- if (!step)
- {
- /* This is not hard single step. */
- if (!gdbarch_software_single_step_p (gdbarch))
- {
- /* This is a normal continue. */
- step = 1;
- }
- else
- {
- /* This arch support soft sigle step. */
- if (single_step_breakpoints_inserted ())
- {
- /* This is a soft single step. */
- record_resume_step = 1;
- }
- else
- {
- /* This is a continue.
- Try to insert a soft single step breakpoint. */
- if (!gdbarch_software_single_step (gdbarch,
- get_current_frame ()))
- {
- /* This system don't want use soft single step.
- Use hard sigle step. */
- step = 1;
- }
- }
- }
- }
-
- /* Make sure the target beneath reports all signals. */
- target_pass_signals (0, NULL);
-
- record_beneath_to_resume (record_beneath_to_resume_ops,
- ptid, step, signal);
- }
-
- /* We are about to start executing the inferior (or simulate it),
- let's register it with the event loop. */
- if (target_can_async_p ())
- {
- target_async (inferior_event_handler, 0);
- /* Notify the event loop there's an event to wait for. We do
- most of the work in record_wait. */
- mark_async_event_handler (record_async_inferior_event_token);
- }
-}
-
-static int record_get_sig = 0;
-
-/* SIGINT signal handler, registered by "to_wait" method. */
-
-static void
-record_sig_handler (int signo)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
-
- /* It will break the running inferior in replay mode. */
- record_resume_step = 1;
-
- /* It will let record_wait set inferior status to get the signal
- SIGINT. */
- record_get_sig = 1;
-}
-
-static void
-record_wait_cleanups (void *ignore)
-{
- if (execution_direction == EXEC_REVERSE)
- {
- if (record_list->next)
- record_list = record_list->next;
- }
- else
- record_list = record_list->prev;
-}
-
-/* "to_wait" target method for process record target.
-
- In record mode, the target is always run in singlestep mode
- (even when gdb says to continue). The to_wait method intercepts
- the stop events and determines which ones are to be passed on to
- gdb. Most stop events are just singlestep events that gdb is not
- to know about, so the to_wait method just records them and keeps
- singlestepping.
-
- In replay mode, this function emulates the recorded execution log,
- one instruction at a time (forward or backward), and determines
- where to stop. */
-
-static ptid_t
-record_wait_1 (struct target_ops *ops,
- ptid_t ptid, struct target_waitstatus *status,
- int options)
-{
- struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_wait "
- "record_resume_step = %d, record_resumed = %d, direction=%s\n",
- record_resume_step, record_resumed,
- record_execution_dir == EXEC_FORWARD ? "forward" : "reverse");
-
- if (!record_resumed)
- {
- gdb_assert ((options & TARGET_WNOHANG) != 0);
-
- /* No interesting event. */
- status->kind = TARGET_WAITKIND_IGNORE;
- return minus_one_ptid;
- }
-
- record_get_sig = 0;
- signal (SIGINT, record_sig_handler);
-
- if (!RECORD_IS_REPLAY && ops != &record_core_ops)
- {
- if (record_resume_step)
- {
- /* This is a single step. */
- return record_beneath_to_wait (record_beneath_to_wait_ops,
- ptid, status, options);
- }
- else
- {
- /* This is not a single step. */
- ptid_t ret;
- CORE_ADDR tmp_pc;
- struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid);
-
- while (1)
- {
- ret = record_beneath_to_wait (record_beneath_to_wait_ops,
- ptid, status, options);
- if (status->kind == TARGET_WAITKIND_IGNORE)
- {
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_wait "
- "target beneath not done yet\n");
- return ret;
- }
-
- if (single_step_breakpoints_inserted ())
- remove_single_step_breakpoints ();
-
- if (record_resume_step)
- return ret;
-
- /* Is this a SIGTRAP? */
- if (status->kind == TARGET_WAITKIND_STOPPED
- && status->value.sig == GDB_SIGNAL_TRAP)
- {
- struct regcache *regcache;
- struct address_space *aspace;
-
- /* Yes -- this is likely our single-step finishing,
- but check if there's any reason the core would be
- interested in the event. */
-
- registers_changed ();
- regcache = get_current_regcache ();
- tmp_pc = regcache_read_pc (regcache);
- aspace = get_regcache_aspace (regcache);
-
- if (target_stopped_by_watchpoint ())
- {
- /* Always interested in watchpoints. */
- }
- else if (breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- /* There is a breakpoint here. Let the core
- handle it. */
- if (software_breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- struct gdbarch *gdbarch
- = get_regcache_arch (regcache);
- CORE_ADDR decr_pc_after_break
- = gdbarch_decr_pc_after_break (gdbarch);
- if (decr_pc_after_break)
- regcache_write_pc (regcache,
- tmp_pc + decr_pc_after_break);
- }
- }
- else
- {
- /* This is a single-step trap. Record the
- insn and issue another step.
- FIXME: this part can be a random SIGTRAP too.
- But GDB cannot handle it. */
- int step = 1;
-
- if (!record_message_wrapper_safe (regcache,
- GDB_SIGNAL_0))
- {
- status->kind = TARGET_WAITKIND_STOPPED;
- status->value.sig = GDB_SIGNAL_0;
- break;
- }
-
- if (gdbarch_software_single_step_p (gdbarch))
- {
- /* Try to insert the software single step breakpoint.
- If insert success, set step to 0. */
- set_executing (inferior_ptid, 0);
- reinit_frame_cache ();
- if (gdbarch_software_single_step (gdbarch,
- get_current_frame ()))
- step = 0;
- set_executing (inferior_ptid, 1);
- }
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_wait "
- "issuing one more step in the target beneath\n");
- record_beneath_to_resume (record_beneath_to_resume_ops,
- ptid, step,
- GDB_SIGNAL_0);
- continue;
- }
- }
-
- /* The inferior is broken by a breakpoint or a signal. */
- break;
- }
-
- return ret;
- }
- }
- else
- {
- struct regcache *regcache = get_current_regcache ();
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
- struct address_space *aspace = get_regcache_aspace (regcache);
- int continue_flag = 1;
- int first_record_end = 1;
- struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
- CORE_ADDR tmp_pc;
-
- record_hw_watchpoint = 0;
- status->kind = TARGET_WAITKIND_STOPPED;
-
- /* Check breakpoint when forward execute. */
- if (execution_direction == EXEC_FORWARD)
- {
- tmp_pc = regcache_read_pc (regcache);
- if (breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: break at %s.\n",
- paddress (gdbarch, tmp_pc));
-
- if (decr_pc_after_break
- && !record_resume_step
- && software_breakpoint_inserted_here_p (aspace, tmp_pc))
- regcache_write_pc (regcache,
- tmp_pc + decr_pc_after_break);
- goto replay_out;
- }
- }
-
- /* If GDB is in terminal_inferior mode, it will not get the signal.
- And in GDB replay mode, GDB doesn't need to be in terminal_inferior
- mode, because inferior will not executed.
- Then set it to terminal_ours to make GDB get the signal. */
- target_terminal_ours ();
-
- /* In EXEC_FORWARD mode, record_list points to the tail of prev
- instruction. */
- if (execution_direction == EXEC_FORWARD && record_list->next)
- record_list = record_list->next;
-
- /* Loop over the record_list, looking for the next place to
- stop. */
- do
- {
- /* Check for beginning and end of log. */
- if (execution_direction == EXEC_REVERSE
- && record_list == &record_first)
- {
- /* Hit beginning of record log in reverse. */
- status->kind = TARGET_WAITKIND_NO_HISTORY;
- break;
- }
- if (execution_direction != EXEC_REVERSE && !record_list->next)
- {
- /* Hit end of record log going forward. */
- status->kind = TARGET_WAITKIND_NO_HISTORY;
- break;
- }
-
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->type == record_end)
- {
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: record_end %s to "
- "inferior.\n",
- host_address_to_string (record_list));
-
- if (first_record_end && execution_direction == EXEC_REVERSE)
- {
- /* When reverse excute, the first record_end is the part of
- current instruction. */
- first_record_end = 0;
- }
- else
- {
- /* In EXEC_REVERSE mode, this is the record_end of prev
- instruction.
- In EXEC_FORWARD mode, this is the record_end of current
- instruction. */
- /* step */
- if (record_resume_step)
- {
- if (record_debug > 1)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: step.\n");
- continue_flag = 0;
- }
-
- /* check breakpoint */
- tmp_pc = regcache_read_pc (regcache);
- if (breakpoint_inserted_here_p (aspace, tmp_pc))
- {
- int decr_pc_after_break
- = gdbarch_decr_pc_after_break (gdbarch);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: break "
- "at %s.\n",
- paddress (gdbarch, tmp_pc));
- if (decr_pc_after_break
- && execution_direction == EXEC_FORWARD
- && !record_resume_step
- && software_breakpoint_inserted_here_p (aspace,
- tmp_pc))
- regcache_write_pc (regcache,
- tmp_pc + decr_pc_after_break);
- continue_flag = 0;
- }
-
- if (record_hw_watchpoint)
- {
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: hit hw "
- "watchpoint.\n");
- continue_flag = 0;
- }
- /* Check target signal */
- if (record_list->u.end.sigval != GDB_SIGNAL_0)
- /* FIXME: better way to check */
- continue_flag = 0;
- }
- }
-
- if (continue_flag)
- {
- if (execution_direction == EXEC_REVERSE)
- {
- if (record_list->prev)
- record_list = record_list->prev;
- }
- else
- {
- if (record_list->next)
- record_list = record_list->next;
- }
- }
- }
- while (continue_flag);
-
-replay_out:
- if (record_get_sig)
- status->value.sig = GDB_SIGNAL_INT;
- else if (record_list->u.end.sigval != GDB_SIGNAL_0)
- /* FIXME: better way to check */
- status->value.sig = record_list->u.end.sigval;
- else
- status->value.sig = GDB_SIGNAL_TRAP;
-
- discard_cleanups (old_cleanups);
- }
-
- signal (SIGINT, handle_sigint);
-
- do_cleanups (set_cleanups);
- return inferior_ptid;
-}
-
-static ptid_t
-record_wait (struct target_ops *ops,
- ptid_t ptid, struct target_waitstatus *status,
- int options)
-{
- ptid_t return_ptid;
-
- return_ptid = record_wait_1 (ops, ptid, status, options);
- if (status->kind != TARGET_WAITKIND_IGNORE)
- {
- /* We're reporting a stop. Make sure any spurious
- target_wait(WNOHANG) doesn't advance the target until the
- core wants us resumed again. */
- record_resumed = 0;
- }
- return return_ptid;
-}
-
-static int
-record_stopped_by_watchpoint (void)
-{
- if (RECORD_IS_REPLAY)
- return record_hw_watchpoint;
- else
- return record_beneath_to_stopped_by_watchpoint ();
-}
-
-static int
-record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
-{
- if (RECORD_IS_REPLAY)
- return 0;
- else
- return record_beneath_to_stopped_data_address (ops, addr_p);
-}
-
-/* "to_disconnect" method for process record target. */
-
-static void
-record_disconnect (struct target_ops *target, char *args, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n");
-
- unpush_target (&record_ops);
- target_disconnect (args, from_tty);
-}
-
-/* "to_detach" method for process record target. */
-
-static void
-record_detach (struct target_ops *ops, char *args, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n");
-
- unpush_target (&record_ops);
- target_detach (args, from_tty);
-}
-
-/* "to_mourn_inferior" method for process record target. */
-
-static void
-record_mourn_inferior (struct target_ops *ops)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: "
- "record_mourn_inferior\n");
-
- unpush_target (&record_ops);
- target_mourn_inferior ();
-}
-
-/* Close process record target before killing the inferior process. */
-
-static void
-record_kill (struct target_ops *ops)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
-
- unpush_target (&record_ops);
- target_kill ();
-}
-
-/* Record registers change (by user or by GDB) to list as an instruction. */
-
-static void
-record_registers_change (struct regcache *regcache, int regnum)
-{
- /* Check record_insn_num. */
- record_check_insn_num (0);
-
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
-
- if (regnum < 0)
- {
- int i;
-
- for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
- {
- if (record_arch_list_add_reg (regcache, i))
- {
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
- }
- }
- }
- else
- {
- if (record_arch_list_add_reg (regcache, regnum))
- {
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
- }
- }
- if (record_arch_list_add_end ())
- {
- record_list_release (record_arch_list_tail);
- error (_("Process record: failed to record execution log."));
- }
- record_list->next = record_arch_list_head;
- record_arch_list_head->prev = record_list;
- record_list = record_arch_list_tail;
-
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
-}
-
-/* "to_store_registers" method for process record target. */
-
-static void
-record_store_registers (struct target_ops *ops, struct regcache *regcache,
- int regno)
-{
- if (!record_gdb_operation_disable)
- {
- if (RECORD_IS_REPLAY)
- {
- int n;
-
- /* Let user choose if he wants to write register or not. */
- if (regno < 0)
- n =
- query (_("Because GDB is in replay mode, changing the "
- "value of a register will make the execution "
- "log unusable from this point onward. "
- "Change all registers?"));
- else
- n =
- query (_("Because GDB is in replay mode, changing the value "
- "of a register will make the execution log unusable "
- "from this point onward. Change register %s?"),
- gdbarch_register_name (get_regcache_arch (regcache),
- regno));
-
- if (!n)
- {
- /* Invalidate the value of regcache that was set in function
- "regcache_raw_write". */
- if (regno < 0)
- {
- int i;
-
- for (i = 0;
- i < gdbarch_num_regs (get_regcache_arch (regcache));
- i++)
- regcache_invalidate (regcache, i);
- }
- else
- regcache_invalidate (regcache, regno);
-
- error (_("Process record canceled the operation."));
- }
-
- /* Destroy the record from here forward. */
- record_list_release_following (record_list);
- }
-
- record_registers_change (regcache, regno);
- }
- record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
- regcache, regno);
-}
-
-/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY.
- In replay mode, we cannot write memory unles we are willing to
- invalidate the record/replay log from this point forward. */
-
-static LONGEST
-record_xfer_partial (struct target_ops *ops, enum target_object object,
- const char *annex, gdb_byte *readbuf,
- const gdb_byte *writebuf, ULONGEST offset, LONGEST len)
-{
- if (!record_gdb_operation_disable
- && (object == TARGET_OBJECT_MEMORY
- || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
- {
- if (RECORD_IS_REPLAY)
- {
- /* Let user choose if he wants to write memory or not. */
- if (!query (_("Because GDB is in replay mode, writing to memory "
- "will make the execution log unusable from this "
- "point onward. Write memory at address %s?"),
- paddress (target_gdbarch (), offset)))
- error (_("Process record canceled the operation."));
-
- /* Destroy the record from here forward. */
- record_list_release_following (record_list);
- }
-
- /* Check record_insn_num */
- record_check_insn_num (0);
-
- /* Record registers change to list as an instruction. */
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
- if (record_arch_list_add_mem (offset, len))
- {
- record_list_release (record_arch_list_tail);
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: failed to record "
- "execution log.");
- return -1;
- }
- if (record_arch_list_add_end ())
- {
- record_list_release (record_arch_list_tail);
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "Process record: failed to record "
- "execution log.");
- return -1;
- }
- record_list->next = record_arch_list_head;
- record_arch_list_head->prev = record_list;
- record_list = record_arch_list_tail;
-
- if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
- else
- record_insn_num++;
- }
-
- return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
- object, annex, readbuf, writebuf,
- offset, len);
-}
-
-/* This structure represents a breakpoint inserted while the record
- target is active. We use this to know when to install/remove
- breakpoints in/from the target beneath. For example, a breakpoint
- may be inserted while recording, but removed when not replaying nor
- recording. In that case, the breakpoint had not been inserted on
- the target beneath, so we should not try to remove it there. */
-
-struct record_breakpoint
-{
- /* The address and address space the breakpoint was set at. */
- struct address_space *address_space;
- CORE_ADDR addr;
-
- /* True when the breakpoint has been also installed in the target
- beneath. This will be false for breakpoints set during replay or
- when recording. */
- int in_target_beneath;
-};
-
-typedef struct record_breakpoint *record_breakpoint_p;
-DEF_VEC_P(record_breakpoint_p);
-
-/* The list of breakpoints inserted while the record target is
- active. */
-VEC(record_breakpoint_p) *record_breakpoints = NULL;
-
-static void
-record_sync_record_breakpoints (struct bp_location *loc, void *data)
-{
- if (loc->loc_type != bp_loc_software_breakpoint)
- return;
-
- if (loc->inserted)
- {
- struct record_breakpoint *bp = XNEW (struct record_breakpoint);
-
- bp->addr = loc->target_info.placed_address;
- bp->address_space = loc->target_info.placed_address_space;
-
- bp->in_target_beneath = 1;
-
- VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
- }
-}
-
-/* Sync existing breakpoints to record_breakpoints. */
-
-static void
-record_init_record_breakpoints (void)
-{
- VEC_free (record_breakpoint_p, record_breakpoints);
-
- iterate_over_bp_locations (record_sync_record_breakpoints);
-}
-
-/* Behavior is conditional on RECORD_IS_REPLAY. We will not actually
- insert or remove breakpoints in the real target when replaying, nor
- when recording. */
-
-static int
-record_insert_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- struct record_breakpoint *bp;
- int in_target_beneath = 0;
-
- if (!RECORD_IS_REPLAY)
- {
- /* When recording, we currently always single-step, so we don't
- really need to install regular breakpoints in the inferior.
- However, we do have to insert software single-step
- breakpoints, in case the target can't hardware step. To keep
- things single, we always insert. */
- struct cleanup *old_cleanups;
- int ret;
-
- old_cleanups = record_gdb_operation_disable_set ();
- ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
- do_cleanups (old_cleanups);
-
- if (ret != 0)
- return ret;
-
- in_target_beneath = 1;
- }
-
- bp = XNEW (struct record_breakpoint);
- bp->addr = bp_tgt->placed_address;
- bp->address_space = bp_tgt->placed_address_space;
- bp->in_target_beneath = in_target_beneath;
- VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
- return 0;
-}
-
-/* "to_remove_breakpoint" method for process record target. */
-
-static int
-record_remove_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- struct record_breakpoint *bp;
- int ix;
-
- for (ix = 0;
- VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp);
- ++ix)
- {
- if (bp->addr == bp_tgt->placed_address
- && bp->address_space == bp_tgt->placed_address_space)
- {
- if (bp->in_target_beneath)
- {
- struct cleanup *old_cleanups;
- int ret;
-
- old_cleanups = record_gdb_operation_disable_set ();
- ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
- do_cleanups (old_cleanups);
-
- if (ret != 0)
- return ret;
- }
-
- VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix);
- return 0;
- }
- }
-
- gdb_assert_not_reached ("removing unknown breakpoint");
-}
-
-/* "to_can_execute_reverse" method for process record target. */
-
-static int
-record_can_execute_reverse (void)
-{
- return 1;
-}
-
-/* "to_get_bookmark" method for process record and prec over core. */
-
-static gdb_byte *
-record_get_bookmark (char *args, int from_tty)
-{
- gdb_byte *ret = NULL;
-
- /* Return stringified form of instruction count. */
- if (record_list && record_list->type == record_end)
- ret = xstrdup (pulongest (record_list->u.end.insn_num));
-
- if (record_debug)
- {
- if (ret)
- fprintf_unfiltered (gdb_stdlog,
- "record_get_bookmark returns %s\n", ret);
- else
- fprintf_unfiltered (gdb_stdlog,
- "record_get_bookmark returns NULL\n");
- }
- return ret;
-}
-
-/* The implementation of the command "record goto". */
-static void cmd_record_goto (char *, int);
-
-/* "to_goto_bookmark" method for process record and prec over core. */
-
-static void
-record_goto_bookmark (gdb_byte *bookmark, int from_tty)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- "record_goto_bookmark receives %s\n", bookmark);
-
- if (bookmark[0] == '\'' || bookmark[0] == '\"')
- {
- if (bookmark[strlen (bookmark) - 1] != bookmark[0])
- error (_("Unbalanced quotes: %s"), bookmark);
-
- /* Strip trailing quote. */
- bookmark[strlen (bookmark) - 1] = '\0';
- /* Strip leading quote. */
- bookmark++;
- /* Pass along to cmd_record_goto. */
- }
-
- cmd_record_goto ((char *) bookmark, from_tty);
- return;
-}
-
-static void
-record_async (void (*callback) (enum inferior_event_type event_type,
- void *context), void *context)
-{
- /* If we're on top of a line target (e.g., linux-nat, remote), then
- set it to async mode as well. Will be NULL if we're sitting on
- top of the core target, for "record restore". */
- if (record_beneath_to_async != NULL)
- record_beneath_to_async (callback, context);
-}
-
-static int
-record_can_async_p (void)
-{
- /* We only enable async when the user specifically asks for it. */
- return target_async_permitted;
-}
-
-static int
-record_is_async_p (void)
-{
- /* We only enable async when the user specifically asks for it. */
- return target_async_permitted;
-}
-
-static enum exec_direction_kind
-record_execution_direction (void)
-{
- return record_execution_dir;
-}
-
-static void
-init_record_ops (void)
-{
- record_ops.to_shortname = "record";
- record_ops.to_longname = "Process record and replay target";
- record_ops.to_doc =
- "Log program while executing and replay execution from log.";
- record_ops.to_open = record_open;
- record_ops.to_close = record_close;
- record_ops.to_resume = record_resume;
- record_ops.to_wait = record_wait;
- record_ops.to_disconnect = record_disconnect;
- record_ops.to_detach = record_detach;
- record_ops.to_mourn_inferior = record_mourn_inferior;
- record_ops.to_kill = record_kill;
- record_ops.to_create_inferior = find_default_create_inferior;
- record_ops.to_store_registers = record_store_registers;
- record_ops.to_xfer_partial = record_xfer_partial;
- record_ops.to_insert_breakpoint = record_insert_breakpoint;
- record_ops.to_remove_breakpoint = record_remove_breakpoint;
- record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
- record_ops.to_stopped_data_address = record_stopped_data_address;
- record_ops.to_can_execute_reverse = record_can_execute_reverse;
- record_ops.to_stratum = record_stratum;
- /* Add bookmark target methods. */
- record_ops.to_get_bookmark = record_get_bookmark;
- record_ops.to_goto_bookmark = record_goto_bookmark;
- record_ops.to_async = record_async;
- record_ops.to_can_async_p = record_can_async_p;
- record_ops.to_is_async_p = record_is_async_p;
- record_ops.to_execution_direction = record_execution_direction;
- record_ops.to_magic = OPS_MAGIC;
-}
-
-/* "to_resume" method for prec over corefile. */
-
-static void
-record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
- enum gdb_signal signal)
-{
- record_resume_step = step;
- record_resumed = 1;
- record_execution_dir = execution_direction;
-
- /* We are about to start executing the inferior (or simulate it),
- let's register it with the event loop. */
- if (target_can_async_p ())
- {
- target_async (inferior_event_handler, 0);
-
- /* Notify the event loop there's an event to wait for. */
- mark_async_event_handler (record_async_inferior_event_token);
- }
-}
-
-/* "to_kill" method for prec over corefile. */
-
-static void
-record_core_kill (struct target_ops *ops)
-{
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
-
- unpush_target (&record_core_ops);
-}
-
-/* "to_fetch_registers" method for prec over corefile. */
-
-static void
-record_core_fetch_registers (struct target_ops *ops,
- struct regcache *regcache,
- int regno)
-{
- if (regno < 0)
- {
- int num = gdbarch_num_regs (get_regcache_arch (regcache));
- int i;
-
- for (i = 0; i < num; i ++)
- regcache_raw_supply (regcache, i,
- record_core_regbuf + MAX_REGISTER_SIZE * i);
- }
- else
- regcache_raw_supply (regcache, regno,
- record_core_regbuf + MAX_REGISTER_SIZE * regno);
-}
-
-/* "to_prepare_to_store" method for prec over corefile. */
-
-static void
-record_core_prepare_to_store (struct regcache *regcache)
-{
-}
-
-/* "to_store_registers" method for prec over corefile. */
-
-static void
-record_core_store_registers (struct target_ops *ops,
- struct regcache *regcache,
- int regno)
-{
- if (record_gdb_operation_disable)
- regcache_raw_collect (regcache, regno,
- record_core_regbuf + MAX_REGISTER_SIZE * regno);
- else
- error (_("You can't do that without a process to debug."));
-}
-
-/* "to_xfer_partial" method for prec over corefile. */
-
-static LONGEST
-record_core_xfer_partial (struct target_ops *ops, enum target_object object,
- const char *annex, gdb_byte *readbuf,
- const gdb_byte *writebuf, ULONGEST offset,
- LONGEST len)
-{
- if (object == TARGET_OBJECT_MEMORY)
- {
- if (record_gdb_operation_disable || !writebuf)
- {
- struct target_section *p;
-
- for (p = record_core_start; p < record_core_end; p++)
- {
- if (offset >= p->addr)
- {
- struct record_core_buf_entry *entry;
- ULONGEST sec_offset;
-
- if (offset >= p->endaddr)
- continue;
-
- if (offset + len > p->endaddr)
- len = p->endaddr - offset;
-
- sec_offset = offset - p->addr;
-
- /* Read readbuf or write writebuf p, offset, len. */
- /* Check flags. */
- if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
- || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
- {
- if (readbuf)
- memset (readbuf, 0, len);
- return len;
- }
- /* Get record_core_buf_entry. */
- for (entry = record_core_buf_list; entry;
- entry = entry->prev)
- if (entry->p == p)
- break;
- if (writebuf)
- {
- if (!entry)
- {
- /* Add a new entry. */
- entry = (struct record_core_buf_entry *)
- xmalloc (sizeof (struct record_core_buf_entry));
- entry->p = p;
- if (!bfd_malloc_and_get_section (p->bfd,
- p->the_bfd_section,
- &entry->buf))
- {
- xfree (entry);
- return 0;
- }
- entry->prev = record_core_buf_list;
- record_core_buf_list = entry;
- }
-
- memcpy (entry->buf + sec_offset, writebuf,
- (size_t) len);
- }
- else
- {
- if (!entry)
- return record_beneath_to_xfer_partial
- (record_beneath_to_xfer_partial_ops,
- object, annex, readbuf, writebuf,
- offset, len);
-
- memcpy (readbuf, entry->buf + sec_offset,
- (size_t) len);
- }
-
- return len;
- }
- }
-
- return -1;
- }
- else
- error (_("You can't do that without a process to debug."));
- }
-
- return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
- object, annex, readbuf, writebuf,
- offset, len);
-}
-
-/* "to_insert_breakpoint" method for prec over corefile. */
-
-static int
-record_core_insert_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- return 0;
-}
-
-/* "to_remove_breakpoint" method for prec over corefile. */
-
-static int
-record_core_remove_breakpoint (struct gdbarch *gdbarch,
- struct bp_target_info *bp_tgt)
-{
- return 0;
-}
-
-/* "to_has_execution" method for prec over corefile. */
-
-static int
-record_core_has_execution (struct target_ops *ops, ptid_t the_ptid)
-{
- return 1;
-}
-
-static void
-init_record_core_ops (void)
-{
- record_core_ops.to_shortname = "record-core";
- record_core_ops.to_longname = "Process record and replay target";
- record_core_ops.to_doc =
- "Log program while executing and replay execution from log.";
- record_core_ops.to_open = record_open;
- record_core_ops.to_close = record_close;
- record_core_ops.to_resume = record_core_resume;
- record_core_ops.to_wait = record_wait;
- record_core_ops.to_kill = record_core_kill;
- record_core_ops.to_fetch_registers = record_core_fetch_registers;
- record_core_ops.to_prepare_to_store = record_core_prepare_to_store;
- record_core_ops.to_store_registers = record_core_store_registers;
- record_core_ops.to_xfer_partial = record_core_xfer_partial;
- record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint;
- record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint;
- record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint;
- record_core_ops.to_stopped_data_address = record_stopped_data_address;
- record_core_ops.to_can_execute_reverse = record_can_execute_reverse;
- record_core_ops.to_has_execution = record_core_has_execution;
- record_core_ops.to_stratum = record_stratum;
- /* Add bookmark target methods. */
- record_core_ops.to_get_bookmark = record_get_bookmark;
- record_core_ops.to_goto_bookmark = record_goto_bookmark;
- record_core_ops.to_async = record_async;
- record_core_ops.to_can_async_p = record_can_async_p;
- record_core_ops.to_is_async_p = record_is_async_p;
- record_core_ops.to_execution_direction = record_execution_direction;
- record_core_ops.to_magic = OPS_MAGIC;
+ error (_("No record target is currently active.\n"));
}
/* Implement "show record debug" command. */
@@ -2227,7 +58,7 @@ show_record_debug (struct ui_file *file, int from_tty,
static void
cmd_record_start (char *args, int from_tty)
{
- execute_command ("target record", from_tty);
+ execute_command ("target record-full", from_tty);
}
/* Truncate the record log from the present point
@@ -2236,21 +67,9 @@ cmd_record_start (char *args, int from_tty)
static void
cmd_record_delete (char *args, int from_tty)
{
- if (current_target.to_stratum == record_stratum)
- {
- if (RECORD_IS_REPLAY)
- {
- if (!from_tty || query (_("Delete the log from this point forward "
- "and begin to record the running message "
- "at current PC?")))
- record_list_release_following (record_list);
- }
- else
- printf_unfiltered (_("Already at end of record list.\n"));
+ require_record ();
- }
- else
- printf_unfiltered (_("Process record is not started.\n"));
+ target_delete_record (args, from_tty);
}
/* Implement the "stoprecord" or "record stop" command. */
@@ -2258,36 +77,27 @@ cmd_record_delete (char *args, int from_tty)
static void
cmd_record_stop (char *args, int from_tty)
{
- if (current_target.to_stratum == record_stratum)
- {
- unpush_target (&record_ops);
- printf_unfiltered (_("Process record is stopped and all execution "
- "logs are deleted.\n"));
+ struct target_ops *t;
- observer_notify_record_changed (current_inferior (), 0);
- }
- else
- printf_unfiltered (_("Process record is not started.\n"));
-}
+ require_record ();
-/* Set upper limit of record log size. */
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_stratum == record_stratum)
+ {
+ unpush_target (t);
-static void
-set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c)
-{
- if (record_insn_num > record_insn_max_num && record_insn_max_num)
- {
- /* Count down record_insn_num while releasing records from list. */
- while (record_insn_num > record_insn_max_num)
- {
- record_list_release_first ();
- record_insn_num--;
- }
- }
+ printf_unfiltered (_("Process record is stopped and all execution "
+ "logs are deleted.\n"));
+
+ observer_notify_record_changed (current_inferior (), 0);
+ return;
+ }
+
+ /* We should not get here. */
+ internal_error (__FILE__, __LINE__, _("Couldn't find record target."));
}
-static struct cmd_list_element *record_cmdlist, *set_record_cmdlist,
- *show_record_cmdlist, *info_record_cmdlist;
+/* The "set record" command. */
static void
set_record_command (char *args, int from_tty)
@@ -2297,385 +107,36 @@ set_record_command (char *args, int from_tty)
help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout);
}
+/* The "show record" command. */
+
static void
show_record_command (char *args, int from_tty)
{
cmd_show_list (show_record_cmdlist, from_tty, "");
}
-/* Display some statistics about the execution log. */
+/* The "info record" command. */
static void
info_record_command (char *args, int from_tty)
{
- struct record_entry *p;
-
- if (current_target.to_stratum == record_stratum)
- {
- if (RECORD_IS_REPLAY)
- printf_filtered (_("Replay mode:\n"));
- else
- printf_filtered (_("Record mode:\n"));
-
- /* Find entry for first actual instruction in the log. */
- for (p = record_first.next;
- p != NULL && p->type != record_end;
- p = p->next)
- ;
-
- /* Do we have a log at all? */
- if (p != NULL && p->type == record_end)
- {
- /* Display instruction number for first instruction in the log. */
- printf_filtered (_("Lowest recorded instruction number is %s.\n"),
- pulongest (p->u.end.insn_num));
-
- /* If in replay mode, display where we are in the log. */
- if (RECORD_IS_REPLAY)
- printf_filtered (_("Current instruction number is %s.\n"),
- pulongest (record_list->u.end.insn_num));
-
- /* Display instruction number for last instruction in the log. */
- printf_filtered (_("Highest recorded instruction number is %s.\n"),
- pulongest (record_insn_count));
-
- /* Display log count. */
- printf_filtered (_("Log contains %d instructions.\n"),
- record_insn_num);
- }
- else
- {
- printf_filtered (_("No instructions have been logged.\n"));
- }
- }
- else
- {
- printf_filtered (_("target record is not active.\n"));
- }
-
- /* Display max log size. */
- printf_filtered (_("Max logged instructions is %d.\n"),
- record_insn_max_num);
-}
-
-/* Record log save-file format
- Version 1 (never released)
-
- Header:
- 4 bytes: magic number htonl(0x20090829).
- NOTE: be sure to change whenever this file format changes!
-
- Records:
- record_end:
- 1 byte: record type (record_end, see enum record_type).
- record_reg:
- 1 byte: record type (record_reg, see enum record_type).
- 8 bytes: register id (network byte order).
- MAX_REGISTER_SIZE bytes: register value.
- record_mem:
- 1 byte: record type (record_mem, see enum record_type).
- 8 bytes: memory length (network byte order).
- 8 bytes: memory address (network byte order).
- n bytes: memory value (n == memory length).
-
- Version 2
- 4 bytes: magic number netorder32(0x20091016).
- NOTE: be sure to change whenever this file format changes!
-
- Records:
- record_end:
- 1 byte: record type (record_end, see enum record_type).
- 4 bytes: signal
- 4 bytes: instruction count
- record_reg:
- 1 byte: record type (record_reg, see enum record_type).
- 4 bytes: register id (network byte order).
- n bytes: register value (n == actual register size).
- (eg. 4 bytes for x86 general registers).
- record_mem:
- 1 byte: record type (record_mem, see enum record_type).
- 4 bytes: memory length (network byte order).
- 8 bytes: memory address (network byte order).
- n bytes: memory value (n == memory length).
-
-*/
-
-/* bfdcore_read -- read bytes from a core file section. */
-
-static inline void
-bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
-{
- int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
-
- if (ret)
- *offset += len;
- else
- error (_("Failed to read %d bytes from core file %s ('%s')."),
- len, bfd_get_filename (obfd),
- bfd_errmsg (bfd_get_error ()));
-}
-
-static inline uint64_t
-netorder64 (uint64_t input)
-{
- uint64_t ret;
-
- store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
- BFD_ENDIAN_BIG, input);
- return ret;
-}
-
-static inline uint32_t
-netorder32 (uint32_t input)
-{
- uint32_t ret;
-
- store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
- BFD_ENDIAN_BIG, input);
- return ret;
-}
-
-static inline uint16_t
-netorder16 (uint16_t input)
-{
- uint16_t ret;
-
- store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
- BFD_ENDIAN_BIG, input);
- return ret;
-}
-
-/* Restore the execution log from a core_bfd file. */
-static void
-record_restore (void)
-{
- uint32_t magic;
- struct cleanup *old_cleanups;
- struct record_entry *rec;
- asection *osec;
- uint32_t osec_size;
- int bfd_offset = 0;
- struct regcache *regcache;
-
- /* We restore the execution log from the open core bfd,
- if there is one. */
- if (core_bfd == NULL)
- return;
-
- /* "record_restore" can only be called when record list is empty. */
- gdb_assert (record_first.next == NULL);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n");
-
- /* Now need to find our special note section. */
- osec = bfd_get_section_by_name (core_bfd, "null0");
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n",
- osec ? "succeeded" : "failed");
- if (osec == NULL)
- return;
- osec_size = bfd_section_size (core_bfd, osec);
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec));
-
- /* Check the magic code. */
- bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset);
- if (magic != RECORD_FILE_MAGIC)
- error (_("Version mis-match or file format error in core file %s."),
- bfd_get_filename (core_bfd));
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading 4-byte magic cookie "
- "RECORD_FILE_MAGIC (0x%s)\n",
- phex_nz (netorder32 (magic), 4));
-
- /* Restore the entries in recfd into record_arch_list_head and
- record_arch_list_tail. */
- record_arch_list_head = NULL;
- record_arch_list_tail = NULL;
- record_insn_num = 0;
- old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
- regcache = get_current_regcache ();
-
- while (1)
- {
- uint8_t rectype;
- uint32_t regnum, len, signal, count;
- uint64_t addr;
-
- /* We are finished when offset reaches osec_size. */
- if (bfd_offset >= osec_size)
- break;
- bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset);
-
- switch (rectype)
- {
- case record_reg: /* reg */
- /* Get register number to regnum. */
- bfdcore_read (core_bfd, osec, ®num,
- sizeof (regnum), &bfd_offset);
- regnum = netorder32 (regnum);
-
- rec = record_reg_alloc (regcache, regnum);
-
- /* Get val. */
- bfdcore_read (core_bfd, osec, record_get_loc (rec),
- rec->u.reg.len, &bfd_offset);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading register %d (1 "
- "plus %lu plus %d bytes)\n",
- rec->u.reg.num,
- (unsigned long) sizeof (regnum),
- rec->u.reg.len);
- break;
-
- case record_mem: /* mem */
- /* Get len. */
- bfdcore_read (core_bfd, osec, &len,
- sizeof (len), &bfd_offset);
- len = netorder32 (len);
-
- /* Get addr. */
- bfdcore_read (core_bfd, osec, &addr,
- sizeof (addr), &bfd_offset);
- addr = netorder64 (addr);
-
- rec = record_mem_alloc (addr, len);
-
- /* Get val. */
- bfdcore_read (core_bfd, osec, record_get_loc (rec),
- rec->u.mem.len, &bfd_offset);
-
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading memory %s (1 plus "
- "%lu plus %lu plus %d bytes)\n",
- paddress (get_current_arch (),
- rec->u.mem.addr),
- (unsigned long) sizeof (addr),
- (unsigned long) sizeof (len),
- rec->u.mem.len);
- break;
-
- case record_end: /* end */
- rec = record_end_alloc ();
- record_insn_num ++;
-
- /* Get signal value. */
- bfdcore_read (core_bfd, osec, &signal,
- sizeof (signal), &bfd_offset);
- signal = netorder32 (signal);
- rec->u.end.sigval = signal;
-
- /* Get insn count. */
- bfdcore_read (core_bfd, osec, &count,
- sizeof (count), &bfd_offset);
- count = netorder32 (count);
- rec->u.end.insn_num = count;
- record_insn_count = count + 1;
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Reading record_end (1 + "
- "%lu + %lu bytes), offset == %s\n",
- (unsigned long) sizeof (signal),
- (unsigned long) sizeof (count),
- paddress (get_current_arch (),
- bfd_offset));
- break;
-
- default:
- error (_("Bad entry type in core file %s."),
- bfd_get_filename (core_bfd));
- break;
- }
-
- /* Add rec to record arch list. */
- record_arch_list_add (rec);
- }
-
- discard_cleanups (old_cleanups);
-
- /* Add record_arch_list_head to the end of record list. */
- record_first.next = record_arch_list_head;
- record_arch_list_head->prev = &record_first;
- record_arch_list_tail->next = NULL;
- record_list = &record_first;
-
- /* Update record_insn_max_num. */
- if (record_insn_num > record_insn_max_num)
+ if (RECORD_IS_USED)
{
- record_insn_max_num = record_insn_num;
- warning (_("Auto increase record/replay buffer limit to %d."),
- record_insn_max_num);
+ printf_filtered (_("Active record target: %s\n"), target_shortname);
+ target_info_record (args, from_tty);
}
-
- /* Succeeded. */
- printf_filtered (_("Restored records from core file %s.\n"),
- bfd_get_filename (core_bfd));
-
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
-}
-
-/* bfdcore_write -- write bytes into a core file section. */
-
-static inline void
-bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
-{
- int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
-
- if (ret)
- *offset += len;
else
- error (_("Failed to write %d bytes to core file %s ('%s')."),
- len, bfd_get_filename (obfd),
- bfd_errmsg (bfd_get_error ()));
-}
-
-/* Restore the execution log from a file. We use a modified elf
- corefile format, with an extra section for our data. */
-
-static void
-cmd_record_restore (char *args, int from_tty)
-{
- core_file_command (args, from_tty);
- record_open (args, from_tty);
-}
-
-static void
-record_save_cleanups (void *data)
-{
- bfd *obfd = data;
- char *pathname = xstrdup (bfd_get_filename (obfd));
-
- gdb_bfd_unref (obfd);
- unlink (pathname);
- xfree (pathname);
+ printf_filtered (_("No record target is currently active.\n"));
}
-/* Save the execution log to a file. We use a modified elf corefile
- format, with an extra section for our data. */
+/* The "record save" command. */
static void
cmd_record_save (char *args, int from_tty)
{
char *recfilename, recfilename_buffer[40];
- struct record_entry *cur_record_list;
- uint32_t magic;
- struct regcache *regcache;
- struct gdbarch *gdbarch;
- struct cleanup *old_cleanups;
- struct cleanup *set_cleanups;
- bfd *obfd;
- int save_size = 0;
- asection *osec = NULL;
- int bfd_offset = 0;
- if (strcmp (current_target.to_shortname, "record") != 0)
- error (_("This command can only be used with target 'record'.\n"
- "Use 'target record' first.\n"));
+ require_record ();
if (args && *args)
recfilename = args;
@@ -2687,220 +148,7 @@ cmd_record_save (char *args, int from_tty)
recfilename = recfilename_buffer;
}
- /* Open the save file. */
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n",
- recfilename);
-
- /* Open the output file. */
- obfd = create_gcore_bfd (recfilename);
- old_cleanups = make_cleanup (record_save_cleanups, obfd);
-
- /* Save the current record entry to "cur_record_list". */
- cur_record_list = record_list;
-
- /* Get the values of regcache and gdbarch. */
- regcache = get_current_regcache ();
- gdbarch = get_regcache_arch (regcache);
-
- /* Disable the GDB operation record. */
- set_cleanups = record_gdb_operation_disable_set ();
-
- /* Reverse execute to the begin of record list. */
- while (1)
- {
- /* Check for beginning and end of log. */
- if (record_list == &record_first)
- break;
-
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->prev)
- record_list = record_list->prev;
- }
-
- /* Compute the size needed for the extra bfd section. */
- save_size = 4; /* magic cookie */
- for (record_list = record_first.next; record_list;
- record_list = record_list->next)
- switch (record_list->type)
- {
- case record_end:
- save_size += 1 + 4 + 4;
- break;
- case record_reg:
- save_size += 1 + 4 + record_list->u.reg.len;
- break;
- case record_mem:
- save_size += 1 + 4 + 8 + record_list->u.mem.len;
- break;
- }
-
- /* Make the new bfd section. */
- osec = bfd_make_section_anyway_with_flags (obfd, "precord",
- SEC_HAS_CONTENTS
- | SEC_READONLY);
- if (osec == NULL)
- error (_("Failed to create 'precord' section for corefile %s: %s"),
- recfilename,
- bfd_errmsg (bfd_get_error ()));
- bfd_set_section_size (obfd, osec, save_size);
- bfd_set_section_vma (obfd, osec, 0);
- bfd_set_section_alignment (obfd, osec, 0);
- bfd_section_lma (obfd, osec) = 0;
-
- /* Save corefile state. */
- write_gcore_file (obfd);
-
- /* Write out the record log. */
- /* Write the magic code. */
- magic = RECORD_FILE_MAGIC;
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing 4-byte magic cookie "
- "RECORD_FILE_MAGIC (0x%s)\n",
- phex_nz (magic, 4));
- bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset);
-
- /* Save the entries to recfd and forward execute to the end of
- record list. */
- record_list = &record_first;
- while (1)
- {
- /* Save entry. */
- if (record_list != &record_first)
- {
- uint8_t type;
- uint32_t regnum, len, signal, count;
- uint64_t addr;
-
- type = record_list->type;
- bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset);
-
- switch (record_list->type)
- {
- case record_reg: /* reg */
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing register %d (1 "
- "plus %lu plus %d bytes)\n",
- record_list->u.reg.num,
- (unsigned long) sizeof (regnum),
- record_list->u.reg.len);
-
- /* Write regnum. */
- regnum = netorder32 (record_list->u.reg.num);
- bfdcore_write (obfd, osec, ®num,
- sizeof (regnum), &bfd_offset);
-
- /* Write regval. */
- bfdcore_write (obfd, osec, record_get_loc (record_list),
- record_list->u.reg.len, &bfd_offset);
- break;
-
- case record_mem: /* mem */
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing memory %s (1 plus "
- "%lu plus %lu plus %d bytes)\n",
- paddress (gdbarch,
- record_list->u.mem.addr),
- (unsigned long) sizeof (addr),
- (unsigned long) sizeof (len),
- record_list->u.mem.len);
-
- /* Write memlen. */
- len = netorder32 (record_list->u.mem.len);
- bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset);
-
- /* Write memaddr. */
- addr = netorder64 (record_list->u.mem.addr);
- bfdcore_write (obfd, osec, &addr,
- sizeof (addr), &bfd_offset);
-
- /* Write memval. */
- bfdcore_write (obfd, osec, record_get_loc (record_list),
- record_list->u.mem.len, &bfd_offset);
- break;
-
- case record_end:
- if (record_debug)
- fprintf_unfiltered (gdb_stdlog,
- " Writing record_end (1 + "
- "%lu + %lu bytes)\n",
- (unsigned long) sizeof (signal),
- (unsigned long) sizeof (count));
- /* Write signal value. */
- signal = netorder32 (record_list->u.end.sigval);
- bfdcore_write (obfd, osec, &signal,
- sizeof (signal), &bfd_offset);
-
- /* Write insn count. */
- count = netorder32 (record_list->u.end.insn_num);
- bfdcore_write (obfd, osec, &count,
- sizeof (count), &bfd_offset);
- break;
- }
- }
-
- /* Execute entry. */
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->next)
- record_list = record_list->next;
- else
- break;
- }
-
- /* Reverse execute to cur_record_list. */
- while (1)
- {
- /* Check for beginning and end of log. */
- if (record_list == cur_record_list)
- break;
-
- record_exec_insn (regcache, gdbarch, record_list);
-
- if (record_list->prev)
- record_list = record_list->prev;
- }
-
- do_cleanups (set_cleanups);
- gdb_bfd_unref (obfd);
- discard_cleanups (old_cleanups);
-
- /* Succeeded. */
- printf_filtered (_("Saved core file %s with execution log.\n"),
- recfilename);
-}
-
-/* record_goto_insn -- rewind the record log (forward or backward,
- depending on DIR) to the given entry, changing the program state
- correspondingly. */
-
-static void
-record_goto_insn (struct record_entry *entry,
- enum exec_direction_kind dir)
-{
- struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
- struct regcache *regcache = get_current_regcache ();
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
-
- /* Assume everything is valid: we will hit the entry,
- and we will not hit the end of the recording. */
-
- if (dir == EXEC_FORWARD)
- record_list = record_list->next;
-
- do
- {
- record_exec_insn (regcache, gdbarch, record_list);
- if (dir == EXEC_REVERSE)
- record_list = record_list->prev;
- else
- record_list = record_list->next;
- } while (record_list != entry);
- do_cleanups (set_cleanups);
+ target_save_record (recfilename, from_tty);
}
/* "record goto" command. Argument is an instruction number,
@@ -2911,62 +159,9 @@ record_goto_insn (struct record_entry *entry,
static void
cmd_record_goto (char *arg, int from_tty)
{
- struct record_entry *p = NULL;
- ULONGEST target_insn = 0;
+ require_record ();
- if (arg == NULL || *arg == '\0')
- error (_("Command requires an argument (insn number to go to)."));
-
- if (strncmp (arg, "start", strlen ("start")) == 0
- || strncmp (arg, "begin", strlen ("begin")) == 0)
- {
- /* Special case. Find first insn. */
- for (p = &record_first; p != NULL; p = p->next)
- if (p->type == record_end)
- break;
- if (p)
- target_insn = p->u.end.insn_num;
- }
- else if (strncmp (arg, "end", strlen ("end")) == 0)
- {
- /* Special case. Find last insn. */
- for (p = record_list; p->next != NULL; p = p->next)
- ;
- for (; p!= NULL; p = p->prev)
- if (p->type == record_end)
- break;
- if (p)
- target_insn = p->u.end.insn_num;
- }
- else
- {
- /* General case. Find designated insn. */
- target_insn = parse_and_eval_long (arg);
-
- for (p = &record_first; p != NULL; p = p->next)
- if (p->type == record_end && p->u.end.insn_num == target_insn)
- break;
- }
-
- if (p == NULL)
- error (_("Target insn '%s' not found."), arg);
- else if (p == record_list)
- error (_("Already at insn '%s'."), arg);
- else if (p->u.end.insn_num > record_list->u.end.insn_num)
- {
- printf_filtered (_("Go forward to insn number %s\n"),
- pulongest (target_insn));
- record_goto_insn (p, EXEC_FORWARD);
- }
- else
- {
- printf_filtered (_("Go backward to insn number %s\n"),
- pulongest (target_insn));
- record_goto_insn (p, EXEC_REVERSE);
- }
- registers_changed ();
- reinit_frame_cache ();
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+ target_goto_record (arg, from_tty);
}
/* Provide a prototype to silence -Wmissing-prototypes. */
@@ -2977,16 +172,6 @@ _initialize_record (void)
{
struct cmd_list_element *c;
- /* Init record_first. */
- record_first.prev = NULL;
- record_first.next = NULL;
- record_first.type = record_end;
-
- init_record_ops ();
- add_target (&record_ops);
- init_record_core_ops ();
- add_target (&record_core_ops);
-
add_setshow_zuinteger_cmd ("record", no_class, &record_debug,
_("Set debugging of record/replay feature."),
_("Show debugging of record/replay feature."),
@@ -2996,7 +181,7 @@ _initialize_record (void)
&showdebuglist);
c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
- _("Abbreviated form of \"target record\" command."),
+ _("Start recording."),
&record_cmdlist, "record ", 0, &cmdlist);
set_cmd_completer (c, filename_completer);
@@ -3021,12 +206,6 @@ Default filename is 'gdb_record.<process_id>'."),
&record_cmdlist);
set_cmd_completer (c, filename_completer);
- c = add_cmd ("restore", class_obscure, cmd_record_restore,
- _("Restore the execution log from a file.\n\
-Argument is filename. File must be created with 'record save'."),
- &record_cmdlist);
- set_cmd_completer (c, filename_completer);
-
add_cmd ("delete", class_obscure, cmd_record_delete,
_("Delete the rest of execution log and start recording it anew."),
&record_cmdlist);
@@ -3038,40 +217,8 @@ Argument is filename. File must be created with 'record save'."),
&record_cmdlist);
add_alias_cmd ("s", "stop", class_obscure, 1, &record_cmdlist);
- /* Record instructions number limit command. */
- add_setshow_boolean_cmd ("stop-at-limit", no_class,
- &record_stop_at_limit, _("\
-Set whether record/replay stops when record/replay buffer becomes full."), _("\
-Show whether record/replay stops when record/replay buffer becomes full."),
- _("Default is ON.\n\
-When ON, if the record/replay buffer becomes full, ask user what to do.\n\
-When OFF, if the record/replay buffer becomes full,\n\
-delete the oldest recorded instruction to make room for each new one."),
- NULL, NULL,
- &set_record_cmdlist, &show_record_cmdlist);
- add_setshow_uinteger_cmd ("insn-number-max", no_class,
- &record_insn_max_num,
- _("Set record/replay buffer limit."),
- _("Show record/replay buffer limit."), _("\
-Set the maximum number of instructions to be stored in the\n\
-record/replay buffer. Zero means unlimited. Default is 200000."),
- set_record_insn_max_num,
- NULL, &set_record_cmdlist, &show_record_cmdlist);
-
add_cmd ("goto", class_obscure, cmd_record_goto, _("\
Restore the program to its state at instruction number N.\n\
Argument is instruction number, as shown by 'info record'."),
&record_cmdlist);
-
- add_setshow_boolean_cmd ("memory-query", no_class,
- &record_memory_query, _("\
-Set whether query if PREC cannot record memory change of next instruction."),
- _("\
-Show whether query if PREC cannot record memory change of next instruction."),
- _("\
-Default is OFF.\n\
-When ON, query if PREC cannot record memory change of next instruction."),
- NULL, NULL,
- &set_record_cmdlist, &show_record_cmdlist);
-
}
diff --git a/gdb/record.h b/gdb/record.h
index 9cf9223..1c57fa5 100644
--- a/gdb/record.h
+++ b/gdb/record.h
@@ -20,20 +20,16 @@
#ifndef _RECORD_H_
#define _RECORD_H_
+#include "target.h"
+
#define RECORD_IS_USED (current_target.to_stratum == record_stratum)
extern unsigned int record_debug;
-extern int record_memory_query;
-
-extern int record_arch_list_add_reg (struct regcache *regcache, int num);
-extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
-extern int record_arch_list_add_end (void);
-extern struct cleanup *record_gdb_operation_disable_set (void);
-
-/* Wrapper for target_read_memory that prints a debug message if
- reading memory fails. */
-extern int record_read_memory (struct gdbarch *gdbarch,
- CORE_ADDR memaddr, gdb_byte *myaddr,
- ssize_t len);
+
+/* Allow record targets to add their own sub-commands. */
+extern struct cmd_list_element *record_cmdlist;
+extern struct cmd_list_element *set_record_cmdlist;
+extern struct cmd_list_element *show_record_cmdlist;
+extern struct cmd_list_element *info_record_cmdlist;
#endif /* _RECORD_H_ */
diff --git a/gdb/target.c b/gdb/target.c
index 25f4629..42ddb3b 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -4231,6 +4231,71 @@ target_read_btrace (struct btrace_target_info *btinfo)
return NULL;
}
+/* See target.h. */
+
+void
+target_info_record (char *args, int from_tty)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_info_record != NULL)
+ {
+ t->to_info_record (args, from_tty);
+ return;
+ }
+}
+
+/* See target.h. */
+
+void
+target_save_record (char *filename, int from_tty)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_save_record != NULL)
+ {
+ t->to_save_record (filename, from_tty);
+ return;
+ }
+
+ error (_("The current record target does not support saving."));
+}
+
+/* See target.h. */
+
+void
+target_delete_record (char *args, int from_tty)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_delete_record != NULL)
+ {
+ t->to_delete_record (args, from_tty);
+ return;
+ }
+
+ error (_("The current record target does not support deleting."));
+}
+
+/* See target.h. */
+
+void
+target_goto_record (char *args, int from_tty)
+{
+ struct target_ops *t;
+
+ for (t = current_target.beneath; t != NULL; t = t->beneath)
+ if (t->to_goto_record != NULL)
+ {
+ t->to_goto_record (args, from_tty);
+ return;
+ }
+
+ error (_("The current record target does not support this operation."));
+}
static void
debug_to_prepare_to_store (struct regcache *regcache)
diff --git a/gdb/target.h b/gdb/target.h
index 1d73336..d5a1f86 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -876,6 +876,18 @@ struct target_ops
/* Read branch trace data. */
VEC (btrace_block_s) *(*to_read_btrace) (struct btrace_target_info *);
+ /* Print information about the recording. */
+ void (*to_info_record) (char *args, int from_tty);
+
+ /* Save the recorded execution trace into a file. */
+ void (*to_save_record) (char *filename, int from_tty);
+
+ /* Delete the recorded execution trace. */
+ void (*to_delete_record) (char *args, int from_tty);
+
+ /* Go to a specific location in the recorded execution trace. */
+ void (*to_goto_record) (char *args, int from_tty);
+
int to_magic;
/* Need sub-structure for target machine related rather than comm related?
*/
@@ -1938,5 +1950,16 @@ extern int target_btrace_has_changed (struct btrace_target_info *btinfo);
Returns a vector of branch trace blocks with the latest entry at index 0. */
extern VEC (btrace_block_s) *target_read_btrace (struct btrace_target_info *);
+/* Print record information for this record target. */
+extern void target_info_record (char *args, int from_tty);
+
+/* Save the recorded execution trace into a file. */
+extern void target_save_record (char *filename, int from_tty);
+
+/* Delete the recorded execution trace. */
+extern void target_delete_record (char *args, int from_tty);
+
+/* Go to a specific location in the recorded execution trace. */
+extern void target_goto_record (char *args, int from_tty);
#endif /* !defined (TARGET_H) */
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: record-btrace
2013-02-04 14:31 ` record-btrace Metzger, Markus T
@ 2013-02-05 19:45 ` Jan Kratochvil
2013-02-06 12:25 ` record-btrace Metzger, Markus T
0 siblings, 1 reply; 10+ messages in thread
From: Jan Kratochvil @ 2013-02-05 19:45 UTC (permalink / raw)
To: Metzger, Markus T; +Cc: markus.t.metzger, gdb-patches
[-- Attachment #1: Type: text/plain, Size: 7441 bytes --]
On Mon, 04 Feb 2013 15:31:10 +0100, Metzger, Markus T wrote:
> I added a new function add_deprecated_target_alias to do this. Patch attached.
Yes, that looks nice.
> Please let me know if you'd rather want it inline in a separate email.
Official way is to put it into the mail text. Just some mailers wrap the
lines or corrupt it some other way - unaware if your mailer will or will not.
Therefore it is also accepted to send it as attachments. But in such case:
* only one patch/attachment per mail, so that they can be reviewed
separately during mail reply
* Content-Disposition: inline (not attachment as yours), so that reply will
automatically quote it.
* Content-Transfer-Encoding: 7bit or quoted-printable (not base64 as yours) as
reportedly some people still uses MIME non-compliant mailers.
git diff --check also helps to catch various whitespacing issues (sometimes
violated in GDB...).
> > > On a similar matter, I renamed the existing "set/show record" subcommands by
> > > prepending "full-", i.e. "insn-number-max" becomes "full-insn-number-max".
> >
> > Could it be a prefix command? "set record full insn-number-max" etc.
> >
> > To match "record full" (vs. "record btrace").
>
> Done.
Use --enable-targets=all (and possibly --enable-64-bit-bfd), currently your
patch:
moxie-tdep.c:575:3: error: implicit declaration of function ‘record_arch_list_add_reg’ [-Werror=implicit-function-declaration]
arm-tdep.c:12603:7: error: implicit declaration of function ‘record_arch_list_add_reg’ [-Werror=implicit-function-declaration]
> When I use add_alias_cmd, I do not get the deprecated warning. Am I doing something
> wrong (patch attached)?
deprecate_cmd does it, I get the warning with your patch:
(gdb) target record
Warning: command 'target record' is deprecated.
Use 'target record-full'.
[...]
> > > For record-btrace and record-full, to_record_list will be quite different. For all
> > > other targets, it will be NULL.
> > >
> > > We could share to_disconnect, to_detach, to_kill, and to_mourn_inferior. They
> > > are just forwarding the request after unpushing the record target.
> > >
> > > I made the "record stop" command generic. It searches for a record target
> > > beneath the current and unpushes the first it finds. I could do something
> > > similar for the above.
> >
> > It looks OK to me, it would be better to see the code.
>
> Please find the patch attached.
I find (and I do) such changes better to be multi-part, first really just some
moving of code and in another patch adjustments of the content.
It does not have to be buildable separately, it can be checked-in as a single
commit.
> @@ -567,7 +568,7 @@ record_check_insn_num (int set_terminal)
> target_terminal_ours ();
> q = yquery (_("Do you want to auto delete previous execution "
> "log entries when record/replay buffer becomes "
> - "full (record stop-at-limit)?"));
> + "full (record full-stop-at-limit)?"));
record full stop-at-limit
> if (set_terminal)
> target_terminal_inferior ();
> if (q)
> @@ -1915,11 +1917,15 @@ record_goto_bookmark (gdb_byte *bookmark, int from_tty)
> bookmark[strlen (bookmark) - 1] = '\0';
> /* Strip leading quote. */
> bookmark++;
> - /* Pass along to cmd_record_goto. */
> + /* Pass along to "record goto". */
> }
>
> - cmd_record_goto ((char *) bookmark, from_tty);
> - return;
> + cmd = xstrprintf ("record goto %s", bookmark);
> + cleanup = make_cleanup (xfree, cmd);
> +
> + execute_command (cmd, from_tty);
> +
> + do_cleanups (cleanup);
Do you have a specific reason for this execute_command call? It could just
call target_goto_record.
(It was being done in record.c, I do not understand why, it is OK to keep such
code as is but it should not be newly introduced; I understand it is difficult
to find out which part of GDB code should be mimicked and which not.)
> }
>
> static void
> @@ -1953,10 +1959,171 @@ record_execution_direction (void)
> return record_execution_dir;
> }
>
> +/* The "to_info_record" method. */
> +
> +static void record_info (char *args, int from_tty)
Formatting is:
static void
record_info (char *args, int from_tty)
The functions got reordered, which makes their diff with "git diff -B -M"
a bit difficult to follow, I have to reorder them myself to see their changes,
I have attached the reorder when I already did it.
> --- /dev/null
> +++ b/gdb/record-full.h
> @@ -0,0 +1,38 @@
> +/* Process record and replay target for GDB, the GNU debugger.
> +
> + Copyright (C) 2008-2013 Free Software Foundation, Inc.
This is a new file, 2013 is enough.
> +
> + This file is part of GDB.
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; either version 3 of the License, or
> + (at your option) any later version.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program. If not, see <http://www.gnu.org/licenses/>. */
> +
> +#ifndef _RECORD_FULL_H_
GDB uses just "RECORD_FULL_H".
> +#define _RECORD_FULL_H_
> +
> +#include "record.h"
I do not see a need for this #include here.
> +
> +extern int record_memory_query;
> +
> +extern int record_arch_list_add_reg (struct regcache *regcache, int num);
> +extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
> +extern int record_arch_list_add_end (void);
> +extern struct cleanup *record_gdb_operation_disable_set (void);
> +
> +/* Wrapper for target_read_memory that prints a debug message if
> + reading memory fails. */
> +extern int record_read_memory (struct gdbarch *gdbarch,
> + CORE_ADDR memaddr, gdb_byte *myaddr,
> + ssize_t len);
> +
> +#endif /* _RECORD_FULL_H_ */
[...]
> +/* Truncate the record log from the present point
> + of replay until the end. */
> +
> +static void
> +cmd_record_delete (char *args, int from_tty)
> +{
> + require_record ();
> +
> + target_delete_record (args, from_tty);
I do not think the to_delete_record (and other new to_*_record methods) should
be really at such high level. target_* (to_*) methods should be at API level,
not at user command level.
Communication with user should be done in cmd_record_delete and only backend
specific data handling should be in target_delete_record.
Therefore there could be:
static void
cmd_record_delete (char *args, int from_tty)
{
require_record ();
if (target_record_at_end ())
printf_unfiltered (_("Already at end of record list.\n"));
else if (!from_tty || query (_("Delete the log from this point forward "
"and begin to record the running message "
"at current PC?")))
target_record_delete_to_end ();
}
Typically "from_tty" should never be passed to an API method. This will also
lead to unification of the user interface across record backends.
Sorry if I was not clear enough before, I was also offering this record.c
refactorization to do myself as it sidetracks you a lot from the btrace
implementation.
Thanks,
Jan
[-- Attachment #2: reorder.patch --]
[-- Type: text/plain, Size: 11475 bytes --]
diff --git a/gdb/record-full.c b/gdb/record-full.c
index 3b49296..32c6558 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -1959,167 +1959,6 @@ record_execution_direction (void)
return record_execution_dir;
}
-/* The "to_info_record" method. */
-
-static void record_info (char *args, int from_tty)
-{
- struct record_entry *p;
-
- if (current_target.to_stratum == record_stratum)
- {
- if (RECORD_IS_REPLAY)
- printf_filtered (_("Replay mode:\n"));
- else
- printf_filtered (_("Record mode:\n"));
-
- /* Find entry for first actual instruction in the log. */
- for (p = record_first.next;
- p != NULL && p->type != record_end;
- p = p->next)
- ;
-
- /* Do we have a log at all? */
- if (p != NULL && p->type == record_end)
- {
- /* Display instruction number for first instruction in the log. */
- printf_filtered (_("Lowest recorded instruction number is %s.\n"),
- pulongest (p->u.end.insn_num));
-
- /* If in replay mode, display where we are in the log. */
- if (RECORD_IS_REPLAY)
- printf_filtered (_("Current instruction number is %s.\n"),
- pulongest (record_list->u.end.insn_num));
-
- /* Display instruction number for last instruction in the log. */
- printf_filtered (_("Highest recorded instruction number is %s.\n"),
- pulongest (record_insn_count));
-
- /* Display log count. */
- printf_filtered (_("Log contains %d instructions.\n"),
- record_insn_num);
- }
- else
- {
- printf_filtered (_("No instructions have been logged.\n"));
- }
- }
- else
- {
- printf_filtered (_("target record is not active.\n"));
- }
-
- /* Display max log size. */
- printf_filtered (_("Max logged instructions is %d.\n"),
- record_insn_max_num);
-}
-
-/* record_goto_insn -- rewind the record log (forward or backward,
- depending on DIR) to the given entry, changing the program state
- correspondingly. */
-
-static void
-record_goto_insn (struct record_entry *entry,
- enum exec_direction_kind dir)
-{
- struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
- struct regcache *regcache = get_current_regcache ();
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
-
- /* Assume everything is valid: we will hit the entry,
- and we will not hit the end of the recording. */
-
- if (dir == EXEC_FORWARD)
- record_list = record_list->next;
-
- do
- {
- record_exec_insn (regcache, gdbarch, record_list);
- if (dir == EXEC_REVERSE)
- record_list = record_list->prev;
- else
- record_list = record_list->next;
- } while (record_list != entry);
- do_cleanups (set_cleanups);
-}
-
-/* The "to_goto_record" method. */
-
-static void record_goto (char *arg, int from_tty)
-{
- struct record_entry *p = NULL;
- ULONGEST target_insn = 0;
-
- if (arg == NULL || *arg == '\0')
- error (_("Command requires an argument (insn number to go to)."));
-
- if (strncmp (arg, "start", strlen ("start")) == 0
- || strncmp (arg, "begin", strlen ("begin")) == 0)
- {
- /* Special case. Find first insn. */
- for (p = &record_first; p != NULL; p = p->next)
- if (p->type == record_end)
- break;
- if (p)
- target_insn = p->u.end.insn_num;
- }
- else if (strncmp (arg, "end", strlen ("end")) == 0)
- {
- /* Special case. Find last insn. */
- for (p = record_list; p->next != NULL; p = p->next)
- ;
- for (; p!= NULL; p = p->prev)
- if (p->type == record_end)
- break;
- if (p)
- target_insn = p->u.end.insn_num;
- }
- else
- {
- /* General case. Find designated insn. */
- target_insn = parse_and_eval_long (arg);
-
- for (p = &record_first; p != NULL; p = p->next)
- if (p->type == record_end && p->u.end.insn_num == target_insn)
- break;
- }
-
- if (p == NULL)
- error (_("Target insn '%s' not found."), arg);
- else if (p == record_list)
- error (_("Already at insn '%s'."), arg);
- else if (p->u.end.insn_num > record_list->u.end.insn_num)
- {
- printf_filtered (_("Go forward to insn number %s\n"),
- pulongest (target_insn));
- record_goto_insn (p, EXEC_FORWARD);
- }
- else
- {
- printf_filtered (_("Go backward to insn number %s\n"),
- pulongest (target_insn));
- record_goto_insn (p, EXEC_REVERSE);
- }
- registers_changed ();
- reinit_frame_cache ();
- print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
-}
-
-/* The "to_delete_record" method of target record-full. */
-
-static void
-record_delete (char *args, int from_tty)
-{
- if (RECORD_IS_REPLAY)
- {
- if (!from_tty || query (_("Delete the log from this point forward "
- "and begin to record the running message "
- "at current PC?")))
- record_list_release_following (record_list);
- }
- else
- printf_unfiltered (_("Already at end of record list.\n"));
-}
-
static void
init_record_ops (void)
{
@@ -2387,6 +2226,33 @@ init_record_core_ops (void)
record_core_ops.to_magic = OPS_MAGIC;
}
+/* Alias for "target record". */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+ if (args != NULL && *args != 0)
+ error (_("Invalid argument."));
+
+ execute_command ("target record-full", from_tty);
+}
+
+/* The "to_delete_record" method of target record-full. */
+
+static void
+record_delete (char *args, int from_tty)
+{
+ if (RECORD_IS_REPLAY)
+ {
+ if (!from_tty || query (_("Delete the log from this point forward "
+ "and begin to record the running message "
+ "at current PC?")))
+ record_list_release_following (record_list);
+ }
+ else
+ printf_unfiltered (_("Already at end of record list.\n"));
+}
+
/* Set upper limit of record log size. */
static void
@@ -2403,6 +2269,60 @@ set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c)
}
}
+/* The "to_info_record" method. */
+
+static void record_info (char *args, int from_tty)
+{
+ struct record_entry *p;
+
+ if (current_target.to_stratum == record_stratum)
+ {
+ if (RECORD_IS_REPLAY)
+ printf_filtered (_("Replay mode:\n"));
+ else
+ printf_filtered (_("Record mode:\n"));
+
+ /* Find entry for first actual instruction in the log. */
+ for (p = record_first.next;
+ p != NULL && p->type != record_end;
+ p = p->next)
+ ;
+
+ /* Do we have a log at all? */
+ if (p != NULL && p->type == record_end)
+ {
+ /* Display instruction number for first instruction in the log. */
+ printf_filtered (_("Lowest recorded instruction number is %s.\n"),
+ pulongest (p->u.end.insn_num));
+
+ /* If in replay mode, display where we are in the log. */
+ if (RECORD_IS_REPLAY)
+ printf_filtered (_("Current instruction number is %s.\n"),
+ pulongest (record_list->u.end.insn_num));
+
+ /* Display instruction number for last instruction in the log. */
+ printf_filtered (_("Highest recorded instruction number is %s.\n"),
+ pulongest (record_insn_count));
+
+ /* Display log count. */
+ printf_filtered (_("Log contains %d instructions.\n"),
+ record_insn_num);
+ }
+ else
+ {
+ printf_filtered (_("No instructions have been logged.\n"));
+ }
+ }
+ else
+ {
+ printf_filtered (_("target record is not active.\n"));
+ }
+
+ /* Display max log size. */
+ printf_filtered (_("Max logged instructions is %d.\n"),
+ record_insn_max_num);
+}
+
/* Record log save-file format
Version 1 (never released)
@@ -2664,27 +2584,6 @@ record_restore (void)
print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
}
-/* Restore the execution log from a file. We use a modified elf
- corefile format, with an extra section for our data. */
-
-static void
-cmd_record_restore (char *args, int from_tty)
-{
- core_file_command (args, from_tty);
- record_open (args, from_tty);
-}
-
-/* Alias for "target record". */
-
-static void
-cmd_record_start (char *args, int from_tty)
-{
- if (args != NULL && *args != 0)
- error (_("Invalid argument."));
-
- execute_command ("target record-full", from_tty);
-}
-
/* bfdcore_write -- write bytes into a core file section. */
static inline void
@@ -2700,6 +2599,16 @@ bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
bfd_errmsg (bfd_get_error ()));
}
+/* Restore the execution log from a file. We use a modified elf
+ corefile format, with an extra section for our data. */
+
+static void
+cmd_record_restore (char *args, int from_tty)
+{
+ core_file_command (args, from_tty);
+ record_open (args, from_tty);
+}
+
static void
record_save_cleanups (void *data)
{
@@ -2915,6 +2824,97 @@ record_save (char *recfilename, int from_tty)
recfilename);
}
+/* record_goto_insn -- rewind the record log (forward or backward,
+ depending on DIR) to the given entry, changing the program state
+ correspondingly. */
+
+static void
+record_goto_insn (struct record_entry *entry,
+ enum exec_direction_kind dir)
+{
+ struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
+ struct regcache *regcache = get_current_regcache ();
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+
+ /* Assume everything is valid: we will hit the entry,
+ and we will not hit the end of the recording. */
+
+ if (dir == EXEC_FORWARD)
+ record_list = record_list->next;
+
+ do
+ {
+ record_exec_insn (regcache, gdbarch, record_list);
+ if (dir == EXEC_REVERSE)
+ record_list = record_list->prev;
+ else
+ record_list = record_list->next;
+ } while (record_list != entry);
+ do_cleanups (set_cleanups);
+}
+
+/* The "to_goto_record" method. */
+
+static void record_goto (char *arg, int from_tty)
+{
+ struct record_entry *p = NULL;
+ ULONGEST target_insn = 0;
+
+ if (arg == NULL || *arg == '\0')
+ error (_("Command requires an argument (insn number to go to)."));
+
+ if (strncmp (arg, "start", strlen ("start")) == 0
+ || strncmp (arg, "begin", strlen ("begin")) == 0)
+ {
+ /* Special case. Find first insn. */
+ for (p = &record_first; p != NULL; p = p->next)
+ if (p->type == record_end)
+ break;
+ if (p)
+ target_insn = p->u.end.insn_num;
+ }
+ else if (strncmp (arg, "end", strlen ("end")) == 0)
+ {
+ /* Special case. Find last insn. */
+ for (p = record_list; p->next != NULL; p = p->next)
+ ;
+ for (; p!= NULL; p = p->prev)
+ if (p->type == record_end)
+ break;
+ if (p)
+ target_insn = p->u.end.insn_num;
+ }
+ else
+ {
+ /* General case. Find designated insn. */
+ target_insn = parse_and_eval_long (arg);
+
+ for (p = &record_first; p != NULL; p = p->next)
+ if (p->type == record_end && p->u.end.insn_num == target_insn)
+ break;
+ }
+
+ if (p == NULL)
+ error (_("Target insn '%s' not found."), arg);
+ else if (p == record_list)
+ error (_("Already at insn '%s'."), arg);
+ else if (p->u.end.insn_num > record_list->u.end.insn_num)
+ {
+ printf_filtered (_("Go forward to insn number %s\n"),
+ pulongest (target_insn));
+ record_goto_insn (p, EXEC_FORWARD);
+ }
+ else
+ {
+ printf_filtered (_("Go backward to insn number %s\n"),
+ pulongest (target_insn));
+ record_goto_insn (p, EXEC_REVERSE);
+ }
+ registers_changed ();
+ reinit_frame_cache ();
+ print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
+}
+
/* The "set record full" command. */
static void
^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: record-btrace
2013-02-05 19:45 ` record-btrace Jan Kratochvil
@ 2013-02-06 12:25 ` Metzger, Markus T
2013-02-06 14:58 ` record-btrace Jan Kratochvil
0 siblings, 1 reply; 10+ messages in thread
From: Metzger, Markus T @ 2013-02-06 12:25 UTC (permalink / raw)
To: Jan Kratochvil; +Cc: markus.t.metzger, gdb-patches
> -----Original Message-----
> From: Jan Kratochvil [mailto:jan.kratochvil@redhat.com]
> Sent: Tuesday, February 05, 2013 8:45 PM
> > > > On a similar matter, I renamed the existing "set/show record" subcommands by
> > > > prepending "full-", i.e. "insn-number-max" becomes "full-insn-number-max".
> > >
> > > Could it be a prefix command? "set record full insn-number-max" etc.
> > >
> > > To match "record full" (vs. "record btrace").
> >
> > Done.
>
> Use --enable-targets=all (and possibly --enable-64-bit-bfd), currently your
> patch:
> moxie-tdep.c:575:3: error: implicit declaration of function 'record_arch_list_add_reg' [-Werror=implicit-function-declaration]
> arm-tdep.c:12603:7: error: implicit declaration of function 'record_arch_list_add_reg' [-Werror=implicit-function-declaration]
Thanks. I'll fix that.
> > When I use add_alias_cmd, I do not get the deprecated warning. Am I doing something
> > wrong (patch attached)?
>
> deprecate_cmd does it, I get the warning with your patch:
> (gdb) target record
> Warning: command 'target record' is deprecated.
> Use 'target record-full'.
> [...]
For "target record", I added a full command using add_cmd. When I use add_alias_cmd
and then deprecate_cmd on the returned command, I do not get the warning.
> > if (set_terminal)
> > target_terminal_inferior ();
> > if (q)
> > @@ -1915,11 +1917,15 @@ record_goto_bookmark (gdb_byte *bookmark, int from_tty)
> > bookmark[strlen (bookmark) - 1] = '\0';
> > /* Strip leading quote. */
> > bookmark++;
> > - /* Pass along to cmd_record_goto. */
> > + /* Pass along to "record goto". */
> > }
> >
> > - cmd_record_goto ((char *) bookmark, from_tty);
> > - return;
> > + cmd = xstrprintf ("record goto %s", bookmark);
> > + cleanup = make_cleanup (xfree, cmd);
> > +
> > + execute_command (cmd, from_tty);
> > +
> > + do_cleanups (cleanup);
>
> Do you have a specific reason for this execute_command call? It could just
> call target_goto_record.
>
> (It was being done in record.c, I do not understand why, it is OK to keep such
> code as is but it should not be newly introduced; I understand it is difficult
> to find out which part of GDB code should be mimicked and which not.)
That was exactly the reason - I tried to do what record.c did. I'll remove it.
> > --- /dev/null
> > +++ b/gdb/record-full.h
> > @@ -0,0 +1,38 @@
> > +/* Process record and replay target for GDB, the GNU debugger.
> > +
> > + Copyright (C) 2008-2013 Free Software Foundation, Inc.
>
> This is a new file, 2013 is enough.
It has been copied from record.h so I kept the copyright.
> > +/* Truncate the record log from the present point
> > + of replay until the end. */
> > +
> > +static void
> > +cmd_record_delete (char *args, int from_tty)
> > +{
> > + require_record ();
> > +
> > + target_delete_record (args, from_tty);
>
> I do not think the to_delete_record (and other new to_*_record methods) should
> be really at such high level. target_* (to_*) methods should be at API level,
> not at user command level.
>
> Communication with user should be done in cmd_record_delete and only backend
> specific data handling should be in target_delete_record.
>
> Therefore there could be:
>
> static void
> cmd_record_delete (char *args, int from_tty)
> {
> require_record ();
>
> if (target_record_at_end ())
> printf_unfiltered (_("Already at end of record list.\n"));
> else if (!from_tty || query (_("Delete the log from this point forward "
> "and begin to record the running message "
> "at current PC?")))
> target_record_delete_to_end ();
> }
>
> Typically "from_tty" should never be passed to an API method. This will also
> lead to unification of the user interface across record backends.
OK.
> Sorry if I was not clear enough before, I was also offering this record.c
> refactorization to do myself as it sidetracks you a lot from the btrace
> implementation.
Sorry, I didn't get that. You can help me hook up a stack unwinder in exchange;-)
I'll send an updated version as an rfc patch series.
Regards,
Markus.
Intel GmbH
Dornacher Strasse 1
85622 Feldkirchen/Muenchen, Deutschland
Sitz der Gesellschaft: Feldkirchen bei Muenchen
Geschaeftsfuehrer: Christian Lamprechter, Hannes Schwaderer, Douglas Lusk
Registergericht: Muenchen HRB 47456
Ust.-IdNr./VAT Registration No.: DE129385895
Citibank Frankfurt a.M. (BLZ 502 109 00) 600119052
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: record-btrace
2013-02-06 12:25 ` record-btrace Metzger, Markus T
@ 2013-02-06 14:58 ` Jan Kratochvil
0 siblings, 0 replies; 10+ messages in thread
From: Jan Kratochvil @ 2013-02-06 14:58 UTC (permalink / raw)
To: Metzger, Markus T; +Cc: markus.t.metzger, gdb-patches
On Wed, 06 Feb 2013 13:25:07 +0100, Metzger, Markus T wrote:
> For "target record", I added a full command using add_cmd. When I use add_alias_cmd
> and then deprecate_cmd on the returned command, I do not get the warning.
OK, I see there now your:
/* If we use add_alias_cmd, here, we do not get the deprecated warning. */
I have added PR cli/15104 for that, it could be mentioned in the comment,
otherwise I am OK with it this way.
> > > + Copyright (C) 2008-2013 Free Software Foundation, Inc.
> >
> > This is a new file, 2013 is enough.
>
> It has been copied from record.h so I kept the copyright.
I do not understand the rules but AFAIK new files do not / should not keep
copyright years for their possible moved in content.
> You can help me hook up a stack unwinder in exchange;-)
The stack unwinder which refuses unwind when we are in btrace history, OK.
Thanks,
Jan
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2013-02-06 14:58 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-28 16:14 record-btrace Metzger, Markus T
2013-01-29 9:53 ` record-btrace Jan Kratochvil
2013-01-29 11:05 ` record-btrace Metzger, Markus T
2013-01-29 15:36 ` record-btrace Jan Kratochvil
2013-01-31 15:38 ` record-btrace Metzger, Markus T
2013-02-01 14:18 ` record-btrace Jan Kratochvil
2013-02-04 14:31 ` record-btrace Metzger, Markus T
2013-02-05 19:45 ` record-btrace Jan Kratochvil
2013-02-06 12:25 ` record-btrace Metzger, Markus T
2013-02-06 14:58 ` record-btrace Jan Kratochvil
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox