Search
  • daveor

Reverse engineering PDP-11 BASIC: Part 20

This post will describe the operation of a few TRAPs that are used to support the functionality that will be analysed in the next posts. We'll be looking at TRAP 142, TRAP 150, TRAP 146, TRAP 144 and TRAP 132.


For context and a list of other posts on this topic, see the PDP-11 BASIC reverse engineering project page.


TRAP 142

TRAP 142 is used for pushing a floating point value onto the stack.

002414 010346 MOV R3, -(SP)
002416 010246 MOV R2, -(SP)
002420 016646 MOV 4(SP), -(SP)
002424 010466 MOV R4, 6(SP)
002430 020506 CMP R5, SP
002432 103001 BCC 2436
002434 000207 RTS PC
002436 104401 TRAP 1

Before beginning this analysis it is worth recalling that these TRAPs are called as subroutines, so the top value on the stack when a TRAP is invoked is the return address where execution should continue. Therefore, in order for this TRAP to have enduring effect, the floating point value being pushed onto the stack needs to be pushed UNDER the return address, so that execution can continue as expected once the TRAP is complete.

002414 010346 MOV R3, -(SP)
002416 010246 MOV R2, -(SP)

Firstly R3 and R2 are pushed onto the stack.

002420 016646 MOV 4(SP), -(SP)

The value from SP+4, which is the return address, is pushed onto the stack again.

002424 010466 MOV R4, 6(SP)

Next, R4 is copied into the location formerly occupied by the return address so now the floating point value is in the three words below the return address.

002430 020506 CMP R5, SP
002432 103001 BCC 2436
002434 000207 RTS PC
002436 104401 TRAP 1

The value of the stack pointer is compared to the value in R5. If R5 is greater than SP the carry flag will be clear, which means that the stack has impinged on the runtime state, which is an error condition so control jumps to 2436 to generate an error.


Otherwise, control returns from the TRAP.


TRAP 150

TRAP 150 is used for storing a floating point value in the runtime state storage.

001322 010200 MOV R2, R0
001324 104512 TRAP 112
001326 010300 MOV R3, R0
001330 104512 TRAP 112
001332 010400 MOV R4, R0
001334 104512 TRAP 112
001336 000207 RTS PC

R2 is copied to R0 and then TRAP 112 is used to store R0 to the runtime state storage.

This is repeated for R3 and R4 and then control returns from the trap.


TRAP 146

TRAP 146 is used for storing a variable into the runtime state storage.

001302 104512 TRAP 112
001304 005000 CLR R0
001306 104512 TRAP 112
001310 010500 MOV R5, R0
001312 104512 TRAP 112
001314 104512 TRAP 112
001316 104512 TRAP 112
001320 000207 RTS PC

TRAP 112 is used to push the value from R0 into the runtime state storage. This is the variable identifier passed into the TRAP. R0 is then cleared and the zero word is pushed into the runtime state storage using TRAP 112.


R5 is copied into R0 and then TRAP 112 is used to allocate three more words onto the stack. This will be the memory addresses used for storing the floating point value of the variable.


Control then returns from the TRAP.


TRAP 144

This TRAP searches the runtime state storage and returns a pointer to the value in R0. If the value is not found R0 will have the value zero and the zero flag will be set.

006010 104526 TRAP 126
006012 102422 BVS 6060
006014 005301 DEC R1
006016 005000 CLR R0
006020 104534 TRAP 134
006022 001415 BEQ 6056
006024 104514 TRAP 114
006026 001413 BEQ 6056
006030 021627 CMP (SP), #4334
006034 001413 BEQ 6064
006036 005723 TST (R3)+
006040 121127 CMPB (R1), #50
006044 001006 BNE 6062
006046 005201 INC R1
006050 010446 MOV R4, -(SP)
006052 104532 TRAP 132
006054 012604 MOV (SP)+, R4
006056 005700 TST R0
006060 000207 RTS PC
006062 005723 TST (R3)+
006064 010300 MOV R3, R0
006066 000207 RTS PC

