| 1 | # Overview # |
| 2 | |
| 3 | Although GCC claims to support PDP-11 targets there are some bugs that must be |
| 4 | worked around both in GCC and the GNU assembler. |
| 5 | |
| 6 | # GNU binutils Bugs # |
| 7 | |
| 8 | ## Problem Description ## |
| 9 | |
| 10 | One of the addressing modes supported by the PDP-11 is 'index deferred', |
| 11 | represented by `@X(Rn)`. This operand indicates that `Rn` contains a pointer |
| 12 | which should be dereferenced and the result added to `X` to generate a new |
| 13 | pointer to the final location. For example, consider the following four values, |
| 14 | one stored in a register and the other three in memory. Then `@2(R1)` is the |
| 15 | value `222`. |
| 16 | |
| 17 | R1: 1000 |
| 18 | 1000: 2000 |
| 19 | 2000: 111 |
| 20 | 2002: 222 |
| 21 | |
| 22 | Similarly, `@0(R1)` is the value `111`. In most PDP-11 assemblers, including |
| 23 | DEC's MACRO-11 assembler, the string `@(Rn)` is an alias to `@0(Rn)`. But when |
| 24 | the GNU assembler encounters `@(Rn)` it assembles it as though it were `(Rn)`, |
| 25 | a single level of indirection instead of two levels! |
| 26 | |
| 27 | If we're only writing assembly then we can work around this bug by always using |
| 28 | the form `@0(Rn)`. But what if we're writing C and using GCC to compile it? |
| 29 | Consider the following C code example, taken directly from some stack-based |
| 30 | debugger code written for the PDP-11. |
| 31 | |
| 32 | uint16_t ** csp = (uint16_t **) 070000; |
| 33 | *csp = (uint16_t *) 060000; |
| 34 | **csp = 0; |
| 35 | |
| 36 | When GCC transpiles this to assembly it generates code of the form `@(Rn)` when |
| 37 | assigning a value to `**csp` thus causing the value `0` to overwrite the value |
| 38 | `060000` at `*csp` if GNU `as` is used to assemble the code. |
| 39 | |
| 40 | ## Solution Description ## |
| 41 | |
| 42 | The following patch, tested on GNU binutils 2.28, fixes the bug. Since it |
| 43 | overloads the `operand->code` variable to pass unrelated state information to |
| 44 | `parse_reg()` I haven't submitted it for inclusion in GNU binutils. Once I'm |
| 45 | done bug-hunting in my toolchain I will clean up all the fixes and submit them |
| 46 | in one bundle. |
| 47 | |
| 48 | --- tc-pdp11.c 2017-06-24 22:33:00.260210000 -0700 |
| 49 | +++ tc-pdp11.c.fixed 2017-06-24 22:32:12.455205000 -0700 |
| 50 | @@ -431,6 +431,9 @@ |
| 51 | { |
| 52 | LITTLENUM_TYPE literal_float[2]; |
| 53 | |
| 54 | + /* Store the value (if any) passed by parse_op_noreg() before parse_reg() overwrites it. */ |
| 55 | + int deferred = operand->code; |
| 56 | + |
| 57 | str = skip_whitespace (str); |
| 58 | |
| 59 | switch (*str) |
| 60 | @@ -451,6 +454,15 @@ |
| 61 | operand->code |= 020; |
| 62 | str++; |
| 63 | } |
| 64 | + /* |
| 65 | + * This catches the case where @(Rn) is interpreted as (Rn) rather than @0(Rn) |
| 66 | + */ |
| 67 | + else if (deferred) |
| 68 | + { |
| 69 | + operand->additional = 1; |
| 70 | + operand->word = 0; |
| 71 | + operand->code |= 060; |
| 72 | + } |
| 73 | else |
| 74 | { |
| 75 | operand->code |= 010; |
| 76 | @@ -581,6 +593,12 @@ |
| 77 | |
| 78 | if (*str == '@' || *str == '*') |
| 79 | { |
| 80 | + /* |
| 81 | + * operand->code is overwritten by parse_reg() inside parse_op_no_deferred() |
| 82 | + * We use it to temporarily catch the alias @(Rn) -> @0(Rn) since |
| 83 | + * parse_op_no_deferred() starts at str+1 and thus misses the '@'. |
| 84 | + */ |
| 85 | + operand->code |= 010; |
| 86 | str = parse_op_no_deferred (str + 1, operand); |
| 87 | if (operand->error) |
| 88 | return str; |