Menu

#2930 [Z80...] Invalid relocation of 24bit values

open
nobody
z80 port (190)
sdas
5
2025-12-26
2019-08-21
No

I try to implement banking support, but confusing by strange linker behavior.
Code on assembler:
.dw _label
.dw (_label >> 16)
If _label on linker stage is equal to 0x1801E (-Wl-b_BANK01=0x18000) then result code will be:
1E 80 1E 80
I try to analize rel file and found that it does not contain instruction for 24 bit relocation (there should be codes in R section with four high bits set due to rel file format).
To reproduce:
$ sdcc -mez80_z80 -c aaa.c
$ sdasz80 -o bbb.rel bbb.asm
$ sdcc -mez80_z80 -Wl-b_BANK01=0x18000 aaa.rel bbb.rel
$ makebin -s 131072 aaa.ihx aaa.bin

2 Attachments

Related

Support Requests: #206

Discussion

  • Philipp Klaus Krause

    24 bit relocation is working for stm8. I guess it could be ported to z80 and related.

    Philipp

     
  • Sergey Belyashov

    Currently I do not understand how it may work, because shifts are done in common code (asxxsrc).

     
  • Philipp Klaus Krause

    • assigned_to: Philipp Klaus Krause --> nobody
     
  • Janko Stamenović

    Since 2019 the rules for making the banked code changed. I have adjusted the code from the original example for the current rules.

    The line I used to link was like:

    sdcc -mez80_z80 -Wl-b_BANK07=0x28000 -o bmain.ihx bmain.rel aaa.rel
    makebin -s 0x30000 bmain.ihx bmain.bin
    

    and the relevant code produced is:

    bmain.lst:
                                         15     .globl b_resetBit
                                         16     .globl _resetBit
                                         17     .globl b_setBit
                                         18     .globl _setBit
                                         83 ;bmain.c:19: setBit( & x );
        0000000D 1Er00            [ 2]   87     ld  e, #b_setBit
        0000000F 21r00r00         [ 3]   88     ld  hl, #_setBit
        00000012 CDr00r00         [ 5]   89     call    ___sdcc_bcall_ehl
        00000015 33               [ 1]   90     inc sp
        00000016 33               [ 1]   91     inc sp
                                         92 ;bmain.c:20: resetBit( & x );
        00000017 1Er00            [ 2]   93     ld  e, #b_resetBit
        00000019 21r00r00         [ 3]   94     ld  hl, #_resetBit
        0000001C CDr00r00         [ 5]   95     call    ___sdcc_bcall_ehl
        0000001F 33               [ 1]   96     inc sp
        00000020 33               [ 1]   97     inc sp
    
    Map:
         00000007  b_resetBit                         aaa
         00000007  b_setBit                           aaa
         00028000  _setBit                            aaa
         0002801E  _resetBit                          aaa
    

    The resulting values in binary can be recognized by noting the pattern that the codes for ld e are 1E and, to locate them easily, after the call there are two inc sp which are ASCII '3' or hex 33:

    bmain.bin:
    00000210: 80 c9 01 01 80 c5 c5 1e 07 21 00 80 cd 50 02 33  .........!...P.3
    00000220: 33 1e 07 21 1e 80 cd 50 02 33 33 21 00 00 c9 af  3..!...P.33!....
    

    So the current practice is that the NN of BANKNN (here, 07) is embedded in the resulting binary to be passed in E to the ___sdcc_bcall_ehl and the actual upper byte of the address is not passed there at all, but the mechanism for banking, as far as I see, is consistent with the SDCC manual.

    So the currently functioning rules and functionality should not prevent the possibility to produce banking code following the rules described in the current SDCC manual.

    In an attempt to demonstrate the problem of the linker being able to use the upper byte of the 24-bit address, I've also written:

    void dummy1( void ) __naked {
    __asm
        ret
        ret
        ret
        .db (_setBit >> 16)
        .db (_resetBit >> 16)
        ret
        ret
        ret
    __endasm;
    }
    

    where two values in the resulting binary will be recognized by being between three C9 values:

    00000210: 80 c9 c9 c9 c9 00 1e c9 c9 c9 01 01 80 c5 c5 1e  ................
    

    and here we see that the value for .db (_setBit >> 16) is still 00 and for (_resetBit >> 16)is 1E, comparing with the values from the map, it's still the lowest byte in both cases.

    At the moment I still haven't localized the points where the assembler and linker should cooperate to be able to resolve the upper parts of the global address directly as the .dbvalue inside of the resulting binary file.

    The remaining question is, how should exactly the scenario, where the parts of the address should be resolved with a linker, look like, it the existing solution is not enough? If I understand correctly, the linker is limited by what can be passed through the format of .rel files, which is kind of standardized.

     

    Last edit: Janko Stamenović 2025-09-03
    • Janko Stamenović

      I've also played with the last version of the upstream assembler and linker (intentionally, as our version is based on the mix of the older versions) and that version also wouldn't handle the expressions like

          .db (_setBit >> 16)
      

      for the target bytes that the linker should resolve after the relocation.

      What would work there are:

              .msb    2  ; select MSB as 3rd byte of address (0,1,[2],3)
      

      and

         ; the asxxxx's > operator has to be used now
         ; linker resolves this to the byte 2 as described above
         ; and modified by the .msb command
          .db >_setBit 
      

      Edit: The reason > operator is used there is that the format of .rel (relocatable objects) files doesn't allow arbitrary expressions to be calculated after the relocation. The most complex use possible is selecting bits from the relocated value, like, in this case, the byte 2 of the 3-byte address can be selected with that special asxxxx's > operator which has clear mapping to the format below.

      Edit2: The SDAS assemblers don't use .msbat all, and where it is in the sources, it's commented out.

      as6500/r65pst.c:/*    { NULL,   ".msb",         S_MSB,          0,      0       },      */
      as6808/m08pst.c:/*    { NULL,   ".msb",         S_MSB,          0,      0       },      */
      asf8/f8pst.c:    //{    NULL,   ".msb",         S_MSB,          0,      0       },
      asstm8/stm8pst.c:    //{        NULL,   ".msb",         S_MSB,          0,      0       },
      

      Edit3: the function with .msb related comments:

      void dummy1( void ) __naked {
      __asm
          ret
          ret
          ret
          ; .msb    2  ; goal: get the byte 2 from 3-byte big address
          ; using the > operator of ASxxxx
          ; Here .msb fails with current sdasz80 "V02.00+NoICE+SDCC mods":
          ; bmain.asm:88: Error: <o> .org in REL area or directive / mnemonic error
          ; but works with asez80: "ASxxxx Assembler V05.50  (Zilog eZ80)"
          ; (note different binaries upstream: asez80 != asz80)
          .db >_setBit
          .db >_resetBit
          ; .msb    1
          ret
          ret
          ret
      __endasm;
      }
      
       

      Last edit: Janko Stamenović 2025-09-07
      • Philipp Klaus Krause

        Ok, but why does it work for stm8 (didn't check again today, but when I did months ago, AFAIR it did work for stm8), and can we make the rab assembler behave like stm8? And maybe ez80 and tlcs90, too?

         

        Last edit: Philipp Klaus Krause 2025-12-19
        • Janko Stamenović

          Can you give me an example for stm8 for which you know that it works (or worked) as you expected? Thanks.

           
          • Philipp Klaus Krause

            Well, the test-stm8-large regression tests pass, and they use 24-bit addresses for functions. For most of the tests the top byte will be 0x00, but tests/bug-3778.c and tests/bug-3786.c should have non-zero top bytes in 24bit addresses.

             
            👍
            1

Log in to post a comment.

MongoDB Logo MongoDB