Let's see how this works.

006010 104526 TRAP 126
006012 102422 BVS 6060

TRAP 126 is used to parse a variable name identifier from the command sequence. If a valid variable name is found, the resulting identifier is stored in R4. If an error is encountered, or no valid variable name is identified, the overflow flag is set, in which case control branches to 6060 to return from TRAP 144.

006014 005301 DEC R1
006016 005000 CLR R0

When TRAP 126 returns, R1 will point at the character two after the identified variable name. So, R1 is decremented to point at the character after the variable name and R0 is cleared.

006020 104534 TRAP 134
006022 001415 BEQ 6056

TRAP 134 is now used to set R3 to the beginning of the running state storage. If the returned value is zero then control branches to 6056 to return.

006024 104514 TRAP 114
006026 001413 BEQ 6056

TRAP 114 is then used to locate the variable with the identifier parsed using TRAP 126 in the runtime state storage. If the zero flag is set, that means that the variable was not found and control branches to 6056 to return. Otherwise after this TRAP R3 will point at the variable entry in the runtime state storage.

006030 021627 CMP (SP), #4334
006034 001413 BEQ 6064

The value on the top of the stack, which is the return address to which control will return when this TRAP completes, is compared to 4334. Address 4334 is part of the BASIC DIM command code, so this test is checking whether or not TRAP is being called by the BASIC DIM code. If it is, control jumps to 6064 to move R3 into R0 and then return.


Otherwise:

006036 005723 TST (R3)+

As described in TRAP 146 above, a variable entry in the runtime state storage consists of:

  1. The variable identifying value

  2. A zero word for a non-array variable or a word with the dimensions of an array for an array variable (high byte = x dimension, low byte = y dimension (or sole dimension for single dimensional array)).

  3. The three word floating point value of the variable

At the moment, R3 points at the variable entry in the runtime state storage, so this instruction moves R3 on by one word so it will now be pointing at the zero word after the variable identifying value.

006040 121127 CMPB (R1), #50
006044 001006 BNE 6062

The value of the next character in the instruction string is checked to see if it equals an opening bracket (ASCII 50). If not equal branch to 6062, in which case:

006062 005723 TST (R3)+
006064 010300 MOV R3, R0
006066 000207 RTS PC

The value of R3 is incremented so that it now points at the variable value. The value in R3 is then moved to R0 and then control returns.


Otherwise there is an open bracket following the variable name, indicating that the variable is an array.

006046 005201 INC R1
006050 010446 MOV R4, -(SP)
006052 104532 TRAP 132
006054 012604 MOV (SP)+, R4
006056 005700 TST R0
006060 000207 RTS PC

In this case R1 is incremented so that it now points at the array index (or indices in the case of a two dimensional array). R4 is pushed to the stack and then TRAP 132 is invoked to extract the relevant value from the array. Afterwards, R4 is restored from the stack.


Then, the value in R0 is tested and then control returns to the calling code.


TRAP 132

TRAP 132 is used by TRAP 144 in the case where an element from an array needs to be extracted. This TRAP is only used by TRAP 144. The TRAP returns the location of the selected array entry in R0.

