linkerriscv

GCC Linker error relocation error in RISCV 64 bit


This is my C code – Main.c :

static volatile unsigned int G_var;

void main()
{
                G_var |= 1;
}

This is my linker file:

/*
Linker script
*/

OUTPUT_ARCH( "riscv" )

ENTRY( main )

MEMORY
{
  bram      (wxa!ri) : ORIGIN = 0x00000000, LENGTH = 64K
  dccm      (wa!ri)  : ORIGIN = 0x80000000, LENGTH = 64K   
}

PHDRS
{
  ram_load   PT_LOAD;
  dccm_load  PT_LOAD;
}


/*----------------------------------------------------------------------*/
/* Sections                            */
/*----------------------------------------------------------------------*/

SECTIONS
{
  .text :
  {
    *(.text.unlikely .text.unlikely.*)
    *(.text.startup .text.startup.*)
    . = ALIGN(8);
  } > bram : ram_load



  .bss :
  {
    *(.sbss .sbss.* .gnu.linkonce.sb.*)
    *(.scommon)
    *(.bss .bss.*) 
    . = ALIGN(8);
  } >dccm


}  

Compilation command:

../toolchains/gcc/bin/riscv32-swerv-elf-gcc -march=rv64imac -mabi=lp64 -nostartfiles -nostdlib Main.c -o main.elf -T link.lds

Why when dccm memory is 0x40000000 compilation is pass but when dccm is 0x80000000 I get this error:

Main.c:(.text+0x8): relocation truncated to fit: R_RISCV_HI20 against `G_var' collect2: error: ld returned 1 exit status

Also why im not getting that linker error when compiling on 32 bit machine?


Solution

  • A boring answer to your first question is "you can't do that". More specifically: I'm not sure what exact toolchain you are using but its default code model is probably medlow or medany. Medlow means all the code and all the data must fit in the first 2GiB of memory. Medany means all the code and all the data must fit within 2GiB of memory and be somewhere in first or last 2GiB of memory (or both). Your linker script violates places the data outside of 2GiB boundary so it violates both of those models. See https://www.sifive.com/blog/all-aboard-part-4-risc-v-code-models for an excellent explanation of the concept (I'll also link Does code location matter in RISC-V code model? as being somewhat relevant ).

    Second question is MUCH more interesting. Let's see what is in the assembly (for medany model):

       6:   00000797                auipc   a5,0x0
                            6: R_RISCV_PCREL_HI20   G_var
                            6: R_RISCV_RELAX        *ABS*
       a:   00078793                mv      a5,a5
                            a: R_RISCV_PCREL_LO12_I .L0
                            a: R_RISCV_RELAX        *ABS*
       e:   439c                    lw      a5,0(a5)
    

    That is:

    1. auipc: take current PC, add 20-bit relocation (HI20) multiplied by 2^12
    2. mv - actually that's addi: add 12-bit relocation (LO12) to that
    3. use the resulting address

    Ok, so that means that G_var is expected to be within 2GB of the code, right? 20-bit + 12 bit is 32 bit, so anything within +-2GiB should work... And G_var is just there, right on that edge (it should be placed right at 0x80000000 during linking, and PC should be at 0x00000006). So let's see how 32-bit version looks like after linking:

       6:   80000797                auipc   a5,0x80000
       a:   ffa78793                addi    a5,a5,-6 # 80000000 <G_var>
       e:   439c                    lw      a5,0(a5)
    

    So this is how it works: Take PC=6, add 0x80000000 to get 0x80000006 and then go back 6 to get to final 0x80000000. This is not a 32-bit signed combined relocation, that is 2 distinct signed relocations. That works for 32-bit but not for 64-bit because 0x80000000 relocation on 32-bits refers both to +2GiB and -2GiB. 64-bit would refer to -2GiB (top of the 64-bit memory space) and not 2GiB in the low memory so it doesn't work there. The actual limit on the code model is not exactly 2GiB, it is 2GiB-2KiB. Interestingly, RISC-V psABI Chapter 5 (CodeModels) specifies this for medlow:

    The medium low code model, or medlow, allows the code to address the whole RV32 address space or the lower 2 GiB and highest 2 GiB of the RV64 address space (0xFFFFFFFF7FFFF800 ~ 0xFFFFFFFFFFFFFFFF and 0x0 ~ 0x000000007FFFF7FF).

    and follows it with the explanation on why these specific limits are there. But then for medany is just says

    5.2. Medium any code model The medium any code model, or medany, allows the code to address the range between -2 GiB and +2 GiB from its position. By using auipc and load / store instructions, when referring to an object, or addi, when calculating an address literal, for example, a signed 32-bit offset, relative to the value of the pc register, can be produced.

    Yet actual limit is the same: +-(2GiB-2KiB), due to exact same reason.

    So the official psABI is just a wee bit wrong. Your linker script can be changed to say dccm: ORIGIN = 0x7FFFFF00, LENGTH = 256 which would place all the code and data squarely with 2GiB limit of each other (and within 2GiB of low memory). It would be perfectly valid according to psABI wording - when compiled for medany model - and yet it still wouldn't work! Isn't it cool to stumble upon such edge cases?