Debugging a program written in machine code on any computer can be an absolute nightmare, and ARM code is no exception. Whether the elusive bug is a subtle one that causes the program to provide erroneous results, or a real humdinger which crashes the whole system, it can take ages just to find out where the bug is, let aloneput it right. One aid to debugging that can prove a godsend is a single stepper. Quite simply, what this does is to allow a program to be executed one instruction at a time, giving you the opportunity to examine the contents of memory and the processor registers after each instruction. This makes it much easier to identify where a problem has occurred.
The program presented here is just such a utility, which is itself written in machine code in the form of a relocatable module. To use the module, enter the listing and save it. When run, the program will assemble the module and save it with the filename 'SSTEP'. The module can then be installed by typing QUIT (to exit from Basic) followed by SSTEP.
The SSTEP module provides a single command:
*SINGLESTEP <address>
which enters the single stepper ready to step through from the given address, which should be in hex. At this point you are presented with a '^' as a prompt, which signifies that the stepper is waiting for one of several single character commands to be entered. The possible commands are:
R | Display registers |
A | Alter register |
Space bar | Execute next instruction |
G | Continue execution |
0,1,2 | Set automatic display level |
N,Z,C,V,I,F | Toggle flags |
* | OS Command |
Escape | Terminate execution |
Most of the commands should be fairly self-explanatory. Perhaps the most important one is 'Space', which causes the next instruction to be executed. Initially, the 'next' instruction will be the instruction at the address specified in the *SingleStep command. Normally, the program will be stepped through one instruction at a time. However, in the case of a Branch with Link (BL) instruction, you are given the option of whether you wish to trace the subroutine or not. If you answer no, then the entire subroutine will be treated as a single instruction. SWI calls are always treated as a single instruction.
In some cases, you may wish to step through the first part of a machine code program, and then when you are satisfied with the operation, allow the program to run as normal. To do this, use the 'G' command instead of 'Space'. In this case, control of the program cannot be regained. Whenever a program executed via *SINGLESTEP terminates, a message to that effect will be displayed, and if an error occurs, this is displayed too. While single stepping, you can exit from the program prematurely by pressing Escape.
The commands '0', '1' and '2' control what information is displayed after each instruction is executed. Level '0' causes no display, while level '1' will display just the next instruction, and level '2' will display the next instruction and a dump of the register contents. The default is level '1'.
The 'R' command will list the current contents of all the registers, together with the next instruction to be executed, the processor mode, and the flag settings. To alter the contents of any register you can use the 'A' command, Selecting this will produce a '?' prompt. You should now enter the register number (in decimal), and the new value for it (in hex), separated by a space. For example:
1 31FFC
will set register R1 to &31FFC. A special case is if the register number is entered as 'P'. In this case, the Program Counter (PC) is altered without affecting the status bits in register R15. Therefore, this effectively performs a jump operation to alter the flow of control. To toggle the contents of the flags directly, press the key corresponding to the flag, thus:
N | Negative flag | |
Z | Zero flag | |
C | Carry flag | |
V | Overflow flag | |
I | Interrupt disable flag | |
F | Fast interrupt disable flag |
For editing memory, any star command can be issued. You could for example, use the memory editor built into the RISC User Toolbox, or the *MEMORYA command supported by the RISC OS Debugger module.
Should you wish to start debugging part way through a program, then this can be done by embedding a SWI call in your machine code, rather than using the *SINGLESTEP command. The SWI used is:
SWI "SingleStep_StartStep"
and has the number &C0800. When this SWI is executed, the single stepper will be entered at the instruction immediately after the SWI. The registers will be retained exactly as they were before the SWI. You can then step through the remainder of the program exactly as for *SINGLESTEP.
If you wish to study the workings of the single step module, then this month's magazine disc contains a fully commented version of the source code. See the inside-back cover for ordering details.
10 REM > SingleP
20 REM Program Single Stepper
30 REM Version A 1.0
40 REM Author David Spencer
50 REM RISC User September 1989
60 REM Program Subject to Copyright
70 :
80 DIM code 3000
90 FOR pass=4 TO 7 STEP 3
100 O%=code:P%=0
110 [OPT pass:EQUD 0:EQUD init
120 EQUD 0:EQUD 0:EQUD title
130 EQUD help:EQUD commtab:EQUD &C0800
140 EQUD swi:EQUD switab
150 .switab EQUS "SingleStep":EQUB 0
160 EQUS "StartStep":EQUB 0:EQUB 0
170 .title
180 EQUS "SingleStepper":EQUB 0
190 .help
200 EQUS "Single Stepper":EQUB 9
210 EQUS "1.00 (21 Jun 1989)":EQUB 0
220 ALIGN
230 .commtab
240 EQUS "Singlestep":EQUB 0:ALIGN
250 EQUD step:EQUD &10001:EQUD ssyn
260 EQUD shelp:EQUB 0:ALIGN
270 .shelp
280 EQUS "*Singlestep invokes the machine code single stepper.":EQUB 13
290 .ssyn
300 EQUS "Syntax: Singlestep <start address>":EQUB 0:ALIGN
310 .init MOV R6,R14:MOV R3,#1024
320 MOV R0,#6:SWI "XOS_Module"
330 MOVVS PC,R6:STR R2,[R12]
340 MOV R0,#0:STRB R0,[R2,#169]
350 MOVS PC,R6
360 .swi CMP R11,#0:MOVNES PC,R14
370 LDR R12,[R12]:LDRB R11,[R12,#169]
380 CMP R11,#0:MOVNES PC,R14
390 STR R14,[R12,#14*4]
400 BIC R14,R14,#&FC000003
410 STR R14,[R12,#172]
420 LDR R14,[R13,#4]
430 BIC R2,R14,#&FC000003
440 STR R2,[R12,#15*4]
450 STMIA R12,{R0-R10}:B step2
460 .step LDR R12,[R12]
470 LDRB R1,[R12,#169]:CMP R1,#0
480 MOVNES PC,R14:STMFD R13!,{R14}
490 MOV R1,R0:MOV R0,#16
500 ORR R0,R0,#1<<31
510 SWI "XOS_ReadUnsigned"
520 LDMVSFD R13!,{PC}
530 BIC R2,R2,#&FC000003
540 STR R2,[R12,#15*4]:MOV R0,#0
550 MOV R1,#0
560 .clear STR R0,[R12,R1]
570 ADD R1,R1,#4:CMP R1,#13*4
580 BNE clear:ADR R14,terminate
590 STR R14,[R12,#14*4]
600 .step2 ADD R0,R12,#1024
610 STR R0,[R12,#13*4]
620 MOV R0,#patchend-patch
630 ADR R1,patch:ADD R2,R12,#68
640 .setpatch LDR R3,[R1],#4
650 STR R3,[R2],#4:SUBS R0,R0,#4
660 BNE setpatch:MOV R1,#1
670 STRB R1,[R12,#168]
680 STRB R1,[R12,#169]:MOV R1,#0
690 STRB R1,[R12,#170]
700 STR R13,[R12,#64]:B main
710 .patch STR R12,r12save
720 STR R13,r13save
730 LDR R0,[R12,#15*4]
740 AND R0,R0,#&FC000003:MOV R1,R12
750 TEQP R0,#0:MOVNV R0,R0
760 LDMIA R1,{R0-R14}
770 .here
780 LDR PC,[PC,#-(here+8-patch+8)]
790 .getback STR R14,[PC,#-(getback+8-patch+12)]:BL nextinst
800 .nextinst STR R14,[PC,#-(nextinst+
8-patch+8)]
810 .here3
820 SUB R14,PC,#here3+8-patch+68
830 STMIA R14,{R0-R13}
840 SWI "OS_EnterOS":LDR R12,r12save
850 LDR R13,r13save:LDR PC,return
860 .return EQUD 0
870 .r12save EQUD 0
880 .r13save EQUD 0
890 .patchend
900 .main SWI &10A:SWI &10D
910 SWI &100+ASC"^":SWI &120
920 SWI "OS_ReadC":BCC notesc
930 MOV R0,#&7E:SWI "OS_Byte":B quit
940 .notesc SWI "OS_WriteC"
950 CMP R0,#ASC" ":BEQ execute
960 CMP R0,#ASC"*":BEQ star
970 CMP R0,#ASC"0":BCC notmode
980 CMP R0,#ASC"2":BHI notmode
990 SUB R0,R0,#ASC"0"
1000 STRB R0,[R12,#168]:B main
1010 .notmode BIC R0,R0,#&20
1020 MOV R1,#1<<26:MOV R2,#5
1030 ADR R3,flags
1040 .checkforflags LDRB R4,[R3,R2]
1050 CMP R4,R0:LDREQ R0,[R12,#15*4]
1060 EOREQ R0,R0,R1
1070 STREQ R0,[R12,#15*4]:BEQ main
1080 MOV R1,R1,LSL #1:SUBS R2,R2,#1
1090 BPL checkforflags:CMP R0,#ASC"A"
1100 BEQ alter:CMP R0,#ASC"R"
1110 BEQ registers:CMP R0,#ASC"G"
1120 BEQ go:SWI "OS_WriteS":EQUW &D0A
1130 EQUS "Unrecognised command"
1140 EQUW &D0A:EQUB 7:EQUB 0:B main
1150 .go MOV R1,#1:STRB R1,[R12,#170]
1160 B execute
1170 .terminate MOV R1,PC:TEQP PC,#0
1180 SWI "XOS_EnterOS":TST R1,#1<<28
1190 BNE error:SWI "XOS_WriteS"
1200 EQUS "Program terminated normally.":EQUW &D0A:EQUB 0
1210 LDMFD R13!,{R14}
1220 BICS PC,R14,#1<<28
1230 .error SWI "OS_WriteS"
1240 EQUS "Program terminated with error:":EQUW &D0A:EQUB 0
1250 LDMFD R13!,{R14}
1260 ORRS PC,R14,#1<<28
1270 .quit LDR R13,[R12,#64]
1280 SWI "OS_WriteS":EQUW &D0A
1290 EQUS "Execution aborted."
1300 EQUW &D0A:EQUB 0:MOV R0,#0
1310 STRB R0,[R12,#169]
1320 LDMFD R13!,{R14}
1330 BICS PC,R14,#1<<28
1340 :
1350 .star ADD R0,R12,#256:MOV R1,#256
1360 MOV R2,#ASC" ":MOV R3,#&FF
1370 SWI "OS_ReadLine":BCC noesc2
1380 .escesc MOV R0,#&7E:SWI "OS_Byte"
1390 SWI "OS_WriteS":EQUW &D0A
1400 EQUS "Escape":EQUW &D0A:EQUB 0
1410 B main
1420 .noesc2 ADD R0,R12,#256
1430 SWI "XOS_CLI":BVC main
1440 ADD R0,R0,#4:SWI "OS_Write0"
1450 B regout
1460 :
1470 .registers SWI &10A:SWI &10D
1480 MOV R3,R12:MOV R4,#0
1490 .reg2 SWI &100+ASC"R":MOV R0,R4
1500 CMP R0,#10:SWICS &100+ASC"1"
1510 SUBCS R0,R0,#10:ADD R0,R0,#ASC"0"
1520 SWI "OS_WriteC":CMP R4,#10
1530 SWICC &120:SWI "OS_WriteS"
1540 EQUS " = ":EQUB 0:LDR R0,[R3],#4
1550 ADD R1,R12,#256:MOV R2,#9
1560 SWI "OS_ConvertHex8"
1570 SWI "OS_Write0":SWI "OS_WriteS"
1580 EQUS " ":EQUB 0:ADD R4,R4,#1
1590 TST R4,#3:SWIEQ &10A:SWIEQ &10D
1600 CMP R4,#16:BNE reg2:BL disinst
1610 SWI "OS_WriteS"
1620 EQUS "Processor is in ":EQUB 0
1630 AND R0,R3,#3:ADR R1,modes
1640 LDR R0,[R1,R0,LSL #2]
1650 SWI "OS_WriteC":MOV R0,R0,LSR #8
1660 SWI "OS_WriteC":MOV R0,R0,LSR #8
1670 SWI "OS_WriteC":SWI "OS_WriteS"
1680 EQUS " mode":EQUW &D0A:EQUB 0
1690 AND R3,R3,#&FC000000:CMP R3,#0
1700 BNE reg3:SWI "OS_WriteS"
1710 EQUS "No flags set":EQUB 0
1720 B regout
1730 .reg3 ADR R1,flags
1740 MOV R2,#6:SWI "OS_WriteS"
1750 EQUS "Flags set are : ":EQUB 0
1760 .reg4 LDRB R0,[R1],#1
1770 MOVS R3,R3,ASL #1
1780 SWICS "OS_WriteC":SUBS R2,R2,#1
1790 BNE reg4
1800 .regout SWI &10A:SWI &10D:B main
1810 :
1820 .modes EQUS "USR FIQ IRQ SVC "
1830 .flags EQUS "NZCVIF":ALIGN
1840 .disinst MOV R10,R14
1850 SWI "OS_WriteS"
1860 EQUS "Next instruction is : "
1870 EQUB 0:LDR R3,[R12,#15*4]
1880 BIC R0,R3,#&FC000003
1890 ADD R1,R12,#256:MOV R2,#9
1900 SWI "OS_ConvertHex8"
1910 SWI "OS_Write0":SWI &120
1920 BIC R1,R3,#&FC000003
1930 LDR R0,[R1]
1940 SWI "Debugger_Disassemble"
1950 MOV R0,R1:SWI "OS_Write0":SWI &10A
1960 SWI &10D:MOV PC,R10
1970 :
1980 .alter SWI "OS_WriteS":EQUW &D0A
1990 EQUS "? ":EQUB 0:ADD R0,R12,#256
2000 MOV R1,#256:MOV R2,#ASC" "
2010 MOV R3,#&FF:SWI "OS_ReadLine"
2020 BCS escesc:ADD R1,R12,#256
2030 .alter2 LDRB R0,[R1],#1
2040 CMP R0,#ASC" ":BEQ alter2
2050 BCC regout:CMP R0,#ASC"P"
2060 BNE alter3:LDRB R0,[R1],#1
2070 CMP R0,#ASC" ":BNE badalter
2080 MOV R2,#16:B alter4
2090 .alter3 SUB R1,R1,#1:MOV R0,#10
2100 ORR R0,R0,#1<<29:MOV R2,#15
2110 SWI "XOS_ReadUnsigned"
2120 BVS badalter
2130 .alter4 MOV R3,R2:MOV R0,#16
2140 SWI "XOS_ReadUnsigned":CMP R3,#16
2150 BEQ alter5:STR R2,[R12,R3,LSL #2]
2160 B main
2170 .alter5 LDR R0,[R12,#15*4]
2180 MOV R0,R0,ROR #2
2190 AND R0,R0,#&FF000000
2200 MOV R0,R0,ROR #30
2210 BIC R2,R2,#&FC000000:BIC R2,R2,#3
2220 ORR R0,R0,R2:STR R0,[R12,#15*4]
2230 B main
2240 .badalter SWI "OS_WriteS"
2250 EQUW &D0A:EQUS "Bad alter":EQUB 0
2260 B regout
2270 :
2280 .execute SWI &10A:SWI &10D
2290 BL findnext:LDRB R1,[R12,#170]
2300 CMP R1,#1:BEQ execute2
2310 ADR R1,terminate:CMP R0,R1
2320 BEQ execute2:LDR R1,[R12,#172]
2330 CMP R0,R1:LDREQ R1,[R12,#14*4]
2340 ORREQ R1,R1,#1<<27
2350 STREQ R1,[R12,#14*4]
2360 .execute2 MOVEQ R1,#0
2370 STREQB R1,[R12,#169]
2380 STRNE R0,[R12,#160]
2390 LDRNE R1,[R0]:STRNE R1,[R12,#164]
2400 ADDNE R1,R12,#getback-patch+68
2410 SUBNE R1,R1,R0:SUBNE R1,R1,#8
2420 MOVNE R1,R1,LSR #2
2430 ORRNE R1,R1,#&EA000000
2440 STRNE R1,[R0]:ADRNE R0,backin
2450 STRNE R0,[R12,#return-patch+68]
2460 ADD PC,R12,#68
2470 .backin LDR R0,[R12,#160]
2480 LDR R1,[R12,#164]:STR R1,[R0]
2490 LDR R1,[R12,#15*4]
2500 AND R1,R1,#&FC000003:ORR R1,R1,R0
2510 STR R1,[R12,#15*4]
2520 LDRB R0,[R12,#168]
2530 SUBS R0,R0,#1:BMI main
2540 BNE registers:SWI "OS_NewLine"
2550 BL disinst:B main
2560 :
2570 .findnext MOV R11,R14
2580 LDR R0,[R12,#15*4]
2590 BIC R0,R0,#&FC000003
2600 LDR R1,[R0]:LDR R2,addinst
2610 AND R1,R1,#&F0000000:ORR R1,R1,R2
2620 STR R1,[R12,#256]
2630 LDR R1,jumpback
2640 STR R1,[R12,#260]
2650 ADR R14,ccdone:MVN R2,#0
2660 LDR R1,[R12,#15*4]:ORR R1,R1,#3
2670 TEQP R1,#0:ADD PC,R12,#256
2680 .ccdone CMP R2,#0:ADDNE R0,R0,#4
2690 MOVNE PC,R11:LDR R1,[R0]
2700 BIC R1,R1,#&F0000000:SUB R2,R1,#1
2710 CMP R2,#&F000000:BEQ oswrites
2720 CMP R1,#&C000000:BCS sequential
2730 CMP R1,#&A000000:BCS branch
2740 CMP R1,#&8000000:BCS block
2750 CMP R1,#&4000000:BCS transfer
2760 CMP R1,#&400000:BCS notmult
2770 AND R2,R1,#&90:CMP R2,#&90
2780 BEQ sequential
2790 .notmult AND R2,R1,#&F000
2800 CMP R2,#&F000:BNE sequential
2810 AND R2,R1,#&F<<21:CMP R2,#8<<21
2820 BCC dprocess:CMP R2,#12<<21
2830 BCC sequential
2840 .dprocess AND R4,R1,#&F0000
2850 MOV R4,R4,LSR #16:AND R5,R1,#&F
2860 AND R6,R1,#&F00:MOV R6,R6,LSR #8
2870 BIC R1,R1,#&F000:BIC R1,R1,#&F0000
2880 ORR R1,R1,#&10000
2890 ORR R1,R1,#&E0000000
2900 TST R1,#1<<25:BNE dprocimm
2910 BIC R1,R1,#&F:ORR R1,R1,#2
2920 TST R1,#16:BICNE R1,R1,#&F00
2930 ORRNE R1,R1,#&300
2940 .dprocimm STR R1,[R12,#256]
2950 LDR R1,jumpback:STR R1,[R12,#260]
2960 LDR R1,[R12,R4,LSL #2]:CMP R4,#15
2970 BICEQ R1,R1,#&FC000003
2980 LDR R2,[R12,R5,LSL #2]
2990 LDR R3,[R12,R6,LSL #2]
3000 ADR R14,dprocdone:ADD PC,R12,#256
3010 .dprocdone BIC R0,R0,#&FC000003
3020 MOV PC,R11
3030 :
3040 .sequential ADD R0,R0,#4
3050 MOV PC,R11
3060 .oswrites ADD R0,R0,#4
3070 .oswrites2 LDRB R1,[R0],#1
3080 CMP R1,#0:BNE oswrites2
3090 ADD R0,R0,#3:BIC R0,R0,#3
3100 MOV PC,R11
3110 .branch MOV R4,R0:ADD R2,R0,#8
3120 BIC R3,R1,#&F000000
3130 MOV R3,R3,LSL #8
3140 ADD R3,R2,R3,ASR #6
3150 CMP R1,#&B000000:BCC noquestion
3160 SWI "OS_WriteS"
3170 EQUS "Follow branch to ":EQUB 0
3180 MOV R0,R3:ADD R1,R12,#256
3190 MOV R2,#9:SWI "OS_ConvertHex8"
3200 SWI "OS_Write0":SWI "OS_WriteS"
3210 EQUS " (Y or N) ? ":EQUB 0
3220 SWI "OS_ReadC":BCC notesc2
3230 MOV R0,#&7E:SWI "OS_Byte"
3240 MOV R0,#ASC"N"
3250 .notesc2 BIC R0,R0,#&20
3260 CMP R0,#ASC"Y":MOVNE R0,#ASC"N"
3270 SWI "OS_WriteC":MOV R1,R0
3280 SWI "OS_NewLine":CMP R1,#ASC"Y"
3290 MOVNE R0,R4:BNE sequential
3300 .noquestion:MOV R0,R3:MOV PC,R11
3310 .block TST R1,#1<<20
3320 TSTNE R1,#1<<15:BEQ sequential
3330 MOV R2,R1,LSR #16:AND R2,R2,#15
3340 LDR R2,[R12,R2,LSL #2]
3350 TST R1,#1<<23:BNE indexedup
3360 TST R1,#1<<24:SUBNE R2,R2,#4
3370 B getnewpc
3380 .indexedup MOV R3,#0
3390 MOV R4,R1,LSL #17
3400 .regcount MOVS R4,R4,LSL #1
3410 ADC R3,R3,#0:CMP R4,#0
3420 BNE regcount:ADD R2,R2,R3,LSL #2
3430 TST R1,#1<<24:ADDNE R2,R2,#4
3440 .getnewpc LDR R0,[R2]
3450 BIC R0,R0,#&FC000003:MOV PC,R11
3460 .transfer TST R1,#1<<20
3470 BEQ sequential:AND R2,R1,#&F000
3480 CMP R2,#&F000:BNE sequential
3490 AND R4,R1,#&F0000
3500 MOV R4,R4,LSR #16:AND R5,R1,#&F
3510 BIC R0,R1,#&FF000:TST R0,#1<<25
3520 BICNE R0,R0,#&F
3530 ORR R0,R0,#&E0000000
3540 ORR R0,R0,#&10000:ORRNE R0,R0,#2
3550 STR R0,[R12,#256]:LDR R0,jumpback
3560 STR R0,[R12,#260]
3570 ADR R14,transfer2
3580 LDR R1,[R12,R4,LSL #2]
3590 CMP R4,#15
3600 BICEQ R1,R1,#&FC000003
3610 LDR R2,[R12,R5,LSL #2]:CMP R5,#15
3620 BICEQ R2,R2,#&FC000003
3630 ADD PC,R12,#256
3640 .transfer2 BIC R0,R0,#&FC000003
3650 MOV PC,R11
3660 .addinst ADDEQS R2,R2,#1
3670 .jumpback MOV PC,R14:]NEXT
3680 SYS "OS_File",10,"Sstep",&FFA,,code,O%