002144 010346 MOV R3, -(SP)
002146 104536 TRAP 136
002150 102437 BVS 2250
002152 121127 CMPB (R1), #54
002156 001041 BNE 2262
002160 004767 JSR PC, 2266
002164 104472 TRAP 72
002166 010046 MOV R0, -(SP)
002170 104536 TRAP 136
002172 102033 BVC 2262
002174 104440 TRAP 40
002176 100432 BMI 2264
002200 012602 MOV (SP)+, R2
002202 017604 MOV @(SP), R4
002206 042704 BIC #177400, R4
002212 020004 CMP R0, R4
002214 003023 BGT 2264
002216 010146 MOV R1, -(SP)
002220 010201 MOV R2, R1
002222 010046 MOV R0, -(SP)
002224 010400 MOV R4, R0
002226 005200 INC R0
002230 104416 TRAP 16
002232 062600 ADD (SP)+, R0
002234 104530 TRAP 130
002236 012601 MOV (SP)+, R1
002240 061600 ADD (SP), R0
002242 005720 TST (R0)+
002244 012603 MOV (SP)+, R3
002246 000207 RTS PC
002250 004767 JSR PC, 2266
002254 010002 MOV R0, R2
002256 005000 CLR R0
002260 000750 BR 2202
002262 104413 TRAP 13
002264 104415 TRAP 15

002266 104440 TRAP 40   
002270 100775 BMI 2264
002272 017604 MOV @2(SP), R4
002276 000304 SWAB R4
002300 042704 BIC #177400, R4
002304 020004 CMP R0, R4
002306 003366 BGT 2264
002310 000207 RTS PC

Let's see how it works.

002144 010346 MOV R3, -(SP)

First, R3 is pushed onto the stack.

002146 104536 TRAP 136
002150 102437 BVS 2250

The element specifier in the array may be any form of expression, so TRAP 136 is used to evaluate the expression. The value of the expression is stored in R2/R3/R4.


If the overflow flag is set by TRAP 136 that means that the TRAP also consumed a closing bracket at the end of the expression. This implies that there is not a comma at the end of the expression, so the variable is not multidimensional. In this case, control branches to 2250:

002250 004767 JSR PC, 2266
002254 010002 MOV R0, R2
002256 005000 CLR R0
002260 000750 BR 2202

In this case the subroutine at 2266 is invoked to verify that the value specified is less than the corresponding array dimension. The required array dimension is copied from R0 to R2. R0 (the other array dimension) is cleared and then control branches to 2202.


In the case where the overflow flag was clear after TRAP 136, it may be that we are dealing with a multi-dimensional array, so:

002152 121127 CMPB (R1), #54
002156 001041 BNE 2262

The next character is compared to "," (ASCII 54). If it is not equal, control branches to 2262.

002160 004767 JSR PC, 2266

The subroutine at address 2266 is invoked to verify that the value specified is less than the corresponding array dimension.

002164 104472 TRAP 72

TRAP 72 is used to get the next non-whitespace character, which is stored in R2.

002166 010046 MOV R0, -(SP)

The value in R0, which is the first dimension of the variable being sought, is pushed onto the stack.

002170 104536 TRAP 136
002172 102033 BVC 2262

TRAP 136 is then used to parse the expression for the second dimension of the variable. The result will be stored in R2/R3/R4. In this case, we expect that TRAP 136 will consume the closing bracket after the expression for the second domension of the variable and therefore, that the overflow flag will be set. If the overflow flag is clear, we branch to 2262 to return an error.

002174 104440 TRAP 40
002176 100432 BMI 2264

TRAP 40 is then used to convert the float in R2/R3/R4 to an integer value in R0. If the resulting value is negative, this is invalid and control branches to 2264 to return an error.

002200 012602 MOV (SP)+, R2

The first array dimension, stored on the stack, is popped into R2.

002202 017604 MOV @(SP), R4
002206 042704 BIC #177400, R4
002212 020004 CMP R0, R4
002214 003023 BGT 2264

This code is used to verify that the value specified for the y (low byte) or single dimension (depending on the codepath taken to arrive at this code) is less than the corresponding array dimension. The value pointed to by the value at the top of the stack is moved into R4. The high byte is masked, leaving just the low byte value. This is compared to the value in R0 and if R0 is greater, this is an error, and control branches to 2264 to return an error.


If no error is identified, we now have one or two valid dimensions, depending on the code path taken. In other words, the requested array indices are within the bounds of the array.


