1 /*  src/sh2trace.c: SH-2 tracing code for debugging
2     Copyright 2009 Andrew Church
3 
4     This file is part of Yabause.
5 
6     Yabause is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     Yabause is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with Yabause; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
19 */
20 
21 /*! \file sh2trace.c
22     \brief SH2 execution tracing.
23 */
24 
25 
26 #include <stdio.h>
27 #include "sh2core.h"
28 #include "sh2d.h"
29 #include "sh2trace.h"
30 
31 /*************************************************************************/
32 
33 /* Define BINARY_LOG to log traces in a binary format (faster than text) */
34 //#define BINARY_LOG
35 
36 /* Define GZIP_LOG to compress log as it's created */
37 #ifdef __linux__
38 # define GZIP_LOG
39 #endif
40 
41 /* Define ECHO_TO_STDERR to log traces to stderr as well as logfile
42  * (ignored in BINARY_LOG mode) */
43 // #define ECHO_TO_STDERR
44 
45 /*----------------------------------*/
46 
47 static const u64 trace_start =  000000000ULL;  // First cycle to trace
48 static const u64 trace_stop  = 2800000000ULL;  // Last cycle to trace + 1
49 
50 /*----------------------------------*/
51 
52 static int is_ins_enabled = 0;
53 
54 static FILE *logfile;                // Trace log file
55 static u64 cycle_accum = 0;     // Global cycle accumulator
56 static u64 current_cycles = 0;  // Cycle count on last call to sh2_trace()
57 
58 /*************************************************************************/
59 
SH2SetInsTracing(int enable)60 void SH2SetInsTracing(int enable)
61 {
62    is_ins_enabled = enable;
63 }
64 
sh2_cycle_count(void)65 FASTCALL u64 sh2_cycle_count(void)
66 {
67     return current_cycles;
68 }
69 
70 /*-----------------------------------------------------------------------*/
71 
sh2_trace_add_cycles(s32 cycles)72 FASTCALL void sh2_trace_add_cycles(s32 cycles)
73 {
74     cycle_accum += cycles;
75 }
76 
77 /*-----------------------------------------------------------------------*/
78 
sh2_trace_writeb(u32 address,u32 value)79 FASTCALL void sh2_trace_writeb(u32 address, u32 value)
80 {
81 #ifdef BINARY_LOG
82         struct {
83             u16 id;  // 1 = byte store
84             u16 pad1;
85             u32 address;
86             u32 value;
87             u32 pad2;
88         } buf;
89 #endif
90     if (logfile) {
91         value &= 0xFF;
92 #ifdef BINARY_LOG
93         buf.id = 1;
94         buf.pad1 = 0;
95         buf.address = address;
96         buf.value = value;
97         buf.pad2 = 0;
98         fwrite(&buf, sizeof(buf), 1, logfile);
99 #else
100         fprintf(logfile, "WRITEB %08X <- %02X\n", (int)address, (int)value);
101 # ifdef ECHO_TO_STDERR
102         fprintf(stderr, "WRITEB %08X <- %02X\n", (int)address, (int)value);
103 # endif
104 #endif
105     }
106 }
107 
sh2_trace_writew(u32 address,u32 value)108 FASTCALL void sh2_trace_writew(u32 address, u32 value)
109 {
110 #ifdef BINARY_LOG
111         struct {
112             u16 id;  // 2 = word store
113             u16 pad1;
114             u32 address;
115             u32 value;
116             u32 pad2;
117         } buf;
118 #endif
119     if (logfile) {
120         value &= 0xFFFF;
121 #ifdef BINARY_LOG
122         buf.id = 2;
123         buf.pad1 = 0;
124         buf.address = address;
125         buf.value = value;
126         buf.pad2 = 0;
127         fwrite(&buf, sizeof(buf), 1, logfile);
128 #else
129         fprintf(logfile, "WRITEW %08X <- %04X\n", (int)address, (int)value);
130 # ifdef ECHO_TO_STDERR
131         fprintf(stderr, "WRITEW %08X <- %04X\n", (int)address, (int)value);
132 # endif
133 #endif
134     }
135 }
136 
sh2_trace_writel(u32 address,u32 value)137 FASTCALL void sh2_trace_writel(u32 address, u32 value)
138 {
139     if (logfile) {
140 #ifdef BINARY_LOG
141         struct {
142             u16 id;  // 4 = long store
143             u16 pad1;
144             u32 address;
145             u32 value;
146             u32 pad2;
147         } buf;
148         buf.id = 4;
149         buf.pad1 = 0;
150         buf.address = address;
151         buf.value = value;
152         buf.pad2 = 0;
153         fwrite(&buf, sizeof(buf), 1, logfile);
154 #else
155         fprintf(logfile, "WRITEL %08X <- %08X\n", (int)address, (int)value);
156 # ifdef ECHO_TO_STDERR
157         fprintf(stderr, "WRITEL %08X <- %08X\n", (int)address, (int)value);
158 # endif
159 #endif
160     }
161 }
162 
163 /*-----------------------------------------------------------------------*/
164 
165 #ifndef BINARY_LOG
HEXIT(char * const ptr,u32 val,int ndigits)166 static INLINE void HEXIT(char * const ptr, u32 val, int ndigits)
167 {
168     while (ndigits-- > 0) {
169         const int digit = val & 0xF;
170         val >>= 4;
171         ptr[ndigits] = (digit>9 ? digit+7+'0' : digit+'0');
172     }
173 }
174 #endif
175 
sh2_trace(SH2_struct * state,u32 address)176 FASTCALL void sh2_trace(SH2_struct *state, u32 address)
177 {
178     if (!is_ins_enabled)
179         return;
180 
181     current_cycles = cycle_accum + state->cycles;
182 
183     if (current_cycles < trace_start) {
184 
185         /* Before first instruction: do nothing */
186 
187     } else if (current_cycles >= trace_stop) {
188 
189         /* After last instruction: close log file if it's open */
190         if (logfile) {
191 #ifdef GZIP_LOG
192             pclose(logfile);
193 #else
194             fclose(logfile);
195 #endif
196             logfile = NULL;
197         }
198 
199     } else {
200         u16 opcode;
201 #ifdef BINARY_LOG
202         struct {
203             u16 id;  // 1/2/4 = store; 0x80 = MSH2 insn; 0x81 = SSH2 insn
204             u16 opcode;
205             u32 regs[23];
206             u64 cycles;
207             u32 pad[2];
208         } buf;
209 #else
210         char buf[100];
211         /* This looks ugly, but it's faster than fprintf() in this case */
212         static char regbuf[] = "  R0: XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX\n  R8: XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX\n  PR: XXXXXXXX  SR: XXX  MAC: XXXXXXXX/XXXXXXXX  GBR: XXXXXXXX  VBR: XXXXXXXX\n";
213         int i;
214 #endif
215 
216         if (!logfile) {
217             const char *filename = "sh2.log";
218 #ifdef GZIP_LOG
219             char cmdbuf[100];
220             snprintf(cmdbuf, sizeof(cmdbuf), "gzip -3 >'%s'.gz", filename);
221             logfile = popen(cmdbuf, "w");
222 #else
223             logfile = fopen(filename, "w");
224 #endif
225             if (!logfile) {
226                 return;
227             }
228             setvbuf(logfile, NULL, _IOFBF, 65536);
229         }
230 
231         opcode = MappedMemoryReadWordNocache(state, address);
232 
233 #ifdef BINARY_LOG
234 
235         buf.id = state==SSH2 ? 0x81 : 0x80;
236         buf.opcode = opcode;
237         /* sh2int leaves the branch target in regs.PC during a delay slot,
238          * so insert the proper PC manually */
239         SH2GetRegisters(state, (sh2regs_struct *)buf.regs);
240         buf.regs[22] = address;
241         buf.cycles = current_cycles;
242         buf.pad[0] = 0;
243         buf.pad[1] = 0;
244         fwrite(&buf, sizeof(buf), 1, logfile);
245 
246 #else  // !BINARY_LOG
247 
248         SH2GetRegisters(state, &state->regs);
249 
250         SH2Disasm(address, opcode, 0, &state->regs, buf);
251         fprintf(logfile, "[%c] %08X: %04X  %-44s [%12llu]\n",
252                 state==SSH2 ? 'S' : 'M', (int)address, (int)opcode, buf+12,
253                 (unsigned long long)current_cycles);
254 #ifdef ECHO_TO_STDERR
255         fprintf(stderr, "[%c] %08X: %04X  %-44s [%12llu]\n",
256                 state==SSH2 ? 'S' : 'M', (int)address, (int)opcode, buf+12,
257                 (unsigned long long)current_cycles);
258 #endif
259 
260         for (i = 0; i < 16; i++) {
261             HEXIT(i>=8 ? &regbuf[12+i*9] : &regbuf[6+i*9], state->regs.R[i], 8);
262         }
263         HEXIT(&regbuf[162], state->regs.PR, 8);
264         HEXIT(&regbuf[176], state->regs.SR.all, 3);
265         HEXIT(&regbuf[186], state->regs.MACH, 8);
266         HEXIT(&regbuf[195], state->regs.MACL, 8);
267         HEXIT(&regbuf[210], state->regs.GBR, 8);
268         HEXIT(&regbuf[225], state->regs.VBR, 8);
269         fwrite(regbuf, sizeof(regbuf)-1, 1, logfile);
270 #ifdef ECHO_TO_STDERR
271         fwrite(regbuf, sizeof(regbuf)-1, 1, stderr);
272 #endif
273 
274 #endif  // BINARY_LOG
275 
276     }  // current_cycles >= trace_start && current_cycles < trace_stop
277 }
278 
279 /*************************************************************************/
280 
281 /*
282  * Local variables:
283  *   c-file-style: "stroustrup"
284  *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
285  *   indent-tabs-mode: nil
286  * End:
287  *
288  * vim: expandtab shiftwidth=4:
289  */
290