-
Notifications
You must be signed in to change notification settings - Fork 0
/
StackVisualizer.java
1361 lines (1202 loc) · 46.9 KB
/
StackVisualizer.java
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
/*
* MIT License
*
* Copyright (c) 2018-2021 George Z. Zachos and Petros Manousis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package mars.tools;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Observable;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import mars.Globals;
import mars.ProgramStatement;
import mars.assembler.Symbol;
import mars.assembler.SymbolTable;
import mars.mips.hardware.AccessNotice;
import mars.mips.hardware.AddressErrorException;
import mars.mips.hardware.Memory;
import mars.mips.hardware.MemoryAccessNotice;
import mars.mips.hardware.Register;
import mars.mips.hardware.RegisterAccessNotice;
import mars.mips.hardware.RegisterFile;
import mars.mips.instructions.Instruction;
import mars.simulator.BackStepper;
import mars.simulator.Simulator;
import mars.simulator.SimulatorNotice;
import mars.util.Binary;
import mars.venus.FileStatus;
import mars.venus.VenusUI;
/**
* Allows the user to view in real time the memory modification operations taking place in the stack segment with emphasis to
* `$sp`-relative memory accesses. The user can also observe how pushes/pops to/from the stack take place. The address pointed
* by the stack pointer is displayed in an orange background while the whole word-length data in yellow. Lower addresses
* have a grey background (given that stack growth takes place from higher to lower addresses). The names of the registers whose
* contents are stored (`sw`, `sh`, `sb` etc.) in the stack, are shown in the "Stored Reg" column. In the "Call Layout" column,
* the subroutine frame (activation record) layout is displayed, with subroutine names placed on the first address written in
* the corresponding frame.
*
* GitHub repository: <a href="https://github.com/gzachos/mars-stack-visualizer">https://github.com/gzachos/mars-stack-visualizer</a>
*
* @author George Z. Zachos <gzachos@cse.uoi.gr>
* @author Petros Manousis <pmanousi@cs.uoi.gr>
*/
@SuppressWarnings({ "serial", "deprecation" })
public class StackVisualizer extends AbstractMarsToolAndApplication {
private static String name = "Stack Visualizer";
private static String versionID = "1.5";
private static String version = "Version " + versionID + " (George Z. Zachos, Petros Manousis)";
private static String heading = "Visualizing Stack Modification Operations";
private static String releaseDate = "06-Feb-2021";
// We need the following definition here to initialize numberOfColumns
/** Table column names for displaying data per byte. */
private final String[] colNamesWhenDataPerByte = {"Address", "+3", "+2", "+1", "+0", "Stored Reg", "Call Layout"};
/** Table column names for displaying data per word. */
private final String[] colNamesWhenNotDataPerByte = {"Address", "Word-length Data", "Stored Reg", "Call Layout"};
/**
* True if {@link StackVisualizer} is currently running
* as a stand-alone program (MARS application)
*/
private static boolean inStandAloneMode = false;
/*
* Memory.stackBaseAddress: word-aligned
* Memory.stackLimitAddress: word-aligned
* Max stack address value: Memory.stackBaseAddress + (WORD_LENGTH_BYTES-1)
* Min stack address value: Memory.stackLimitAddress
*
* Stack grows towards lower addresses: .stackBaseAddress > .stackLimitAddress
* Word-length operations can take place in both .stackBaseAddress and .stackLimitAddress
*/
/** Register number of stack pointer (29) */
private final int SP_REG_NUMBER = RegisterFile.STACK_POINTER_REGISTER;
/** Register number of return address (31) */
private final int RA_REG_NUMBER = RegisterFile.getNumber("$ra");
/** Stack pointer's initial address/value */
private final int SP_INIT_ADDR = Memory.stackPointer;
private final Memory memInstance = Memory.getInstance();
private final boolean endianness = memInstance.getByteOrder();
private final boolean LITTLE_ENDIAN = Memory.LITTLE_ENDIAN; // for quick access
/** MIPS word length in bytes. */
private final int WORD_LENGTH_BYTES = Memory.WORD_LENGTH_BYTES; // for quick access
/** MIPS word length in bits. */
private final int WORD_LENGTH_BITS = WORD_LENGTH_BYTES << 3;
/** I-format RS (source register) index in operand list. */
private final int I_RS_OPERAND_LIST_INDEX = 0;
/** J-format Address index in operand list. */
private final int J_ADDR_OPERAND_LIST_INDEX = 0;
/** R-format RS (source register) index in operand list. */
private final int R_RS_OPERAND_LIST_INDEX = 0;
/** Maximum value stack pointer can currently take (word-aligned). */
private int maxSpValueWordAligned = SP_INIT_ADDR;
/** Register name to be stored in stack segment. */
private String regNameToBeStoredInStack = null;
/** Name of the (subroutine) frame to be allocated in stack segment. */
private String frameNameToBeCreated = null;
/** Whether $ra was written/updated in the last instruction. */
private boolean raWrittenInPrevInstr = false;
/**
* Return Address Stack. Target addresses of jal instructions are pushed and
* then are popped and matched when jr instructions are encountered.
*/
private final ArrayList<Integer> ras = new ArrayList<Integer>();
/**
* Active subroutine statistics. Stores how many times each subroutine is active
* (called but is yet to complete execution).
*/
private final ActiveSubroutineStats activeFunctionCallStats = new ActiveSubroutineStats();
/** Current stack base address. Used to detect memory configuration changes */
private int currStackBaseAddress = Memory.stackBaseAddress;
// GUI-Related fields
private final int windowWidth = 600;
private final int windowHeight = 600;
/** Table column index where memory address should be stored. Should always be first column. */
private final int ADDRESS_COLUMN = 0;
/** Table column index where the first byte of memory data should be stored. Should always be second column. */
private final int FIRST_BYTE_COLUMN = 1;
/** Table column index where the last byte of memory data should be stored. */
private final int LAST_BYTE_COLUMN = FIRST_BYTE_COLUMN + (WORD_LENGTH_BYTES - 1);
/** Table column index where the word-length memory data should be stored. Should always be second column. */
private final int WORD_COLUMN = 1;
/** How many rows the table should initially have. */
private final int INITIAL_ROW_COUNT = 30;
/** Current number of table columns. */
private int numberOfColumns = colNamesWhenDataPerByte.length;
/** Current number of table rows. (-1) before table initialization. */
private int numberOfRows = 0;
/** Offset of frame name ("Call Layout") column from table end. */
private final int frameNameColOffsetFromEnd = 0;
/** Offset of stored register column from table end. */
private final int storedRegColOffsetFromEnd = 1;
/** Table column index where frame name should be stored. */
private int frameNameColumn = calcTableColIndex(frameNameColOffsetFromEnd);
/** Table column index where stored register name should be stored. */
private int storedRegisterColumn = calcTableColIndex(storedRegColOffsetFromEnd);
/** Threshold to decide whether more table rows should be added. */
private final int REMAINING_ROWS_THRESHOLD = 5;
/** Table row where stack pointer points to. */
private int spDataRowIndex = 0;
/** Table column where stack pointer points to. */
private int spDataColumnIndex = LAST_BYTE_COLUMN;
private JTable table;
private JPanel panel;
private JScrollPane scrollPane;
private JCheckBox dataPerByte;
private JCheckBox hexAddressesCheckBox;
private JCheckBox hexValuesCheckBox;
private JCheckBox jalEquivInstrCheckBox;
private final int LIGHT_YELLOW = 0xFFFF99;
private final int LIGHT_ORANGE = 0xFFC266;
private final int LIGHT_GRAY = 0xE0E0E0;
private final int GRAY = 0x999999;
private final int WHITE = 0xFFFFFF;
private boolean disabledBackStep = false;
private boolean displayDataPerByte = true;
private boolean displayHexAddresses = true;
private boolean displayHexValues = true;
private boolean detectJalEquivalentInstructions = false;
private final DefaultTableModel tableModel = new DefaultTableModel();
/** Used for debugging purposes. */
private final boolean debug = false, printMemContents = false, debugBackStepper = false;
protected StackVisualizer(String title, String heading) {
super(title, heading);
}
public StackVisualizer() {
super(StackVisualizer.name + ", " + StackVisualizer.version, StackVisualizer.heading);
}
/**
* Main method provided for use as a MARS application (stand-alone program).
*/
public static void main(String[] args) {
inStandAloneMode = true;
new StackVisualizer(StackVisualizer.name + " stand-alone, " + StackVisualizer.version, StackVisualizer.heading).go();
}
@Override
public String getName() {
return name;
}
@Override
protected JComponent buildMainDisplayArea() {
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridx = c.gridy = 0;
c.weightx = c.weighty = 1.0;
panel = new JPanel(new GridBagLayout());
panel.setPreferredSize(new Dimension(windowWidth, windowHeight));
for (String s : colNamesWhenDataPerByte)
tableModel.addColumn(s);
table = new JTable(tableModel);
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
table.getTableHeader().setReorderingAllowed(false);
table.setEnabled(false);
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
final Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
c.setBackground(calcTableCellColor(row, column));
if ((displayDataPerByte && column >= FIRST_BYTE_COLUMN && column <= LAST_BYTE_COLUMN) ||
(!displayDataPerByte && column == WORD_COLUMN))
setHorizontalAlignment(SwingConstants.RIGHT);
else if (column == storedRegisterColumn || column == frameNameColumn)
setHorizontalAlignment(SwingConstants.CENTER);
else
setHorizontalAlignment(SwingConstants.LEFT);
return c;
}
});
// table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
resizeTableColumns();
scrollPane = new JScrollPane(table);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setVisible(true);
panel.add(scrollPane, c);
table.setFillsViewportHeight(true);
c.gridy++; // change line
c.weightx = 1.0;
c.weighty = 0;
dataPerByte = new JCheckBox("Display data per byte");
dataPerByte.setSelected(true);
panel.add(dataPerByte, c);
dataPerByte.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
displayDataPerByte = dataPerByte.isSelected();
if(displayDataPerByte == false)
transformTableModel(colNamesWhenNotDataPerByte);
else
transformTableModel(colNamesWhenDataPerByte);
}
});
c.gridy++; // change line
hexAddressesCheckBox = new JCheckBox("Hexadecimal Addresses");
hexAddressesCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
displayHexAddresses = hexAddressesCheckBox.isSelected();
getStackData();
table.repaint();
}
});
hexAddressesCheckBox.setSelected(true);
panel.add(hexAddressesCheckBox, c);
c.gridy++; // change line
hexValuesCheckBox = new JCheckBox("Hexadecimal Values");
hexValuesCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
displayHexValues = hexValuesCheckBox.isSelected();
getStackData();
table.repaint();
}
});
hexValuesCheckBox.setSelected(true);
panel.add(hexValuesCheckBox, c);
c.gridy++;
panel.add(new JSeparator(), c);
c.gridy++;
jalEquivInstrCheckBox = new JCheckBox("Detect jal-equivalent instructions");
jalEquivInstrCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
detectJalEquivalentInstructions = jalEquivInstrCheckBox.isSelected();
}
});
jalEquivInstrCheckBox.setSelected(false);
panel.add(jalEquivInstrCheckBox, c);
return panel;
}
/**
* Calculates what color table cell ({@code row},{@code column}) should be colored.
*
* @param row table cell row.
* @param column table cell column.
* @return the calculated color.
*/
private Color calcTableCellColor(int row, int column) {
int color = WHITE;
if (row == spDataRowIndex) {
color = LIGHT_YELLOW;
// $sp cell coloring doesn't work with user column reordering
if (displayDataPerByte && column == spDataColumnIndex)
color = LIGHT_ORANGE;
}
else if (row > spDataRowIndex) {
color = GRAY;
}
else if (row % 2 == 0) {
color = LIGHT_GRAY;
}
return new Color(color);
}
private void resizeTableColumns() {
TableColumnModel columnModel = table.getColumnModel();
for (int colIndex = 0 ; colIndex < columnModel.getColumnCount(); colIndex++) {
TableColumn col = columnModel.getColumn(colIndex);
int min, pref;
min = pref = 75;
if (displayDataPerByte) {
if (colIndex >= FIRST_BYTE_COLUMN && colIndex <= LAST_BYTE_COLUMN) {
min = 25;
pref = 50;
} else if (colIndex == ADDRESS_COLUMN) {
min = 75;
pref = 100;
} else if (colIndex == storedRegisterColumn) {
min = 25;
pref = 75;
} else if (colIndex == frameNameColumn) {
min = 25;
pref = 150;
}
} else {
if (colIndex == frameNameColumn) {
min = 25;
pref = 150;
}
}
col.setMinWidth(min);
col.setPreferredWidth(pref);
}
}
/**
* Transform table model so that new columns match {@code columnNames[]}.
*
* @param columnNames the new table columns.
*/
private synchronized void transformTableModel(String columnNames[]) {
Object storedRegColumnData[] = new Object[numberOfRows];
Object frameNameColumnData[] = new Object[numberOfRows];
// Backup storedRegister and frameName columns
for (int row = 0; row < numberOfRows; row++) {
storedRegColumnData[row] = tableModel.getValueAt(row, storedRegisterColumn);
frameNameColumnData[row] = tableModel.getValueAt(row, frameNameColumn);
}
table.setVisible(false); // Used to avoid rendering delays
tableModel.setColumnCount(0); // Clear table columns
for (String s : columnNames)
tableModel.addColumn(s); // Add new table columns
// Update table-related data
numberOfColumns = tableModel.getColumnCount();
numberOfRows = tableModel.getRowCount();
frameNameColumn = calcTableColIndex(frameNameColOffsetFromEnd);
storedRegisterColumn = calcTableColIndex(storedRegColOffsetFromEnd);
resizeTableColumns();
// Restore toredRegister and frameName columns
for (int row = 0; row < numberOfRows; row++) {
tableModel.setValueAt(storedRegColumnData[row], row, storedRegisterColumn);
tableModel.setValueAt(frameNameColumnData[row], row, frameNameColumn);
}
getStackData();
table.repaint();
table.setVisible(true);
}
@Override
protected void initializePostGUI() {
if (inStandAloneMode == false) {
connectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (connectButton.isConnected()) {
restoreBackStepper();
} else {
checkMemConfChanged();
/*
* User program should be recompiled (and executed) after
* StackVisualizer is launched. This is required for
* coherently storing the subroutine call stack.
*/
runButtonsSetEnabled(false);
/* Connecting StackVisualizer in the middle of program execution
* will disable Back Stepper but the button will show as enabled.
* Maybe we should disable it by hand or just don't mess with it.
*/
disableBackStepper();
refreshGui();
String msg = "Back Stepping has been disabled.\n"
+ "Already running programs should be assembled again.";
showMessageWindow(msg);
}
}
});
}
checkMemConfChanged();
Simulator.getInstance().addObserver(this);
addNewTableRows(INITIAL_ROW_COUNT);
updateSpDataRowColIndex();
table.repaint();
}
/**
* Equivalent to {@code getStackData(0, numberOfRows)}.
*/
private void getStackData() {
getStackData(0, numberOfRows-1);
}
/**
* Equivalent to {@code getStackData(row, row)}.
*/
private void getStackData(int row) {
getStackData(row, row);
}
/**
* Fills/updates table rows [{@code startRow}, {@code endRow}] with data directly
* from Mars' memory instance.
*
* This method fires a {@link MemoryAccessNotice} every time it reads from memory.
* For this reason it should not be called in a code block handling a
* {@link MemoryAccessNotice} of {@code AccessNotice.READ} type as it will lead
* in infinite recursive calls of itself.
*/
private synchronized void getStackData(int startRow, int endRow) {
int row, col, addr;
if (startRow < 0 || endRow < 0 || endRow >= numberOfRows) {
if (printMemContents)
System.out.println("getStackData end (invalid arguments)\n");
return;
}
if (printMemContents)
System.out.println("getStackData(" + row + "," + endRow + ") start");
addr = maxSpValueWordAligned - startRow * WORD_LENGTH_BYTES;
/*
* MARS supports three memory configurations. Only in the default configuration
* the initial stack pointer value does NOT point to the highest address of the
* stack. Nevertheless, in all three configurations, the initial address pointed
* by $sp is a valid address whose contents can be written.
*
* Initial value of spAddr is 0x7FFFEFFC = 2147479548 (Default MIPS memory configuration).
* The first 4 bytes (1 word) to be displayed are:
* 0x7FFFEFFF, 0x7FFFEFFE, 0x7FFFEFFD, 0x7FFFEFFC or in decimal value:
* 2147479551, 2147479550, 2147479549, 2147479548.
*/
for (row = startRow; row <= endRow; row++, addr -= WORD_LENGTH_BYTES) {
tableModel.setValueAt(formatAddress(addr), row, ADDRESS_COLUMN);
try {
if (displayDataPerByte) {
/* Access word one byte at a time */
for (int bi = 0; bi < WORD_LENGTH_BYTES; bi++) {
int byteValue;
/*
* Endianness determines whether byte position in value and
* byte position in memory match.
*/
col = (endianness == LITTLE_ENDIAN) ? LAST_BYTE_COLUMN - bi : FIRST_BYTE_COLUMN + bi;
/*
* MARS checks for addresses out of range using word-aligned addresses.
* This means that the word on Memory.stackBaseAddress (highest stack address) can
* be accessed during word-length data operations but not during half-word or byte-length
* operations. This behavior is asymmetrical among the three memory configurations
* supported by MARS.
*/
if (addr >= Memory.stackBaseAddress && addr <= (Memory.stackBaseAddress + (WORD_LENGTH_BYTES-1))) {
/* In case of highest stack address, access whole word and then
* use shift and bitwise operations to get each byte.
*/
int word = memInstance.getWordNoNotify(alignToCurrentWordBoundary(addr));
byteValue = (word >> (bi << 3)) & 0xff;
} else {
byteValue = memInstance.getByte(addr + bi);
}
tableModel.setValueAt(formatNByteLengthMemContents(1, byteValue), row, col);
}
} else {
tableModel.setValueAt(formatNByteLengthMemContents(WORD_LENGTH_BYTES, memInstance.getWord(addr)),
row, WORD_COLUMN);
}
if (printMemContents) {
System.out.print(tableModel.getValueAt(row, 0) + ": ");
if (displayDataPerByte) {
for (int i = FIRST_BYTE_COLUMN; i <= LAST_BYTE_COLUMN; i++)
System.out.print(tableModel.getValueAt(row, i) + (i == LAST_BYTE_COLUMN ? "" : ","));
} else {
System.out.print(tableModel.getValueAt(row, WORD_COLUMN));
}
System.out.print(" (" + tableModel.getValueAt(row, storedRegisterColumn) + ")");
System.out.println(" [" + tableModel.getValueAt(row, frameNameColumn) + "]");
}
} catch (AddressErrorException aee) {
System.err.println("getStackData(): " + formatAddress(aee.getAddress()) + " AddressErrorException");
} catch (Exception e) {
e.printStackTrace();
}
}
if (printMemContents)
System.out.println("getStackData end\n");
}
@Override
protected void addAsObserver() {
// To observe stack segment, actual parameters should be
// reversed due to higher to lower address stack growth.
addAsObserver(Memory.stackLimitAddress, Memory.stackBaseAddress);
addAsObserver(RegisterFile.getRegisters()[SP_REG_NUMBER]);
addAsObserver(Memory.textBaseAddress, Memory.textLimitAddress);
addAsObserver(RegisterFile.getRegisters()[RA_REG_NUMBER]);
}
@Override
protected void deleteAsObserver() {
super.deleteAsObserver(); // Stop observing memory (default)
deleteAsObserver(RegisterFile.getRegisters()[SP_REG_NUMBER]); // Stop observing $sp
deleteAsObserver(RegisterFile.getRegisters()[RA_REG_NUMBER]); // Stop observing $ra
}
@Override
protected void processMIPSUpdate(Observable resource, AccessNotice notice) {
// System.out.println(notice.accessIsFromMIPS() +" " + notice.accessIsFromGUI() + " " + notice);
if (!notice.accessIsFromMIPS())
return;
if (notice instanceof MemoryAccessNotice) {
MemoryAccessNotice m = (MemoryAccessNotice) notice;
if (Memory.inTextSegment(m.getAddress()))
processTextMemoryUpdate(m);
else
processStackMemoryUpdate(m);
}
else if (notice instanceof RegisterAccessNotice) {
RegisterAccessNotice r = (RegisterAccessNotice) notice;
processRegisterAccessNotice(r);
}
}
/**
* Processes a received register update/notice (Read or Write).
*/
private void processRegisterAccessNotice(RegisterAccessNotice notice) {
// Currently only $sp is observed ($ra also but not for stack modification ops)
// TODO: What about observing frame pointer?
if (notice.getAccessType() == AccessNotice.READ)
return;
if (debug)
System.out.println("\nRegisterAccessNotice (W): " + notice.getRegisterName() + " value: " + getSpValue());
if (notice.getRegisterName().equals("$sp")) {
int oldSpDataRowIndex = spDataRowIndex;
updateSpDataRowColIndex();
resetStoredRegAndFrameNameColumns(spDataRowIndex + 1, oldSpDataRowIndex);
// System.out.println("SP value: " + formatAddress(getSpValue()) + " - tableIndex: " + spDataRowIndex);
// Add more rows if we are reaching current row count
if (spDataRowIndex + REMAINING_ROWS_THRESHOLD > numberOfRows) {
addNewTableRows(5);
}
table.repaint(); // Required for coloring $sp position during popping.
} else if (notice.getRegisterName().equals("$ra")) {
raWrittenInPrevInstr = true;
}
}
/**
* Processes a received memory update/notice (Read or Write) targeting the stack segment.
*/
private void processStackMemoryUpdate(MemoryAccessNotice notice) {
String regName = "", frameName = "";
if (notice.getAccessType() == AccessNotice.READ)
return;
if (regNameToBeStoredInStack != null) {
regName = regNameToBeStoredInStack;
regNameToBeStoredInStack = null;
}
if (frameNameToBeCreated != null) {
frameName = frameNameToBeCreated;
frameNameToBeCreated = null;
}
if (debug) {
System.out.println("\nStackAccessNotice (" +
((notice.getAccessType() == AccessNotice.READ) ? "R" : "W") + "): "
+ notice.getAddress() + " value: " + notice.getValue() +
" (stored: " + regName + ")");
}
int row;
try {
row = getTableRowIndex(notice.getAddress());
} catch (SVException sve) {
System.err.println("processStackMemoryUpdate(): " + sve.getMessage());
return;
}
if (debug)
System.out.println("Addr: " + formatAddress(notice.getAddress()) + " - tableIndex: " + row + " (" + regName + ")");
tableModel.setValueAt(regName, row, storedRegisterColumn);
tableModel.setValueAt(frameName, row, frameNameColumn);
getStackData(row);
table.repaint();
}
/**
* Adds more rows in table.
*
* @param numRowsToAdd the number of rows to add.
*/
private synchronized void addNewTableRows(int numRowsToAdd) {
int remainingRowsToAdd = maxTableRowsAllowed() - numberOfRows;
if (numRowsToAdd > remainingRowsToAdd)
numRowsToAdd = remainingRowsToAdd;
if (numRowsToAdd == 0) {
return;
}
for (int ri = 0; ri < numRowsToAdd; ri++)
tableModel.addRow(new Object[numberOfColumns]);
numberOfRows = tableModel.getRowCount();
getStackData();
}
/**
* @return the maximum allowed number of table rows.
*/
private int maxTableRowsAllowed() {
return (maxSpValueWordAligned - Memory.stackLimitAddress) / WORD_LENGTH_BYTES;
}
/**
* @return the index of the table row that {@code memAddress} should be stored
* if it belongs to the stack segment; else (-1).
* @throws SVException
*/
private int getTableRowIndex(int memAddress) throws SVException {
if (!isStackSegAddress(memAddress)) {
throw new SVException("An address not in the stack segment was provided (" + formatAddress(memAddress) + ")");
}
int rowIndex = (maxSpValueWordAligned - alignToCurrentWordBoundary(memAddress)) / WORD_LENGTH_BYTES;
if (rowIndex >= numberOfRows) {
addNewTableRows(rowIndex - numberOfRows + 10);
table.repaint();
}
if (rowIndex < 0) { // Higher address than $sp value at program start
int numNewRows = -rowIndex;
int newMaxSpValueWordAligned = maxSpValueWordAligned + numNewRows * WORD_LENGTH_BYTES;
if (newMaxSpValueWordAligned > Memory.stackBaseAddress) {
numNewRows -= (newMaxSpValueWordAligned - Memory.stackBaseAddress) / WORD_LENGTH_BYTES;
}
maxSpValueWordAligned += numNewRows * WORD_LENGTH_BYTES;
addNewTableRows(numNewRows);
refreshGui();
return (maxSpValueWordAligned - alignToCurrentWordBoundary(memAddress)) / WORD_LENGTH_BYTES;
}
return rowIndex;
}
/**
* @return the index of the table column that {@code memAddress} should be stored
* if it belongs to the stack segment; else (-1).
* @throws SVException
*/
private int getTableColumnIndex(int memAddress) throws SVException {
if (!isStackSegAddress(memAddress))
throw new SVException("An address not in the stack segment was provided (" + formatAddress(memAddress) + ")");
return LAST_BYTE_COLUMN - (memAddress % WORD_LENGTH_BYTES);
}
/**
* @return true if {@code memAddress} is in stack segment; else false.
*/
private boolean isStackSegAddress(int memAddress) {
/*
* In default memory configuration .stackLimitAddress is address 0x7fbffffc instead of 0x10040000
* mentioned in "MIPS Memory Configuration" window.
*/
return (memAddress > Memory.stackLimitAddress && memAddress <= Memory.stackBaseAddress);
}
/**
* Processes a received memory update/notice (Read or Write).
*/
private void processTextMemoryUpdate(MemoryAccessNotice notice) {
if (notice.getAccessType() == AccessNotice.WRITE)
return;
if (debug) {
System.out.println("\nTextAccessNotice (R): " + notice.getAddress()
+ " value: " + notice.getValue() /*+ " = "*/);
}
// printBin(notice.getValue());
boolean localRaWrittenInPrevInstr = raWrittenInPrevInstr;
raWrittenInPrevInstr = false;
try {
ProgramStatement stmnt = memInstance.getStatementNoNotify(notice.getAddress());
/*
* The check below is required in case user program is finished running
* by dropping of the bottom. This happens when an execution termination
* service (Code 10 in $v0) does NOT take place.
*/
if (stmnt == null)
return;
Instruction instr = stmnt.getInstruction();
String instrName = instr.getName();
int[] operands;
if (isStoreInstruction(instrName)) {
if (debug)
System.out.println("Statement TBE: " + stmnt.getPrintableBasicAssemblyStatement());
operands = stmnt.getOperands();
// for (int i = 0; i < operands.length; i++)
// System.out.print(operands[i] + " ");
// System.out.println();
regNameToBeStoredInStack = RegisterFile.getRegisters()[operands[I_RS_OPERAND_LIST_INDEX]].getName();
}
else if (isJumpInstruction(instrName) || isJumpAndLinkInstruction(instrName)) {
int targetAdrress = stmnt.getOperand(J_ADDR_OPERAND_LIST_INDEX) * WORD_LENGTH_BYTES;
String targetLabel = addrToTextSymbol(targetAdrress);
if (isJumpAndLinkInstruction(instrName)) {
registerNewSubroutineCall(stmnt, targetLabel);
} else if (detectJalEquivalentInstructions == true && isJumpInstruction(instrName)) {
if (localRaWrittenInPrevInstr == true) {
registerNewSubroutineCall(stmnt, targetLabel);
}
}
if (targetLabel != null) {
if (debug) {
System.out.print("Jumping to: " + targetLabel);
if (isJumpAndLinkInstruction(instrName))
System.out.println(" (" + (ras.size()) + ")");
else
System.out.println("");
}
}
}
else if (isJumpRegInstruction(instrName)) {
int targetRegister = stmnt.getOperand(R_RS_OPERAND_LIST_INDEX);
Register reg = RegisterFile.getRegisters()[targetRegister];
int returnAddress = reg.getValue();
// returnAddress-WORD_LENGTH_BYTES is needed as PC+WORD_LENGTH_BYTES is stored in $ra when jal is executed.
int jalStatementAddress = returnAddress - WORD_LENGTH_BYTES;
ProgramStatement jalStatement = memInstance.getStatementNoNotify(jalStatementAddress);
int jalTargetAddress = jalStatement.getOperand(J_ADDR_OPERAND_LIST_INDEX) * WORD_LENGTH_BYTES;
String exitingSubroutineName = addrToTextSymbol(jalTargetAddress);
activeFunctionCallStats.removeCall(exitingSubroutineName);
if (debug) {
System.out.println("Returning from: " + exitingSubroutineName + " (" + ras.size() +
") to line: " + jalStatement.getSourceLine());
}
try {
Integer rasTopAddress = ras.remove(ras.size()-1);
if (rasTopAddress.compareTo(jalStatementAddress) != 0) {
System.err.println("Mismatching return address: " + formatAddress(rasTopAddress) + " vs " + formatAddress(jalStatementAddress) +
" (Expected/jal vs Actual/jr)");
}
} catch (IndexOutOfBoundsException iobe) {
/* Exception is thrown whenever:
* 1) Subroutine calling instructions are back-stepped (undone) and again executed.
* Undoing the last step should not be supported! FIXED: BackStepper is disabled.
*
* 2) In case StackVisualizer gets disconnected while user program is executing and
* then is again connected. FIXED: Tool's disconnect button is disabled during
* execution/simulation and then again enabled at the end.
*
* 3) When tool's Reset button is pressed while the user program is executing.
* FIXED: Removed ras and activeFunctionCallStats reset operations from reset()
*/
System.err.println("Mismatching number of subroutine calls and returns.");
}
}
} catch (AddressErrorException aee) {
System.err.println("processTextMemoryUpdate(): " + formatAddress(aee.getAddress()) + " AddressErrorException");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Update {@code ras}, {@code activeFunctionCallStats} and {@code frameNameToBeCreated}
* as of a new subroutine call.
* @param stmnt the jump/jal instruction statement that invokes the new subroutine call.
* @param targetLabel the name/label of the new subroutine that is called.
*/
private void registerNewSubroutineCall(ProgramStatement stmnt, String targetLabel) {
ras.add(stmnt.getAddress());
Integer count = activeFunctionCallStats.addCall(targetLabel);
frameNameToBeCreated = targetLabel + " (" + count + ")";
}
/**
* @param instrName instruction name.
*
* @return true if instruction name matches "sw", "sh", "sc" or "sb"; else false.
*/
private boolean isStoreInstruction(String instrName) {
if (instrName.equals("sw") || instrName.equals("sh") ||
instrName.equals("sc") || instrName.equals("sb"))
return true;
return false;
}
/**
* @param instrName instruction name.
*
* @return true if instruction name matches "j"; else false.
*/
private boolean isJumpInstruction(String instrName) {
return (instrName.equals("j"));
}
/**
* @param instrName instruction name.
*
* @return true if instruction name matches "jal"; else false.
*/
private boolean isJumpAndLinkInstruction(String instrName) {
return (instrName.equals("jal"));
}
/**
* @param instrName instruction name.
*
* @return true if instruction name matches "jr"; else false.
*/
private boolean isJumpRegInstruction(String instrName) {
return (instrName.equals("jr"));
}
/**
* Translates a text segment address ({@code memAddress}) to a symbol/label.
*
* @return the corresponding label; else null.
*/
private String addrToTextSymbol(int memAddress) {
String addrStr = String.valueOf(memAddress);
SymbolTable localSymTable = Globals.program.getLocalSymbolTable();
Symbol symbol = localSymTable.getSymbolGivenAddressLocalOrGlobal(addrStr);
if (symbol != null) {
// System.out.println("Symbol: " + symbol.getName());
return symbol.getName();
}
System.err.println("addrToTextSymbol(): Error translating address to label");
return null;
}
/**
* @return the current stack pointer ($sp) value.
*/
private int getSpValue() {
return RegisterFile.getValue(SP_REG_NUMBER);
}
/**
* Disables back stepping.
*/
private void disableBackStepper() {
/*
* The ignoreObserving flag is required for disabling
* BackStepper when the Connect button is pressed.
* (The tool is not yet registered as observing)
*/
if (Globals.program == null)
return;
BackStepper bs = Globals.program.getBackStepper();
if (bs == null)
return;
if (bs.enabled()) {
if (debugBackStepper)
System.out.println("Disabled BackStepper");
bs.setEnabled(false);
disabledBackStep = true;
}
}
/**
* Re-enables back stepping.
*/
private void restoreBackStepper() {
if (disabledBackStep) {
disabledBackStep = false;
if (Globals.program == null)
return;
BackStepper bs = Globals.program.getBackStepper();
if (bs == null)
return;
if (!bs.enabled()) {
if (debugBackStepper)
System.out.println("Enabled BackStepper");
bs.setEnabled(true);
}
}
}
/**
* Enables or disables Run buttons as of parameter.
*/
private void runButtonsSetEnabled(boolean enable) {