At this point, the requested y dimension (or sole dimension for a unidimensional array) is contained in R0 and the requested x dimension is stored in R2. R4 contains the maximum value of the x dimension or zero for a unidimensional array.


Suppose we are looking for dimension (x, y) in an array of size (xmax, ymax), we are about to perform the calculation (x * ymax) + y, which is a pretty standard way to flatten a two-dimensional array for storage in memory.

002216 010146 MOV R1, -(SP)
002220 010201 MOV R2, R1
002222 010046 MOV R0, -(SP)
002224 010400 MOV R4, R0
002226 005200 INC R0

R1 is pushed onto the stack and then R2 is copied into R1. This is the x dimension, or zero in the case of a unidimensional array. Similarly, R0 is pushed onto the stack and R4 is copied into R0. R0 is also incremented.

002230 104416 TRAP 16

The values in R0 and R1 are multiplied together and the result is stored in R0 (high word) and R1 (low word).

002232 062600 ADD (SP)+, R0

The value at the top of the stack, the y dimension, is popped and added to the result of the multiplication.

002234 104530 TRAP 130

TRAP 130 is used to multiply the value in R0 by six (three words for each floating point value).

002236 012601 MOV (SP)+, R1

R1 is restored from the stack.

002240 061600 ADD (SP), R0
002242 005720 TST (R0)+

The base memory location of the variable value (which was passed into this TRAP in R3 and then pushed onto the stack) is added to the value in R0, which is the offset to seek forward to the relevant entry in the array. The value at R0 is then incremented.

002244 012603 MOV (SP)+, R3
002246 000207 RTS PC

Finally R3 is restored from the stack and control then returns to the calling code.


This is the subroutine used to verify that the value specified for the x dimension is less than the corresponding array dimension (stored in the high byte of the array dimensions word).

002266 104440 TRAP 40   
002270 100775 BMI 2264
002272 017604 MOV @2(SP), R4
002276 000304 SWAB R4
002300 042704 BIC #177400, R4
002304 020004 CMP R0, R4
002306 003366 BGT 2264
002310 000207 RTS PC

Let's see how this works.

002266 104440 TRAP 40  
002270 100775 BMI 2264 

Firstly, TRAP 40 is used to convert a floating point number stored in R2/R3/R4 into an integer in R0. If the resulting value is negative, this is invalid and control branches to 2264 to return an error.

002272 017604 MOV @2(SP), R4

Then, the value contained in the memory address at SP+2 is copied to R4. This is the memory location of the word after the variable identifier in the runtime state storage that was pushed to the stack at the beginning of TRAP 132. See TRAP 144 for more detail.

002276 000304 SWAB R4
002300 042704 BIC #177400, R4

The bytes of R4 are swapped and the high byte is masked so that R4 contains one byte of the array dimensions.

002304 020004 CMP R0, R4
002306 003366 BGT 2264
002310 000207 RTS PC

The value in R0 is compared to R4. If the value in R0 is greater than the value in R4, this means that the dimension being sought is larger than the dimension of the array, so control jumps to 2264 to return an error. Otherwise control returns from the subroutine.


Conclusion

This completes the analysis of these various TRAPs that are associated with the management of variables.


Thanks for reading!



14 views0 comments

Recent Posts

See All

Reverse engineering PDP-11 BASIC: Part 26

This short post describes TRAP 116 and the BASIC RUN, STOP, END and RANDOMIZE commands. For context and a list of other posts on this topic, see the PDP-11 BASIC reverse engineering project page. TRAP

Reverse engineering PDP-11 BASIC: Part 25

This post describes the BASIC FOR and NEXT loop commands. For context and a list of other posts on this topic, see the PDP-11 BASIC reverse engineering project page. Introduction Loops are implemented

Reverse engineering PDP-11 BASIC: Part 24

This post describes the BASIC READ, INPUT and RESTORE commands. For context and a list of other posts on this topic, see the PDP-11 BASIC reverse engineering project page. Introduction The INPUT comma