forked from CTSRD-CHERI/qemu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlog_instr.c
More file actions
1686 lines (1495 loc) · 53.8 KB
/
Copy pathlog_instr.c
File metadata and controls
1686 lines (1495 loc) · 53.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2020 Alfredo Mazzinghi
*
* This software was developed by SRI International and the University of
* Cambridge Computer Laboratory (Department of Computer Science and
* Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
* DARPA SSITH research programme.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stdint.h>
#include "qemu/osdep.h"
#include "qemu/range.h"
#include "qemu/log.h"
#include "cpu-param.h"
#include "cpu.h"
#include "exec/exec-all.h"
#include "exec/log.h"
#include "exec/helper-proto.h"
#include "exec/log_instr.h"
#include "exec/memop.h"
#include "disas/disas.h"
#include "exec/translator.h"
#include "tcg/tcg.h"
#include "tcg/tcg-op.h"
/*
* CHERI common instruction logging.
*
* This is the central implementation of the CPU_LOG_INSTR tracing.
* The same functions can be used by CHERI targets to append to the instruction
* log buffer. Once the instruction is fully processed, the target commits the
* log buffer and depending on the instruction operations and -dfilter options
* we either flush the buffer or drop it.
* A central desing goal is to reliably log multiple register updates and memory
* accesses performed by an instruction. We also want to allow to log arbitary
* events via special no-op instructions. Extra text debug output can also be
* appended to the instruction log info.
*
* The output trace format can be easily changed by implementing a new set of
* trace_fmt_hooks.
*
* The CPU_LOG_INSTR flag is used as a global enable to signal that logging is
* active. Each CPU holds a private logging state, that can be controlled
* individually.
*
* TODO(am2419): how do we deal with orderding in case multiple registers are
* updated? This is critical to recognize which value goes in which register,
* and also how to tie multiple memory accesses to the respective
* value/register. We could add an explicit target-specific register ID handle
* in place of the register name. This could be used also to fetch the register
* name and would provide an identifier to external parsers. Memory updates are
* harder to deal with, at least in the current format, perhaps the semantic of
* the instruction is enough to recover the ordering from a trace.
*/
#ifdef CONFIG_TCG_LOG_INSTR
// #define CONFIG_DEBUG_TCG
#ifdef CONFIG_DEBUG_TCG
#define log_assert(x) assert((x))
#else
#define log_assert(x)
#endif
#ifndef TARGET_MAX_INSN_SIZE
#error "Target does not define TARGET_MAX_INSN_SIZE in cpu-param.h"
#endif
/*
* -dfilter ranges in common logging implementation.
*/
extern GArray *debug_regions;
/*
* Instruction log info associated with each committed log entry.
* This is stored in the per-cpu log cpustate.
*/
typedef struct cpu_log_instr_info {
#define cpu_log_iinfo_startzero asid
uint16_t asid;
int flags;
/* Entry contains a synchronous exception */
#define LI_FLAG_INTR_TRAP 1
/* Entry contains an asynchronous exception */
#define LI_FLAG_INTR_ASYNC (1 << 1)
#define LI_FLAG_INTR_MASK 0x3
/* Entry contains a CPU mode-switch and associated code */
#define LI_FLAG_MODE_SWITCH (1 << 2)
qemu_log_instr_cpu_mode_t next_cpu_mode;
uint32_t intr_code;
target_ulong intr_vector;
target_ulong intr_faultaddr;
target_ulong pc;
/* Generic instruction opcode buffer */
int insn_size;
char insn_bytes[TARGET_MAX_INSN_SIZE];
#define cpu_log_iinfo_endzero mem
/*
* For now we allow multiple accesses to be tied to one instruction.
* Some architectures may have multiple memory accesses
* in the same instruction (e.g. x86-64 pop r/m64,
* vector/matrix instructions, load/store pair). It is unclear
* whether we would treat these as multiple trace "entities".
*
* Array of log_meminfo_t
*/
GArray *mem;
/* Register modifications. Array of log_reginfo_t */
GArray *regs;
/* Extra text-only log */
GString *txt_buffer;
} cpu_log_instr_info_t;
/*
* Register update info.
* This records a CPU register update occurred during an instruction.
*/
typedef struct {
uint16_t flags;
const char *name;
uint16_t index;
union {
target_ulong gpr;
#ifdef TARGET_CHERI
cap_register_t cap;
#endif
};
} log_reginfo_t;
#define reginfo_is_cap(ri) (ri->flags & LRI_CAP_REG)
#define reginfo_has_cap(ri) (reginfo_is_cap(ri) && (ri->flags & LRI_HOLDS_CAP))
/*
* Memory access info.
* This records a memory access occurred during an instruction.
*/
typedef struct {
uint8_t flags;
#define LMI_LD 1
#define LMI_ST 2
#define LMI_CAP 4
MemOp op;
target_ulong addr;
union {
uint64_t value;
#ifdef TARGET_CHERI
cap_register_t cap;
#endif
};
} log_meminfo_t;
/*
* Callbacks defined by a trace format implementation.
* These are called to covert instruction tracing events to the corresponding
* binary or text format.
*/
struct trace_fmt_hooks {
void (*emit_header)(CPUArchState *env);
void (*emit_start)(CPUArchState *env, target_ulong pc);
void (*emit_stop)(CPUArchState *env, target_ulong pc);
void (*emit_entry)(CPUArchState *env, cpu_log_instr_info_t *iinfo);
};
typedef struct trace_fmt_hooks trace_fmt_hooks_t;
/* Global trace format selector. Defaults to text tracing */
qemu_log_instr_fmt_t qemu_log_instr_format = QLI_FMT_TEXT;
/* Current format callbacks. */
static trace_fmt_hooks_t *trace_format = NULL;
/* Existing format callbacks list, indexed by qemu_log_instr_fmt_t */
static trace_fmt_hooks_t trace_formats[];
/*
* CHERI binary trace format, originally used for MIPS only.
* The format is limited to one entry per instruction, each
* entry can hold at most one register modification and one
* memory address.
* Note that the CHERI format is the legacy MIPS format and
* assumes big-endian byte order.
*/
typedef struct {
uint8_t entry_type;
#define CTE_NO_REG 0 /* No register is changed. */
#define CTE_GPR 1 /* GPR change (val2) */
#define CTE_LD_GPR 2 /* Load into GPR (val2) from address (val1) */
#define CTE_ST_GPR 3 /* Store from GPR (val2) to address (val1) */
#define CTE_CAP 11 /* Cap change (val2,val3,val4,val5) */
#define CTE_LD_CAP 12 /* Load Cap (val2,val3,val4,val5) from addr (val1) */
#define CTE_ST_CAP 13 /* Store Cap (val2,val3,val4,val5) to addr (val1) */
uint8_t exception; /* 0=none, 1=TLB Mod, 2=TLB Load, 3=TLB Store, etc. */
#define CTE_EXCEPTION_NONE 31
uint16_t cycles; /* Currently not used. */
uint32_t inst; /* Encoded instruction. */
uint64_t pc; /* PC value of instruction. */
uint64_t val1; /* val1 is used for memory address. */
uint64_t val2; /* val2, val3, val4, val5 are used for reg content. */
uint64_t val3;
uint64_t val4;
uint64_t val5;
uint8_t thread; /* Hardware thread/CPU (i.e. cpu->cpu_index ) */
uint8_t asid; /* Address Space ID */
} __attribute__((packed)) cheri_trace_entry_t;
/* Version 3 Cheri Stream Trace header info */
#define CTE_QEMU_VERSION (0x80U + 3)
#define CTE_QEMU_MAGIC "CheriTraceV03"
/* Number of per-cpu ring buffer entries for ring-buffer tracing mode */
#define MIN_ENTRY_BUFFER_SIZE (1 << 16)
static unsigned long reset_entry_buffer_size = MIN_ENTRY_BUFFER_SIZE;
/*
* Fetch the log state for a cpu.
*/
static inline cpu_log_instr_state_t *get_cpu_log_state(CPUArchState *env)
{
return &env_cpu(env)->log_state;
}
/*
* Fetch the given cpu current instruction info
*/
static inline cpu_log_instr_info_t *get_cpu_log_instr_info(CPUArchState *env)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
return &g_array_index(cpulog->instr_info, cpu_log_instr_info_t,
cpulog->ring_head);
}
/* Text trace format emitters */
/*
* Emit textual trace representation of memory access
*/
static inline void emit_text_ldst(log_meminfo_t *minfo, const char *direction)
{
#ifndef TARGET_CHERI
log_assert((minfo->flags & LMI_CAP) == 0 &&
"Capability memory access without CHERI support");
#else
if (minfo->flags & LMI_CAP) {
qemu_log(" Cap Memory %s [" TARGET_FMT_lx "] = v:%d PESBT:"
TARGET_FMT_lx " Cursor:" TARGET_FMT_lx "\n",
direction, minfo->addr, minfo->cap.cr_tag,
CAP_cc(compress_mem)(&minfo->cap),
cap_get_cursor(&minfo->cap));
} else
#endif
{
switch (memop_size(minfo->op)) {
default:
qemu_log(" Unknown memory access width\n");
/* fallthrough */
case 8:
qemu_log(" Memory %s [" TARGET_FMT_lx "] = " TARGET_FMT_plx "\n",
direction, minfo->addr, minfo->value);
break;
case 4:
qemu_log(" Memory %s [" TARGET_FMT_lx "] = %08x\n",
direction, minfo->addr, (uint32_t)minfo->value);
break;
case 2:
qemu_log(" Memory %s [" TARGET_FMT_lx "] = %04x\n",
direction, minfo->addr, (uint16_t)minfo->value);
break;
case 1:
qemu_log(" Memory %s [" TARGET_FMT_lx "] = %02x\n",
direction, minfo->addr, (uint8_t)minfo->value);
break;
}
}
}
/*
* Emit textual trace representation of register modification
*/
static inline void emit_text_reg(log_reginfo_t *rinfo)
{
#ifndef TARGET_CHERI
log_assert(!reginfo_is_cap(rinfo) && "Register marked as capability "
"register whitout CHERI support");
#else
if (reginfo_is_cap(rinfo)) {
if (reginfo_has_cap(rinfo))
qemu_log(" Write %s|" PRINT_CAP_FMTSTR "\n",
rinfo->name, PRINT_CAP_ARGS(&rinfo->cap));
else
qemu_log(" %s <- " TARGET_FMT_lx " (setting integer value)\n",
rinfo->name, rinfo->gpr);
} else
#endif
{
qemu_log(" Write %s = " TARGET_FMT_lx "\n", rinfo->name,
rinfo->gpr);
}
}
/*
* Emit textual trace entry to the log.
*/
static void emit_text_entry(CPUArchState *env, cpu_log_instr_info_t *iinfo)
{
QemuLogFile *logfile;
int i;
/* Dump CPU-ID:ASID + address */
qemu_log("[%d:%d] ", env_cpu(env)->cpu_index, iinfo->asid);
/*
* Instruction disassembly, note that we use the instruction info
* opcode bytes, without accessing target memory here.
*/
rcu_read_lock();
logfile = qatomic_rcu_read(&qemu_logfile);
if (logfile) {
target_disas_buf(logfile->fd, env_cpu(env), iinfo->insn_bytes,
sizeof(iinfo->insn_bytes), iinfo->pc, 1);
}
rcu_read_unlock();
/*
* TODO(am2419): what to do with injected instructions?
* Is the rvfi_dii_trace state valid at log commit?
*/
/* Dump mode switching info */
if (iinfo->flags & LI_FLAG_MODE_SWITCH)
qemu_log("-> Switch to %s mode\n", cpu_get_mode_name(iinfo->next_cpu_mode));
/* Dump interrupt/exception info */
switch (iinfo->flags & LI_FLAG_INTR_MASK) {
case LI_FLAG_INTR_TRAP:
qemu_log("-> Exception #%u vector 0x" TARGET_FMT_lx
" fault-addr 0x" TARGET_FMT_lx "\n",
iinfo->intr_code, iinfo->intr_vector, iinfo->intr_faultaddr);
break;
case LI_FLAG_INTR_ASYNC:
qemu_log("-> Interrupt #%04x vector 0x" TARGET_FMT_lx "\n",
iinfo->intr_code, iinfo->intr_vector);
break;
default:
/* No interrupt */
break;
}
/* Dump memory access */
for (i = 0; i < iinfo->mem->len; i++) {
log_meminfo_t *minfo = &g_array_index(iinfo->mem, log_meminfo_t, i);
if (minfo->flags & LMI_LD) {
emit_text_ldst(minfo, "Read");
} else if (minfo->flags & LMI_ST) {
emit_text_ldst(minfo, "Write");
}
}
/* Dump register changes and side-effects */
for (i = 0; i < iinfo->regs->len; i++) {
log_reginfo_t *rinfo = &g_array_index(iinfo->regs, log_reginfo_t, i);
emit_text_reg(rinfo);
}
/* Dump extra logged messages, if any */
if (iinfo->txt_buffer->len > 0)
qemu_log("%s", iinfo->txt_buffer->str);
}
/*
* Emit text tracing start event.
*/
static void emit_text_start(CPUArchState *env, target_ulong pc)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
if (cpulog->loglevel == QEMU_LOG_INSTR_LOGLEVEL_USER) {
qemu_log("[%u:%u] Requested user-mode only instruction logging "
"@ " TARGET_FMT_lx " \n",
env_cpu(env)->cpu_index, cpu_get_asid(env, pc), pc);
} else {
qemu_log("[%u:%u] Requested instruction logging @ " TARGET_FMT_lx " \n",
env_cpu(env)->cpu_index, cpu_get_asid(env, pc), pc);
}
}
/*
* Emit text tracing stop event.
*/
static void emit_text_stop(CPUArchState *env, target_ulong pc)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
if (cpulog->loglevel == QEMU_LOG_INSTR_LOGLEVEL_USER) {
qemu_log("[%u:%u] Disabled user-mode only instruction logging "
"@ " TARGET_FMT_lx " \n",
env_cpu(env)->cpu_index, cpu_get_asid(env, pc), pc);
} else {
qemu_log("[%u:%u] Disabled instruction logging @ " TARGET_FMT_lx " \n",
env_cpu(env)->cpu_index, cpu_get_asid(env, pc), pc);
}
}
/* CHERI trace V3 format emitters */
/*
* Emit cvtrace trace trace header. This is a magic byte + string
*/
static void emit_cvtrace_header(CPUArchState *env)
{
FILE *logfile = qemu_log_lock();
char buffer[sizeof(cheri_trace_entry_t)];
buffer[0] = CTE_QEMU_VERSION;
g_strlcpy(buffer + 1, CTE_QEMU_MAGIC, sizeof(buffer) - 2);
fwrite(buffer, sizeof(buffer), 1, logfile);
qemu_log_unlock(logfile);
}
/*
* Emit cvtrace trace entry.
* Note: this format is very MIPS-specific.
*/
static void emit_cvtrace_entry(CPUArchState *env, cpu_log_instr_info_t *iinfo)
{
FILE *logfile;
cheri_trace_entry_t entry;
static uint16_t cycles = 0; // TODO(am2419): this should be a per-cpu counter.
uint32_t *insn = (uint32_t *)&iinfo->insn_bytes[0];
entry.entry_type = CTE_NO_REG;
entry.thread = (uint8_t)env_cpu(env)->cpu_index;
entry.asid = (uint8_t)iinfo->asid;
entry.pc = cpu_to_be64(iinfo->pc);
entry.cycles = cpu_to_be16(cycles++);
/*
* TODO(am2419): The instruction bytes are alread in target byte-order, however
* cheritrace does not currently expect this.
*/
entry.inst = cpu_to_be32(*insn);
switch (iinfo->flags & LI_FLAG_INTR_MASK) {
case LI_FLAG_INTR_TRAP:
entry.exception = (uint8_t)(iinfo->intr_code & 0xff);
break;
case LI_FLAG_INTR_ASYNC:
entry.exception = 0;
break;
default:
entry.exception = CTE_EXCEPTION_NONE;
break;
}
if (iinfo->regs->len) {
log_reginfo_t *rinfo = &g_array_index(iinfo->regs, log_reginfo_t, 0);
#ifndef TARGET_CHERI
log_assert(!reginfo_is_cap(rinfo) && "Capability register access "
"without CHERI support");
#else
if (reginfo_is_cap(rinfo)) {
cap_register_t intcap;
const cap_register_t *cr = &rinfo->cap;
if (!reginfo_has_cap(rinfo)) {
/* cvtrace expects a null capability in the integer case. */
intcap = make_capability_from_int(env, rinfo->gpr);
cr = &intcap;
}
uint64_t metadata = (((uint64_t)cr->cr_tag << 63) |
((uint64_t)cap_get_otype_signext(cr) << 32) |
((uint64_t)cap_get_all_perms(cr) << 1) |
(uint64_t)(cap_is_unsealed(cr) ? 0 : 1));
entry.entry_type = CTE_CAP;
entry.val2 = cpu_to_be64(metadata);
entry.val3 = cpu_to_be64(cap_get_cursor(cr));
entry.val4 = cpu_to_be64(cap_get_base(cr));
entry.val5 = cpu_to_be64(cap_get_length_sat(cr));
} else
#endif
{
entry.entry_type = CTE_GPR;
entry.val2 = cpu_to_be64(rinfo->gpr);
}
}
if (iinfo->mem->len) {
log_meminfo_t *minfo = &g_array_index(iinfo->mem, log_meminfo_t, 0);
#ifndef TARGET_CHERI
log_assert((minfo->flags & LMI_CAP) == 0 && "Capability memory access "
"without CHERI support");
#endif
entry.val1 = cpu_to_be64(minfo->addr);
// Hack to avoid checking for GPR or CAP
if (minfo->flags & LMI_LD)
entry.entry_type += 1;
else if (minfo->flags & LMI_ST)
entry.entry_type += 2;
}
logfile = qemu_log_lock();
fwrite(&entry, sizeof(entry), 1, logfile);
qemu_log_unlock(logfile);
}
static void emit_cvtrace_start(CPUArchState *env, target_ulong pc)
{
// TODO(am2419) Emit an event for instruction logging start
}
static void emit_cvtrace_stop(CPUArchState *env, target_ulong pc)
{
// TODO(am2419) Emit an event for instruction logging stop
}
/* Core instruction logging implementation */
static inline void emit_start_event(CPUArchState *env, target_ulong pc)
{
if ((get_cpu_log_state(env)->flags & QEMU_LOG_INSTR_FLAG_BUFFERED) == 0)
trace_format->emit_start(env, pc);
}
static inline void emit_stop_event(CPUArchState *env, target_ulong pc)
{
if ((get_cpu_log_state(env)->flags & QEMU_LOG_INSTR_FLAG_BUFFERED) == 0)
trace_format->emit_stop(env, pc);
}
static inline void emit_entry_event(CPUArchState *env, cpu_log_instr_info_t *iinfo)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
if (cpulog->flags & QEMU_LOG_INSTR_FLAG_BUFFERED) {
cpulog->ring_head = (cpulog->ring_head + 1) % cpulog->instr_info->len;
if (cpulog->ring_tail == cpulog->ring_head)
cpulog->ring_tail = (cpulog->ring_tail + 1) % cpulog->instr_info->len;
}
else {
trace_format->emit_entry(env, iinfo);
}
}
/* Reset instruction info buffer for next instruction */
static void reset_log_buffer(cpu_log_instr_state_t *cpulog,
cpu_log_instr_info_t *iinfo)
{
memset(&iinfo->cpu_log_iinfo_startzero, 0,
((char *)&iinfo->cpu_log_iinfo_endzero -
(char *)&iinfo->cpu_log_iinfo_startzero));
g_array_remove_range(iinfo->regs, 0, iinfo->regs->len);
g_array_remove_range(iinfo->mem, 0, iinfo->mem->len);
g_string_erase(iinfo->txt_buffer, 0, -1);
cpulog->force_drop = false;
cpulog->starting = false;
}
/* Common instruction commit implementation */
static void do_instr_commit(CPUArchState *env)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);
log_assert(cpulog != NULL && "Invalid log state");
log_assert(iinfo != NULL && "Invalid log buffer");
if (cpulog->force_drop)
return;
if (cpulog->starting) {
cpulog->starting = false;
emit_start_event(env, cpu_get_recent_pc(env));
return;
}
/* Check for dfilter matches in this instruction */
if (debug_regions) {
int i, j;
bool match = false;
for (i = 0; !match && i < debug_regions->len; i++) {
Range *range = &g_array_index(debug_regions, Range, i);
match = range_contains(range, iinfo->pc);
if (match)
break;
for (j = 0; j < iinfo->mem->len; j++) {
log_meminfo_t *minfo = &g_array_index(iinfo->mem, log_meminfo_t, j);
match = range_contains(range, minfo->addr);
if (match)
break;
}
}
if (match)
emit_entry_event(env, iinfo);
} else {
/* dfilter disabled, always log */
emit_entry_event(env, iinfo);
}
}
/*
* Perform the actual work to change per-CPU log level.
* This runs in the CPU exclusive context.
*
* Note:
* If we start logging, we delay emitting the start event until the next commit.
* This is because on the path from the exclusive context to the translation
* loop we may get an interrupt/exception causing a switch in CPU mode, causing
* to stop logging. This would result in a pointless start/stop sequence with
* no instructions executed in beteween.
*/
static void do_cpu_loglevel_switch(CPUState *cpu, run_on_cpu_data data)
{
CPUArchState *env = cpu->env_ptr;
tcg_debug_assert(env != NULL && "Called to early?");
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);
qemu_log_instr_loglevel_t prev_level = cpulog->loglevel;
bool prev_level_active = cpulog->loglevel_active;
qemu_log_instr_loglevel_t next_level = data.host_int;
bool next_level_active = 0;
/* Decide whether we have to pause/resume logging */
switch (next_level) {
case QEMU_LOG_INSTR_LOGLEVEL_NONE:
next_level_active = false;
break;
case QEMU_LOG_INSTR_LOGLEVEL_ALL:
next_level_active = true;
break;
case QEMU_LOG_INSTR_LOGLEVEL_USER:
/*
* Assume iinfo holds the mode switch that caused cpu_loglevel_switch
* to be called
*/
if (iinfo->flags & LI_FLAG_MODE_SWITCH)
next_level_active = (iinfo->next_cpu_mode == QEMU_LOG_INSTR_CPU_USER);
else
next_level_active = cpu_in_user_mode(env);
break;
default:
log_assert(false && "Invalid cpu instruction log level");
warn_report("Invalid cpu %d instruction log level\r",
cpu->cpu_index);
}
/* Update level */
cpulog->loglevel = next_level;
cpulog->loglevel_active = next_level_active;
/* Check if this was a no-op */
if (next_level == prev_level && prev_level_active == next_level_active)
return;
/* Emit start/stop events */
if (prev_level_active) {
if (cpulog->starting) {
reset_log_buffer(cpulog, iinfo);
return;
}
do_instr_commit(env);
emit_stop_event(env, cpu_get_recent_pc(env));
/* Instruction commit may have advanced to the next iinfo buffer slot */
iinfo = get_cpu_log_instr_info(env);
reset_log_buffer(cpulog, iinfo);
}
/*
* This function is called when tcg generates code for a dummy slti
* instruction that changes the log level (or when qemu is started).
* The generated code terminates the current TB.
* We have to propagate the updated logging status to the next TB.
*/
if (next_level_active) {
cpulog->starting = true;
cpu->cflags_next_tb = curr_cflags(cpu) | CF_LOG_INSTR;
} else {
cpu->cflags_next_tb = curr_cflags(cpu) & ~CF_LOG_INSTR;
}
/*
* It seems that cpu->cflags_next_tb affect only the next block, not the
* ones after this. Update cpu->tcg_cflags to set the updated flags for
* all following blocks.
*/
cpu->tcg_cflags = cpu->cflags_next_tb;
}
static void cpu_loglevel_switch(CPUArchState *env,
qemu_log_instr_loglevel_t level)
{
async_safe_run_on_cpu(env_cpu(env), do_cpu_loglevel_switch,
RUN_ON_CPU_HOST_INT(level));
}
/* Start global logging flag if it was disabled */
static void global_loglevel_enable(void)
{
if (!qemu_loglevel_mask(CPU_LOG_INSTR))
qemu_set_log_internal(qemu_loglevel | CPU_LOG_INSTR);
}
/*
* Handle global logging switch, triggered by the qemu monitor or
* other external events.
* This runs in the CPU exclusive context.
*/
static void do_global_loglevel_switch(CPUState *cpu, run_on_cpu_data data)
{
qemu_log_instr_loglevel_t level = data.host_int;
if (level != QEMU_LOG_INSTR_LOGLEVEL_NONE)
global_loglevel_enable();
/*
* TODO(am2419): To do things cleanly, we should clear the CPU_LOG_INSTR
* flag when stopping, however to do this we would need to keep track
* of the number of CPUs that we have disabled so far, so that we only
* clear the flag on the last CPU.
* qemu_set_log_internal(qemu_loglevel & (~CPU_LOG_INSTR));
*/
do_cpu_loglevel_switch(cpu, RUN_ON_CPU_HOST_INT(level));
}
/*
* Interface for the monitor to start and stop tracing on all CPUs.
* Note: It is critical that when stopping we delay the stop until
* all the CPUs have exited their TCG exec loop. This will happen when
* the current TB is finished. If we clear the global flag immediately
* we will end up emitting stale instructions.
*/
int qemu_log_instr_global_switch(int log_flags)
{
CPUState *cpu;
qemu_log_instr_loglevel_t level;
if (log_flags & CPU_LOG_INSTR_U) {
level = QEMU_LOG_INSTR_LOGLEVEL_USER;
log_flags |= CPU_LOG_INSTR;
} else if (log_flags & CPU_LOG_INSTR) {
level = QEMU_LOG_INSTR_LOGLEVEL_ALL;
} else {
level = QEMU_LOG_INSTR_LOGLEVEL_NONE;
}
CPU_FOREACH(cpu) {
async_safe_run_on_cpu(cpu, do_global_loglevel_switch,
RUN_ON_CPU_HOST_INT(level));
}
return log_flags;
}
/*
* Initialize instruction info entry from the ring buffer.
*/
static void qemu_log_instr_info_init(cpu_log_instr_info_t *iinfo)
{
if (iinfo->txt_buffer == NULL)
iinfo->txt_buffer = g_string_new(NULL);
if (iinfo->regs == NULL)
iinfo->regs = g_array_new(false, true, sizeof(log_reginfo_t));
if (iinfo->mem == NULL)
iinfo->mem = g_array_new(false, true, sizeof(log_meminfo_t));
}
/*
* Clear an instruction info entry from the ring buffer.
*/
static void qemu_log_instr_info_destroy(gpointer data)
{
cpu_log_instr_info_t *iinfo = data;
g_string_free(iinfo->txt_buffer, TRUE);
g_array_free(iinfo->regs, TRUE);
g_array_free(iinfo->mem, TRUE);
}
/*
* This must be called upon cpu creation.
* Initializes the per-CPU logging state and data structures.
*
* Currently the instruction info ring buffer size is fixed and can not
* be changed at runtime.
*/
void qemu_log_instr_init(CPUState *cpu)
{
cpu_log_instr_state_t *cpulog = &cpu->log_state;
GArray *iinfo_ring = g_array_sized_new(FALSE, TRUE,
sizeof(cpu_log_instr_info_t), reset_entry_buffer_size);
cpu_log_instr_info_t *iinfo = NULL;
int i;
g_array_set_size(iinfo_ring, reset_entry_buffer_size);
g_array_set_clear_func(iinfo_ring, qemu_log_instr_info_destroy);
for (i = 0; i < iinfo_ring->len; i++) {
iinfo = &g_array_index(iinfo_ring, cpu_log_instr_info_t, i);
qemu_log_instr_info_init(iinfo);
}
cpulog->loglevel = QEMU_LOG_INSTR_LOGLEVEL_NONE;
cpulog->loglevel_active = false;
cpulog->instr_info = iinfo_ring;
cpulog->ring_head = 0;
cpulog->ring_tail = 0;
reset_log_buffer(cpulog, iinfo);
// Make sure we are using the correct trace format.
if (trace_format == NULL) {
trace_format = &trace_formats[qemu_log_instr_format];
// Only emit header on first init
if (trace_format->emit_header)
trace_format->emit_header(cpu->env_ptr);
}
/* If we are starting with instruction logging enabled, switch it on now */
if (qemu_loglevel_mask(CPU_LOG_INSTR_U))
do_cpu_loglevel_switch(
cpu, RUN_ON_CPU_HOST_INT(QEMU_LOG_INSTR_LOGLEVEL_USER));
else if (qemu_loglevel_mask(CPU_LOG_INSTR))
do_cpu_loglevel_switch(cpu,
RUN_ON_CPU_HOST_INT(QEMU_LOG_INSTR_LOGLEVEL_ALL));
}
static void
do_log_buffer_resize(CPUState *cpu, run_on_cpu_data data)
{
unsigned long new_size = data.host_ulong;
cpu_log_instr_state_t *cpulog = get_cpu_log_state(cpu->env_ptr);
cpu_log_instr_info_t *iinfo;
int i;
g_array_set_size(cpulog->instr_info, new_size);
cpulog->ring_head = 0;
cpulog->ring_tail = 0;
for (i = 0; i < cpulog->instr_info->len; i++) {
/*
* Clear and reinitialize all the entries,
* a bit overkill but should not be a frequent operation.
*/
iinfo = &g_array_index(cpulog->instr_info, cpu_log_instr_info_t, i);
qemu_log_instr_info_init(iinfo);
reset_log_buffer(cpulog, iinfo);
}
}
void qemu_log_instr_set_buffer_size(unsigned long new_size)
{
CPUState *cpu;
if (new_size < MIN_ENTRY_BUFFER_SIZE) {
warn_report("New trace entry buffer size is too small < %zu, ignored.\n",
(size_t)MIN_ENTRY_BUFFER_SIZE);
return;
}
/* Set this in case this is called from qemu option parsing */
reset_entry_buffer_size = new_size;
CPU_FOREACH(cpu) {
async_safe_run_on_cpu(cpu, do_log_buffer_resize,
RUN_ON_CPU_HOST_ULONG(new_size));
}
}
/*
* Check whether instruction logging is enabled on this CPU.
*/
bool qemu_log_instr_check_enabled(CPUArchState *env)
{
return (qemu_loglevel_mask(CPU_LOG_INSTR) &&
get_cpu_log_state(env)->loglevel_active);
}
/*
* Record a change in CPU mode.
* Any instruction calling this should exit the TB.
* This will also trigger pause and resume of user-only logging activity.
*
* We flush the TCG buffer when we have to change logging level, this
* will cause an exit from the cpu_exec() loop, the bulk of the log level
* switching is performed in exclusive context during the TCG flush
* initiated here.
*/
void qemu_log_instr_mode_switch(CPUArchState *env,
qemu_log_instr_cpu_mode_t mode, target_ulong pc)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);
log_assert(cpulog != NULL && "Invalid log state");
log_assert(iinfo != NULL && "Invalid log info");
iinfo->flags |= LI_FLAG_MODE_SWITCH;
iinfo->next_cpu_mode = mode;
/* If we are not logging in user-only mode, bail */
if (!qemu_loglevel_mask(CPU_LOG_INSTR) ||
cpulog->loglevel != QEMU_LOG_INSTR_LOGLEVEL_USER)
return;
/* Check if we are switching to an interesting mode */
if ((mode == QEMU_LOG_INSTR_CPU_USER) != cpulog->loglevel_active) {
cpu_loglevel_switch(env, cpulog->loglevel);
}
}
void qemu_log_instr_drop(CPUArchState *env)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
log_assert(cpulog != NULL && "Invalid log state");
cpulog->force_drop = true;
}
void qemu_log_instr_commit(CPUArchState *env)
{
cpu_log_instr_state_t *cpulog = get_cpu_log_state(env);
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);
log_assert(cpulog != NULL && "Invalid log state");
log_assert(iinfo != NULL && "Invalid log info");
do_instr_commit(env);
/* commit may have advanced to the next iinfo buffer slot */
iinfo = get_cpu_log_instr_info(env);
reset_log_buffer(cpulog, iinfo);
}
void qemu_log_instr_reg(CPUArchState *env, const char *reg_name,
target_ulong value, uint32_t index, uint32_t type)
{
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);
log_reginfo_t r;
r.flags = type;
r.index = index;
r.name = reg_name;
r.gpr = value;
g_array_append_val(iinfo->regs, r);
}
void helper_qemu_log_instr_reg(CPUArchState *env, const void *reg_name,
target_ulong value, uint32_t index,
uint32_t type)
{
if (qemu_log_instr_check_enabled(env))
qemu_log_instr_reg(env, (const char *)reg_name, value, index, type);
}
#ifdef TARGET_CHERI
void qemu_log_instr_cap(CPUArchState *env, const char *reg_name,
const cap_register_t *cr, uint32_t index, uint32_t type)
{
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);
log_reginfo_t r;
r.flags = type | LRI_CAP_REG | LRI_HOLDS_CAP;
r.index = index;
r.name = reg_name;
r.cap = *cr;
g_array_append_val(iinfo->regs, r);
}
void helper_qemu_log_instr_cap(CPUArchState *env, const void *reg_name,
const void *cr, uint32_t index, uint32_t type)
{
if (qemu_log_instr_check_enabled(env))
qemu_log_instr_cap(env, reg_name, cr, index, type);
}
void qemu_log_instr_cap_int(CPUArchState *env, const char *reg_name,
target_ulong value, uint32_t index, uint32_t type)
{
cpu_log_instr_info_t *iinfo = get_cpu_log_instr_info(env);